丽水市住房和城乡建设局网站,网站视频模块配置参数,微信app下载安装官方版,做网站外包死锁 想获取到第二把锁#xff0c;就需要执行完第一层大括号#xff0c;想要执行完第一层大括号#xff0c;就要先获取到第二层的锁。
synchronized (counter2){
synchronized (counter2){}
}
例子:t2先启动#xff0c;t2进行加锁后一定成功#xff0c;但是如果t2进行二…死锁 想获取到第二把锁就需要执行完第一层大括号想要执行完第一层大括号就要先获取到第二层的锁。
synchronized (counter2){
synchronized (counter2){}
}
例子:t2先启动t2进行加锁后一定成功但是如果t2进行二次加锁的时候因为counter2已经被锁定了所以他需要外层大括号的counter2进行解锁但是这又是加锁操作所以就会一直阻塞等待于是就矛盾了产生了对峙的画面(狗咬狗不松口)。
引用计数
可重入锁防止程序员搞成死锁。
如何判定,当前遇到的}是最外层的}??JVM 是咋知道的??
更简单的办法,就是给锁对象里也维护一个计数器
每次{n每次遇到}n--。
就相当于当自己的锁给自己的锁加锁的时候就会形成嵌套锁的时候就会防止形成嵌套锁的情况
synchronized不存在问题idea没必要提示~
死锁的场景 场景一锁是不可重入锁并且一个线程针对一个锁对象连续加锁两次通过引入可重入锁问题就迎刃而解了九月场景一是锁不住的对吧
场景二两个线程两把锁
有线程1和线程2以及有锁A和锁B
现在,线程1和2 都需要获取到锁A和锁B
拿到锁A之后不释放A继续获取锁B
先让两个线程分别拿到一把锁然后在去尝试获取对方的锁
public class Test2 {public static void main(String[] args) throws InterruptedException {Object o1 new Object();Object o2 new Object();Thread thread1new Thread(()-{synchronized (o1){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (o2){System.out.println(两把锁);}}});Thread thread2new Thread(()-{
synchronized (o2){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (o1){System.out.println(两把锁);}
}});thread2.start();thread1.start();thread1.join();thread2.join();System.out.println(结束);}}
这会产生死锁状态。
场景三:N个线程M把锁 死锁的四个必要条件 !!!
1.锁具有互斥特性.(基本特点,一个线程拿到锁之后,其他线程就得阻塞等待)
2.锁不可抢占(不可被剥夺) 一个线程拿到锁之后,除非他自己主动释放锁,否则别人抢不走~~
3. 请求和保持一个线程拿到一把锁之后,不释放这个锁的前提下,再尝试获取其他锁!
4.循环等待. 多个线程获取多个锁的过程中,出现了循环等待. A 等待 B,B 又等待 A.
必要条件缺一不可任何一个死锁的场景都必须同时具备上述四点只要缺少一个都不会构成死锁
public class Test2 {public static void main(String[] args) throws InterruptedException {Object o1 new Object();Object o2 new Object();Thread thread1new Thread(()-{synchronized (o1){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (o2){System.out.println(两把锁);}}});Thread thread2new Thread(()-{
synchronized (o1){try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (o2){System.out.println(两把锁);}
}});thread2.start();thread1.start();thread1.join();thread2.join();System.out.println(结束);}}
约定每个哲学家必须先获取编号小的筷子后获取编号大的筷子~ 内存可见性
import java.util.Scanner;public class Test3 {public static int count0;public static void main(String[] args) {Thread threadnew Thread(()-{while (count0){}System.out.println(t1 执行结束);});Thread thread1new Thread(()-{Scanner scannernew Scanner(System.in);countscanner.nextInt();});thread1.start();thread.start();}
}import java.util.Scanner;public class Test3 {public static int count0;public static void main(String[] args) {Thread threadnew Thread(()-{while (count0){}});Thread thread1new Thread(()-{Scanner scannernew Scanner(System.in);countscanner.nextInt();});thread1.start();thread.start();}
}代码转换为下面的时候就会一直进入无限循环不能跳出循环。
由于系统自带简化
while (count0){}
会load从内存读取数据到cpu寄存器cmp比较同时会产生跳转条件成立继续顺序执行条件不成立就跳转到另外一个地址来执行。
但是循环过快的时候load循环速度慢执行load的时间使上万次的cmp执行效率。
所以就把load优化掉了专业昂就不会进行判断这样就会使效率提高
上述问题本质上还是编译器优化引起的.优化掉load操作之后使t2线程的修改没有被t1线程感知到“内存可见性”问题
volatile
是告诉编译器不要触发上述优化
如何解决上述内存可见性问题就内存可见性问题来说可以通过特殊的方式来控制不让它触发优化的volatile关键字。
volatile是专门针对内存可见性的场景来解决问题的并不能解决之前两个线程循环count的问题
引l入synchronized其实是因为加锁操作本身太重量了.相比于load来说开销更大编译器自然就不会对load优化了.(和加上sleep/io操作
当t1执行的时候要从工作内存中读取count的值而不是从主内存中.后续t2修改count,也是会先修改工作内存同步拷贝到主内存.但是由于t1没有重新读取主内存导致最终t1没有感知到t2的修改. 线程等待通知机制
系统内部线程是抢占式执行随机调度.程序员也是有手段干预的.通过“等待”的方式能够让线程一定程度的按照咱们预期的顺序来执行。
例子在ATM机取钱的时候如果1号去取钱但是ATM正好没钱这时候2号再进去取钱就会产生频繁的没有意义的系统调度cpu永远在做无效的工作就会影响效率。
这时候就需要应用线程等待的方法查看当前的逻辑是否能执行如果不能执行就主动wait避免造成无效工作等待后续时机成熟(ATM有人存钱了)阻塞就会自动被唤醒 synchronized (object){try {object.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}} wait使先解锁然后再阻塞等待其他的线程就可以获取到object这个锁防止了死锁的产生。
import java.util.Scanner;public class Test4 {public static void main(String[] args) {Object onew Object();Thread threadnew Thread(()-{synchronized (o){try {o.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread thread1new Thread(()-{Scanner scannernew Scanner(System.in);synchronized (o) {scanner.nextInt();o.notify();}});thread.start();thread1.start();}}先解除再堵塞等待可以通过notiify来唤醒。
唤醒后不会立刻执行从WAITING-----RUNNABLE---BLOCKED
但是没有规定执行顺序的时候很有可能会导致,thread1先执行最后导致notify产生不了效果 o.notifyAll();//唤醒全部等待的线程
多线程代码
1.单例模式 单例模式是一个经典的设计模式。
单例模式--》单个实例instance就是对象。
整个过程中的某个类有且只有一个对象不会再new出来新的对象
饿汉模式
只要运行就会立刻instance无论后面用不用都会调用
public class Singleton {private static Singleton instancenew Singleton();//只要启动就会立刻生成给instance这个对象public static Singleton getInstance(){//就可以通过getlnstance来获取已经new好的这个而不是重新newreturn instance;}//要禁止外部代码来创建该类的实例~private Singleton(){
//类之外的代码尝试new的时候势必就要调用构造方法由于构造方法私有的.无法调用就会编译出错!}
}懒汉模式 计算机中谈到懒往往是一个褒义词而且是“高效率”的代表
懒汉模式不是在程序启动的时候创建实例而是在第一次使用的时候才去创建如果不使用了就会把创建实例的代价就节省下来了
public class SingletonLazy {private static SingletonLazy instancenull;public static SingletonLazy getInstance(){if (instancenull){instancenew SingletonLazy();}
return instance;}private SingletonLazy(){//重中之重}}在只考虑一个方法getInsttance方法的情况下考虑
饿汉模式是否安全安全1
创建实例的时机是在java进程启动(比main调用还早的时机)
懒汉模式是否安全不安全 t2切换回来之后还是要进行新的new对象操作就会产生多个对象了。
Instance中的地址指向的那个对象可就是一个大的对象了。
产生的对象会被覆盖但是产生以及浪费的时间可是真金白银。
添加
synchronized锁操作。
public class SingletonLazy {public static Object object new Object();public static void main(String[] args) {}private static SingletonLazy instance null;//只要启动就会立刻生成给instance这个对象public static SingletonLazy getInstance() {//就可以通过getlnstance来获取已经new好的这个而不是重新newif (instance null) {synchronized (object) {instance new SingletonLazy();}return instance;}//要禁止外部代码来创建该类的实例~private SingletonLazy() {
//类之外的代码尝试new的时候势必就要调用构造方法由于构造方法私有的.无法调用就会编译出错!}}
}public static SingletonLazy getInstance() {//就可以通过getlnstance来获取已经new好的这个而不是重新newsynchronized (object) {if (instance null) {instance new SingletonLazy();}return instance;}
针对后续调用明明没有线程安全问题还要加锁就是画蛇添足加锁本身也是有开销的可能会使线程阻塞
所以要进行优化操作。 StringBuilder 不带锁
StringBuffer 带锁
public static SingletonLazy getInstance() {if (instancenull) {//判定是否要加锁实例化之后线程自然安全了就无需加锁了实例化之前new之前就应该要加锁synchronized (object) {//在这俩if之间synchronized会使该线程阻塞阻塞过程中其他线程就可能会修改Instance的值if (instance null) {//判定是否要创建对象instance new SingletonLazy();}return instance;}private SingletonLazy() {}}}此外还要加上volatile这样防止他进行优化操作。
t1线程修改了Instance引l用t2有可能读不到.概率应该是比较小.加上volatile主要是为了万无一失.
指令重排序
加了volatile也能够解决指令重排序引l起的线程安全问题
调整顺序最主要的目的就是提高效率.前提是保证逻辑是等价的
重排序的前提一定是重新排序之后逻辑和之前等价单线程下编译器进行指令重排序的操作一般都是没问题的.编译器可以准确的识别出哪些操作可以重排序而不会影响到逻辑~
instancenew SingletonLazy
这一行代码其实还可以简要细分成三个步骤
例子
1.买了个房子)2.装修3.拿到钥匙
123(精装房开发商直接给你装修好你收房的时候已经装修完了
132毛坏房自己装修
1.申请内存空间
2.调用构造方法.对内存空间进行初始化
3.把此时内存空间的地址赋值给Instance引用
在指令重排序优化策略下上述执行的过程不一定是123也可能是132(1一定是先执行的
如果这样执行1.申请内存3把地址赋值给引用
一旦执行完意味着Instance就非null !!但是指向的对象其实是一个未初始化的对象里面的成员都是0这样就会返回没有初始化的对象。
如果在执行其他方法的时候就会出现没有初始化的对象在操作会产生非常严重的问题/
2.调用方法 所以要引用volatile
按照加上volatile之后此时t2线程读到的数据一定是t1已经构造完毕的完整对象了.一定是123都执行完毕的对象)
public class SingletonLazy {private static volatile SingletonLazy instance null;//3public static Object object new Object();public static SingletonLazy getInstance() {if (instance null) {//2synchronized (object) {//1if (instance null) {instance new SingletonLazy();}}}return instance;}private SingletonLazy() {}
}2.阻塞队列 0.普通队列线程不安全的
1.优先级队列.
2.阻塞队列先进先出线程安全并且带有阻塞功能-------》1.队列为空尝试出队列出队列操作就会阻塞一直阻塞到队列不空为止2.队列为满尝试入队列入队列操作也会阻塞一直阻塞到队列不满为止.
3.消息队列不是普通的先进先出而是通过topic(一块)这样的参数来对数据进行归类出队列的时候指定topic每个topic下的数据是先进先出的.
生产者消费者模型 在开发中主要又有两方面的意义
1.能够让程序进行解耦
举个包饺子的例子
中间的盖帘相当于一个阻塞队列/消息队列
如果师娘擀的慢我俩包的快此时盖帘上就空着了我和小汤就会阻塞等待如果我俩包的慢师娘擀的快~很快盖帘放满了师娘就要阻塞等待。 如果让A直接调用B意味着A的代码中就要包含很多和B相关的逻辑B的代码中也会包含和A相关的逻辑彼此之间就有了一定的耦合。
一旦对A做出修改可能就会影响到B反之亦然一旦A出bug也容易把B牵连到反之也是亦然。 站在A的视角不知道B的存在只关心和队列的交互站在B的视角不知道A的存在只关心和队列的交互此时对A的修改就不太容易影响到BA如果挂了也不会影响到B。 2.能够使程序削峰填谷
客户端发来的请求个数多少没法提前预知.遇到某些突发事件就可能会导致客户端给服务器的请求激增~~ 如果是这样的话b要进行许多重量级操作一旦A收到的请求增加了B的请求也会增加A做的工作简单B做的复杂这样B就容易崩溃。 增加了mq后无论A给队列写多快B都可以按照固有的节奏来消费数据B的节奏就不一定完全跟着A了.相当于队列把B保护起来了。
阻塞队列
BlockingQueueE 阻塞队列只需要考虑入队列和出队列即可.阻塞队列没有“取队首元素”操作.也不是完全没有只不过是没有阻塞功能)
其中的offer和put没有阻塞功能但是put和take有