线程通信

线程通信
synchronized 关键字

wait、notify、notifyAll 方法调用需要获取对象监视器。

	synchronized (obj)

获取方式是通过synchronized 关键字实现的,synchronized 是一个重量级锁。

Java中的每一个对象都可以作为锁,这是为什么呢?
Java对象头里的Mark Word里默认存储对象的HashCode、分代年龄和锁标记位。

在64位虚拟机下,Mark Word是64bit大小的,其存储结构如下:
在这里插入图片描述
synchronized正是通过锁标志来判断当前对象锁是否可用,在获取锁的过程中可能会涉及到锁的升级。

Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级。

wait/notify
	public class WaitNotifyDemo {
	
	    public static void main(String[] args) {
	        Object object = new Object();
	        Thread thread=new Thread(()->{
	            try {
	                object.wait();
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	            }
	            System.out.println(Thread.currentThread().getName()+"  over");
	        },"waitThread");
	        thread.start();
	
	        try {
	            Thread.sleep(1000);
	        } catch (InterruptedException e) {
	            e.printStackTrace();
	        }
	        object.notify();//唤醒
	
	    }
	}
		

没有获取对象锁,就执行wait方法,会抛出异常IllegalMonitorStateException

Exception in thread "waitThread" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at thread.WaitNotifyDemo.lambda$main$0(WaitNotifyDemo.java:10)
	at java.lang.Thread.run(Thread.java:748)

对wait和notify操作加锁以后就可以正常执行了

//wait
synchronized(object){
	object.wait();
}

synchronized(object){
	object.notify();
}

还有如果父线程中 notify方法不幸比子线程wait方法先执行,那么子线程将一直等待。

上面只是一个简单例子,通常 wait / notify 不会这么应用。wait、 notify典型的应用方式是生产者、消费者。

等待方遵(消费者)循如下原则。

  1. 获取对象的锁。
  2. 如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。
  3. 条件满足则执行对应的逻辑

wait 方法的调用通常符合下面逻辑

synchronized(obj) {
	//防止假醒
	while(条件不满足) {
		obj.wait();
	} 
	//处理逻辑
}

通知方(生产者)遵循如下原则。

  1. 获得对象的锁。
  2. 改变条件。
  3. 通知所有等待在对象上的线程。
synchronized(obj) {
	// 改变条件
	obj.notifyAll();
}

下面实现一个 简单的无界阻塞队列。

public class BlockingArrayObject<T> {
    private List<T> list = new ArrayList<>();
	
	//生产元素
    public synchronized void put(T t) {
        list.add(t);
        this.notifyAll();
    }

	//消费元素
    public synchronized T pop() throws InterruptedException {
        while (list.size() == 0)
            this.wait();
        return list.remove(0);
    }
}

上面使用BlockingArrayObject 对象作为 对象监视器。

测试程序

class Demo {

    @Test
    public void test() {
        BlockingArrayObject<String> demo = new BlockingArrayObject<>();

        new Thread(() -> {
            int num = 0;
            for (int i = 0; i < 5; i++) {
                demo.put("str" + num);
                System.out.println("生产:str" + num++);
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        consume(demo);
        // consume(demo);
    }

    private void consume(BlockingArrayObject demo) {
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + "消费:" + demo.pop());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

输出结果:

生产:str0
Thread-1消费:str0
生产:str1
Thread-1消费:str1
生产:str2
Thread-1消费:str2
生产:str3
Thread-1消费:str3
生产:str4
Thread-1消费:str4

Condition

在jdk1.5之后,除了使用synchronized 关键字可以实现线程同步之外还可以使用 Lock 类实现同步。

与synchronized 对比:

  1. Lock有更大的自主权,可以指定锁的公平性,锁的释放时机。 而synchronized 是非公平锁。
  2. Lock是jdk层面的锁(使用 等待队列 和 LockSupport实现 ),使用Lock 对象作对独占锁。synchronized用的锁是存在Java对象头里的。
  3. 二者都提供了 消息通信的方式。synchronized 使用监视器对象(调用wait/notify方法)进行通信。Lock 使用 Condition 对象(调用await/signal方法)进行通信

Lock 常用实现类:

  • ReentrantLock :可重入独占锁。
  • ReentrantReadWriteLock。 可重入读写锁

Lock接口中提供了 newCondition 方法来获取Condition

 /**
     * Returns a new {@link Condition} instance that is bound to this
     * {@code Lock} instance.
     *
     * <p>Before waiting on the condition the lock must be held by the
     * current thread.
     * A call to {@link Condition#await()} will atomically release the lock
     * before waiting and re-acquire the lock before the wait returns.
     *
     * <p><b>Implementation Considerations</b>
     *
     * <p>The exact operation of the {@link Condition} instance depends on
     * the {@code Lock} implementation and must be documented by that
     * implementation.
     *
     * @return A new {@link Condition} instance for this {@code Lock} instance
     * @throws UnsupportedOperationException if this {@code Lock}
     *         implementation does not support conditions
     */
Condition newCondition();

使用Condition 的awaitsignalsignalAll 方法可以实现线程通信,功能和使用方式都与 wait、notify、notifyAll 一致。同样的 await方法 也会抛出异常InterruptedException。

下面是通Condition实现的简单的阻塞队列。

public class BlockingArrayDemo<T> {
    final Lock lock = new ReentrantLock(); //锁
    final Condition notFull = lock.newCondition();//条件: 队列不满
    final Condition notEmpty = lock.newCondition();//条件:队列不空
    final Object [] items = new Object[1]; // 队列存储元素
    int putIndex, takeIndex, count;

    /**
     * 入队
     * @param o
     * @throws InterruptedException
     */
    public void put(T o) throws InterruptedException {
        //获取锁
        lock.lock();
        try{
            // 队列已满等待
            while(count==items.length)
                notFull.await();

            items[putIndex] = o;
            putIndex = (putIndex++) % items.length;
            count++;
            //添加元素,此时队列非空
            notEmpty.signal();
        }finally {
            //释放锁
            lock.unlock();
        }
    }

    /**
     * 出队
     * @return
     * @throws InterruptedException
     */
    public T take()throws InterruptedException{
        //获取锁
        lock.lock();
        try{
            // 队列已空等待
            while(count==0)
                notEmpty.await();
            T takeObj = (T) items[takeIndex];
            takeIndex = (takeIndex++) % items.length;
            count--;
            //已移除元素,此时队列非满
            notFull.signal();
            return takeObj;
        }finally {
            lock.unlock();
        }
    }
 }

测试程序:

 public static void main(String[] args) {
        BlockingArrayDemo blockingArray = new BlockingArrayDemo();
        new Thread(()->{
            for (int i=0;i<5;i++){
                try {
                    System.out.println("take:   "+blockingArray.take());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        for (int i=0;i<5;i++){
            try {
                Thread.sleep(1000);
                blockingArray.put("num: "+i);
                System.out.println("put num:    "+i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

输出结果

put num: 0
take: num: 0
put num: 1
take: num: 1
put num: 2
take: num: 2
put num: 3
take: num: 3
put num: 4
take: num: 4

可以看到 使用方式和wait/notify 是一致的,本就是synchronized / (wait/notify) 替换方案。当然完全可以将Lock替换为synchronized,将Condition 替换为 普通对象

  Object notFull = new Object();
  Object notEmpty = new Object();
  public  void put(T o) throws InterruptedException {
	 	while (count == items.length)
            synchronized (notFull) {
                notFull.wait();
            }
        items[putIndex] = o;
        putIndex = (putIndex++) % items.length;
        count++;
        //添加元素,此时队列非空
        synchronized (notEmpty) {
            notEmpty.signal();
        }
  }

有兴趣可以了解一下博客: ReentrantLock 源码浅读阻塞队列-ArrayBlockingQueue源码解析

Join

如果一个线程A执行了thread.join()语句,其含义是:当前线程A等待thread线程终止之后才从thread.join()返回。Thread中还提供了具有超时参数的方法,如果线程thread在给定的超时时间里没有终止,那么将会从该超时方法中返回

介绍方法的时候提到过 join 实际上是使用wait 方法实现的。

join的使用场景是控制线程执行顺序。 比如:主线程需要等待子线程的返回结果。

public class JoinThreadDemo {
    public static void main(String[] args) throws InterruptedException {

        Thread previous = Thread.currentThread();
        for (int i = 0; i < 4; i++) {
            // 每个线程拥有前一个线程的引用,需要等待前一个线程终止,才能从等待中返回
            Thread thread = new Thread(new Domino(previous), String.valueOf(i));
            thread.start();
            previous = thread;
        }
        TimeUnit.SECONDS.sleep(5);
        System.out.println(Thread.currentThread().getName() + " terminate.");
    }

    static class Domino implements Runnable {
        private Thread thread;

        public Domino(Thread thread) {
            this.thread = thread;
        }

        public void run() {
            try {
                thread.join();
            } catch (InterruptedException e) {
            }
            System.out.println(Thread.currentThread().getName() + " terminate.");
        }
    }
}

输出结果

main terminate.
0 terminate.
1 terminate.
2 terminate.
3 terminate.

如果只是为了多线程计算数据,最后合并结果的话。 在juc 包下提供了join的替代方案。比如CountDownLatch,CyclicBarrier。

参考:

  1. 疯狂java讲义
  2. java并发编程的艺术
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值