读书笔记《JAVA并发编程的艺术》 第四章 Java并发编程基础 4.3 线程间的通信

4.3 线程间的通信

4.3.1 volatile和synchronized关键字

关键字volatile可以用来修饰字段(成员变量),就是告知程序任何对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷新到共享内存,它能保证所有线程对变量访问的可见性
关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性
示例代码:

public class Synchronized {
    public static void main(String[] args){
        // 对Synchronized Class 对象进行加锁
        synchronized(Synchronized.class){

        }
        // 静态同步方法,对Synchronized Class对象进行加锁
        m();
    }
    public static synchronized void m(){

    }
}

在Synchronized.class同级目录执行javap -v Synchronized.class,部分相关输出如下所示:

public static void main(java.lang.String[]);
    Code:
        stack=2, locals=1,args_size=1
        0:ldc      #1 //class com/../Synchronized
        2:dup
        3:monitorenter //monitorenter:监视器进入,获得锁
        4:monitorexit  //monitorexit:监视器退出,释放锁
        5:invokestatic   #1 6 //Method m:()V
        8:return
    public static synchronized void m();
    flags:ACC_PUBLIC,ACC_STATIC,ACC_SYNCHRONIZED
        Code:
            stack=0,locals=0,args_size=0
            0:return 

上面的class信息中,对于同步块实现使用了monitorenter和monitorexit指令,而同步方法则是依靠方法修饰符上的ACC_SYNCHRONIZED来完成的。

任意线程对Object(Object由synchronized保护)的访问,首先要获得Object的监视器。

4.3.2 等待/通知机制

等待/通知的相关方法

方法名称 描述
notify() 通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁
notifyAll() 通知所有等待在该对象上的线程
wait() 调用该方法的线程进入WAITTING状态,只有等待另外线程的通知或被中断才会返回,需要注意,调用wait()方法后,会释放对象的锁
wait(long) 超时等待一段时间(单位:毫秒)后,如果没有通知就返回
wait(long,int) 对于超时时间更细粒度的控制,可以达到纳秒
public class WaitNotify {
static boolean flag = true;
static Object lock = new Object();


public static void main(String[] args) {
    Thread waitThread = new Thread(new Wait(), "WaitThread");
    waitThread.start();
    try {
        TimeUnit.SECONDS.sleep(1);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    Thread notifyThread = new Thread(new Notify(), "NotifyThread");
    notifyThread.start();
}

static class Wait implements Runnable{
    @Override
    public void run() {
        //加锁,拥有lock的Monitor
        synchronized (lock){
            //条件不满足时,继续wait,同时释放了lock的锁
            while (flag){
                try{
                    System.out.println(Thread.currentThread() + " flag is true. wait @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    lock.wait();
                }catch (InterruptedException e){
                }
            }
            //条件满足时,完成工作
            System.out.println(Thread.currentThread() + " flag is false. running @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        }
    }
}

static class Notify implements Runnable{
    @Override
    public void run() {
        //加锁,拥有lock的Monitor
        synchronized (lock){
            // 获取lock的锁,然后进行通知,通知时不会释放lock的锁,
            // 直到当前线程释放了lock后,WaitThread 才能从wait方法中返回
            System.out.println(Thread.currentThread() + " hold lock.notify @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            lock.notifyAll();
            flag = false;
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 再次加锁
        synchronized (lock){
            System.out.println(Thread.currentThread() + " hold lock again .sleep @ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

}

输出结果如下:

Thread[WaitThread,5,main] flag is true. wait @ 09:49:29
Thread[NotifyThread,5,main] hold lock.notify @ 09:49:30
Thread[NotifyThread,5,main] hold lock again .sleep @ 09:49:35
Thread[WaitThread,5,main] flag is false. running @ 09:49:40

4.3.3 等待/通知的经典范式

等待方遵循的原则:
1. 获取对象的锁
2. 如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件
3. 条件满足则执行对应的逻辑
伪代码如下:

synchronized(对象){
    while(条件不满足){
        对象.wait();
    }   
    对应的处理逻辑
}

通知方遵循的原则:

  1. 获得对象的锁
  2. 改变条件
  3. 通知所有等待在对象上的线程

    扫描二维码关注公众号,回复: 2616144 查看本文章
    synchronized(对象){
        改变条件
        对象.notifyAll();
    }
    

4.3.4 管道输入/输出

它主要用于线程之间的数据传输,而传输的媒介是内存。

public class Piped {
public static void main(String[] args) throws Exception {
    PipedWriter out = new PipedWriter();
    PipedReader in = new PipedReader();
    // 将输出流和输入流进行连接,否则在使用时会抛出IOException
    out.connect(in);
    Thread printThread = new Thread(new Print(in),"PrintThread");
    printThread.start();
    int receive = 0;
    try {
        while ((receive = System.in.read()) != -1) {
            out.write(receive);
        }
    } finally {
        out.close();
    }
}

static class Print implements Runnable {
    private PipedReader in;

    public Print(PipedReader in) {
        this.in = in;
    }

    @Override
    public void run() {
        int receive = 0;
        try {
            while ((receive = in.read()) != -1) {
                System.out.print((char) receive);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
}

4.3.5 Thread.join()的使用

如果一个线程A执行了B.join()语句,意味着:当前线程A等待线程B中终止后才从B.join()返回。
查看java.lang.Thread的源码,对应的join部分的代码如下:

 public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    *if (millis == 0) {
        //条件不满足,继续等待
        while (isAlive()) {
            wait(0);
        }
        //条件满足,返回
    }* else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

当线程终止时,会调用线程自身的notifyAll()方法,会通知所有等待在该线程对象上的所有线程。

4.3.6 ThreadLocal的使用

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。

以一个统计耗时的Profiler类为例,一般的耗时工具类方法可能如下:

public class LogTimeUtils {
 private long beginTime;
 private long endTime;

 public LogTimeUtils(){
     beginTime = System.currentTimeMillis();
     endTime = System.currentTimeMillis();
 }

 /**
  * 统计总时间
      */
 public Long countTotalTime(){
    return System.currentTimeMillis() - beginTime;
 }

 /**
  * 统计间隔时间
   */
 public Long countTime(){
     long time = System.currentTimeMillis() - endTime;
     endTime = System.currentTimeMillis();
     return time;
 }

}

该工具类适用于同一个方法的统计耗时,如果涉及到跨方法的耗时统计,需要显性的传递该logTimeUtil,污染了代码结构。
而利用ThreadLocal可以很好的解决这个问题,好处就是即使统计的begin()方法和end()方法不在一个方法或者类中,都可以适用。

public class Profiler {
private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>(){
    @Override
    protected Long initialValue(){
        return System.currentTimeMillis();
    }
};

public static final void begin(){
    TIME_THREADLOCAL.set(System.currentTimeMillis());
}

public static final long end(){
    return System.currentTimeMillis() - TIME_THREADLOCAL.get();
}

public static void main(String[] args) throws Exception{
    Profiler.begin();
    TimeUnit.SECONDS.sleep(1);
    System.out.println("Cost : " + Profiler.end() + " mills.");
}

}

猜你喜欢

转载自blog.csdn.net/maohoo/article/details/81324148