三、多线程的生命周期
3.1JDK5.0之前:五种状态
3.1JDK5.0之后:六种状态
四、线程安全问题及解决
1.概念
线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。
2.出现的原因
-- 抢占式执行——罪魁祸首
CPU的调度方法为抢占式执行,随机调度,这个是导致线程安全问题的最根本的原因。但是这个原因我们无能为力,无法改变。
--多个线程同时修改同一个变量
当修改变量这个操作并非原子性的。这样在并发的环境下就很容易出现线程安全问题。这种情况可以通过代码结构来进行一定的规避,但是这种方法不是一个普适性特别高的方案。
--原子性
如果修改操作是原子性的,那么出现线程安全问题的概率还是比较低的。但如果是非原子的(++操作,其可以被拆分为load,add,save三个操作),那么出现问题的概率就非常高了。能够把修改行为变成原子操作,就是解决线程安全问题的关键方法!(synchronized关键字)
--内存可见性问题
内存可见性是指当一个线程修改了共享变量的值,其他线程能够立即感知这个修改。
如果一个线程读,一个线程改(这样的操作就可能引起内存可见性问题,也会出现线程安全问题)。此时前一个线程读取到的值,不一定是修改之后的值,即读线程没有感知到变量的变化——归根结底是编译器/JVM在多线程环境下优化时产生了误判。(volatile关键字)
--指令重排序
指令重排序是因为编译器对我们的代码进行了一些“自作主张”的优化,编译器会在保持逻辑不变的情况下。调整代码的顺序,从而加快代码的执行效率。这样也会出现线程安全问题。
4.线程的同步机制
synchronized解决了线程安全的问题,但在数据共享时多线程是串行执行,性能低。
synchronized 的使用方法:
1.修饰方法
1)修饰普通方法
锁对象是this,谁调用这个普通方法,锁的对象就是谁
2).修饰类方法
锁对象是类对象
2.修饰代码块
显式/手动指定锁对象.
3.可重入
一个线程针对同一个对象加锁两次,是否会有问题,如果没有问题,就叫可重入,如果有问题,就叫不可重入.
a.同步代码块
说明:
--需要被同步的代码,即为操作共享数据的代码。
--共享数据,即多个线程都需要操作的数据。
--需要被同步的代码,在被synchronized包裹以后,就使得一个线程在操作这些代码过程中,其他线程必须等待。
--同步监视器,俗称锁,哪个线程获取了锁,哪个线程就能执行需要被同步的代码
--同步监视器,可以使用任何一个类的对象充当。但是,多个线程必须共同用一个同步监视器。
注意: 在继承Thread类的方式中,同步监视器要慎用this,可以考虑使用:当前类.class。
在实现Runnable接口的方式中,同步监视器可以考虑使用:this
//用Thread类 class Window extends Thread{ private static int ticket=100; // private static Object object=new Object(); @Override public void run() { while(true) { // synchronized (object){ synchronized (Window.class) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket); ticket--; } else { break; } } } } } public class synchronizedThread { public static void main(String[] args) { Window w1 = new Window(); Window w2 = new Window(); Window w3 = new Window(); w1.setName("窗口1"); w2.setName("窗口2"); w3.setName("窗口3"); w1.start(); w2.start(); w3.start(); } }
//用Runnable接口 class Window2 implements Runnable{ private Integer ticket=100; @Override public void run() { while(true) { synchronized (this) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket); ticket--; } else { break; } } } } } public class synchronizedRunnable { public static void main(String[] args) { Window2 w = new Window2(); new Thread(w,"窗口1").start(); new Thread(w,"窗口2").start(); new Thread(w,"窗口3").start(); } }
b.同步方法
说明:
--如果操作共享数据的代码完整的声明在了一个方法中,就称此方法为同步方法。
--非静态的同步方法,默认同步监视器是this
静态的同步方法,默认同步监视器是当前类的本身。
(本文图片及内容来自尚硅谷视频,部分补充内容来自:原文链接
//用Runnable接口 class method2 implements Runnable{ private Integer ticket=100; @Override public void run() { while(true){ buy(); } } public synchronized void buy(){ // 同步监视器为this ,而且方法也不是static的 if (ticket > 0) { System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket); ticket--; } } } public class synchronizedRunnableMethod { public static void main(String[] args) { method2 m = new method2(); new Thread(m,"窗口1").start(); new Thread(m,"窗口2").start(); new Thread(m,"窗口3").start(); } }
//用Thread类 class method extends Thread{ private static Integer ticket=100; @Override public void run() { while(true){ buy(); } } // 因为是继承Thread,创建多个线程会创建method类的多个实例,必须声明为static public synchronized static void buy(){ // 同步监视器还是类对象本身 method.class if (ticket > 0) { System.out.println(Thread.currentThread().getName() + ":售票,票号为:" + ticket); ticket--; } } } public class synchronizedThreadMethod { public static void main(String[] args) { method m1 = new method(); method m2 = new method(); method m3 = new method(); m1.setName("窗口1"); m2.setName("窗口2"); m3.setName("窗口3"); m1.start(); m2.start(); m3.start(); } }