一、一个线程的生命周期
阻塞状态(Blocked):线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态
二、几类线程的概念
主线程:JVM调用main产生
当前线程:Thread.currentThread()获取到的
守护线程 aka 后台线程,isDaemon()判断、setDaemon()设置。为其他线程提供服务,不依赖主线程结束而结束。
三、线程安全 aka 为啥需要做多线程开发
多个线程访问同一个共享资源时,可能会因为抢占cpu时间片而造成问题
四、线程锁的三种方法
示例:卖票程序
public class TestDemo{ static int count = 100; static int sale =0; static Runnable r = new Runnable() { @Override public void run() { // TODO Auto-generated method stub while(count>0) { System.out.println(Thread.currentThread().getName()+"--"); count--; sale++; if(count<=0) { System.out.println("售罄"); return; } System.out.println("已售:"+sale+",剩余:"+count); } } }; public static void main(String args[]) { Thread t1 = new Thread(r); Thread t2 = new Thread(r); Thread t3 = new Thread(r); t1.start(); t2.start(); t3.start(); } }
打印出来是错乱的结果,不是顺序连着的票
所以需要锁机制,让现成的访问有先来后到,前一个正在取值的线程未结束,锁不会得到释放,后面的线程也无法访问
加入synchronize关键字(最重要的一种同步锁):
法一:同步代码块 static Object obj =new Object();
public class TestDemo{ static int count = 100; static int sale =0; static Object obj =new Object(); static Runnable r = new Runnable() { @Override public void run() { // TODO Auto-generated method stub while(count>0) { System.out.println(Thread.currentThread().getName()+"--"); synchronized (obj) { count--; sale++; if(count<=0) { System.out.println("售罄"); return; } System.out.println("已售:"+sale+",剩余:"+count); } // if(sale%10==0) { // System.out.println("已售:"+sale+",剩余:"+count); // } } } }; public static void main(String args[]) { Thread t1 = new Thread(r); Thread t2 = new Thread(r); Thread t3 = new Thread(r); t1.start(); t2.start(); t3.start(); } }
输出顺序正常
PS:也可以使用static byte[] obj = new byte[0];代替static Object obj =new Object();
因为零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
但是在synchronize外声明byte或Object对象都需要用static关键字,因为静态变量才可以独立与对象存在,否则无法调用。
法二:同步类 Math.class
上述代码中,synchronize(obj)也可以改为synchronize(Math.class),即将同步代码块改为同步类锁,都是synchronize实现的方法
public class TestDemo{ static int count = 100; static int sale =0; //static Object obj =new Object(); static Runnable r = new Runnable() { @Override public void run() { // TODO Auto-generated method stub while(count>0) { System.out.println(Thread.currentThread().getName()+"--"); synchronized (Math.class) { count--; sale++; if(count<=0) { System.out.println("售罄"); return; } System.out.println("已售:"+sale+",剩余:"+count); } } } }; public static void main(String args[]) { Thread t1 = new Thread(r); Thread t2 = new Thread(r); Thread t3 = new Thread(r); t1.start(); t2.start(); t3.start(); } }
法三:同步方法
直接定义方法不加锁:
public class TestDemo{ static int count = 10; static int sale =0; //static Object obj =new Object(); static Runnable r = new Runnable() { @Override public void run() { // TODO Auto-generated method stub while(count>0) { saleFunc(); } } public void saleFunc() { if(count<=0) { System.out.println("售罄"); return; } System.out.println(Thread.currentThread().getName()+"--"); count--; sale++; System.out.println("已售:"+sale+",剩余:"+count); if(count<=0) { System.out.println("售罄"); return; } } }; public static void main(String args[]) { Thread t1 = new Thread(r); Thread t2 = new Thread(r); Thread t3 = new Thread(r); t1.start(); t2.start(); t3.start(); } }
会造成打印错乱
同步方法:直接在方法定义时加上synchronize关键字即可
五、死锁
在多线程开发中会遇到,一般出现在高负载场景下
what:因争抢资源而陷入僵局,互相等待无法执行下去
why:
当前线程拥有其他线程需要的资源
当前线程等待其他线程已拥有的资源
都不放弃自己拥有的资源
how:
1.线程之间交错执行 解决:以固定的顺序加锁
2.执行某方法时就需要持有锁,且不释放 解决:缩减同步代码块范围,最好仅操作共享变量时才加锁
3.永久等待 解决:使用tryLock()定时锁,超过时限则返回错误信息
4.JDK提供了两种方式在编码阶段来检测死锁:
JconsoleJDK自带的图形化界面工具,使用JDK给我们的的工具JConsole
Jstack是JDK自带的命令行工具,主要用于线程Dump分析。