概念
什么是线程:运行程序会创建一个进程。进程里面包含多个线程,OS调度的最小单元是线程(轻量级进程)。
运行一个普通的java程序包含的线程:
1 package com.lgstudy; 2 3 import java.lang.management.ManagementFactory; 4 import java.lang.management.ThreadInfo; 5 import java.lang.management.ThreadMXBean; 6 7 /** 8 * lgs 9 * 10 * 11 * 一个java程序包含的线程 12 */ 13 public class ShowMainThread { 14 15 public static void main(String[] args) { 16 //java虚拟机的线程管理接口 17 ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); 18 //获取线程信息的方法 19 ThreadInfo[] threadInfos = 20 threadMXBean.dumpAllThreads(false,false); 21 for(ThreadInfo threadInfo:threadInfos){ 22 System.out.println(threadInfo.getThreadId()+":"+threadInfo.getThreadName()); 23 } 24 } 25 26 }
输出:
5:Attach Listener //获取内存dump,线程dump
4:Signal Dispatcher //将信号分发给jvm的线程
3:Finalizer //调用对象的finalizer 方法 进行垃圾回收
2:Reference Handler //清除Reference
1:main //程序的主入口
为什么要用线程?
1、 充分利用CPU多处理核心;
2、 更快的响应时间(用户订单的场景,发送邮件等部分与订单业务无关和没有要求数据一致性的功能可由其他线程执行)
启动线程和退出线程
创建线程的方法
extends Thread
implements Runnable
启动线程:threadl类的start()
线程完成:1、run()方法执行完成;2、抛出一个未处理的异常导致线程的提前结束
1 package com.lgstudy; 2 3 import jdk.management.resource.internal.inst.ThreadRMHooks; 4 5 /** 6 * lgs 7 * 8 * 如何创建一个线程 9 */ 10 public class HowStartThread { 11 12 private static class TestThread extends Thread{ 13 @Override 14 public void run() { 15 System.out.println("TestThread is runing"); 16 17 } 18 } 19 20 private static class TestRunable implements Runnable{ 21 22 @Override 23 public void run() { 24 System.out.println("TestRunable is runing"); 25 } 26 } 27 28 public static void main(String[] args) { 29 Thread t1 = new TestThread(); 30 Thread t2 = new Thread(new TestRunable()); 31 t1.start(); 32 t2.start(); 33 34 } 35 36 }
取消和中断
不安全的取消:
单独使用一个取消标志位.
1 package com.lgstudy.interrupt; 2 3 /** 4 * lgs 5 * 6 * 使用自定义的取消标志位中断线程(不安全) 7 */ 8 public class FlagCancel { 9 10 private static class TestRunable implements Runnable{ 11 12 private volatile boolean on = true; 13 private long i =0; 14 15 @Override 16 public void run() { 17 while(on){ 18 i++; 19 //阻塞方法,on不起作用 20 //wait,sleep,blockingqueue(put,take) 21 try { 22 Thread.sleep(20000); 23 } catch (InterruptedException e) { 24 e.printStackTrace(); 25 } 26 } 27 System.out.println("TestRunable is runing :"+i); 28 } 29 30 public void cancel(){ 31 on = false; 32 } 33 } 34 35 }
Stop(),suspend(),resume()是过期的api,很大的副作用,容易导致死锁(suspend():将线程挂起不会释放锁)或者数据不一致(Stop():线程在未处理完数据时就停止)
如何安全的终止线程
使用线程的中断 :
interrupt() 中断线程,本质是将线程的中断标志位设为true,其他线程向需要中断的线程打个招呼。是否真正进行中断由线程自己决定。
isInterrupted() 线程检查自己的中断标志位
静态方法Thread.interrupted() 将中断标志位复位为false
由上面的中断机制可知Java里是没有抢占式任务,只有协作式任务。
为何要用中断,线程处于阻塞(如调用了java的sleep,wait等等方法时)的时候,是不会理会我们自己设置的取消标志位的,但是这些阻塞方法都会检查线程的中断标志位。
1 package com.lgstudy.interrupt; 2 3 /** 4 * lgs 5 * 6 * 安全的中断线程 7 */ 8 public class SafeInterrupt implements Runnable { 9 10 private volatile boolean on = true; 11 private long i =0; 12 13 @Override 14 public void run() { 15 //阻塞方法wait,sleep,blockingqueue(put,take),on不起作用 16 //要加上线程的中断才能安全的终止线程 17 while(on&&Thread.currentThread().isInterrupted()){ 18 i++; 19 } 20 System.out.println("TestRunable is runing :"+i); 21 } 22 23 public void cancel(){ 24 on = false; 25 //在java线程很忙的时候可能不会理会中断,所以定义一个标志位on更好 26 Thread.currentThread().interrupt(); 27 } 28 }
处理不可中断的阻塞
IO通信 inputstream read/write等阻塞方法,不会理会中断,而关闭底层的套接字socket.close()会抛出socketException
NIO: selector.select()会阻塞,调用selector的wakeup和close方法会抛出ClosedSelectorException
死锁状态不响应中断的请求,这个必须重启程序,检查程序找到死锁的位置修改错误。
1 package com.lgstudy.interrupt; 2 3 /** 4 * lgs 5 * 6 * 调用阻塞方法时,如何中断线程 7 */ 8 public class BlockInterrupt { 9 10 private static volatile boolean on = true; 11 12 private static class WhenBlock implements Runnable { 13 14 @Override 15 public void run() { 16 while (on && !Thread.currentThread().isInterrupted()) { 17 try { 18 //抛出中断异常的阻塞方法(wait,sleep,blockingqueue(put,take)),抛出异常后,中断标志位改成false 19 Thread.sleep(100); 20 } catch (InterruptedException e) { 21 Thread.currentThread().interrupt();//重新设置一下 22 //do my work 23 } 24 //清理工作结束线程 25 } 26 } 27 28 //外部线程调用方法阻塞 29 public void cancel() { 30 on = false; 31 Thread.currentThread().interrupt(); 32 } 33 34 } 35 }
如何让我们的代码既可以响应普通的中断,又可以关闭底层的套接字呢?
覆盖线程的interrupt方法,在处理套接字异常时,再用super.interrupt()自行中断线程
1 package com.lgstudy.interrupt; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.net.Socket; 6 7 /** 8 * lgs 9 * 10 * 如何覆盖线程的interrupt() 方法 11 */ 12 public class OverrideInterrupt extends Thread { 13 private final Socket socket; 14 private final InputStream in; 15 16 public OverrideInterrupt(Socket socket, InputStream in) { 17 this.socket = socket; 18 this.in = in; 19 } 20 21 private void t(){ 22 } 23 24 @Override 25 public void interrupt() { 26 try { 27 //关闭底层的套接字 28 socket.close(); 29 } catch (IOException e) { 30 e.printStackTrace(); 31 //..... 32 }finally { 33 //同时中断线程 34 super.interrupt(); 35 } 36 37 } 38 }
线程的状态
新创建 线程被创建,但是没有调用start方法
可运行(RUNNABLE) 运行状态,由cpu决定是不是正在运行
被阻塞(BLOCKING) 阻塞,线程被阻塞于锁
等待/计时等待(WAITING) 等待某些条件成熟
被终止 线程执行完毕
线程的优先级
成员变量priority控制优先级,范围1-10之间,数字越高优先级越高,缺省为5,创建线程时setPriotity()可以设置优先级,不要指望他发挥作用,因为线程优先级是由操作系统决定的,有的操作系统甚至会忽略jvm的线程优先级。
Daemon线程
守护型线程(如GC线程),程序里没有非Daemon线程时,java程序就会退出。一般用不上,也不建议我们平时开发时使用,因为Try/Finally里的代码不一定执行的。
1 package com.lgstudy; 2 3 import com.lgstudy.threadstate.SleepUtils; 4 5 /** 6 * lgs 7 * 8 * 守护线程 9 */ 10 public class Daemon { 11 public static void main(String[] args) { 12 Thread thread = new Thread(new DaemonRunner()); 13 //将线程置为守护线程 14 thread.setDaemon(true); 15 thread.start(); 16 } 17 18 static class DaemonRunner implements Runnable { 19 @Override 20 public void run() { 21 try { 22 SleepUtils.second(100); 23 } finally { 24 System.out.println("DaemonThread finally run."); 25 } 26 } 27 } 28 }
常用方法深入理解
run()和start()
run就是一个普通的方法,跟其他类的实例方法没有任何区别,他之所以能在线程里面运行时因为调用了start()方法。
Sleep
不会释放锁,当前线程变成了休眠状态,所以我们在用sleep时,要把sleep放在同步代码块的外面。
yield()
不会释放锁,当前线程出让cpu占有权,当前线程变成了可运行状态,下一时刻仍然可能被cpu选中。
wait()和 notify()/notiyfAll()
调用以前,当前线程必须要持有锁,调用了wait() notify()/notiyfAll()会释放锁。
等待通知机制:
线程 A调用了对象O的wait方法进入等待状态,线程 B调用了对象O的notify方法进行唤醒,唤醒的是在对象O上wait的线程(比如线程A)
notify() 唤醒一个线程,唤醒哪一个完全看cpu的心情(谨慎使用)
notiyfAll() 所有在对象O上wait的线程全部唤醒(应该用notiyfAll())