线程之间的通信
1 volatile 和 synchronize 关键字
java支持多个线程同时访问一个对象或者对象的成员变量。但是每个线程由都可以拥有这个变量的拷贝对象,也就是缓存。这样做的目的是为了加快程序的执行速度。但是在一个多核处理器中,这个变量在各个线程的缓存中可能不是最新的。因为主内存中的值可能已经被其他线程修改了。
- volatile(点击查看详情描述)
关键字volatile可以用来修饰一个成员变量,也就是告诉了程序任何对该对象的访问均需要从共享内存中获取,而且对它的改变也必须同步刷新回共享内存中,它能保证所有线程的变量访问的可见行。
但是过多是使用volatile是不必要的,因为它会降低程序执行的效率。 - synchronized (点击查看详情描述)
关键字synchronized可以修饰方法或者同步块,它主要是确保了多个线程在同一个时刻,只能有一个线程处于方法或者同步中,它保证了线程对变量访问的可见行和排他性。
使用了同步块或者同步方法,是可以通过javap -v 工具查看生成的class文件来分析synchronized关键字的细节。
同步块实现是使用了monitorenter和monitorexit指令,同步方法则是依靠方法修饰符ACC_SYNCHRONIZED来完成。两种方式的本质都是对一个对象的监视器(monitor)进行获取。而且这个获取过程是排他的。而且任意一个对象都拥有自己的监视器 当这个对象由同步方法或者同步块调用的时候,都必须先获取到这个对象的监视器。具体的流程如下:
2 等待和通知机制
一个线程修改了一个对象的值,而且另外一个线程感知到了变化,然后进行相关的操作,整个过程开始于一个线程,而最终在我另外一个线程执行。前者是生产者,后者是消费者,这种模式在功能上实现了解耦,让系统架构也有了更好的延展性。
而这种等待和通知机制是任何一个java对象都具备的。这些方法都定义在超类Object中
方法名称 | 描述 |
---|---|
notify() | 通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是这个线程获得到了对象的锁 |
notifyAll() | 通知所有等待该对象的线程 |
wait() | 调用该方法的线程进入WAITING状态,只有等待另外线程的通知或被中断才会返回,调用wait()方法后,会释放对象的锁 |
wait(long) | 超时等待一段时间,这里的参数时间是毫秒,也就是等待n毫秒之后,如果没有通知也会返回 |
wait(long,int) | 对于超时等待的时间更加精准的控制,可以达到纳秒 |
使用上面的方法需要注意的细节如下:
- 使用wait()、notify() 和 notifyAll()时需要先对调用对象加锁
- 调用wait()方法后,会释放对象锁,线程状态由RUNNING转换成WAITING,并将当前线程放置到对象的等待队列中
- notify()和notifyAll()方法调用后,等待线程依旧不会马上从wait()返回,而且需要等执行notify()和notifyAll()的线程释放锁之后,等待线程才有机会从wait()返回。
- notify()方法将等待队列中的一个等待线程从等待队列中移除到同步队列中,notifyAll()方法则是将等待队列中的线程全部移动到同步队列中。被移动的线程状态由waiting变成blocked状态
- wait返回返回的前提是获得了调用对象的锁
3 Thread.join()的使用
如果一个现场A执行了thread.join()语句,则表示 当前线程A等待thread线程终止之后,才从thread.join()放回。也就是如下图所示,只有等otherThread中等run方法执行完成之后,代码才会执行otherThread.join()后面的方法体。
public static void main(String[] args) throws InterruptedException {
Thread otherThread = new Thread(new OtherThread());
otherThread.start();
otherThread.join();
//其他的代码
}
所以main线程终止的前提是 otherThread线程的终止。这里就涉及到等待/通知机制(等待前驱线程结束之后,接受前驱线程结束通知)。
在主线程(main)中调用 otherThread线程的join方法,主线程(main)将一直处于等待过程中,直到otherThread线程终止,如果otherThread线程在执行过程中出现了异常,join()也会返回,main线程会继续执行。但是otherThread 会抛出 InterruptedException 异常。
另外join方法也允许设置超时时间。
public static void main(String[] args) throws InterruptedException {
Thread otherThread = new Thread(new OtherThread());
otherThread.start();
otherThread.join(1000);
//其他的代码
}
这种情况下,main线程会等待大约1秒。就算otherThread没有完成,那么这个时候主线程也将继续执行。
4 ThreadLocal 的使用
官方中对ThreadLocal的描述是:
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联
- 每个线程都有自己的局部变量
每个线程都有自己独立的变量空(缓存/副本空间),并且对其他线程是不可见对 - 独立于变量对初始化副本
ThreadLocal可以给一个初始值,而每个线程都会获得这个初始化值对一个副本,这样惨的保证不同的线程都有一份拷贝 - 状态和某个线程关联
ThreadLocal不是用于解决共享变量的问题,也不是为了协调线程同步的问题。而是为了方便每个线程处理自己的状态而引入的一个自制。
也就是虽然定义了ThreadLocal()对了,但是实际上每个线程都对这个对象有自己的副本,而且多个线程直接是互补影响的。
参考相关文档