线程协作

来源:互联网 发布:如何发布php网站 编辑:程序博客网 时间:2024/05/19 06:48

通过一个经典的例子—生产者与消费者模型来理解一下线程的协作:

当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。

java中线程协作的两种方式:

1、利用Object.wait()Object.notify()
2、使用Condition

第一种:利用Object.wait()、Object.notify();

1)wait()notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。

2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁);

3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;

4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;

注意:

1、这三个方法不是Thread类声明中的方法,而是Object类中声明的方法(当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法;)由于每个对象都拥有monitor(即锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。

2、wait()

如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。同时,调用某个对象的wait()方法,相当于让当前线程交出此对象的monitor,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁);

3、wait(long)和wait(long,int)

显然,这两个方法是设置等待超时时间的,后者在超值时间上加上ns,精度也难以达到,因此,该方法很少使用。对于前者,如果在等待线程接到通知或被中断之前,已经超过了指定的毫秒数,则它通过竞争重新获得锁,并从wait(long)返回。另外,需要知道,如果设置了超时时间,当wait()返回时,我们不能确定它是因为接到了通知还是因为超时而返回的,因为wait()方法不会返回任何相关的信息。但一般可以通过设置标志位来判断,在notify之前改变标志位的值,在wait()方法后读取该标志位的值来判断,当然为了保证notify不被遗漏,我们还需要另外一个标志位来循环判断是否调用wait()方法。

4、notify()

notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的monitor的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。同样地,调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。

5、nofityAll()

nofityAll()方法能够唤醒所有正在等待该对象的monitor的线程,这一点与notify()方法是不同的。notify()和notifyAll()方法只是唤醒等待该对象的monitor的线程,并不决定哪个线程能够获取到monitor。

6、一个线程被唤醒不代表立即获取了对象的monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。

  举个简单的例子:假如有三个线程Thread1Thread2Thread3都在等待对象objectAmonitor,此时Thread4拥有对象objectAmonitor,当在Thread4中调用objectA.notify()方法之后,Thread1Thread2Thread3只有一个能被唤醒。注意,被唤醒不等于立刻就获取了objectAmonitor。假若在Thread4中调用objectA.notifyAll()方法,则Thread1Thread2Thread3三个线程都会被唤醒,至于哪个线程接下来能够获取到objectAmonitor就具体依赖于操作系统的调度了。

举例如下:

TestThread.java

public class TestThread {    public static Object object = new Object();    static class Thread-0 extends Thread{        public void run() {            synchronized (object) {                try {                    object.wait();                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println("线程"+Thread.currentThread().getName()+"获取到了锁");            }        }    }    static class Thread-1 extends Thread{        public void run() {            synchronized (object) {                object.notify();//将处于等待状态的线程(Thread-0)唤醒                System.out.println("线程"+Thread.currentThread().getName()+"调用了object.notify()");            }            System.out.println("线程"+Thread.currentThread().getName()+"释放了锁");        }    }    public static void main(String[] args) {        Thread-0 thread-0 = new Thread-0();        Thread-1 thread-1 = new Thread-1();        thread-0.start();        try {            Thread.sleep(200);        } catch (InterruptedException e) {            e.printStackTrace();        }        thread-1.start();    } }

运行结果:
线程Thread-1调用了object.notify()
线程Thread-1释放了锁
线程Thread-0获取到了锁

第二种:利用condition;

1、Condition是在java 1.5中才出现的,它用来替代传统的Objectwait()、notify()实现线程间的协作,相比使用Objectwait()、notify(),使用Condition1await()、signal()这种方式实现线程间协作更加安全和高效。Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。因此通常来说比较推荐使用Condition,在阻塞队列那一篇博文中就讲述到了,阻塞队列实际上是使用了Condition来模拟线程间协作。

2、Condition是个接口,基本的方法就是await()和signal()方法;

3、Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()

4、 调用Conditionawait()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()lock.unlock之间才可以使用

5、Conditon中的await()对应Objectwait();

6、Condition中的signal()对应Objectnotify();

7、Condition中的signalAll()对应ObjectnotifyAll()。

8、条件(也称为条件队列 或条件变量)为线程提供了一个含义,以便在某个状态条件现在可能为 true 的另一个线程通知它之前,一直挂起该线程(即让其“等待”)。因为访问此共享状态信息发生在不同的线程中,所以它必须受保护,因此要将某种形式的锁与该条件相关联。等待提供一个条件的主要属性是:释放相关的锁,并挂起当前线程,就像 Object.wait 做的那样。 Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。

注意

Condition 实例只是一些普通的对象,它们自身可以用作 synchronized 语句中的目标,并且可以调用自己的 waitnotification 监视器方法。获取 Condition 实例的监视器锁或者使用其监视器方法,与获取和该 Condition 相关的 Lock 或使用其 waitingsignalling 方法没有什么特定的关系。为了避免混淆,建议除了在其自身的实现中之外,切勿以这种方式使用 Condition 实例。 除非另行说明,否则为任何参数传递 null 值将导致抛出 NullPointerException

有关condition的详细用法请查看相关API

分别用以上两种方法实现生产者—消费者模式:

示例一:利用object.wait和object.notify实现生产者-消费者模式

 
Test01.java

import java.util.PriorityQueue;public class Test01{    private int queueSize = 10;    private PriorityQueue<Integer queue = new PriorityQueue<Integer(queueSize);    public static void main(String[] args)  {        Test01 test = new Test01();        Producer producer = test.new Producer();        Consumer consumer = test.new Consumer();        producer.start();        consumer.start();    }    class Consumer extends Thread{        public void run() {            consume();        }        private void consume() {            while(true){                synchronized (queue) {                    while(queue.size() == 0){                        try {                            System.out.println("队列空,等待数据");                            queue.wait();                        } catch (InterruptedException e) {                            e.printStackTrace();                            queue.notify();                        }                    }                    queue.poll();          //每次移走队首元素                    queue.notify();                    System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素");                }            }        }    }    class Producer extends Thread{        public void run() {            produce();        }        private void produce() {            while(true){                synchronized (queue) {                    while(queue.size() == queueSize){                        try {                            System.out.println("队列满,等待有空余空间");                            queue.wait();                        } catch (InterruptedException e) {                            e.printStackTrace();                            queue.notify();                        }                    }                    queue.offer(1);        //每次插入一个元素                    queue.notify();                    System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size()));                }            }        }    }}

示例二:利用condition实现生产者-消费者模式

 
Test02 .java

import java.util.PriorityQueue;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class Test02 {    private int queueSize = 10;    private PriorityQueue<Integer> queue = new PriorityQueue<Integer(queueSize);    private Lock lock = new ReentrantLock();    private Condition notFull = lock.newCondition();    private Condition notEmpty = lock.newCondition();    public static void main(String[] args)  {        Test02 test = new Test02();        Producer producer = test.new Producer();        Consumer consumer = test.new Consumer();        producer.start();        consumer.start();    }    class Consumer extends Thread{        public void run() {            consume();        }        private void consume() {            while(true){                lock.lock();                try {                    while(queue.size() == 0){                        try {                            System.out.println("队列空,等待数据");                            notEmpty.await();//让当前线程(消费者线程)在接到信号或被中断之前一直处于等待状态                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    }                    queue.poll();                //每次移走队首元素                    notFull.signal();//唤醒消费者线程                    System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素");                } finally{                    lock.unlock();                }            }        }    }    class Producer extends Thread{        public void run() {            produce();        }        private void produce() {            while(true){                lock.lock();                try {                    while(queue.size() == queueSize){                        try {                            System.out.println("队列满,等待有空余空间");                            notFull.await();//让当前线程(生产者线程)在接到信号或被中断之前一直处于等待状态                        } catch (InterruptedException e) {                            e.printStackTrace();                        }                    }                    queue.offer(1);        //每次插入一个元素                    notEmpty.signal();//唤醒生产者线程                    System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size()));                } finally{                    lock.unlock();                }            }        }    }}
1 0
原创粉丝点击