join()函数
Join的含义是:将某一线程加入成为另外一个线程的流程之一,换言之就是等待另外一个线程执行完毕。java
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方法就会通知某个正在等待这个对象的控制权的线程能够继续运行。ide
wait方法阻塞本线程,等待其余线程调用notify方法通知本身能够继续执行,也就是说这一对方法应该结合在一块儿使用,只有当A线程在wait以后出让资源是其余线程有机会前进,另外的B线程notifyA才能使A线程恢复执行。在Java多线程任务里常常会出现多个线程争夺同一个资源,若是任由其争夺可能会形成问题,因此有序的争夺离不开阻塞和唤醒线程,能够先对线程已经争得的资源加锁,这时其余资源将没法争夺这个加锁的资源,在试用完资源后对资源进行解锁,使得其余线程可以从新得到这个资源的争夺权。(其实这个过程相似于进程的信号量加锁解锁)。阐明这个问题的最好例子莫过于生产者消费者的模拟:函数
在这个过程当中,生产产品、消费产品时所须要的容器是争夺的资源,对这个这多资源在访问时须要加解锁:this
synchronized(container):对容器加锁以阻塞其余线程同时访问,亦即便得其余线程处于等待状态;线程
container.wait():在容器满时阻塞本线程把容器解锁将容器的控制权交出去,本线程处于等待状态;code
container.notify():在容器空时通知正在等待容器控制权的线程恢复运行,亦即解锁容器;对象
public class ThreadTest { 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(); } } } }
线程死锁的问题
线程和线程若是在运行的过程当中保有一样的资源,若是这些资源的占用在某一时刻没法良好分配,那么就有可能出现死锁:队列
class MyRunnable implements Runnable{ Object a; Object b; public MyRunnable(Object a, Object b) { this.a = a; this.b = b; } @Override public void run() { while(true){ synchronized (a) { synchronized (b) { System.out.println(Thread.currentThread().getName()+" is running"); } } } } } class ThreadTest{ public static void main(String[] args) { Object a = new Object(); Object b = new Object(); MyRunnable myRunnable1 = new MyRunnable(a, b); MyRunnable myRunnable2 = new MyRunnable(b, a); Thread threadA = new Thread(myRunnable1,"t1"); Thread threadB = new Thread(myRunnable2,"t2"); threadA.start(); threadB.start(); } }
在这个例子中两个线程在运行的过程当中必须同时保有两个对象,那么当对象A被一个线程锁定而被另外一个线程须要,同时对象B被一个线程锁定而被另外一个线程须要的时候就会出现死锁。好比t1拿到A和B后锁定它们运行,t2由于没有A和B处于等待状态,t1运行后解锁先解锁B尚未解锁A,t2拿到这个B后锁定B继而须要A,t1解锁A后t2没有拿到A,t1的下一次循环拿到了这个A并锁定A,这个时候t2须要的A被t1锁定,t1须要的B被t2锁定,最终A和B产生死锁。
附上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才会被释放,被唤醒线程才能够真正获得执行权。