java的线程分为5种状态:创建、就绪、运行、阻塞和死亡。
创建:
在java种创建线程的方式有两种,一种是通过继承Thread类并且重写run方法,run方法中执行的代码便是线程执行的代码。另一种是通过实现Runnable接口,并将该接口实例传入一个Thread实例。通过对Thread的引用调用start()方法,即可让线程进入就绪状态。如果直接调用run方法,并不会生成线程,而是在当前线程中把run()当做一个普通方法执行。
public class Thread1 extends Thread{ /* * 实现线程的方法一:通过继承Thread并覆盖run()方法来实现多线程。 */ @Override public void run(){ System.out.PRintln(Thread.currentThread().getName()+"线程开始!"); for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+" "+i); try{ sleep((int)Math.random()*10); }catch(InterruptedException e){ e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"线程结束!"); }}public class Thread2 implements Runnable{ /* * 实现线程的方法二:通过实现Runnable接口来实现多线程 * 实现Runnable接口比继承Thread类所具有的优势: * 1):适合多个相同的程序代码的线程去处理同一个资源 * 2):可以避免java中的单继承的限制 * 3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立 */ @Override public void run(){ System.out.println(Thread.currentThread().getName()+"线程开始!"); for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()+" "+i); try{ Thread.sleep((int)Math.random()*10); }catch(InterruptedException e){ e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"线程结束!"); }}就绪:
处于就绪状态的线程随时可以被JVM的线程调度器调度,进入运行状态。对于处于就绪状态的线程,我们并不能对他们被调度的顺序进行任何估计,也就是说,线程的执行顺序是不可预测的。处于运行状态的线程,通过调用yield()方法,可以返回到就绪状态,然而它有可能瞬间被再次调度。yield()方法把运行机会让给了同等优先级的其他线程。
public class ThreadYield extends Thread{ @Override public void run(){ for (int i = 1; i <= 50; i++) { System.out.println("" +Thread.currentThread().getName() + "-----" + i); // 当i==25时,该线程就会把CPU时间让掉,让其他或者自己的线程执行(也就是谁先抢到谁执行) if (i==25) { this.yield(); } } }}public class ThreadYieldTest { /* * Thread.yield():暂停当前正在执行的线程对象,并执行其他线程。 * 该方法让当前线程回到可运行状态,以允许其他具有相同优先级的线程获得运行机会。 * 但是实际中无法保证yield()达到让步目的,因为当前线程有可能被线程调度程序再次选中。 */ public static void main(String[] args){ ThreadYield thread1=new ThreadYield(); ThreadYield thread2=new ThreadYield(); thread1.start(); thread2.start(); }}运行:
处于运行状态的线程随时有可能被线程调度器换下,进入到就绪状态。想要规定线程的顺序,需要调用join方法,对某个线程 的调用join方法,则主线程会阻塞到该线程执行完后再继续执行。或者使用一种叫做锁的机制(下文会提及)。当一个线程完成它run()里面的所有工作时,线程会自动死亡。调用sleep(),线程会进入休眠,并且在一段时间内不会被再度调用。睡眠时间过后,线程才再次进入就绪队列中。
public class ThreadJoinTest { /* * join是Thread类的一个方法,作用是等待该线程终止。例如对子线程A调用join()方法, * 主线程将等待子线程A终止后才能继续后面的代码。 */ public static void main(String[] args){ System.out.println("主线程开始!"); Thread1 thread1=new Thread1(); Thread1 thread2=new Thread1(); thread1.start(); thread2.start(); try{ thread1.join(); }catch(InterruptedException e){ e.printStackTrace(); } try{ thread2.join(); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println("主线程结束!"); }}死亡:
线程因为代码执行完毕而正常结束自身线程,或者因为某些异常而结束线程。
public class ThreadInterrupt extends Thread{ /* * wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException。 * 如果线程A希望立即结束线程B,则可以对线程B对应的Thread实例调用interrupt方法。如果此刻线程B正 * 在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中 * 直接return即可安全地结束线程。 * 需要注意的是,InterruptedException是线程自己从内部抛出的,并不是interrupt()方法抛出的。 * 对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛 * 出InterruptedException。但是,一旦该线程进入到 wait()/sleep()/join()后,就 * 会立刻抛出InterruptedException 。 */ @Override public void run(){ try{ for(int i=0;i<50;i++){ System.out.println("i="+i); Thread.sleep(100); } }catch(InterruptedException e){ System.out.println("线程被终结!!!!"); //return; } }}锁机制
在介绍阻塞之前,先了解一下java的锁机制。java的锁机制通过synchronized来实现。所有的Java对象都有一个与synchronzied关联的监视器对象(monitor),允许线程在该监视器对象上进行加锁和解锁操作。
a、静态方法:Java类对应的Class类的对象所关联的监视器对象。
b、实例方法:当前对象实例所关联的监视器对象。
c、代码块:代码块声明中的对象所关联的监视器对象。
当锁被释放,对共享变量的修改会写入主存。
public class ThreadSynchronizedTest { private static int value = 0; //获得该类对应的Class类的对象所关联的监视器的锁 public synchronized static int getNext(){ return value++; } //获得当前实例所关联的监视器的锁 public synchronized int getNext2(){ return value++; } //获得当前实例所关联的监视器的锁 public int getNext3(){ synchronized(this){ return value++; } } public static void main(String[] args){ for(int i=0;i<3;i++){ new Thread(new Runnable(){ @Override public void run(){ ThreadSynchronizedTest x=new ThreadSynchronizedTest(); System.out.println("value="+x.getNext()); System.out.println("value="+x.getNext2()); System.out.println("value="+x.getNext3()); } }).start(); } }}阻塞:
阻塞跟Obj.wait(),Obj.notify()方法有关。当调用wait方法时,线程释放对象锁,进入阻塞状态,直到其他线程唤醒它。
Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用
Obj.notify()作用:对对象锁的唤醒操作。notify()调用后,并不是马上就释放对象锁的,而
是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线
程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。
Obj.wait()作用:线程在获取对象锁后,主动释放对象锁,同时本线程休眠,直到有其它线程调用
对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。
下面我们通过一道题目来加深理解。
问题:建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。
代码如下:
public class ThreadPrintABCTest { /* * 建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。 * */ public static void main(String[] args) throws Exception { Object a = new Object(); Object b = new Object(); Object c = new Object(); ThreadPrintABC pa = new ThreadPrintABC("A", c, a); ThreadPrintABC pb = new ThreadPrintABC("B", a, b); ThreadPrintABC pc = new ThreadPrintABC("C", b, c); new Thread(pa).start(); Thread.sleep(100); //确保按顺序A、B、C执行 new Thread(pb).start(); Thread.sleep(100); new Thread(pc).start(); Thread.sleep(100); } }public class ThreadPrintABC implements Runnable{ private String data; private Object pre; private Object self; public ThreadPrintABC(String data,Object pre,Object self){ this.data=data; this.pre=pre; this.self=self; } @Override public void run(){ int count=10; while(count>0){ synchronized(pre){ synchronized(self){ if(data=="C"){ System.out.println(data); }else{ System.out.print(data); } count--; self.notify(); } try{ pre.wait(); }catch(InterruptedException e){ e.printStackTrace(); } } } }}死锁:
两个或两个以上的线程在执行过程当中,由于竞争资源或者彼此之间通信而造成的一种阻塞现象。比如,当线程A调用wait()方法等待线程B的唤醒,而线程B同时也调用wait方法等待线程A的唤醒,这时两个线程将陷入僵持状态,永远处在阻塞状态, 成为死锁进程,即两个线程永远也不会被执行。
sleep方法与wait方法的区别及细节:
sleep()睡眠时,保持对象锁,仍然占有该锁;而wait()睡眠时,释放对象锁。
sleep()使当前线程进入停滞状态(阻塞当前线程),让出CPU的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;
sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。
在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。
wait()方法是Object类里的方法;当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去(释放)了对象的机锁(暂时失去锁,wait(long timeout)超时时间到后还需要返还对象锁);其他线程可以访问;
wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
wiat()必须放在synchronizedblock中,否则扔出”java.lang.IllegalMonitorStateException“异常。
新闻热点
疑难解答