Java多线程之基础篇(二)

上一篇介绍了Java多线程的基础概念和synchronized关键字,这篇继续介绍Java多线程的其他关键字和重要的方法。

文章目录

    • 一、volatile关键字
      • 1.1 Java内存模型
      • 1.2 基本概念
      • 1.3 Volatile原理
    • 二、对线程等待和唤醒的方法
      • 2.1 常用方法介绍
      • 2.2 wait()和notify()示例
      • 2.3 wait(long timeout)和notify()示例
      • 2.4 wait()和notifyAll()示例
      • 2.5 为什么notify()、wait()等函数定义在Object类中而不是Thread类中
    • 三、线程让步和休眠
      • 3.1 线程让步
      • 3.2 yield()和wait()的比较
      • 3.3 线程休眠
      • 3.4 sleep()和wait()的比较
    • 四、join()方法和interrupt()方法
      • 4.1 加入一个线程
      • 4.2 join()源码分析和实例
      • 4.3 interrupt()方法
      • 4.3 线程终止的方式
        • 4.3.1终止处于“阻塞状态”的线程
        • 4.3.1终止处于“运行状态”的线程
      • 4.4 终止线程的实例
        • 4.4.1通过“中断标记”终止线程的实例
        • 4.4.2通过“额外添加标记”终止线程的实例
      • 4.5 interrupt()和isInterrupted()的区别
    • 五、线程优先级和守护线程

一、volatile关键字

1.1 Java内存模型

简单了解一下Java内存模型(JMM,Java Memory Model)
Java多线程之基础篇(二)_第1张图片

所有的变量都是存储在主内存中,每个线程都是独立的工作内存,里面保存该线程使用到的变量的副本。线程对共享共享变量的所有操作必须在自己的工作内存,不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值传递需要通过主内存来完成。例如,线程1对共享变量的修改,要想被线程2及时看到,必须经历如下两个过程:
(1)把工作内存1中更新过的变量刷新到主内存中。
(2)将主内存中最新的共享变量的值更新到线程2中。

1.2 基本概念

可见性:
指线程之间的可见性,一个线程修改状态对另一个线程是可见的。volatile修饰的变量就具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是,volatile修饰的变量不具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就是这个操作同样存在线程安全问题。在Java中volatile、synchronized和final具有可见性。

原子性:
JVM中执行的最小单位,具有不可分割性。比如,int a =0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。但是,a++;这个实际上就是a=a+1;是可分割的,所以不具有原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。Java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。在 Java 中 synchronized 和在 lock、unlock 中操作保证原子性。

有序性:
Java语言提供volatile和synchronized两个关键字来保证线程之间操作的有序性,volatile是因为其本身有禁止指令重排的,synchronized是由一个变量在同一时刻只允许一个线程对齐进行操作,也就决定了持有同一个对象锁的两个同步块只能串行执行。

1.3 Volatile原理

Java语言提供了一种稍微同步机制,即volatile变量,用来确保将变量的更新操作通知其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作仪器重排序。volatile变量不会被缓存在寄存器或者其他处理器不可见的地方,因此在读取volatile类型变量是总会返回最新写入的值。
在访问volatile变量是不会执行加锁操作,因此也就不会重新执行线程阻塞,volatile变量是一种比synchronized关键字轻量级的同步机制。
总的来说,当一个变量被volatile修饰后,不但具有可见性,而且还禁止指令重排。volatile的读性能消耗与普通变量几乎相同,但是写操作就慢一些,因为它要保证本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。

二、对线程等待和唤醒的方法

2.1 常用方法介绍

在Object.java中,定义了wait(),notify()和notifyAll()等接口。wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。

API接口 API说明
notify() 唤醒在此对象监视器上等待的单个线程
notifyAll() 唤醒在此对象监视器上等待的所有线程
wait() 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或是notifyAll()方法”,当前线程被唤醒(进入“就绪状态”)
wait(long timeout) 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或notifyAll()方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)
wait(long timeout,int nanos) 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)

2.2 wait()和notify()示例

public class WaitTest {

	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		class ThreadA extends Thread{
			
			public ThreadA(String name){
				super(name);
			}
			
			@Override
			public void run() {
				synchronized(this){
					System.out.println(Thread.currentThread().getName()+" 正在执行");
					
					System.out.println(Thread.currentThread().getName()+" 现在要执行, call notify()");
					notify();
				}
				
			}
		}

		  ThreadA t1 = new ThreadA("t1");
		  
		  synchronized(t1){
			  //启动线程t1
			  System.out.println(Thread.currentThread().getName()+" start t1");
			  t1.start();
			  			 
			  try {
				//主线程等待t1通过 notify()唤醒。
				System.out.println(Thread.currentThread().getName()+" wait()");
				t1.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			  
			  System.out.println(Thread.currentThread().getName()+" continue");
			  
		  }
	}

}

运行结果:

main start t1
main wait()
t1 正在执行
t1 现在要执行, call notify()
main continue

结果解释:
(1)主线程通过new ThreadA(“t1”);新建线程1,随后通过synchronized(this)获取“t1对象的同步锁”。
(2)然后调用t1.start()启动线程1;主线程执行t1.wait()释放“t1对象的锁”并且进入“等待(阻塞)状态”,等待t1对象上的线程通过notify()或notifyAll()将其唤醒;
(3)线程t1运行之后,通过synchronized(this)获取“当前对象的锁”,接着通过调用notify()唤醒“当前对象上的等待线程”,也就是唤醒主线程;
(4)线程t1运行完毕之后,释放“当前对象的锁”,紧接着,主线程获取“t1对象的锁”,然后接着运行。

注意:t1.wait()为啥不是让“线程t1”等待,而是让“主线程main”等待?
JDK解释中说,wait()的作用是让“当前线程”等待,而“当前线程”是指正在CPU上运行的线程!
这也就意味着,虽然t1.wait()是通过“线程t1”调用wait()方法,但是调用t1.wait()的地方是在“主线程main”中。而主线程必须是“当前线程”,也就是运行状态,才可以执行t1.wait()。所以“当前线程”是“主线程mian”。因此,t1.wait()是让“主线程”等待,而不是“线程t1”。

2.3 wait(long timeout)和notify()示例

public class WaitTimeoutTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		class ThreadA extends Thread{
			public ThreadA(String name){
				super(name);
			}
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName()+" run");
				//死循环
				while(true)
					;
			}
		}
		
		ThreadA t1 = new ThreadA("t1");
		synchronized(t1){
			try {
                // 启动“线程t1”
                System.out.println(Thread.currentThread().getName() + " start t1");
                t1.start();

                // 主线程等待t1通过notify()唤醒 或 notifyAll()唤醒,或超过3000ms延时;然后才被唤醒。
                System.out.println(Thread.currentThread().getName() + " call wait ");
                t1.wait(3000);

                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
		}
	}
}

运行结果:

main start t1
main call wait 
t1 run			//大约3秒之后,输出“main continue”
main continue

流程解释:
(1)主线程通过new ThreadA(“t1”);新建线程1,随后通过synchronized(this)获取“t1对象的同步锁”。
(2)主线程main执行t1.start()启动“线程t1”。
(3)主线程main执行t1.wait(3000),此时,主线程进入“阻塞状态”。需要“用于t1对象锁的线程通过notify()或者notifyAll()将其唤醒”或者“超过3000ms之后”,主线程main才进入“就绪状态”,然后才可以运行。
(4)线程t1运行之后,进入死循环,会一直不断的运行。
(5)超过3000ms之后,主线程会进入到“就绪状态”,然后接着进入“运行状态”。主线程main和线程t1会一直运行下去。

2.4 wait()和notifyAll()示例

通过前面的实例,我们知道notify()可以唤醒在此对象监视器上等待的单个线程。notifyAll()的作用是唤醒在此对象监视器上等待的所有线程。


public class NotifyAllTest {

	private static Object obj = new Object();
	
	static class ThreadA extends Thread{
		
		public ThreadA(String name){
			super(name);
		}
		
		@Override
		public void run() {
			
			synchronized(obj){
				
				try {
					System.out.println(Thread.currentThread().getName()+" wait");
					
					obj.wait();
					
					System.out.println(Thread.currentThread().getName() + " continue");
				} catch (InterruptedException e) {
			
					e.printStackTrace();
				}
			}
		}
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {

		 ThreadA t1 = new ThreadA("t1");
	     ThreadA t2 = new ThreadA("t2");
	     ThreadA t3 = new ThreadA("t3");
	     t1.start();
	     t2.start();
	     t3.start();
	         
	     try {
	    	System.out.println(Thread.currentThread().getName()+" sleep(3000)");
			Thread.sleep(3000);
		} catch (InterruptedException e) {
		
			e.printStackTrace();
		}
		
	     synchronized(obj){
	    	 System.out.println(Thread.currentThread().getName()+ " notifyAll()");
	    	 obj.notifyAll();
	     }
	}
}

运行结果(结果不唯一):

main sleep(3000)
t2 wait
t1 wait
t3 wait
main notifyAll()
t3 continue
t1 continue
t2 continue

流程分析:
(1)主线程中新建并启动3个线程t1、t2和t3,并调用start() 方法进入“就绪状态”,然后可能会进入“运行状态”,所以“main sleep(3000)”不一定是第一个输出,可能会是第二个,这要看CPU 时间片执行到那个线程。t1、t2和t3线程那个先执行时,会调用“obj.wait();”阻塞当前线程进入“阻塞状态”,所以t1、t2和t3线程也会依次进入“阻塞状态”,等待其它线程通过notify()或额nofityAll()来唤醒它。
(2)主线程通过sleep(3000)休眠3秒。在主线程休眠3秒的过程中,t1、t2和t3线程应该都运行了。
(3)主线程休眠3秒后,接着运行。执行obj.notifyAll();唤醒obj上等待的线程,即唤醒t1、t2和t3线程。然后,主线程的synchronized(obj)运行完毕,主线程释放“obj对象锁”。这样t1、t2和t3线程就可以获取“obj锁”继续运行。

2.5 为什么notify()、wait()等函数定义在Object类中而不是Thread类中

  Object中的wait()、notify()等函数和synchronized一样,会对“对象的同步锁”进行操作。wait()会使“当前线程”等待,因为线程应该释放它所持有的“同步锁”,否则其他线程获取不到该“同步锁”而无法运行。线程调用wait()之后,会释放它所持有的“同步锁”;而且,等待线程可以被notify()或notifyAll()唤醒。那么notify()是依据什么唤醒等待线程的?或者wait()和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。
  负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒,但是,它不能立即执行。因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放“该对象的同步锁”之后,等待线程才能获取到“对象的同步锁”,然后继续运行。
  总之,notify()、wait()依赖于“同步锁”,而“同步锁”是该对象所持有,并且每个对象有且仅有一个。所以,你可以把wait()方法放进任何同步控制方法里,而不用考虑这个类是继承自Thread还是实现了Runnable接口。这就是为什么notify()、wait()等函数定义在Object类,而不是Thread类中的原因。实际上,只能在同步控制方法或同步控制块里调用wait()、notify()和notifyAll()(因为不用操作锁,所以sleep()可以在非同步控制方法里调用)。如果在非同步控制方法里调用这些方法,程序可以通过编译,但运行时将得到IllegalMonitorStateException异常,异常的大概是当前线程不是拥有者。意思是,调用wait()、notify()和notifyAll()的任务在调用这些方法前必须获取对象的锁。

三、线程让步和休眠

3.1 线程让步

在Java线程中,yield()方法的作用是让步,它能让当前线程由“运行状态”进入到“就绪状态”,从而让其他具有相同优先级的等待线程获取执行权;但是,并不能保证在当前线程调用yield()之后,其他具有相同优先级的线程就一定能获得执行权;也有可能是当前线程有进入到“运行状态”继续运行。

public class YieldTest {

	
	public static void main(String[] args) {

		class ThreadA extends Thread{
			public ThreadA(String name){
				super(name);
			}
			
			@Override
			public synchronized void run() {

				for(int i=0;i<10;i++){
					System.out.println(" "+this.getName()+" "+i);
					
					if(i%2 == 0){
						Thread.yield();
					}
				}
			}
		}
		
		ThreadA t1 = new ThreadA("t1");
		ThreadA t2 = new ThreadA("t2");
		t1.start();
		t2.start();

	}

}

运行结果:

 t1 0
 t2 0
 t1 1
 t1 2
 t2 1
 t2 2
 t1 3
 t1 4
 t2 3
 t2 4
 t1 5
 t1 6
 t2 5
 t2 6
 t1 7
 t1 8
 t2 7
 t1 9
 t2 8
 t2 9

结果说明:
线程t1在能被2整除的时候,并不一定切换到线程2。这表明,yield()方法虽然可以让线程由“运行状态”进入到“就绪状态”;但是,它不一定会让其他线程获取CPU执行权(其他线程进入到“运行状态”)。即时这个“其他线程”与当前调用yield()的线程具有相同的优先级。

3.2 yield()和wait()的比较

我们知道,wait()的作用是让当前线程由“运行状态”进入到“等待(阻塞)”的同时,也会释放同步锁。而yield()的作用是让步,它也是让当前线程离开“运行状态”。区别是:
(1)wait()是让线程由“运行状态”进入到“等待(阻塞)状态”,而yield()是让线程由“运行状态”进入到“就绪状态”。
(2)wait()是会让线程释放它所持有的对象的同步锁,而yield()方法不会释放对象的同步锁。

public class YieldLockTest {

	public static void main(String[] args) {

		final Object obj = new Object();
		
           class ThreadA extends Thread{
  			
			public ThreadA(String name){
				super(name);
			}
			
			@Override
			public void run() {
				synchronized(obj){
					for(int i=0;i<10;i++){
						System.out.println(this.getName()+" "+i);
						if(i%4==0){
							Thread.yield();
						}
					}
				}
			}
		}
	
		ThreadA  t1 = new ThreadA("t1");
		ThreadA  t2 = new ThreadA("t2");
		t1.start();
		t2.start();

	}
 
}

运行结果:

t1 0
t1 1
t1 2
t1 3
t1 4
t1 5
t1 6
t1 7
t1 8
t1 9
t2 0
t2 1
t2 2
t2 3
t2 4
t2 5
t2 6
t2 7
t2 8
t2 9

结果说明:
线程t1和t2在run()会引用同一个对象的同步锁,即synchronized(obj),在t1运行过程中,虽然它会调用Thread.yield();但是,t2是没有获取到CPU执行权的,因为,t1并没有释放“obj所持有的同步锁”。

3.3 线程休眠

sleep()方法定义在Thread类中,sleep()的作用是让当前线程休眠,即当前线程会从“远程状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时间会大于/等于该休眠时间;在线程重新被唤醒时,它会由“阻塞状态”变成“就绪状态”,从而等待CPU的调度执行。

3.4 sleep()和wait()的比较

我们知道,wait()的作用是让当前的线程由“运行状态”进入到“等待(阻塞)状态”的同时,也会释放同步锁。但是sleep()的作用是让当前线程由“运行状态”进入到“休眠(阻塞)”状态。wait()会释放对象的同步锁,而sleep()则不会释放锁。

public class RunnableTest {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		final Object obj = new Object();
		
		class MyThread extends Thread{
			
			public MyThread(String name){
				super(name);
			}
			@Override
			public void run() {
				synchronized(obj){
					for(int i=0;i<10;i++){
						try {
							 if (i%4 == 0)
								 Thread.sleep(100);
							System.out.println(Thread.currentThread().getName()+" loop "+i);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				}
			}
		}
		
		Thread t1 = new MyThread("t1");
		Thread t2 = new MyThread("t2");
		t1.start();
		t2.start();

	}

}

运行结果:

t2 loop 0
t2 loop 1
t2 loop 2
t2 loop 3
t2 loop 4
t2 loop 5
t2 loop 6
t2 loop 7
t2 loop 8
t2 loop 9
t1 loop 0
t1 loop 1
t1 loop 2
t1 loop 3
t1 loop 4
t1 loop 5
t1 loop 6
t1 loop 7
t1 loop 8
t1 loop 9

结果说明:
主线程main中启动两个线程t1和t2,t1和t2在run()方法中会引用同一个对象的同步锁,即synchronized(obj)。在t2运行过程中,虽然它会调用Thread.sleep(100),但是,t1是不会获取CPU执行权的。因为,t1并没有释放“obj所持有的同步锁”。如果,注释掉synchronized(obj)或再次执行该程序,t1和t2是可以相互切换的。

四、join()方法和interrupt()方法

4.1 加入一个线程

一个线程可以在其他线程之上调用join()方法,其效果是等待一段时间直到第二个线程结束结束才继续执行。如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复(即t.isAlive()返回为假)。也可以在调用jion()时带上一个超时参数(单位可以是毫秒,或者纳秒),这样如果目标线程在这段时间到期时还没有结束的话,join()方法总能返回。对jion()方法的调用可以被中断,做法是在调用线程上调用interrupt()方法,这时需要用到try_catch子句。

4.2 join()源码分析和实例

//无参数的join()方法
public final void join() throws InterruptedException {
        join(0);
}

//两个参数的join()方法
public final synchronized void join(long millis, int nanos)throws InterruptedException {

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
            millis++;
        }

        join(millis);
    }
    
//一个参数的join()方法
public final synchronized void join(long millis)throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

源码说明:
主要是无参数和一个参数的join()方法,从代码中可以发现,当mills == 0 时,会进入while(isAlive())循环,即只要调用该join()方法的线程是活的,那么当前线程就要不停等待。(wait()是让当前线程等待)。
下面是join()实例:

public class JoinTest {

	
	public static void main(String[] args) {


		class ThreadA extends Thread{
			
			public ThreadA(String name){
				super(name);
			}
			
			@Override
			public void run() {
				System.out.println("start "+this.getName());
				
				for(int i=0;i<1000000000;i++)
					;
				
				System.out.println("finish "+ this.getName());
			}
		}

		Thread t1 = new ThreadA("t1");
		t1.start();
		
		try {
			
			t1.join();
			
			System.out.println("finish "+Thread.currentThread().getName());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}

运行结果:

start t1
finish t1
finish main

结果说明:
在主线程main中新建线程t1,接着,通过t1.start()启动线程t1,并执行t1.join()。然后,主线程会进入“阻塞状态”等待t1运行结束。t1结束之后,会唤醒主线程main,主线程重新获取CPU执行权,继续运行。

4.3 interrupt()方法

  interrupt()的作用是中断本线程。本线程中断自己是被允许的;其他线程调用本线程的interrupt()方法时,会通过checkAccess()检查权限。这有可能抛出SecurityException异常。
  如果本线程是处于阻塞状态:调用线程的wait(),wait(long)或wait(long,int)会让它进入等待(阻塞)状态,或者调用线程的join(),join(long),join(long,int),sleep(long),sleep(long,int)也会让它进入阻塞状态。若线程在阻塞状态时,调用了它的interrupt()方法,那么它的“中断状态”会被清理并且会收到一个InterruptedException异常。例如,线程通过wait()进入阻塞状态,此时通过interrupt()中断该线程,调用interrupt()会立即将线程的中断标记设置为“true”,但是由于线程处于阻塞状态,所以该“中断标记”会立即被清除为“false”,同时,会产生一个InterruptedException异常。
  如果线程被阻塞在一个Selector选择器中,那么通过interrupt()中断它时,线程的中断标记会被设置为true,并且它会立即从选择操作中返回。
  如果不属于前面所说的的情况,那么通过interrupt()中断线程时,它的中断标记会被设置为true。中断一个“已经终止的线程”不会产生任何操作。

4.3 线程终止的方式

Thread中的stop()和suspend()方法,由于固有的不安全性,已经不建议使用。下面,通过讨论线程在“阻塞状态”和“运行状态”的终止方式,然后在总结一个通用的方式。

4.3.1终止处于“阻塞状态”的线程

通常,我们通过“中断”方式终止处于“阻塞状态”的线程。当线程由于被调用了sleep(),wait(),join()等方法而进入阻塞状态;若此时调用线程的interrupt()将线程的中断标记设为true。由于处于阻塞状态,中断标记会被清除,同时产生InterruptedException异常。将InterruptedException放在适当的为止就能终止线程,形如如下:

@Override
public void run() {
    try {
        while (true) {
            // 执行任务...
        }
    } catch (InterruptedException ie) {  
        // 由于产生InterruptedException异常,退出while(true)循环,线程终止!
    }
}

说明:在while(true)中不断的执行任务,当线程处于阻塞状态时,调用线程的interrupt()产生InterruptedException异常中断。中断的捕获在while(true)之外,这样就退出while(true)循环。
注意:对InterruptedException的捕获一般放在while(true)循环体的外面,这样产生异常时就退出了while(true)循环。否则,InterruptedException在while(true)循环体之内,就需要额外的添加退出处理。形式如下:

@Override
public void run() {
    while (true) {
        try {
            // 执行任务...
        } catch (InterruptedException ie) {  
            // InterruptedException在while(true)循环体内。
            // 当线程产生了InterruptedException异常时,while(true)仍能继续运行!需要手动退出
            break;
        }
    }
}

说明:
上面的InterruptedException异常捕获在while(true)之内。当产生InterruptedException异常时,被catch处理之外,仍然在while(true)循环体内;要退出while(true)循环体,需要额外的执行退出while(true)的操作。

4.3.1终止处于“运行状态”的线程

通常,我们通过“标记”方式终止处于“运行状态”的线程。其中包括“中断标记”和“额外添加标记”。
(1)通过“中断标记”终止线程。

@Override
public void run() {
    while (!isInterrupted()) {
        // 执行任务...
    }
}

说明:
isInterrupted()来判断线程中的中断标记是不是为true。当线程处于运行状态,并且我们需要终止它时,可以调用线程的interrupt()方法,使用线程的中断标记为true,即isInterrupted()方法会返回true。此时,就会退出while循环。
注意:interrupt()并不会终止处于“运行状态”的线程,它会将线程的中断标记设为true。
(2)通过“额外添加标记”

private volatile boolean flag= true;
protected void stopTask() {
    flag = false;
}

@Override
public void run() {
    while (flag) {
        // 执行任务...
    }
}

说明:线程中有一个flag标记,它的默认值是true;并且我们提供stopTask()来设置flag标记。当我们需要终止该线程时,调用该线程的stopTask()方法就可以让线程退出while循环。
注意:将flag定义为volatile类型,是为了保证flag的可见性。即其它线程通过stopTask()修改了flag之后,本线程能看到修改后的flag的值。
综合线程处于“阻塞状态”和“运行状态”的终止方式,比较通用的终止线程的形式如下:

@Override
public void run() {
    try {
        // 1. isInterrupted()保证,只要中断标记为true就终止线程。
        while (!isInterrupted()) {
            // 执行任务...
        }
    } catch (InterruptedException ie) {  
        // 2. InterruptedException异常保证,当InterruptedException异常产生时,线程被终止。
    }
}

4.4 终止线程的实例

4.4.1通过“中断标记”终止线程的实例

public class InterruptBlock {

	/**
	 * @param args
	 */
	public static void main(String[] args) {


		class MyThread extends Thread{
			
			public MyThread(String name){
				super(name);
			}
			
			@Override
			public void run() {	
				try {
					int i=0;
					while(!isInterrupted()){
					     Thread.sleep(100);
					     i++;
					     System.out.println(Thread.currentThread().getName()+ " ("+this.getState()+") loop "+i);
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
					System.out.println(Thread.currentThread().getName()+ " ("+this.getState()+") catch InterruptedExecption");
				}	
				
			}
		}
		
		
		try {
			
			//新建
			Thread t1 = new MyThread("t1");
			System.out.println(t1.getName()+" ("+t1.getState()+" ) is new.");
			
			System.out.println("luo1:"+t1.isInterrupted());
			//启动
			t1.start();
			System.out.println(t1.getName()+" ("+t1.getState()+" ) is started.");
			System.out.println("luo2:"+t1.isInterrupted());
			//主线程休眠300ms,然后主线程给t1发“中断”指令
			Thread.sleep(300);
			t1.interrupt();
			System.out.println("luo3:"+t1.isInterrupted());
			System.out.println(t1.getName()+" ("+t1.getState()+" ) is interrupted.");
			
			//主线程休眠300ms,然后查看t1的状态
			Thread.sleep(300);
			System.out.println(t1.getName()+" ("+t1.getState()+" ) is interrupted now .");
			
			
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

}

运行结果:

t1 (NEW ) is new.
luo1:false
t1 (RUNNABLE ) is started.
luo2:false
t1 (RUNNABLE) loop 1
t1 (RUNNABLE) loop 2
luo3:true
t1 (RUNNABLE) loop 3
t1 (RUNNABLE ) is interrupted.
t1 (TERMINATED ) is interrupted now .

结果说明:
(1)主线程mian新建线程t1,之后通过t1.start()启动线程t1;
(2)t1启动之后,会不断的检查它的中断标记,如果中断标记为“false”,则休眠100ms;
(3)t1休眠之后,会切换到主线程main,主线程再次运行时,会执行t1.interrupt()中端线程t1。t1收到中断指令之后,会将t1的中断标志设置为“false”,而且会抛出IterruptedException异常。在t1的run()方法中,是在循环体while(true)之外获取的异常,因此循环被终止。
如果,将run()方法中捕获InterruptedException异常的代码块移到while循环体内,那么程序会变成一个死循环。解决的办法是,可以在catch中添加break或return就可以解决该问题。
下面通过“额外添加标记”的方式终止“阻塞状态的”

4.4.2通过“额外添加标记”终止线程的实例

public class InterruptBlock {

	/**
	 * @param args
	 */
	public static void main(String[] args) {

		

		class MyThread extends Thread{
			
			private volatile boolean flag = true;
			
			public void stopTask(){
				flag = false;
			}
			
			public MyThread(String name){
				super(name);
			}
			
			@Override
			public void run() {	
				synchronized(this){
					
					try {
						int i=0;
						while(flag){
						     Thread.sleep(100);
						     i++;
						     System.out.println(Thread.currentThread().getName()+ " ("+this.getState()+") loop "+i);
						}
					} catch (InterruptedException e) {
						e.printStackTrace();
						System.out.println(Thread.currentThread().getName()+ " ("+this.getState()+") catch InterruptedExecption");
					}	
				}
			}
		}
		
		
		try {
			
			//新建
			MyThread t1 = new MyThread("t1");
			System.out.println(t1.getName()+" ("+t1.getState()+" ) is new.");
			
			//System.out.println("luo1:"+t1.isInterrupted());
			//启动
			t1.start();
			System.out.println(t1.getName()+" ("+t1.getState()+" ) is started.");
			//System.out.println("luo2:"+t1.isInterrupted());
			//主线程休眠300ms,然后主线程给t1发“中断”指令
			Thread.sleep(300);
			//t1.interrupt();
			t1.stopTask();
			//System.out.println("luo3:"+t1.isInterrupted());
			System.out.println(t1.getName()+" ("+t1.getState()+" ) is interrupted.");
			
			//主线程休眠300ms,然后查看t1的状态
			Thread.sleep(300);
			System.out.println(t1.getName()+" ("+t1.getState()+" ) is interrupted now .");
			
			
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

}

运行结果:

t1 (NEW ) is new.
t1 (RUNNABLE ) is started.
t1 (RUNNABLE) loop 1
t1 (RUNNABLE) loop 2
t1 (TIMED_WAITING ) is interrupted.
t1 (RUNNABLE) loop 3
t1 (TERMINATED ) is interrupted now .

4.5 interrupt()和isInterrupted()的区别

interrupt()和isInterrupted()都能够用于检测对象的“中断标记”。区别是:interrupt()除了返回中断标记之外,它还会清除中断标记(即将中断标记设为false);而isInterrupted()仅仅返回中断标记。

五、线程优先级和守护线程

  Java中线程的优先级的范围是1~10,默认的优先级为5。“高优先级线程”会优先于“低优先级线程”执行。而且Java中有两种线程:用户线程和守护线程。可以通过isDeamon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。用户线程一般用户执行用户级任务,而守护线程也就是“后台线程”,一般用来执行后台任务。需要注意的是:JVM在“用户线程”都结束后会退出。
  每个线程都有一个优先级。“高优先级线程”会优先于“低优先级线程”执行。每个线程都可以被标记为一个守护进程或非守护进程。在一些运行的主线程中创建新的子线程时,子线程的优先级被设置为等于“创建它的主线程的优先级”,当且仅当“创建它的主线程是守护线程”时“子线程才会是守护线程”。

当Java虚拟机启动时,通常有一个单一的非守护线程(该线程通过是通过main()方法启动)。JVM会一直运行直到下面的任意一个条件发生,JVM就会终止运行:
(1) 调用了exit()方法,并且exit()有权限被正常执行。
(2) 所有的“非守护线程”都死了(即JVM中仅仅只有“守护线程”)。

站在巨人的肩膀上,大神之作
接下来我还会更新,Java多线程之进阶篇。

你可能感兴趣的:(Java,Java多线程,sleep和wait,join和interrupt,volatile关键字,线程优先级和守护进程)