Java多线程:Java多线程同步与对象控制权
join()函数
Join的含义是:将某一线程加入成为另一个线程的流程之一,换言之就是等待另一个线程执行完毕。
public class JoinTest { public static void main(String[] args) throws InterruptedException { Thread otherThread = new Thread( new Runnable() { public void run() {try { for ( int i = 1; i <= 5; i++) { Thread. sleep(1000); System. out.println(i + ":辅助线程执行.." ); } } catch (InterruptedException e) { e.printStackTrace(); } } }); otherThread.start(); try { otherThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } for ( int i = 1; i <= 5; i++) { Thread. sleep(500); System. out.println(i + ": 主线程正在执行..." ); } }}
wait()函数notify()函数
如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态。
如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。
wait方法阻塞本线程,等待其他线程调用notify方法通知自己可以继续执行,也就是说这一对方法应该结合在一起使用,只有当A线程在wait之后出让资源是其他线程有机会前进,另外的B线程notifyA才能使A线程恢复执行。在Java多线程任务里经常会出现多个线程争夺同一个资源,如果任由其争夺可能会造成问题,所以有序的争夺离不开阻塞和唤醒线程,可以先对线程已经争得的资源加锁,这时其他资源将无法争夺这个加锁的资源,在试用完资源后对资源进行解锁,使得其他线程能够重新获得这个资源的争夺权。(其实这个过程类似于进程的信号量加锁解锁)。阐明这个问题的最好例子莫过于生产者消费者的模拟:
在这个过程中,生产产品、消费产品时所需要的容器是争夺的资源,对这个这多资源在访问时需要加解锁:
synchronized(container):对容器加锁以阻塞其他线程同时访问,亦即使得其他线程处于等待状态;
container.wait():在容器满时阻塞本线程把容器解锁将容器的控制权交出去,本线程处于等待状态;
container.notify():在容器空时通知正在等待容器控制权的线程恢复运行,亦即解锁容器;
public class PCTest { private List<Object> container = new ArrayList<Object>(); public static void main(String[] args) { PCTest m = new PCTest(); new Thread( new Consume(m.getContainer()), "消费者1").start(); new Thread( new Product(m.getContainer()), "生产者1").start(); new Thread( new Consume(m.getContainer()), "消费者2").start(); new Thread( new Product(m.getContainer()), "生产者2").start(); } public List<Object> getContainer() { return container; } public void setContainer(List<Object> container) { this. container = container; }}class Product implements Runnable { private List<Object> container = null; public Product(List<Object> lst) { this. container = lst; } public void run() { while ( true) { synchronized (container ) {if ( container.size() >= 5) { try { container.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }try { Thread. sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }container.add( new Object());System. out.println(Thread. currentThread().getName()+"生产了一个产品"); System. out.println( "现在容器中一共有" +container .size()+"个产品" );container.notify(); } } }}class Consume implements Runnable { private List<Object> container = null; public Consume(List<Object> lst) { this. container = lst; } public void run() { while ( true) { synchronized (container ) {if ( container.size() == 0) { try { container.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }try { Thread. sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }container.remove(0);System. out.println(Thread. currentThread().getName()+"消费了一个产品"); System. out.println( "现在容器中一共有" +container .size()+"个产品" );container.notify(); } } }}
附上Java线程的同步原理
线程同步的基本原理
java会为每个object对象分配一个monitor,当某个对象的同步方法(synchronized methods )或同步块被多个线程调用时,该对象的monitor将负责处理这些访问的并发独占要求。
当一个线程调用一个对象的同步方法时,JVM会检查该对象的monitor。如果monitor没有被占用,那么这个线程就得到了monitor的占有权,可以继续执行该对象的同步方法;如果monitor被其他线程所占用,那么该线程将被挂起,直到monitor被释放。
当线程退出同步方法调用时,该线程会释放monitor,这将允许其他等待的线程获得monitor以使对同步方法的调用执行下去。
注意:java对象的monitor机制和传统的临界检查代码区技术不一样。java的一个类一个同步方法并不意味着同时只有一个线程独占执行(不同对象的同步方法可以同时执行),但临界检查代码区技术确会保证同步方法在一个时刻只被一个线程独占执行。
java的monitor机制的准确含义是:任何时刻,对一个指定object对象的某同步方法只能由一个线程来调用。
java对象的monitor是跟随object实例来使用的,而不是跟随程序代码。两个线程可以同时执行相同的同步方法,比如:一个类的同步方法是xMethod(),有a,b两个对象实例,一个线程执行a.xMethod(),另一个线程执行b.xMethod(). 互不冲突。
wait()、notify(),notifyAll()的使用
obj.wait()方法使本线程挂起,并释放obj对象的monitor,只有其他线程调用obj对象的notify()或notifyAll()时,才可以被唤醒。
obj.notifyAll()方法唤醒所有阻塞在obj对象上的沉睡线程,然后被唤醒的众多线程竞争obj对象的monitor占有权,最终得到的那个线程会继续执行下去,但其他线程继续等待。
obj.notify()方法是随机唤醒一个沉睡线程,过程更obj.notifyAll()方法类似。
wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,
如:
synchronized(x){ x.notify() //或者wait()}
以上内容说明了为什么调用wait(),notify(),notifyAll()的线程必须要拥有obj实例对象的monitor占有权。
每个对象实例都有一个等待线程队列。这些线程都是等待对该对象的同步方法的调用许可。对一个线程来说,有两种方法可以进入这个等待线程队列。一个是当其他线程执行同步方法时,自身同时也要执行该同步方法;另一个是调用obj.wait()方法。
当同步方法执行完毕或者执行wait()时,其他某个线程将获得对象的访问权。当一个线程被放入等待队列时,必须要确保可以通过notify()的调用来解冻该线程,以使其能够继续执行下去。
wait()与sleep()的区别
sleep()方法是Thread类的静态方法,不涉及到线程间同步概念,仅仅为了让一个线程自身获得一段沉睡时间。sleep可以在任何地方使用。(所以sleep只跟当前线程自己有关)
wait()方法是object类的方法,解决的问题是线程间的同步,该过程包含了同步锁的获取和释放,调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify()方法才会重新激活调用者。(所以wait适用于多个线程同步协调时才使用的)
注意:线程调用notify()之后,只有该线程完全从 synchronized代码里面执行完毕后,monitor才会被释放,被唤醒线程才可以真正得到执行权。