Java的线程间通信方式

7 篇文章 0 订阅

前言

参考项目:https://github.com/wodongx123/ThreadCommunicationDemo

1. 传统线程通信(Synchronized和wait/notify)

使用Object类自带的wait()和notify()/notifyAll()方法进行线程间的通信。

  • 对于这种方法,对应的代码段要被synchronized(obj)所包括在内,也就是说,要有同步监视器才能进行通信。
  • 调用wait()方法的线程会放弃对象锁,进入堵塞状态。
  • 调用notify()会随机选择一个同代码块上的正在wait()的线程唤醒;notifyAll()会选择所有的wait()的线程唤醒。
  • 调用notify()/notifyAll()的线程放弃对象锁后(执行完成,或者wait())才会唤醒目标线程,而不是在调用notify()后立刻唤醒。

2. Lock和Condition进行通信

如果使用Lock来进行线程同步的话,就不能再用wait()/notify()机制了,但是Java也提供了一个Condition类让我们可以继续进行线程间的同步机制。

  • 使用Lock.newCondition()来实例化一个Condition。
  • condition.await()效果基本等同wait()方法,也是放弃对象锁,进入堵塞状态。
  • condition.signal()/signalAll()基本等同于notify(),不多说明。
  • 对于Condition和Lock机制而言,和Synchronized区别最大的地方是:Synchronized是Java的内置的机能,通过JVM实现;而Lock/Condition则是类,需要通过代码实现。
  • 所以,对于Lock/Condition而言,第一重要的是lock之后必须要手动unlock,第二重要的是不同的线程想要使用lock和Condition时,这些线程的lock和condition都必须要是同一对象

3. 使用volatile关键字

Java支持通过使用volatile关键字的办法来进行线程通信,对于声明了volatile的对象而言,它在所有线程中的数据都会保持相等。

  • 声明volatile的对象,每个线程都会相当于从主存获取这个对象的状态。
  • 实际上,每个线程都拥有自己的工作内存,声明volatile的对象会在每个线程的工作内存中,拷贝一份同样的数据,以此来达到数据同步。
  • 而一旦某个线程修改了自己工作内存的volatile对象的状态,就会立刻通知主存,主存又通知各个线程的工作内存,以此来保证数据的同步。
  • volatile无法保证变量的操作是原子性的,开10个线程,每个线程加1000次,大部分无法达到10000的答案。
public class NoAutoMaticTest {
    public volatile int inc = 0;
     
    public void increase() {
        inc++;
    }
     
    public static void main(String[] args) {
        final NoAutoMaticTest test = new NoAutoMaticTest();
        
        //10个线程对加1000次,本应该得到10000,实际上会因为无法保证变量操作是原子性,经常达不到10000
        //而如果换成synchronized或者lock都可以得到10000
        for(int i=0;i<10;i++){
            new Thread(){
                public void run() {
                    for(int j=0;j<1000;j++)
                        test.increase();
                };
            }.start();
        }
         
        while(Thread.activeCount()>1)  //保证前面的线程都执行完
            Thread.yield();

        System.out.println(test.inc);
    }
}
  • 要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:
    • 对变量的写操作不依赖于当前值。
    • 该变量没有包含在具有其他变量的不变式中。

4. BlockingQueue

阻塞队列从Java5开始提供,是Queue的字接口,主要通过队列的方式做线程通信。

  • 队列是一个FIFO队列,存入元素的位置总是在队列末尾,取出元素的位置总是在队列头部。
  • 队列可以供不同的线程取出或者存入元素。
  • 当队列已满时,存入元素的线程会根据使用的方法不同而抛出异常、返回false、进入堵塞。
  • 当队列元素为空时,取出元素的线程会根据使用的方法不同而抛出异常、返回false、进入堵塞。
抛出异常返回不同值阻塞线程指定超时时间
队尾插入元素add(e)offer(e)put(e)offer(e, time, unit)
队头取出元素remove()poll()take()poll()
对头取出不删除元素element()peek()--
//使用样例
public static void main(String[] args) throws InterruptedException {

	BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(2);

	queue.put(1);
	System.out.print(queue.take());
		
}

参考材料

疯狂Java讲义(第5版)
p757 - 765
Java并发编程:volatile关键字解析
https://www.cnblogs.com/dolphin0520/p/3920373.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值