龙华网站网页设计,阿里巴巴运营视频,用wordpress建公司网站步骤,网站不换域名换空间【JavaEE】线程安全 一、引出线程安全二、引发线程安全的原因三、解决线程安全问题3.1 synchronized关键字#xff08;解决修改操作不是原子的#xff09;3.1.1 synchronized的特性3.1.1 synchronized的使用事例 3.2 volatile 关键字#xff08;解决内存可见性#xff09; … 【JavaEE】线程安全 一、引出线程安全二、引发线程安全的原因三、解决线程安全问题3.1 synchronized关键字解决修改操作不是原子的3.1.1 synchronized的特性3.1.1 synchronized的使用事例 3.2 volatile 关键字解决内存可见性 四、死锁4.1 可重入4.2 两个线程出现的死锁4.3 哲学家就餐问题4.4 造成死锁的原因 博客结尾有此篇博客的全部代码 一、引出线程安全
举例
public class Demo1 {private static int count0;public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(()-{for (int i 0; i 50000; i) {count;}});Thread t2 new Thread(()-{for (int i 0; i 50000; i) {count;}});t1.start();t2.start();t1.join();t2.join();System.out.println(countcount);//结果56154}
}这段代码运行结束发现结果不是理论值100000而是每次运行完出现一个新的数。
在计算机操作系统中count在寄存器中分为三步读取加一写回这三步。
假设两个线程按照这样进行那么得到的count的最终值就是理论值100000。 但往往事实不是这样这样的CPU资源调度是随机的很有可能是这样的这里只列举一种情况给大家示范一下 首先t1线程和t2 线程分别从内存中读取count值此时两个线程读取到的count值都是0然后t1线程进行加一操作后写回到内存中t2线程也是进行加一操作后写回到内存中t2线程得到的count值将t1得到的count覆盖这样count经过两个线程的加一操作之后值还是1
二、引发线程安全的原因 【根本原因】操作系统对于线程的调度是随机的抢占式执行多个线程同时修改同一变量修改操作不是原子的事务中的原子性内存可见性引起的线程不安全指令重排序引起的线程不安全 三、解决线程安全问题 由于线程调度是随机的这个不是我们可以左右的我们确保多个线程不同时修改同一变量 主要带大家学习引发第三个和第四个引起线程安全的解决方法
3.1 synchronized关键字解决修改操作不是原子的
引发线程安全第三个原因是修改操作不是原子的关键字synchronized将修改操作“锁”在一起相当于将读取加一写回三个操作绑定在一起三操作要么全部执行要么全部不执行
3.1.1 synchronized的特性
互斥 synchronized 会起到互斥效果, 某个线程执⾏到某个对象的 synchronized 中时, 其他线程如果也执⾏到同⼀个对象 synchronized 就会阻塞等待. 语法
synchronized(变量){
//修改操作
}• 进⼊ synchronized 修饰的代码块, 相当于 加锁 • 退出 synchronized 修饰的代码块, 相当于 解锁
public class Demo2 {private static int count 0;public static void main(String[] args) throws InterruptedException {Object lock new Object();Thread t1 new Thread(() - {for (int i 0; i 50000; i) {synchronized (lock){count;}}}); Thread t2 new Thread(()-{for (int i 0; i 50000; i) {synchronized (lock){count;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(countcount);//结果count10000}
}当加上锁之后count值就是10000 这里需要注意的事 synchronized(变量)里面的这个变量必须是相同的变量否则就不会发生阻塞等待 事例
public class Demo3 {private static int count 0;public static void main(String[] args) throws InterruptedException {Object lock1 new Object();Object lock2 new Object();Thread t1 new Thread(() - {for (int i 0; i 50000; i) {synchronized (lock1){count;}}}); Thread t2 new Thread(()-{for (int i 0; i 50000; i) {synchronized (lock2){count;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(countcount);//结果count70738}}可重入 可重入就是指一个线程连续针对一个对象加多次锁不会出现“死锁”现象称为可重入。
synchronized (block) {synchronized(block) {//代码} //右大括号}2
} //右大括号}1
按理来说在进入第一个synchronized的时候加上了一把锁此时已经是“锁定状态”当我们进入到第二个synchronized的时候要加锁就发生“阻塞等待”就要等到第一个锁走到右大括号}1解完锁才能加然而第一个锁走到右大括号}1解锁又需要第二把锁创建走完到右大括号}2。 这是线程就卡死了这就是死锁。
Java大佬发现了这个问题所以将synchronized设为可重入锁这样就不会出现死锁的问题。 • 如果某个线程加锁的时候, 发现锁已经被⼈占⽤, 但是恰好占⽤的正是⾃⼰这个锁是自己加的, 那么仍然可以继续获取到锁, 并让计数器⾃增. • 解锁的时候计数器递减为 0 的时候, 才真正释放锁. (才能被别的线程获取到)
3.1.1 synchronized的使用事例
修饰代码块 锁定任意对象 锁住当前对象 public class SynchronizedDemo {public void method() {synchronized (this) {}}}直接修饰普通⽅法
public class SynchronizedDemo {public synchronized void methond() {}}修饰静态⽅法
public class SynchronizedDemo {public synchronized static void methond() {}}3.2 volatile 关键字解决内存可见性 volatile可以保证内存可见性只能修饰变量。并且volatile不能保证原子性 计算机运行代码/程序的时候访问数据常常要从内存中访问定义变量时变量就储存在内存中然而CPU从内存中读取数据相比于从寄存器中读取数据要慢上很多几千上万倍CPU在进行读/写内存的时候速度就会降低。 为了解决这个问题提高效率编译器就可能会对代码优化把一些本来要读取内存的操作优化为读取寄存器减少读取内存的次数。这就会导致内存可见性问题。 以我们接下来的代码为例------当CPU从自身寄存器中读取成千上万次发现count一直是0此时编译器就将代码优化让count一直等于0所以接下来线程1中一直处于循环状态尽管线程2中已经将count修改为1
public class Demo4 {private static int count 0;public static void main(String[] args) throws InterruptedException {Object lock1 new Object();Thread t1 new Thread(() - {while (count 0) {}System.out.println(循环结束);});Thread t2 new Thread(() - {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}count 1;});t1.start();t2.start();System.out.println(main 线程结束);}
}这里修改的方法给线程1中的程序加入sleep让它休眠时间大于线程2的休眠时间这样它读取的count就是1编译器就不会进行优化循环就会结束
Thread t1 new Thread(() - {try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}while (count 0) {}System.out.println(循环结束);});Thread t2 new Thread(() - {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}count 1;});volatile解决内存可见性问题
public class Demo5 {private volatile static int count 0;public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(() - {while (count 0) {}System.out.println(循环结束);});Thread t2 new Thread(() - {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}count 1;});t1.start();t2.start();System.out.println(main 线程结束);}
}四、死锁 死锁是一个非常严重的bug它会让你的代码在执行到这块卡住 4.1 可重入
public class Demo6 {private static int count 0;public static void main(String[] args) throws InterruptedException {Object lock new Object();Thread t1 new Thread(() - {for (int i 0; i 50000; i) {synchronized (lock) {synchronized (lock) {count;}}}System.out.println(循环结束);});t1.start();t1.join();System.out.println(count);}
}由于Java提出了可重入的概念所以这段代码执行的到这里并没有卡住但在C中没有引入可重入的概念所以C这里写出这样的代码就会出现死锁
4.2 两个线程出现的死锁
假设t1线程先拿醋t2线程先拿酱两个线程都将醋和酱已经加上自己的锁了然后t1线程尝试拿酱t2线程尝试拿醋此时就会出现死锁
public class Demo7 {public static void main(String[] args) throws InterruptedException {Object lock1 new Object();Object lock2 new Object();Thread t1 new Thread(() - {synchronized (lock1) {System.out.println(t1拿到醋了);synchronized (lock2) {System.out.println(t1拿到酱了);}}});Thread t2 new Thread(() - {synchronized (lock2) {System.out.println(t2拿到酱了);synchronized (lock1) {System.out.println(t2拿到醋了);}}});t1.start();t2.start();t1.join();t2.join();System.out.println(main 线程结束);}
}如果将两个锁改成并列就不会出现死锁 Thread t2 new Thread(() - {synchronized (lock2) {System.out.println(t2拿到酱了);}synchronized (lock1) {System.out.println(t2拿到醋了);}});4.3 哲学家就餐问题
相当于是两个线程出现死锁的进阶M个线程N把锁 5个哲学家5个线程5只筷子5把锁哲学家坐在圆桌边桌上放有面条每只筷子放在每个哲学家的中间。
每个哲学家会做两件事
思考人生.放下筷子啥都不干吃面条.拿起左右两侧的两根筷子开始吃面条
哲学家啥时候吃面条啥时候思考人生是随机的 哲学家吃面条啥时候吃完也是随机的 哲学家正在吃面条的过程中会持有左右两侧的筷子。此时相邻的哲学家如果也想吃面条就需要阻塞等待 当出现极端情况每个哲学家都想吃面条都拿起自己左手边的筷子并且不会在没吃到面条情况下放下筷子这时就是死锁了。
4.4 造成死锁的原因 互斥使用锁的基本特性当一个线程拿到一把锁后另一个线程要拿到这把锁就要阻塞等待不可抢占锁的基本特性当一把锁被线程拿到后其他线程不能抢占只能等线程自己释放锁请求保持代码结构当一个线程拿到一把锁后再去拿其它锁的时候已经被拿到的锁不会被释放循环/环路 等待代码结构阻塞等待的依赖关系形成环了。 此篇博客的全部代码