问题的原因源自于一道简单的面试题:题目要求以下:前端
创建三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。java
解决问题前咱们前补充一些基本知识:面试
线程的起动并非简单的调用了你的RUN方法,而是由一个线程调度器来分别调用你的全部线程的RUN方法,
咱们普通的RUN方法若是没有执行完是不会返回的,也就是会一直执行下去,这样RUN方法下面的方法就不可能会执行了,但是线程里的RUN方法却不同,它只有必定的CPU时间,执行事后就给别的线程了,这样反复的把CPU的时间切来切去,由于切换的速度很快,因此咱们就感受是不少线程在同时运行同样.
你简单的调用run方法是没有这样效果的,因此你必须调用Thread类的start方法来启动你的线程.因此你启动线程有两种方法
一是写一个类继承自Thread类,而后重写里面的run方法,用start方法启动线程
二是写一个类实现Runnable接口,实现里面的run方法,用new Thread(Runnable target).start()方法来启动
这两种方法都必须实现RUN方法,这样线程起动的时候,线程管理器好去调用你的RUN方法.多线程
经过调用Thread类的start()方法来启动一个线程, 这时此线程是处于就绪状态, 并无运行。 而后经过此Thread类调用方法run()来完成其运行操做的, 这里方法run()称为线程体, 它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止, 而CPU再运行其它线程, 而若是直接用Run方法, 这只是调用一个方法而已, 程序中依然只有主线程--这一个线程, 其程序执行路径仍是只有一条, 这样就没有达到写线程的目的。
Thread类是在java.lang包中定义的。一个类只要继承了Thread类同时覆写了本类中的run()方法就能够实现多线程操做了,可是一个类只能继承一个父类,这是此方法的局限。ide
实现Runnable接口相对于继承Thread类来讲,有以下显著的好处:
(1)适合多个相同程序代码的线程去处理同一资源的状况,把虚拟CPU(线程)同程序的代码,数据有效的分离,较好地体现了面向对象的设计思想。
(2)能够避免因为Java的单继承特性带来的局限。咱们常常碰到这样一种状况,即当咱们要将已经继承了某一个类的子类放入多线程中,因为一个类不能同时有两个父类,因此不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式了。
(3)有利于程序的健壮性,代码可以被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码。多个线程操做相同的数据,与它们的代码无关。当共享访问相同的对象是,即它们共享相同的数据。当线程被构造时,须要的代码和数据经过一个对象做为构造函数实参传递进去,这个对象就是一个实现了Runnable接口的类的实例。 函数
我在测试中发现,继承Thread的时候线程是顺序的,实现Runnable的时候确实不肯定顺序的测试
Obj.wait(),与Obj.notify()必需要与synchronized(Obj)一块儿使用,也就是wait,与notify是针对已经获取了Obj锁进行操做,从语法角度来讲就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内。从功能上来讲wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操做。但有一点须要注意的是notify()调用后,并非立刻就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操做。Thread.sleep()与Object.wait()两者均可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。this
对于sleep()方法,咱们首先要知道该方法是属于Thread类中的。而wait()方法,则是属于Object类中的。spa
sleep()方法致使了程序暂停执行指定的时间,让出cpu该其余线程,可是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。线程
在调用sleep()方法的过程当中,线程不会释放对象锁。
而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备
有了这下知识以后,下面咱们来解决问题吧
从题目咱们能够获得几个要点:
1:线程同时运行:可是对线程的启动没有作要求
2:每一个线程打印十次对应的字母
3:交替打印:意味着三个线程的的打印操做有严格的顺序性
1:每一个线程打印不一样的字母,操做相似,能够用统一的类来实现,而且用不用的本身初始化
2:线程之间之间有前后性,打印A的线程打印了,打印B的线程才能打印,而后到C,循环
如何保证线程之间的执行顺序的有序性是本题目的难点,
打印的线程应该在打印一次A以后就应该进入等待状态,直到打印C的线程打印一次C以后在从新执行
打印B、C的线程也相似
定义三个Object遍历a,b,c,每一个线程同时得到这三个对象锁才打印对应的字母一次,打印一次后释放另外两个对象的锁
每一个线程都要同时得到三个对象锁才能打印,那个第一个线程打印完,若是只是释放另外两个对象的锁,那个就没有线程能够继续打印了;
若是三个都释放,那意味着当前线程能够继续打印了。
因此,用三个对象锁的思路彷佛是行不通了
每一个线程只有同时得到本身和本身的前缀对象的锁(例如A的前缀是C,B的是A),才能打印一次本身的字母,而后释放本身前缀对象的锁,进入本身前端对象的等待线程池里面,等待本身前缀对象被唤醒以后才能继续打印
那么本身前缀对象何时被唤醒呢?
很简单的思路,打印A的线程在打印一次A以后进入等待C对象的线程池里面,那么只要打印C的线程在打印本身的时候唤醒一下本身就行了,这样打印A的线程就能够在打印C的线程打印一次C以后立刻能够打印一次A;
打印完一次本身后,立刻唤醒本身,而后释放本身前缀对象的锁,进入本身前端对象的等待线程池里面,让本身的后缀线程能够立刻打印,依次循环,问题好像已经解决了。
因为每一个线程只拥有两个锁,而且只等待一个锁,那么对线程的启动顺序仍是有要求的。假如启动的顺序是ACB,咱们来分析一下:
线程A:一开始得到AC两个锁,打印A,唤醒A,进入等待C被唤醒的线程池,释放AC
线程B:一开始得到AB两个锁,打印B,唤醒B,进入等待A被唤醒的线程池,释放AB
线程C:一开始得到BC两个锁,打印C,唤醒C,进入等待B被唤醒的线程池,释放BC
这彻底是没有问题的,因此咱们必须保证打印ABC对应的线程顺序启动
作法很简单:启动A后短暂sleep一下就好,若是只是这样
new Thread(aThread).start(); // Thread.sleep(10); new Thread(bThread).start(); // Thread.sleep(10); new Thread(cThread).start(); // Thread.sleep(10);
若是你的类是implements Runnable的话,那个着三个线程启动的顺序是没有保证的,
固然,你能够改为extends Thread,这样的话这三个线程就是顺序启动的。
下面是完整的实现代码:
/** * * @author 戚伟杰 * @version 2015年11月20日 上午10:15:07 */ public class MyThreadPrint implements Runnable{ private String name; private Object prev; private Object self; public MyThreadPrint(String name,Object prev,Object self) { // TODO Auto-generated constructor stub this.name = name; this.prev = prev; this.self = self; } @Override public void run() { // TODO Auto-generated method stub int count = 10; while(count>0){ synchronized (prev) { synchronized (self) { System.out.print(name); count--; // self.notify(); } try { prev.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } public static void main(String[] args) throws InterruptedException{ Object a = new Object(); Object b = new Object(); Object c = new Object(); MyThreadPrint aThread = new MyThreadPrint("a",c,a); MyThreadPrint bThread = new MyThreadPrint("b",a,b); MyThreadPrint cThread = new MyThreadPrint("c",b,c); new Thread(aThread).start(); // Thread.sleep(10); new Thread(bThread).start(); // Thread.sleep(10); new Thread(cThread).start(); // Thread.sleep(10); // aThread.run(); // bThread.run(); // cThread.run(); } }