本文我们来总结一下可以改变线程状态的若干方法。
一. Thread类中的方法
1.sleep
sleep方法属于Thread类,它相当于让线程睡眠,交出CPU,让CPU去执行其他的任务。 但是sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。sleep()可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。
2.yield
yield方法也属于Thread类,它跟sleep方法类似,同样不会释放锁。调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程,但是yield不能控制具体的交出CPU的时间。另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。 调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
3.join
join方法同样也属于Thread类,join方法的作用是让“主线程”等待“子线程”结束之后才能继续运行。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
join方法实际上是调用了Object的wait方法,所以join方法会让线程进入阻塞状态,并且会释放线程占有的锁,并交出CPU执行权限。
4.interrupt
interrupt,顾名思义,即中断的意思,它同样也属于Thread类。单独调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,也就说,它可以用来中断一个正处于阻塞状态的线程,但是直接调用interrupt方法不能中断正在运行中的线程。不过,通过interrupt方法和isInterrupted()方法来停止正在运行的线程。
二. Object类中的方法
wait()、notify()和notifyAll()是Object类中的方法:
1 public final native void notify(); 2 3 public final native void notifyAll(); 4 5 public final native void wait(long timeout) throws InterruptedException;
从这三个方法的文字描述可以知道以下几点信息:
- wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。
- 调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)
- 调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;
- 调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;
有朋友可能会有疑问:为何这三个不是Thread类声明中的方法,而是Object类中声明的方法(当然由于Thread类继承了Object类,所以Thread也可以调用者三个方法)?其实这个问题很简单,由于每个对象都拥有monitor(即锁),所以让当前线程等待某个对象的锁,当然应该通过这个对象来操作了。而不是用当前线程来操作,因为当前线程可能会等待多个线程的锁,如果通过线程来操作,就非常复杂了。
上面已经提到,如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
调用某个对象的wait()方法,相当于让当前线程交出此对象的monitor,然后进入等待状态,等待后续再次获得此对象的锁。
notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的monitor的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。
同样地,调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
nofityAll()方法能够唤醒所有正在等待该对象的monitor的线程,这一点与notify()方法是不同的。
这里要注意一点:notify()和notifyAll()方法只是唤醒等待该对象的monitor的线程,并不决定哪个线程能够获取到monitor。
举个简单的例子:假如有三个线程Thread1、Thread2和Thread3都在等待对象objectA的monitor,此时Thread4拥有对象objectA的monitor,当在Thread4中调用objectA.notify()方法之后,Thread1、Thread2和Thread3只有一个能被唤醒。注意,被唤醒不等于立刻就获取了objectA的monitor。假若在Thread4中调用objectA.notifyAll()方法,则Thread1、Thread2和Thread3三个线程都会被唤醒,至于哪个线程接下来能够获取到objectA的monitor就具体依赖于操作系统的调度了。
上面尤其要注意一点,一个线程被唤醒不代表立即获取了对象的monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。
三. Condition接口中的方法
Condition是在java 1.5中的concurrent包中出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition。
- Condition接口基本的方法就是await()、signal()和signalAll()方法;Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()
-
Conditon中的await()对应Object的wait();
-
Condition中的signal()对应Object的notify();
-
Condition中的signalAll()对应Object的notifyAll()。
-
- 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
四. 示例
生产者-消费者模型的实现:
1.使用Object的wait()和notify()实现
1 public class Test { 2 private int queueSize = 10; 3 private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize); 4 public static void main(String[] args) { 5 Test test = new Test(); 6 Producer producer = test.new Producer(); 7 Consumer consumer = test.new Consumer(); 8 producer.start(); 9 consumer.start(); 10 } 11 12 class Consumer extends Thread{ 13 14 @Override 15 public void run() { 16 consume(); 17 } 18 19 private void consume() { 20 while(true){ 21 synchronized (queue) { 22 while(queue.size() == 0){ 23 try { 24 System.out.println("队列空,等待数据"); 25 queue.wait(); 26 } catch (InterruptedException e) { 27 e.printStackTrace(); 28 queue.notify(); 29 } 30 } 31 queue.poll(); //每次移走队首元素 32 queue.notify(); 33 System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素"); 34 } 35 } 36 } 37 } 38 39 class Producer extends Thread{ 40 41 @Override 42 public void run() { 43 produce(); 44 } 45 46 private void produce() { 47 while(true){ 48 synchronized (queue) { 49 while(queue.size() == queueSize){ 50 try { 51 System.out.println("队列满,等待有空余空间"); 52 queue.wait(); 53 } catch (InterruptedException e) { 54 e.printStackTrace(); 55 queue.notify(); 56 } 57 } 58 queue.offer(1); //每次插入一个元素 59 queue.notify(); 60 System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size())); 61 } 62 } 63 } 64 } 65 }
2.使用Condition实现
1 public class Test { 2 private int queueSize = 10; 3 private PriorityQueue<Integer> queue = new PriorityQueue<Integer>(queueSize); 4 private Lock lock = new ReentrantLock(); 5 private Condition notFull = lock.newCondition(); 6 private Condition notEmpty = lock.newCondition(); 7 8 public static void main(String[] args) { 9 Test test = new Test(); 10 Producer producer = test.new Producer(); 11 Consumer consumer = test.new Consumer(); 12 13 producer.start(); 14 consumer.start(); 15 } 16 17 class Consumer extends Thread{ 18 19 @Override 20 public void run() { 21 consume(); 22 } 23 24 private void consume() { 25 while(true){ 26 lock.lock(); 27 try { 28 while(queue.size() == 0){ 29 try { 30 System.out.println("队列空,等待数据"); 31 notEmpty.await(); 32 } catch (InterruptedException e) { 33 e.printStackTrace(); 34 } 35 } 36 queue.poll(); //每次移走队首元素 37 notFull.signal(); 38 System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素"); 39 } finally{ 40 lock.unlock(); 41 } 42 } 43 } 44 } 45 46 class Producer extends Thread{ 47 48 @Override 49 public void run() { 50 produce(); 51 } 52 53 private void produce() { 54 while(true){ 55 lock.lock(); 56 try { 57 while(queue.size() == queueSize){ 58 try { 59 System.out.println("队列满,等待有空余空间"); 60 notFull.await(); 61 } catch (InterruptedException e) { 62 e.printStackTrace(); 63 } 64 } 65 queue.offer(1); //每次插入一个元素 66 notEmpty.signal(); 67 System.out.println("向队列取中插入一个元素,队列剩余空间:"+(queueSize-queue.size())); 68 } finally{ 69 lock.unlock(); 70 } 71 } 72 } 73 } 74 }