《Java并发编程的艺术》学习 ——线程间的通信

线程之间的通信

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)对于超时等待的时间更加精准的控制,可以达到纳秒

使用上面的方法需要注意的细节如下:

  1. 使用wait()、notify() 和 notifyAll()时需要先对调用对象加锁
  2. 调用wait()方法后,会释放对象锁,线程状态由RUNNING转换成WAITING,并将当前线程放置到对象的等待队列中
  3. notify()和notifyAll()方法调用后,等待线程依旧不会马上从wait()返回,而且需要等执行notify()和notifyAll()的线程释放锁之后,等待线程才有机会从wait()返回。
  4. notify()方法将等待队列中的一个等待线程从等待队列中移除到同步队列中,notifyAll()方法则是将等待队列中的线程全部移动到同步队列中。被移动的线程状态由waiting变成blocked状态
  5. 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)相关联

  1. 每个线程都有自己的局部变量
    每个线程都有自己独立的变量空(缓存/副本空间),并且对其他线程是不可见对
  2. 独立于变量对初始化副本
    ThreadLocal可以给一个初始值,而每个线程都会获得这个初始化值对一个副本,这样惨的保证不同的线程都有一份拷贝
  3. 状态和某个线程关联
    ThreadLocal不是用于解决共享变量的问题,也不是为了协调线程同步的问题。而是为了方便每个线程处理自己的状态而引入的一个自制。

也就是虽然定义了ThreadLocal()对了,但是实际上每个线程都对这个对象有自己的副本,而且多个线程直接是互补影响的。
参考相关文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值