网站网络推广策略和电子商务,怎么建个人网页,iis怎么建设网站,建立网站免费大家新年快乐呀#xff0c;今天是第三期啦#xff0c;大家前几期的内容掌握的怎么样啦#xff1f; 1#xff0c;线程死锁
1.1 构成死锁的场景
a#xff09;一个线程一把锁
这个在java中是不会发生的#xff0c;因为我们之前讲的可重入机制#xff0c;在其他语言中可… 大家新年快乐呀今天是第三期啦大家前几期的内容掌握的怎么样啦 1线程死锁
1.1 构成死锁的场景
a一个线程一把锁
这个在java中是不会发生的因为我们之前讲的可重入机制在其他语言中可能会发生的
public static void main(String[] args) throws InterruptedException {Object locker new Object();Thread t1 new Thread(()-{synchronized (locker){synchronized (locker){System.out.println(1111);}}});t1.start();t1.join();System.out.println(main);}
按理来说t1线程刚进synchronized就获取到了锁对象就要保持进入第二个synchronized就要请求第一个锁对象第一个要保持不给你锁对象它让第二个先给他第二个synchronized说你先给我我才有锁对象给你呀它俩就这么一直僵持着但是java有可重入机制不会发生这样的死锁的
b两个线程两把锁
我们来模拟一个吃饺子的过程小明小亮吃饺子有酱油和醋对应两把锁他们喜欢这两个东西一起加我不喜欢
public class Demo2 {public static void main(String[] args) throws InterruptedException {Object locker1 new Object();//酱油Object locker2 new Object();//醋Thread t1 new Thread(()-{synchronized (locker1){System.out.println(获取到了酱油);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2){System.out.println(酱油和醋都是 Thread.currentThread().getName() 的啦);}}},小明);Thread t2 new Thread(()-{synchronized (locker2){System.out.println(获取到了醋);try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker1){System.out.println(酱油和醋都是 Thread.currentThread().getName() 的啦);}}},小亮);t1.start();t2.start();}
}我们来看运行结果 没有人获得酱油和醋并且程序也没有正常停止 这俩线程都因为锁竞争阻塞了这就构成了死锁我们加那个sleep是为了保证小亮拿醋小明拿酱油之后再竞争互相的不然可能就小明太快了直接全拿走了或者小亮全拿走了
cn个线程m把锁 一个很经典的模型哲学家就餐问题
1哲学家可以放下筷子思考
2哲学家拿筷子可以吃面条没有洁癖两根才能吃
但是哲学家都很固执拿到了筷子是不会放手的那么如果在当前这个图上每个人都想吃面条每个人都到拿到了面前的筷子要吃面条还需要一个筷子他们就会想要别人的筷子然而每个人都不会放开自己的筷子你等我我等你最后大家都饿死这就构成了死锁但是按理来说这个模型出现这样的情况非常非常低那么中国10几亿人这个概率就会无限放大线程安全要做到完全没有危险的概率
1.2 死锁的四个必要条件
a互斥基本特性
一个线程获取到锁其他线程再想获得这个锁就要阻塞等待
b不可剥夺基本特性
也可以叫不可抢占如果线程1获取到了锁线程2再想获取锁是不可以抢夺的必须阻塞等待
c请求和保持
一个线程获取了锁1之后再不放弃锁1的前提下获取锁2
d循环等待
a等待bb等待cc等待dd等待a构成死锁循环
1.3 如何避免死锁
我们刚才说的构成死锁的四种情况中互斥和不可剥夺是锁的基本特性我们是改变不了的我们只能去改变请求保持和循环等待
a打破请求和保持
请求和保持大概率是发生在嵌套中的我们可以用并列来代替嵌套但是通用性较低
我们就拿刚才的吃饺子来举例子把
public class Demo1 {public static void main(String[] args) {Object locker1 new Object();//酱油Object locker2 new Object();//醋Thread t1 new Thread(()-{synchronized (locker1){System.out.println(小明拿到酱油);}try {Thread.sleep(1111);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2){System.out.println(小明拿到醋);}},小明);Thread t2 new Thread(()-{synchronized (locker1){System.out.println(小亮拿到酱油);}try {Thread.sleep(1111);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (locker2){System.out.println(小亮拿到醋);}},小亮);t1.start();t2.start();}
}并列锁虽然没有构成死锁但是违背了我们的想法就是让小明和小亮获得两个锁刚才说的通用性不强也是在这里
b打破循环等待
第二个方法改变加锁的顺序我们还有吃饺子的例子但是这次要拿到两个锁
public class Demo2 {public static void main(String[] args) {Object locker1 new Object();//酱油Object locker2 new Object();//醋Thread t1 new Thread(()-{synchronized (locker1){System.out.println(小明拿到酱油啦);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker2){System.out.println(小明拿到醋和酱油啦);}}},小明);Thread t2 new Thread(()-{synchronized (locker1){System.out.println(小亮拿到酱油啦);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (locker2){System.out.println(小亮拿到醋和酱油啦);}}},小亮);t1.start();t2.start();}
}我们改变了加锁的顺序 也是能避免死锁问题的
———————————————————————————————————————————
2内存可见性
这也是导致线程安全的问题之一
我们来写一个例子嗷
import java.util.Scanner;public class Demo3 {static int i 0;public static void main(String[] args) {Object obj new Object();Thread t1 new Thread(()-{while(i0){}System.out.println(结束);});Thread t2 new Thread(()-{try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}Scanner scanner new Scanner(System.in);synchronized (obj){i scanner.nextInt();}});t1.start();t2.start();}
}我们来看这个代码t1线程根据i的数值一直循环直到i的值被t2线程修改才停止事实是这样的吗。我们来试试 无法暂停这是为啥
这就是因为内存可见性问题程序员的水平参差不齐java大佬为了照顾我们这样的小卡拉米就弄了个编译器优化所以我们写的代码并不会直接执行我们刚才写的while(i0)这段代码我们要等待t2线程来修改i可能我们就用了几秒的时间但对于t1线程这这边是沧海桑田万物轮回谁还记得什么t1呀它等于0就得了再底层一点解释呢就是有“工作内存” 和 “主内存”我们应该是从主内存中拿到数据放到工作内存中再从工作内存放回主内存但是这么一直一直重复去主内存的时间开销是工作内存的几千倍编译器就不去主内存了直接去工作内存中拿数据但是后期修改了主内存然而此处代码已经完全忽略主内存了就无法修改了这就是内存可见性问题那么怎么避免呢
———————————————————————————————————————————
3volatile 关键字
我们可以使用volatile关键字避免内存可见性问题
3.1 volatile 能保证内存可见性
import java.util.Scanner;public class Demo3 {volatile static int i 0;public static void main(String[] args) {Object obj new Object();Thread t1 new Thread(()-{while(i0){}System.out.println(结束);});Thread t2 new Thread(()-{try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}Scanner scanner new Scanner(System.in);synchronized (obj){i scanner.nextInt();}});t1.start();t2.start();}
}看解决了吧就加了一个volatile
3.2 volatile 不能保证原子性
还记得原子性吗就是这个操作在底层是不是原子的是不是分几步再多线程中会影响到这个操作我们拿之前那个两个线程修改一个整形
public class Demo4 {volatile static int count 0;public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(()-{for(int i0;i100000;i){count;}});Thread t2 new Thread(()-{for(int i0;i100000;i){count;}});t1.start();t2.start();t1.join();t2.join();System.out.println(Final count: count);}
}所有我们只有使用锁才行
———————————————————————————————————————————
4wait 和 notify
这是个什么玩意线程不是随机调度抢占式执行的吗我们可以用这个玩意稍加限制协调线程之间的逻辑顺序
4.1 wait() 方法
这个东西跟锁和sleep不一样都是等待但是是有区别的wait(是等的时候会释放锁被唤醒再拿到锁而sleep这个byd它抱着锁睡............锁的话就是阻塞等待嘛
我们来试试wait方法是搭配锁来使用的最好还要加while循环
public class Demo5 {public static void main(String[] args) {Object locker new Object();Thread t1 new Thread(()-{System.out.println(Thread 1);synchronized (locker){System.out.println(1000);try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(2000);}});t1.start();}
}我们来看看运行结果 死等因为没有东西能够唤醒wait
我们可以设置超时时间也可以使用notify方法
public class Demo5 {public static void main(String[] args) {Object locker new Object();Thread t1 new Thread(()-{System.out.println(Thread 1);synchronized (locker){System.out.println(1000);try {locker.wait(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(3000);}});t1.start();}
}这样代码会在2秒后打印3000
4.2 notify() 方法
用来唤醒wait注意这些都是搭配锁对象来用的
对于notify如果存在多个使用同一个锁对象的wait它没有规律会随机唤醒一个wait
public class Demo6 {public static void main(String[] args) throws InterruptedException {Object locker new Object();Thread t1 new Thread(()-{System.out.println(Thread 1);synchronized (locker){System.out.println(线程1获得锁);try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(线程1释放锁);}});Thread t2 new Thread(()-{System.out.println(Thread 2);synchronized (locker){System.out.println(线程2获得锁);try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(线程2释放锁);}});t1.start();t2.start();Thread.sleep(1000);synchronized (locker){locker.notify();}}
}线程1成功释放了锁说明notify唤醒了线程1的waite
我们再试试 4.3 notifyAll() 方法
这个就是全部释放
public class Demo7 {public static void main(String[] args) throws InterruptedException {Object locker new Object();Thread t1 new Thread(()-{System.out.println(Thread 1);synchronized (locker){System.out.println(线程1获得锁);try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(线程1释放锁);}});Thread t2 new Thread(()-{System.out.println(Thread 2);synchronized (locker){System.out.println(线程2获得锁);try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(线程2释放锁);}});t1.start();t2.start();Thread.sleep(1000);synchronized (locker){locker.notifyAll();}}
}完美
4.4 wait 和 sleep的对比
这个没啥好说的了wait先加锁到。wait操作的时候释放锁唤醒的时候再拿着锁而sleep纯抱着锁睡还会被interrupt唤醒说实话抱着锁睡听不好的可能会有很多线程都等着它很浪费时间的sleep会释放Cpu的资源不再占用了就这样吧大家加油等我更新下一期