Java同步和多线程1:多线程的基本操作及synchronized的应用
其实Java多线程中,锁只是一个很抽象的概念。锁是为了实现互斥和同步而设的。“锁”打个比方,获取锁可认为是“获取做某个事情的权限”,而“释放锁”可以认为是把做某件事情的权限交给别人了。也可以这样认为,“锁”锁住的是某个事物。获取锁指的是获得解开这个锁的钥匙,可以对这个事情进行操作,而释放锁是把这条钥匙给别人,或者放回某个钥匙柜子里,等别人来取。Java中锁的机制,是为了在多线程中实现同步互斥。归根到底,Java中线程间的通信,就是同步互斥的具体应用,也可以说是同步互斥的另一个角度的阐述。如果对于单线程的程序,怎么样都无所谓了,也不需要同步的方案。但是对于多线程,如果不熟悉同步互斥的编程,会很容易造成程序的错误。在Java中,上锁的机制主要有几个关键字volatile、synchronized、final以及juc包。其实这里举的例子很多都不是很好,因为我们从代码上根本看不出来哪些操作是原子的,哪些不是原子的。但是,知道原理即可。
一、Java多线程
在Java中,记住是有个主线程的。这个主线程不需要我们自己创建,是一运行就有的了。而我们自己要创建的是我们自己的线程。这是理所当然的,如果没有主线程,那怎么开始我们的程序呢。在Java中,一般通过两种方式来创建多线程。一个是创建一个我们自己的类(后面都命名为MyThread),这个类要继承Thread类,同时覆盖run方法。实例化这个我们自己的类,就创建好一个线程了。另一个方法是创建一个我们自己的类,这个类要实现Runnable接口,同时覆盖run方法。通过new Thread(MyThread())来创建线程。这里要记住,创建线程时候,一个线程是以一个对象的形式呈现的。下面我们以售票员为例展示如何创建多个线程(售票员这个例子很经典。毕竟,每年我们都深受春运之苦。售票员演示了如何在票数一定的情况下,各个不同的窗口卖票的过程)。那如何让线程开始跑呢?很容易,调用对象的start方法即可。不能直接调用run方法,那不是让这个线程启动,而只是执行了一次run方法而已。
使用第一种方法:
public class ThreadTest {
public static int restTickets=10;//一共有10张余票
public static void main(String[] args) {
MyThread t1=new MyThread("Thread1");
MyThread t2=new MyThread("Thread2");
t1.start();
t2.start();
}
}
class MyThread extends Thread{
private String name;
public MyThread(String name) {
this.name=name;
}
public void run() {
while(ThreadTest.restTickets>=0) {
System.out.println(this.name+" sold a ticket. Remain ticket: "+ThreadTest.restTickets);
ThreadTest.restTickets--;
}
}
}
执行的结果如下:
Thread1 sold a ticket. Remain ticket: 10
Thread1 sold a ticket. Remain ticket: 9
Thread1 sold a ticket. Remain ticket: 8
Thread1 sold a ticket. Remain ticket: 7
Thread2 sold a ticket. Remain ticket: 7
Thread1 sold a ticket. Remain ticket: 6
Thread2 sold a ticket. Remain ticket: 5
Thread2 sold a ticket. Remain ticket: 3
Thread2 sold a ticket. Remain ticket: 2
Thread2 sold a ticket. Remain ticket: 1
Thread2 sold a ticket. Remain ticket: 0
Thread1 sold a ticket. Remain ticket: 4
使用第二种方法:
public class ThreadTest {
public static int restTickets=10;//一共有10张余票
public static void main(String[] args) {
Thread t1=new Thread(new MyThread("Thread1"));
Thread t2=new Thread(new MyThread("Thread2"));
t1.start();
t2.start();
}
}
class MyThread implements Runnable{
private String name;
public MyThread(String name) {
this.name=name;
}
public void run() {
while(ThreadTest.restTickets>=0) {
System.out.println(this.name+" sold a ticket. Remain ticket: "+ThreadTest.restTickets);
ThreadTest.restTickets--;
}
}
}
执行的结果如下:
Thread1 sold a ticket. Remain ticket: 10
Thread2 sold a ticket. Remain ticket: 10
Thread1 sold a ticket. Remain ticket: 9
Thread2 sold a ticket. Remain ticket: 8
Thread1 sold a ticket. Remain ticket: 7
Thread1 sold a ticket. Remain ticket: 5
Thread2 sold a ticket. Remain ticket: 6
Thread1 sold a ticket. Remain ticket: 4
Thread2 sold a ticket. Remain ticket: 3
Thread1 sold a ticket. Remain ticket: 2
Thread1 sold a ticket. Remain ticket: 0
Thread2 sold a ticket. Remain ticket: 1
很明显,这个结果是有问题的。跟我们预想的,一张张买票不一样!这是因为,在上面的例子中,完全没有采用同步的方法。这就导致时间片在两个线程直接切换的同时,操作不是原子的,导致线程之间共享的数据紊乱(各种指令重排序、时间片分配等原因)。因此,我们下面要采用一些同步的措施,改变这个现象。
二、synchronized关键字
在java中,每一个对象有且仅有一个同步锁。这也意味着,同步锁是依赖于对象而存在(也就是所谓实例锁。后面可以看到,其实还有可以对整个类上锁的全局锁)。而synchronized就是对象的一个同步锁。这个关键字的作用是,被这个关键字修饰的部分代码,称为互斥区。当某个线程在访问这段代码的时候,其他线程对这段代码的访问是互斥的,被称为临界区。通俗点讲,临界区就是某个对象的一个加了锁的一段代码。假设当前只有两个线程t1和t2。当线程t1在访问某个对象的这段代码的时候,如果没访问完,突然线程t1的时间片被操作系统剥夺了,时间片给了t2。t2也想访问某个对象的这段代码。这个时候,t2是不被允许访问这段代码的。t2就会被阻塞,然后时间片交还给t1,知道t1执行完这段代码了,t1才释放锁,t2才有权访问这段代码。这就很好地避免了临界区内代码被其他线程破坏的可能性。记住,这段临界区不一定在程序上看起来是连续的。一个对象,使用synchronized关键字,有且仅可以划定一个临界区。换一个角度来讲,临界区,看起来就像是一个原子操作!要不不做,要不全部一定做完!为了验证synchronized的原子性,我们来做个试验。
public class ThreadTest {
public static testobj atTestobj=new testobj();
public static void main(String[] args) {
Thread t1=new Thread(new test1(),"t1");
Thread t2=new Thread(new test2(),"t2");
t1.start();
t2.start();
}
}
class testobj{
public void dotestobj() throws InterruptedException {
while(true) {
//synchronized(this) {//没加锁
System.out.println(Thread.currentThread().getName());
System.out.println("dotestobj lock1");
System.out.println(Thread.currentThread().getName());
System.out.println("dotestobj lock2");
Thread.sleep(1000);
//}
}
}
}
class test1 implements Runnable{
public void run() {
//System.out.println("test1");
try {
ThreadTest.atTestobj.dotestobj();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class test2 implements Runnable{
public void run() {
//System.out.println("test2");
try {
ThreadTest.atTestobj.dotestobj();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
在这里,testobj类的方法没有加锁。我们运行代码,会发现有这样一段输出:
t1
dotestobj lock1
t1
dotestobj lock2
t2
t1
dotestobj lock1
t1
dotestobj lock2
dotestobj lock1
t2
dotestobj lock2
t1
dotestobj lock1
t1
dotestobj lock2
明显,testobj里面的语句是没有原子性的。如果是具有原子性的,那必然
t1
dotestobj lock1
t1
dotestobj lock2
或者
t2dotestobj lock1
t2
dotestobj lock2
是成对出现的。而如果加了锁(也就是把注释去掉),可以发现一定是成对出现的。当然,这里其实还有有瑕疵,就是线程的run方法最好也是要上锁的。当然这个是后话了。synchronized关键字有三种使用方法:修饰代码段、修饰方法、修饰一个对象
1、修饰代码段和一个对象
修饰代码段的话,固定的格式是:
synchronized(this) {
code...
}
这里this表示这个对象(当然,如果想修饰一个对象,那就填进去一个对象),而这个代码段是临界区。注意,这个对象的非临界区依然是可以被非互斥访问的。
public class ThreadTest {
public static void main(String[] args) {
SellTicket sellTicket=new SellTicket(10);
Thread t1=new Thread(sellTicket,"t1");
Thread t2=new Thread(sellTicket,"t2");
t1.start();
t2.start();
}
}
class SellTicket implements Runnable{
private int restTickets=0;//把票放在这里
public SellTicket(int num) {
this.restTickets=num;
}
public void run() {
synchronized(this) {
try {
for(int i=1;i<=5&&this.restTickets>0;i++) {//每次卖5张票
this.restTickets--;
//Thread.currentThread().getName()方法的作用是获得当前活动线程的名字
System.out.println(Thread.currentThread().getName()+" sold a ticket, rest "+this.restTickets+" tickets");
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
执行结果:
t1 sold a ticket, rest 9 tickets
t1 sold a ticket, rest 8 tickets
t1 sold a ticket, rest 7 tickets
t1 sold a ticket, rest 6 tickets
t1 sold a ticket, rest 5 tickets
t2 sold a ticket, rest 4 tickets
t2 sold a ticket, rest 3 tickets
t2 sold a ticket, rest 2 tickets
t2 sold a ticket, rest 1 tickets
t2 sold a ticket, rest 0 tickets
可以看出,两个线程,分别卖出5张票。
2、修饰方法
直接在方法前加上关键字即可
public synchronized void run() {
try {
for(int i=1;i<=5&&this.restTickets>0;i++) {
this.restTickets--;
//Thread.currentThread().getName()方法的作用是获得当前活动线程的名字
System.out.println(Thread.currentThread().getName()+" sold a ticket, rest "+this.restTickets+" tickets");
}
}catch (Exception e) {
e.printStackTrace();
}
}
3、修饰静态static方法,就是给类加锁(也就是给整个类加锁,对这个类的对象都有用。对class对象加锁也是这样的效果)。
我们知道了锁的机制后,回到原来的话题,如何写一个让线程t1,t2一起卖票,知道卖完的程序呢?我们可以给run方法内判断加锁,这样一个判断就是一个临界区。
public class ThreadTest {
public static void main(String[] args) {
SellTicket sellTicket=new SellTicket(100);
Thread t1=new Thread(sellTicket,"t1");
Thread t2=new Thread(sellTicket,"t2");
t1.start();
t2.start();
}
}
class SellTicket implements Runnable{
private int restTickets=0;
public SellTicket(int num) {
this.restTickets=num;
}
public void run() {
while(true) {
synchronized(this) {
try {
if(this.restTickets>0) {
this.restTickets--;
System.out.println(Thread.currentThread().getName()+" sold a ticket, rest "+this.restTickets+" tickets");
}
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
执行结果:
...
t1 sold a ticket, rest 50 tickets
t1 sold a ticket, rest 49 tickets
t1 sold a ticket, rest 48 tickets
...
关于synchronized关键字暂时说到这里。要注意,这里获取锁和释放锁这两个抽象概念,要在代码中理解。获取锁,也就是进入互斥区。释放锁,也就是执行完了临界区的代码。如果synchronized是对不同的对象上锁的,当然是不同的锁了。另外,synchronized关键字是无法被继承的。synchronized对象锁锁住的是对象,但是起作用的只是对象中的临界区。这个时候要注意,虽然是临界区的代码才会上锁,但是,事实上这个锁是上在对象或者类中的!如果一个锁把某个对象上锁了,但是这个对象内部的代码块并没有synchronized块,那这样这个对象的全部代码都会被上锁!看一个例子:
public class MultiThread {
public static void main(String[] args){
test myTest=new test();
mythread t1=new mythread(myTest);
mythread t2=new mythread(myTest);
(new Thread(t1,"t1")).start();
(new Thread(t2,"t2")).start();
}
}
class test{
public void start() throws InterruptedException{
for(int i=0;i<100;i++){
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
class mythread implements Runnable{
private test t;
public mythread(test t){
this.t=t;
}
public void run(){
try {
//synchronized (t) {
t.start();//不对t上锁
//}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行如下:
t1 0
t2 0
t1 1
t2 1
t1 2
t2 2
t1 3
t2 3
可以看到,是交错运行的,此时没有加锁。而如果对t对象加了锁,注意,t类中并没有synchronized代码块。
try {
synchronized (t) {//对t对象加锁
t.start();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
运行结果:
t1 0
t1 1
t1 2
t1 3
t1 4
t1 5
t1 6
t1 7
t1 8
可以发现,锁已经加上去了。
二、wait,notify,notifyAll方法
1、为了后面继续了解Java多线程中同步的知识,我们必须了解更多关于线程的知识。我们首先来了解一下线程的状态。在Java中,线程的状态不多,总共有5种状态。这个状态转移图很重要,我们后面对线程操作的各种分析,都是基于这个状态转移图的。
新建(new):当线程对象被创建new后,就进入了新建状态;
就绪状态(runnable):当线程调用了start方法后,就进入了就绪状态,也叫可运行状态。此时,线程可以获得CPU,但线程不一定就立刻被分配CPU时间片!明白这点很重要!
运行状态(running):被分配了CPU时间,正在执行。
阻塞状态(blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。阻塞态有很多种,我们后面再详细分析。
死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
我们下面来看一个例子,我们来更进一步说明线程之间的关系。
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
System.out.println("current thread is "+Thread.currentThread().getName());
Thread thread=new Thread(new newThread(),"newthread");
thread.start();//先令线程newthread进入就绪态
for(int i=0;i<1000;i++) {//这个语句的顺序在newthread线程进入就绪态后
System.out.println("current thread is "+Thread.currentThread().getName()+" "+i);
}
}
}
class newThread implements Runnable{
public void run() {
for(int i=0;i<50;i++) {
System.out.println("current thread is "+Thread.currentThread().getName()+" "+i);
}
}
}
我们会发现,执行结果里,有一次是:
...
current thread is main 325
current thread is main 326
current thread is newthread 0
current thread is main 327
current thread is main 328
current thread is main 329
...
这说明,主线程执行了thread.start()语句后,CPU的时间片并没有立刻交给newthread线程,而是继续在主线程中运行。而运行到326后,才有某个时间片交给了newthread线程,然后又交还给了主线程。线程调度,是由操作系统内核来完成的,我们无从得知操作系统会怎么调度线程。所以我们在编程中要特别注意同步。另外,线程也是有层次关系的。如果一个线程t1,创建了另一个线程t2,那t1就是t2的父线程。这点在后面讲join()方法时候要知道。
这个时候,我们来看wait,notify,notifyAll方法。这几个方法是定义在Object类中的,是Object类的final native方法。所以这些方法不能被子类重写。要记住,对这些方法来说,他们针对作用的对象,是线程,但是却是由某个Object类调用的(其实就是随便一个Object。由于Object是所有类的父类,所以就是随便一个对象调用)。假如一个进程t,调用了某个对象的这些方法。此时这个对象就是这个进程的对象监视器。这些方法,就作用于这个对象监视器监视的进程t。
我们先来看wait()方法。wait()的作用是让当前调用这个方法的线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁,直到它被其他线程通过notify()或者notifyAll()唤醒。该方法只能在同步方法或同步代码段中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。另外,还有void wait(long millis)和void wait(long millis, int nanos),他们导致线程进入等待状态直到它被通知或者经过指定的时间。这些方法只能在同步方法或同步代码段中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。
notify()随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态。该方法只能在同步方法或同步块内部调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。而notifyAll()解除所有那些在该对象上调用wait方法的线程的阻塞状态。该方法只能在同步方法或同步块内部调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。要注意的是,notify()和notifyAll()方法执行后,并不是立刻释放锁,而是先执行完这个同步区代码,再唤醒等待的进程。
从这里看出,其实这几个方法的用处是强行用人工剥夺某个线程的锁,把这把锁交给另外一些线程。我们举个例子。
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
System.out.println("current thread is "+Thread.currentThread().getName());
Thread thread=new Thread(new newThread(),"newthread");
thread.start();//先令线程newthread进入就绪态
for(int i=1;i<=5;i++) {//根据经验,其实在这里main仍然会一直获得CPU时间
System.out.println("current thread is "+Thread.currentThread().getName()+" "+i);
if(i==2) {
synchronized (thread) {
thread.wait();//阻塞
}
}
}
}
}
class newThread implements Runnable{
public synchronized void run() {
for(int i=1;i<=5;i++) {
System.out.println("current thread is "+Thread.currentThread().getName()+" "+i);
if(i==3)//唤醒
notify();
}
}
}
运行结果:
current thread is main
current thread is main 1
current thread is main 2
current thread is newthread 1
current thread is newthread 2
current thread is newthread 3
current thread is newthread 4
current thread is newthread 5
current thread is main 3
current thread is main 4
current thread is main 5
这里我们也就知道了,为什么这几个函数要定义在Object中了。因为Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。而对象的同步锁有且仅有一个。所以这样的操作直观而又简便。notify()等是依旧对象的同步锁来唤醒线程的。假设,t1唤醒了t2,但是此时仍旧是t1在持有该对象监视器的锁。t2虽然已经被唤醒,但是并不一定能立刻执行。必须等t1释放了锁后,t2才会执行。
正因为wait()会令线程释放锁,所以有时候会导致线程不安全!例子:
public class MultiThread {
public static void main(String[] args){
test myTest=new test();
mythread t1=new mythread(myTest);
mythread t2=new mythread(myTest);
(new Thread(t1,"t1")).start();
(new Thread(t2,"t2")).start();
}
}
class test{
private int i=0;
public void start() throws InterruptedException{
for(;i<100;i++){
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==5){
i++;
this.wait();//这个时候,t1释放锁了!
}
if(i==10)
this.notify();//这个时候锁已经被t2占领了,就算notify了t1,t2再也没有机会运行了
}
}
}
class mythread implements Runnable{
private test t;
public mythread(test t){
this.t=t;
}
public void run(){
try {
synchronized (t) {//对t加锁
t.start();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
t1 0t1 1
t1 2
t1 3
t1 4
t1 5
t2 6
t2 7
t2 8
可以看到,后面t2读取到的是6开始的数。所以,这在编程中有可能导致线程不安全,这点要注意。因为一个线程t被wait后,他重新获得锁开始执行,要从被wait的地方开始,这样可能有一些临界区的数据已经被其他线程更改了。
三、yield()
yield()方法是Thread类的静态方法,而且也是一个本地方法。他的作用是让当前线程从运行状态进入就绪态,而时间片有可能会交给另一个线程(优先级越高,交给的可能性越大)。既然是进入就绪态,那这就有一个问题,就是操作系统也许并不会把时间片给其他线程,而是又给回给这个进入就绪态的线程了。而且yield()是不会令线程释放锁的。
既然说到线程的优先级,也可以多说几句。在Java中,线程总是有优先级的,范围是1-10,默认是5。JVM线程调度程序是基于优先级的抢先调度机制。在大多数情况下,当前运行的线程优先级将大于或等于线程池中任何线程的优先级。但这仅仅是大多数情况。 当设计多线程应用程序的时候,一定不要依赖于线程的优先级。因为线程调度优先级操作是没有保障的,只能把线程优先级作用作为一种提高程序效率的方法,但是要保证程序不依赖这种操作。我们来看看下面的例子:
public class ThreadTest {
public static obj myobj1=new obj("1");
public static obj myobj2=new obj("2");
public static void main(String[] args) {
Thread t1=new Thread(new test1(),"t1");
Thread t2=new Thread(new test2(),"t2");
t1.start();
t2.start();
}
}
class obj{
private String name="";
public obj(String name) {
this.name=name;
}
public synchronized void objwork() {
System.out.println(Thread.currentThread().getName()+" obj"+this.name+" before yield");
Thread.yield();
System.out.println(Thread.currentThread().getName()+" obj"+this.name+" after yield");
}
}
class test1 implements Runnable{
public void run() {
synchronized (ThreadTest.myobj1) {
ThreadTest.myobj1.objwork();
}
}
}
class test2 implements Runnable{
public void run() {
synchronized (ThreadTest.myobj2) {
ThreadTest.myobj2.objwork();
}
}
}
从这里可以看出,程序的执行只有两种可能性:如果yeild()完全不起作用,那线程会继续执行,那跟没有这行语句没有什么区别,程序输出仅仅有可能是
t1 obj1 before yield
t1 obj1 after yield
t2 obj2 before yield
t2 obj2 after yield
或者
t2 obj2 before yield
t2 obj2 after yield
t1 obj1 before yield
t1 obj1 after yield
但是事实上,有这样一种输出,这就表明,CPU时间让出去了:
t2 obj2 before yield
t1 obj1 before yield
t2 obj2 after yield
t1 obj1 after yield
四、sleep()
sleep()也是Thread的静态方法。他有两个实现,Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)。他的作用是让当前线程进入阻塞状态持续一段时间,这期间时间片一定会让给其他线程。然后过了这个特定的时间后该线程变回就绪态。可以看出,线程休眠的时间一定大于等于这个设定的时间。但是他不会释放锁。
public class ThreadTest {
public static void main(String[] args) {
Thread t1=new Thread(new test1(),"t1");
Thread t2=new Thread(new test2(),"t2");
t1.start();
t2.start();
}
}
class test1 implements Runnable{
public void run() {
for(int i=0;i<20;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if(i%4==0)//每当可以被4整除,就sleep,把时间片让给另一个线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class test2 implements Runnable{
public void run() {
for(int i=0;i<20;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if(i%4==0)
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
五、join()
join()是Thread类中的方法(不是static)。其实他的主要作用就是同步,令本来交替执行的线程变得顺序执行。例如,在这个例子中,main线程调用了线程son的join方法,会令main线程进入阻塞态,而son线程继续运行,直到son线程运行完了,main线程才会重新运行。当然,join一定要在start后面调用,否则是没啥用的。Join方法实现是通过wait实现的。 当main线程调用son.join时候,main线程会获得线程对象son的锁(wait意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程 ,比如退出后。这就意味着main 线程调用son.join时,必须能够拿到线程son对象的锁。
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread son=new Thread(new test(),"main's son");
son.start();
System.out.println("main's son join, and actually this time is still "+Thread.currentThread().getName());
son.join();
for(int i=0;i<1000;i++) {
System.out.println(Thread.currentThread().getName()+" is running"+i);
}
}
}
class test implements Runnable{
public void run() {
for(int i=0;i<1000;i++) {
System.out.println(Thread.currentThread().getName()+" is running"+i);
}
}
}
这个程序的执行如下:
main's son join, and actually this time is still main
main's son is running0
main's son is running1
main's son is running2
main's son is running3
...
main's son is running998
main's son is running999
main is running0
main is running1
main is running2
...
main is running998
main is running999
这说明,main线程只执行了打印第一句的时间片,就立刻把所有的时间片交给了son线程,son线程执行完后,再开始main线程。
六、终止线程
如果要终止线程,一般都采用interrupt()方法。与线程的中断有关的方法有几个,都是Thread类的方法,分别是public void interrupt(),public static boolean interrupted(), public boolean isInterrupted()。
1、中断处于阻塞态线程
interrupt()方法的作用是将中断标记位置位为true。若线程处于阻塞状态,此时调用线程的interrupt()将线程的中断标记设为true。由于处于阻塞状态,中断标记会被清除,同时产生一个InterruptedException异常。将InterruptedException放在适当的位置进行处理就能终止线程。
2、中断处于运行态线程
调用线程的interrupt()方法,会使线程的中断标记为true,即isInterrupted()会返回true。然后通过判断,就能终止线程。
3、
interrupted() 和 isInterrupted()都能够用于检测对象的“中断标记”。区别是,interrupted()除了返回中断标记之外,它还会清除中断标记(即将中断标记设为false);而isInterrupted()仅仅返回中断标记。
上一篇: 自己实现简易版本的Http服务器
下一篇: ubuntu 16.04 安装有道词典