Java基础_学习笔记_16_线程
1、进程与线程
进程,在多任务操作系统中,每个独立执行的程序称为进程,也就是“正在进行的程序”。进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配的最小单元。
线程,是进程中的一部分,是一个程序内部的一条执行线索。在网络或多用户环境下,一个服务器需要接受大量且不确定用户数量的并发请求,为每一个请求创建一个进程显然是行不通的,因此引入了线程。线程是最小的调度单元。通常在一程序中实现多段代码同时交替运行时,需要产生多个线程,并制定每个线程上所要运行的程序代码块,这就是多线程。
进程是操作系统资源分配的单位,而线程是操作系统执行的单位。
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。(转)
当程序启动运行时,就自动产生了一个线程,主函数main就是在这个线程上运行的,当不再产生新的线程时,程序就是单线程的。
1 public class ThreadDemo 2 { 3 public static void main(String [] args) 4 { 5 new TestThread().run();//代码1 6 while(true) 7 { 8 System.out.println("main Thread is running~");//代码2 9 } 10 } 11 } 12 class TestThread 13 { 14 public void run() 15 { 16 while(true) 17 { 18 System.out.println(Thread.currentThread().getName()+" is running"); 19 //currentTread()返回正在执行的线程对象 20 //getName()是返回当前线程对象的名字 21 } 22 } 23 }
上述代码中,没有创建新的线程,只是打印出当前线程。并且,线程运行是线性的,只有当执行完代码1之后,才会运行代码2,但由于1中是无限循环,所以总是打印出“main is running”
2、创建多线程的方法
1)继承Thread类
1 public class ThreadDemo 2 { 3 public static void main(String [] args) 4 { 5 new TestThread().start();//.run();//代码1 6 //调用新建线程启动 7 while(true) 8 { 9 System.out.println("main Thread is running~");//代码2 10 } 11 } 12 } 13 class TestThread extends Thread//继承Thread类新建线程 14 { 15 public void run() 16 { 17 while(true) 18 { 19 System.out.println(Thread.currentThread().getName()+" is running"); 20 //currentTread()返回正在执行的线程对象 21 //getName()是返回当前线程对象的名字 22 } 23 } 24 }
通过继承Thread类创建线程,并在main函数中调用start将该线程启动,也就是说现在有两个线程,所以交替打印出来两条语句,但是两个线程所执行的时间不相同。上面两段代码,也体现了单线程和多线程的区别。
要将一段代码在一个新的线程上运行,该代码需要放在Thread子类的run()中。也就是说,通过这种方式创建新线程,需要编写Thread的子类,并覆盖Thread类中的run();
启动一个新的线程,不是直接调用Thread子类中的run(),而是调用Thread子类对象的start方法。它会产生一个新的线程,并在该线程上运行该Thread类对象中的run(),根据多态性,其实也就是运行Thread子类对象中的run()。
可以不重写子类中的run么?不行,Thread中的run是空的,如果不重写run,根本就不知道创建的这个线程是用来执行什么的;为什么需要编写一个类来继承Thread类,我可以直接new Thread().start()么?不行,这样操作之后,它是调用Thread中的run,同上,创建了一个空的线程。这两种情况打印出来的结果都是一样的,只显示“main Thread is running~”
2)实现Runnable接口
1 public class ThreadDemo 2 { 3 public static void main(String [] args) 4 { 5 //new TestThread().start(); 6 //TestThread是实现了Runnable接口,Runnable中没有start方法 7 //调用新建线程启动 8 Thread tt=new Thread(new TestThread()); 9 tt.start(); 10 while(true) 11 { 12 System.out.println("main Thread is running~"); 13 } 14 } 15 } 16 class TestThread implements Runnable//extends Thread//继承Thread类新建线程 17 { 18 public void run()//线程的代码段,当执行start时,线程从此处开始执行 19 { 20 while(true) 21 { 22 System.out.println(Thread.currentThread().getName()+" is running"); 23 //currentTread()返回正在执行的线程对象 24 //getName()是返回当前线程对象的名字 25 } 26 } 27 }
Runnable接口中只有一个run方法,所以当我们用TestThread类来实现Runnable时,不能直接创建TestThread的匿名对象调用start方法,只有Thread类中才有start()。那如何才能启动线程呢?我们可以通过public Thread(Runnable target)构造函数来实现,从而来调用start()。
3、比较上述两种创建方式
前提:现在要实现一个多线程售票的功能
1 public class ThreadDemo 2 { 3 public static void main(String [] args) 4 { 5 TestThread t=new TestThread(); 6 t.start(); 7 t.start(); 8 t.start(); 9 t.start(); 10 } 11 } 12 class TestThread extends Thread//继承Thread类新建线程 13 { 14 private int tickets=100; 15 public void run()//线程的代码段,当执行start时,线程从此处开始执行 16 { 17 while(tickets>0) 18 { 19 System.out.println(Thread.currentThread().getName()+" the saling ticket number is "+tickets); 20 tickets--; 21 } 22 } 23 }
结果是一个线程t完成所有的工作,而不是像我们预想的那样是有四个线程来并发执行。也就是说,一个线程对象只能启动一个线程,无论调用多少遍start,都只有那一个线程。
将上述代码改动一下:
1 public class ThreadDemo 2 { 3 public static void main(String [] args) 4 { 5 new TestThread().start(); 6 new TestThread().start(); 7 new TestThread().start(); 8 new TestThread().start(); 9 } 10 } 11 class TestThread extends Thread//继承Thread类新建线程 12 { 13 private int tickets=100; 14 public void run()//线程的代码段,当执行start时,线程从此处开始执行 15 { 16 while(tickets>0) 17 { 18 System.out.println(Thread.currentThread().getName()+" the saling ticket number is "+tickets); 19 tickets--; 20 } 21 } 22 }
创建四个线程对象来执行,但是,结果显示,每一个对象都有售出1-100的票。也就是说,这四个线程是独立的,每个对象都要处理100张票。但我们需要的是这四个线程共同来卖出这100张票,那就只能有一个对象。
从这两段代码我们可以知道,如果我们只创建一个线程对象,那只有一个线程来卖100张票;如果我们创建多个线程,那也无法实现,多线程卖票的功能。
我们改用实现Runnable接口
1 public class ThreadDemo 2 { 3 public static void main(String [] args) 4 { 5 TestThread t=new TestThread(); 6 new Thread(t).start(); 7 new Thread(t).start(); 8 new Thread(t).start(); 9 new Thread(t).start(); 10 } 11 } 12 class TestThread implements Runnable 13 { 14 private int tickets=100; 15 public void run()//线程的代码段,当执行start时,线程从此处开始执行 16 { 17 while(tickets>0) 18 { 19 System.out.println(Thread.currentThread().getName()+" the saling ticket number is "+tickets); 20 tickets--; 21 } 22 } 23 }
这样只有一个TestThread对象t对应那100张票,并且有四个线程交替执行。这样就可以满足我们当初的设想了。
从上面的例子我们可以看出,Runnable接口相对于继承Thread类的优点:
1)适合同一代码中多个线程处理同一资源的情况;
2)可以避免由于Java的单继承性带来的局限;
3)有利于程序的健壮性,代码能够被多个线程共享,代码与数据是独立的。当多个线程的执行代码来自于同一个类的实例时,即称它们共享相同的代码。多个线程可以操作相同的数据,与它们的代码无关。
实际工作中,几乎所有的多线程应用都采用实现Runnable接口的方式。
4、多线程的同步
4.1 线程安全
按照我们编写的代码,在实际售票过程中,可能会出现这样的问题。当某线程正在卖最后一张票,刚比较完ticket>0,此时,CPU切换到另一个线程执行,当它把最后一张票卖完之后,打印出正在卖tickets=1的票,再切回来,此时不用再比较ticket是否大于零(之前已经比较过),那么tickets=1会被再次打印出来。我们可以通过Thread.sleep()进行模拟。
public static void sleep(long millis)
throws InterruptedException
由于Thread.sleep()的定义中通过throws关键字声明该方法中有可能引发异常,所以,我们的程序在调用该方法时,必须使用try...catch代码块处理。
1 public class ThreadDemo 2 { 3 public static void main(String [] args) 4 { 5 TestThread t=new TestThread(); 6 new Thread(t).start(); 7 new Thread(t).start(); 8 new Thread(t).start(); 9 new Thread(t).start(); 10 } 11 } 12 class TestThread implements Runnable 13 { 14 private int tickets=100; 15 public void run()//线程的代码段,当执行start时,线程从此处开始执行 16 { 17 while(tickets>0) 18 { 19 try{ 20 Thread.sleep(10); 21 } 22 catch(Exception e) 23 { 24 System.out.println(e.getMessage()); 25 } 26 System.out.println(Thread.currentThread().getName()+" the saling ticket number is "+tickets); 27 tickets--; 28 } 29 } 30 } 31 /* 32 ...... 33 Thread-2 the saling ticket number is 10 34 Thread-1 the saling ticket number is 10 35 Thread-0 the saling ticket number is 8 36 Thread-3 the saling ticket number is 8 37 Thread-1 the saling ticket number is 6 38 Thread-2 the saling ticket number is 6 39 Thread-0 the saling ticket number is 4 40 Thread-3 the saling ticket number is 4 41 Thread-2 the saling ticket number is 2 42 Thread-1 the saling ticket number is 2 43 Thread-3 the saling ticket number is 0 44 Thread-0 the saling ticket number is 0 45 Thread-2 the saling ticket number is -2 46 */
打印出来的部分结果显示,出现了刚才我们预料的那种错误,这就是线程安全的问题。
4.2 同步代码块
既然存在线程安全的问题,那我们要采取何种措施避免该类问题发生?我们希望当一个进程开始了,能够把代码执行完,再调用下一个进程,这样就保证了,不管什么时候,有且只有一个进程在执行这段代码。那就要引入synchronized。
1 public class ThreadDemo 2 { 3 public static void main(String [] args) 4 { 5 TestThread t=new TestThread(); 6 new Thread(t).start(); 7 new Thread(t).start(); 8 new Thread(t).start(); 9 new Thread(t).start(); 10 } 11 } 12 class TestThread implements Runnable 13 { 14 private int tickets=100; 15 String str=new String(""); 16 public void run()//线程的代码段,当执行start时,线程从此处开始执行 17 { 18 while(true) 19 { 20 synchronized(str) 21 { 22 if(tickets>0) 23 { 24 try{ 25 Thread.sleep(10); 26 } 27 catch(Exception e) 28 { 29 System.out.println(e.getMessage()); 30 } 31 System.out.println(Thread.currentThread().getName()+" the saling ticket number is "+tickets); 32 tickets--; 33 } 34 35 } 36 } 37 } 38 }
上述代码中,我们将具有原子性的代码放入synchronized语句内,形成了同步代码块。在同一时刻只有一个线程可以进入同步代码块内运行,只有当该线程离开同步代码块后,其他线程才能进入同步代码块内运行。synchronized语句格式为:
synchronized(object){代码段};//object可以是任意对象
所以我们创建了一个str对象,作为其参数。
synchronized的原理是,任意对象都有一个标志位,且只有“0”、“1”两种状态,初始值为“1”,当执行synchronized(object)语句后,对象的标志位为“0”,直到执行完整个synchronized代码块后又回到1状态。当一个线程执行到synchronized语句时,会去校验对象的标志位,为1则该进程可以去执行,为0则该进程需要暂时阻塞,让出CPU资源,直至那个正在执行的线程执行完同步代码块。标志位专业术语是锁旗标,用于synchronized语句中的对象,称为监视器。当一个线程获得了synchronized语句中的代码块执行权,即意味着它锁定了一个监视器。
实现同步,需要进行多次校验,所以是以牺牲程序的性能为代价的,如果我们能够确定程序没有安全性的问题,就没有必要使用同步控制。
如果将String str=new String("");放入run()内部,会出现什么结果?每个进程都会有一个str对象,就无法实现原子性了,无法同步。
4.3 同步函数
使用同步函数,同样也能实现多线程同步
1 public class ThreadDemo 2 { 3 public static void main(String [] args) 4 { 5 TestThread t=new TestThread(); 6 new Thread(t).start(); 7 new Thread(t).start(); 8 new Thread(t).start(); 9 new Thread(t).start(); 10 } 11 } 12 class TestThread implements Runnable 13 { 14 private int tickets=100; 15 //String str=new String(""); 16 public void run()//线程的代码段,当执行start时,线程从此处开始执行 17 { 18 String str=new String(""); 19 while(true) 20 { 21 22 sale(); 23 /*synchronized(str) 24 { 25 if(tickets>0) 26 { 27 try{ 28 Thread.sleep(10); 29 } 30 catch(Exception e) 31 { 32 System.out.println(e.getMessage()); 33 } 34 System.out.println(Thread.currentThread().getName()+" the saling ticket number is "+tickets); 35 tickets--; 36 } 37 38 }*/ 39 } 40 } 41 42 public synchronized void sale() 43 { 44 if(tickets>0) 45 { 46 try{ 47 Thread.sleep(10); 48 } 49 catch(Exception e) 50 { 51 System.out.println(e.getMessage()); 52 } 53 System.out.println(Thread.currentThread().getName()+" the saling ticket number is "+tickets); 54 tickets--; 55 } 56 } 57 }
4.4 代码块与函数之间的同步
同步代码块、同步函数其实采用的都是synchronized关键字,只要塔门采用同一个对象进行监视,两者之间是可以同步的,此处就不再验证。
当多个线程共享数据时,必须防止一个线程对共享数据仅仅进行部分操作就退出的情况出现,在这种情况下会破坏数据的一致性。如果要使线程1、2同步,线程3、4同步,那就必须要有两个对象,线程1、2由一个对象来控制,线程3、4的同步由另一个对象来控制,且这两个对象之间没有任何关系。
4.5 死锁
因为线程可以阻塞,并且具有同步控制机制可以防止其他线程在锁还没有释放的情况下访问这个对象,就容易产生矛盾。比如,A线程等待B线程,而B线程又在等待线程A,这样就造成了死锁。
一般造成死锁必须同时满足如下四个条件:
1)互斥条件
县城使用的资源至少有一个是不能共享的。
2)请求与保持条件
至少有一个线程必须持有一个资源并且正在等待获取一个当前被其他线程持有的资源。
3)非剥夺条件
分配的资源不能从相应的线程中被强制剥夺。
4)循环等待条件
第一个线程等待其他线程,后者又在等待第一个线程。
要发生死锁,这四个条件必须同时满足。反之,要防止死锁,只需要破坏其中一个条件即可。
5、线程之间的通信
现在要实现一个生产者、消费者的模式,生产者放一个消息,缓冲区中有消息了则不再放,消费者就取走一个消息,缓冲区中没有消息则不再取。
1 class Producer implements Runnable 2 { 3 Q q; 4 public Producer(Q q) 5 { 6 this.q=q; 7 } 8 public void run() 9 { 10 int i=0; 11 while(true) 12 { 13 if(i==0) 14 { 15 q.name="Jane"; 16 try{Thread.sleep(1);}catch(Exception e){System.out.println(e.getMessage());} 17 q.sex="female"; 18 } 19 else 20 { 21 q.name="Mike"; 22 q.sex="male"; 23 } 24 i=(i+1)%2; 25 } 26 } 27 } 28 class Consumer implements Runnable 29 { 30 Q q; 31 public Consumer(Q q) 32 { 33 this.q=q; 34 } 35 public void run() 36 { 37 while(true) 38 { 39 System.out.print(q.name); 40 System.out.println(" "+q.sex); 41 } 42 43 } 44 } 45 class Q 46 { 47 String name; 48 String sex; 49 } 50 public class ThreadCommunication 51 { 52 public static void main(String [] args) 53 { 54 Q q=new Q(); 55 new Thread(new Producer(q)).start(); 56 new Thread(new Consumer(q)).start(); 57 } 58 }
Q即为缓冲区,这是一个雏形,运行之后,发现,Jane居然对应的性别是male。也就是说可能是这种情况,缓冲区之前存的是Mike,male,这一次当线程刚把Jane写进去,时间片就用完了,转到下一个消费者线程去取缓冲区数据,所以打印出来是Jane,female。
也就是说,存在一个原子性的问题,操作还没有进行完,线程就进行了切换,需要加上synchronized。
1 class Producer implements Runnable 2 { 3 Q q; 4 public Producer(Q q) 5 { 6 this.q=q; 7 } 8 public void run() 9 { 10 int i=0; 11 while(true) 12 { 13 synchronized(q) 14 { 15 if(i==0) 16 { 17 q.name="Jane"; 18 try{Thread.sleep(1);}catch(Exception e){System.out.println(e.getMessage());} 19 q.sex="female"; 20 } 21 else 22 { 23 q.name="Mike"; 24 q.sex="male"; 25 } 26 i=(i+1)%2; 27 } 28 29 } 30 } 31 } 32 class Consumer implements Runnable 33 { 34 Q q; 35 public Consumer(Q q) 36 { 37 this.q=q; 38 } 39 public void run() 40 { 41 while(true) 42 { 43 synchronized(q) 44 { 45 System.out.print(q.name); 46 System.out.println(" "+q.sex); 47 } 48 } 49 50 } 51 } 52 class Q 53 { 54 String name; 55 String sex; 56 } 57 public class ThreadCommunication 58 { 59 public static void main(String [] args) 60 { 61 Q q=new Q(); 62 new Thread(new Producer(q)).start(); 63 new Thread(new Consumer(q)).start(); 64 } 65 }
加上同步代码块之后,运行发现还是没有达到最初的设想,从缓冲区了取了多次,而不是放、取交替进行。那么就要实现,当放入数据后,等待消费者来取,取走数据了,生产者再来放入数据;当生产者没有放入数据时,消费者需要等,回到有数据可以取。那就涉及到让线程等待和唤起的操作。
java中通过Object类的wait、notify、notifyAll这些方法来实现线程间的通信:
wait:告诉当前线程放弃监视器并进入睡眠状态,直到其他线程进入同一监视器并调用notify为止
notify:唤醒同一对象监视器中调用wait的第一个线程。
notifyAll:唤醒同一对象监视器中调用wait的所有进程,有最高优先级的线程首先被唤醒并执行
现在我们知道了可以采用wait、notify来休眠和唤醒线程,但是什么情况下,需要线程休眠或者是唤醒呢?当缓冲区中没数据的时候,消费者线程就需要wait,缓冲区中有数据的时候,生产者线程需要wait。那么我们需要用一个变量来标识缓冲区中是否有数据,如程序中的bFull,false表示缓冲区为空
1 class Producer implements Runnable 2 { 3 Q q; 4 public Producer(Q q) 5 { 6 this.q=q; 7 } 8 public void run() 9 { 10 int i=0; 11 while(true) 12 { 13 synchronized(q) 14 { 15 if(q.bFull)//缓冲区不为空,需要等值被取走之后再放入,以免覆盖之前的值 16 try{q.wait();}catch(Exception e){System.out.println(e.getMessage());} 17 if(i==0) 18 { 19 q.name="Jane"; 20 try{Thread.sleep(1);}catch(Exception e){System.out.println(e.getMessage());} 21 q.sex="female"; 22 } 23 else 24 { 25 q.name="Mike"; 26 q.sex="male"; 27 } 28 i=(i+1)%2; 29 q.bFull=true;//放值之后,缓冲区不为空 30 q.notify(); 31 } 32 33 } 34 } 35 } 36 class Consumer implements Runnable 37 { 38 Q q; 39 public Consumer(Q q) 40 { 41 this.q=q; 42 } 43 public void run() 44 { 45 while(true) 46 { 47 synchronized(q) 48 { 49 if(!q.bFull)//缓冲区Q中是空的,需要等放值之后再来输出 50 try{q.wait();}catch(Exception e){System.out.println(e.getMessage());} 51 //让出监视器 52 System.out.print(q.name); 53 System.out.println(" "+q.sex); 54 q.bFull=false;//取值之后,缓冲区为空 55 q.notify(); 56 } 57 } 58 59 } 60 } 61 class Q 62 { 63 String name; 64 String sex; 65 boolean bFull=false;//标识缓冲区中内容是否为空,false表示为空 66 } 67 public class ThreadCommunication 68 { 69 public static void main(String [] args) 70 { 71 Q q=new Q(); 72 new Thread(new Producer(q)).start(); 73 new Thread(new Consumer(q)).start(); 74 } 75 }
有两个地方需要注意:
bFull是Q类中的成员,需要使用对象来引用;
wait、notify、notifyAll这三个方法只能在synchronized方法中使用,即无论线程调用一个对象的wait还是notify方法,该线程必须要先得到该对象的锁旗标,这样,notify只能唤醒同一个对象监视器中调用wait的线程,使用多个对象监视器,我们就可以分组有多个wait、notify的情况。所以需要使用对象引用wait和notify
将上述完整代码再做一下整合,更好地体现面向对象的思想
1 class Producer implements Runnable 2 { 3 Q q; 4 public Producer(Q q) 5 { 6 this.q=q; 7 } 8 public void run() 9 { 10 int i=0; 11 while(true) 12 { 13 /*synchronized(q) 14 { 15 if(q.bFull)//缓冲区不为空,需要等值被取走之后再放入,以免覆盖之前的值 16 try{q.wait();}catch(Exception e){System.out.println(e.getMessage());} 17 if(i==0) 18 { 19 q.name="Jane"; 20 try{Thread.sleep(1);}catch(Exception e){System.out.println(e.getMessage());} 21 q.sex="female"; 22 } 23 else 24 { 25 q.name="Mike"; 26 q.sex="male"; 27 } 28 i=(i+1)%2; 29 q.bFull=true;//放值之后,缓冲区不为空 30 q.notify(); 31 }*/ 32 if(i==0) 33 q.put("Jane","female"); 34 else 35 q.put("Mike","male"); 36 i=(i+1)%2; 37 38 } 39 } 40 } 41 class Consumer implements Runnable 42 { 43 Q q; 44 public Consumer(Q q) 45 { 46 this.q=q; 47 } 48 public void run() 49 { 50 while(true) 51 { 52 /*synchronized(q) 53 { 54 if(!q.bFull)//缓冲区Q中是空的,需要等放值之后再来输出 55 try{q.wait();}catch(Exception e){System.out.println(e.getMessage());} 56 //让出监视器 57 System.out.print(q.name); 58 System.out.println(" "+q.sex); 59 q.bFull=false;//取值之后,缓冲区为空 60 q.notify(); 61 }*/ 62 q.get(); 63 } 64 65 } 66 } 67 class Q 68 { 69 private String name; 70 private String sex; 71 private boolean bFull=false;//标识缓冲区中内容是否为空,false表示为空 72 public synchronized void put(String name,String sex) 73 { 74 if(bFull)//缓冲区不为空,需要等值被取走之后再放入,以免覆盖之前的值 75 try{wait();}catch(Exception e){System.out.println(e.getMessage());} 76 this.name=name; 77 try{Thread.sleep(1);}catch(Exception e){System.out.println(e.getMessage());} 78 this.sex=sex; 79 bFull=true;//放值之后,缓冲区不为空 80 notify(); 81 } 82 public synchronized void get() 83 { 84 if(!bFull)//缓冲区Q中是空的,需要等放值之后再来输出 85 try{wait();}catch(Exception e){System.out.println(e.getMessage());} 86 //让出监视器 87 System.out.print(name); 88 System.out.println(" "+sex); 89 bFull=false;//取值之后,缓冲区为空 90 notify(); 91 } 92 93 } 94 public class ThreadCommunication 95 { 96 public static void main(String [] args) 97 { 98 Q q=new Q(); 99 new Thread(new Producer(q)).start(); 100 new Thread(new Consumer(q)).start(); 101 } 102 }
6、线程的生命周期
1)当调用start后,线程不是立马被调用,而是标识为Runnable状态,等到调度器将CPU分配给它;
2)当线程在执行过程中,没有遇到任何阻碍,运行完成直接结束,也就是run方法执行完毕;
3)请求锁旗标却一直得不到,这时候它要等待对象的锁旗标,得到锁旗标后才能进入Runnable状态
4)遇到wait方法,它会被放入等待池中继续等待,直到有notify或interrupt方法执行,他才会被唤醒或者打断开始等待对象锁旗标,等到锁旗标后进入Runnable状态继续执行
注意:Thread.interrupt()不会中断一个正在运行的线程。这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,那么,它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。
7、如何控制线程的生命
控制线程的生命周期有很多方法,如:suspend(将线程挂起),resume(将挂起的线程恢复,与suspend成对使用),stop。但这三个方法都停用了,不使用suspend,resume是因为:
1)会导致死锁的发生;
2)它允许一个线程A通过直接控制另外一个线程B的代码来控制线程B
stop虽然不会导致死锁,但是如果一个线程正在操作共享数据段,操作过程没有完成就stop的话,将会导致数据的不完整性。
那我们不用stop,怎么让一个线程停止运行?我们知道,当run方法结束,我们的线程就停止运行。
1 class ThreadLife 2 { 3 public static void main(String [] args) 4 { 5 ThreadTest t=new ThreadTest(); 6 new Thread(t).start(); 7 for(int i=0;i<100;i++) 8 { 9 if(i==50) 10 t.stopMe(); 11 System.out.println("mainThread is running "+i); 12 } 13 } 14 } 15 class ThreadTest implements Runnable 16 { 17 private boolean bFlag=false; 18 public void stopMe() 19 { 20 bFlag=true; 21 } 22 public void run() 23 { 24 while(!bFlag) 25 { 26 System.out.println(Thread.currentThread().getName()+" is running~"); 27 } 28 } 29 }
上述代码中,采用了一个标志,当bFlag为false的时候,mainThread和Thread-0交替运行,当i等于50,即bFlag为true之后,不再去执行Thread-0