JavaWeb|线程安全详解

目录
一、观察线程的所有状态
二、线程状态和状态转移的意义
三、多线程带来的风险
四,解决线程安全问题

一、观察线程的所有状态 线程的状态是一个枚举类型 Thread.State

public static void main(String[] args) { for (Thread.State state : Thread.State.values()){ System.out.println(state); } }

NEW: 安排了工作, 还未开始行动
RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.就绪状态
BLOCKED: 这几个都表示排队等着其他事情
WAITING: 这几个都表示排队等着其他事情
TIMED_WAITING: 这几个都表示排队等着其他事情
TERMINATED: 工作完成了.
二、线程状态和状态转移的意义
JavaWeb|线程安全详解
文章图片

NEW:Thread对象有了,但是PCB还没有
RUNNABLE:线程正在CPU上执行或者即将到CPU上执行(PCB在就绪队列中,随时可能被调度到)
WAITING:wait方法导致
TIMED_WAITING:sleep方法导致
BLOCKED:等待锁导致
【JavaWeb|线程安全详解】TERMINATED:对象还在,但PCB已经没了
public static void main(String[] args) { Thread t = new Thread(){ @Override public void run() { for (int i = 0; i < 100_00; i++){} } }; System.out.println("线程启动前:" + t.getState()); t.start(); while (t.isAlive()){ System.out.println("线程运行中:" + t.getState()); } System.out.println("线程结束后:" + t.getState()); }

JavaWeb|线程安全详解
文章图片

三、多线程带来的风险 线程不安全的原因
①线程是抢占式执行的
线程不安全的万恶之源,线程之间的调度完全由内核负责,用户代码中感知不到,也无法控制。线程之间谁先执行,谁后执行,谁执行到哪里从CPU上下来,这样的过程用户无法控制也无法感知到的。
②自增操作不是原子的
每次++都能拆成三个步骤
把内存中的数据读取到CPU中
把CPU中的数据+1
把计算的数据写回内存中
JavaWeb|线程安全详解
文章图片

如果两个线程串行执行,此时计算结果为2。
如果两个线程并行执行,线程1进行++操作到一半的时候,线程也进行了++操作,此时自增两次,但结果为1。
必须保证线程1save结束了,线程2再load,此时计算结果才正确
③多个线程尝试修改同一个变量
如果是一个线程修改一个变量,线程安全
如果多个线程读取同一个变量,线程安全
如果多个线程修改不同的变量。线程安全
④内存可见性导致线程安全问题
⑤指令重排序
Java的编译器在编译代码时,会对指令进行优化,调整指令的先后顺序,保证原有的逻辑不变的情况下,提高程序的运行效率
四,解决线程安全问题 锁-synchronized
未加锁
static class Counter{ public int count = 0; public void increase(){ count++; } }public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Thread t1 = new Thread(){ @Override public void run() { for (int i = 0; i < 50000; i++){ counter.increase(); } } }; Thread t2 = new Thread(){ @Override public void run() { for (int i = 0; i < 50000; i++){ counter.increase(); } } }; t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(counter.count); }

JavaWeb|线程安全详解
文章图片

JavaWeb|线程安全详解
文章图片

JavaWeb|线程安全详解
文章图片

已加锁
static class Counter{ public int count = 0; synchronized public void increase(){ count++; } }public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Thread t1 = new Thread(){ @Override public void run() { for (int i = 0; i < 50000; i++){ counter.increase(); } } }; Thread t2 = new Thread(){ @Override public void run() { for (int i = 0; i < 50000; i++){ counter.increase(); } } }; t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(counter.count); }

JavaWeb|线程安全详解
文章图片

JavaWeb|线程安全详解
文章图片

此处的synchronized就是针对counter这个对象来加锁,进入increase方法内部,就把加锁状态设为ture,increase方法退出之后,就把加锁状态设为false,如果某个线程已经把加锁状态设为ture,此处的其他的线程尝试去加锁,就会阻塞
synchronized的特性——刷新内存
synchronized 的工作过程:
1. 获得互斥锁
2. 从主内存拷贝变量的最新副本到工作的内存
3. 执行代码
4. 将更改后的共享变量的值刷新到主内存
5. 释放互斥锁
synchronized的特性——互斥
public static void main(String[] args) { Object locker = new Object(); Thread t1 = new Thread(){ @Override public void run() { Scanner scanner = new Scanner(System.in); synchronized (locker) { System.out.println("输入一个整数"); int num = scanner.nextInt(); System.out.println("num= " + num); } } }; t1.start(); Thread t2 = new Thread(){ @Override public void run() { while (true){ synchronized (locker){ System.out.println("线程2获取到锁"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }; t2.start(); }

JavaWeb|线程安全详解
文章图片

一旦线程一获取到锁,并且没有释放的话,线程2就会一直在锁这里阻塞等待
public static void main(String[] args) { Object locker1 = new Object(); Object locker2 = new Object(); Thread t1 = new Thread(){ @Override public void run() { Scanner scanner = new Scanner(System.in); synchronized (locker1) { System.out.println("输入一个整数"); int num = scanner.nextInt(); System.out.println("num= " + num); } } }; t1.start(); Thread t2 = new Thread(){ @Override public void run() { while (true){ synchronized (locker2){ System.out.println("线程2获取到锁"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }; t2.start(); }

JavaWeb|线程安全详解
文章图片

不是同一把锁,就不回出现竞争,就没有互斥了。
public static void main(String[] args) { Object locker1 = new Object(); Object locker2 = new Object(); Thread t1 = new Thread(){ @Override public void run() { Scanner scanner = new Scanner(System.in); synchronized (locker1.getClass()) { System.out.println("输入一个整数"); int num = scanner.nextInt(); System.out.println("num= " + num); } } }; t1.start(); Thread t2 = new Thread(){ @Override public void run() { while (true){ synchronized (locker2.getClass()){ System.out.println("线程2获取到锁"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }; t2.start(); }

JavaWeb|线程安全详解
文章图片

这个代码中,两个线程都在针对locker1和locker2的类对象进行竞争,此处的locker1和locker2的类型都是Object,对应的对象都是相同的对象。

    推荐阅读