1.死锁
1.1产生死锁的四个必要条件
互斥条件:一个线程对获取到的资源具有排他性,及一个资源只能被一个线程所占用。
请求与保持:一个线程因请求被占有的资源发生阻塞的时候,不会释放自己所占有的资源。
不剥夺条件:自己占有的资源不能被其他线程所剥夺,只有自己资源运行完成后才能被释放。
循环与等待:发生死锁的时候一定会形成一个闭环。
1.2解决死锁:
破坏四个条件任意一个
破坏互斥:不可以它是线程的性质
破坏请求与保持:一次性占有所有的资源。
破坏不可剥夺:当一个线程申请资源申请不到时释放自己所占有的资源。
破坏循环与等待:一个线程按一定顺序获取资源,然后按照反序来释放资源。
产生死锁的例子:
public class bingfa {
private static Object object1=new Object();
private static Object object2=new Object();
public static void main(String[] args) {
new Thread(() ->{
synchronized (object1){
System.out.println(Thread.currentThread().getName()+"获取到了资源一");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程一等待一秒钟后");
synchronized (object2){
System.out.println(Thread.currentThread().getName()+"获取到了资源二");
}
}
}).start();
new Thread(() ->{
synchronized (object2){
System.out.println(Thread.currentThread().getName()+"获取到了资源一");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程二等待一秒钟后");
synchronized (object1){
System.out.println(Thread.currentThread().getName()+"获取到了资源二");
}
}
}).start();
}
}
上面代码结果可以看到线程一获取不到资源二、线程二获取不到资源一。
通过破坏循环等待来防止死锁及通过调整顺序来防止死锁。
public class bingfa {
private static Object object1=new Object();
private static Object object2=new Object();
public static void main(String[] args) {
new Thread(() ->{
synchronized (object1){
System.out.println(Thread.currentThread().getName()+"获取到了资源一");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程一等待一秒钟后");
synchronized (object2){
System.out.println(Thread.currentThread().getName()+"获取到了资源二");
}
}
}).start();
new Thread(() ->{
synchronized (object1){
System.out.println(Thread.currentThread().getName()+"获取到了资源二");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程二等待一秒钟后");
synchronized (object2){
System.out.println(Thread.currentThread().getName()+"获取到了资源二");
}
}
}).start();
}
}
悲观锁:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
乐观锁:
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
3.volatile
3.1它时用来保证线程安全的,但是他只保证了程序的可见性不能保证变量的原子性。
原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
public class valititelTest {
private volatile int a = 0;
volatile boolean flag = false;
public void write() {
a = 1;
flag = true;
System.out.println("write over");
}
public void read(){
if (flag){
System.out.println("i is"+a);
System.out.println("read true");
}
else{
System.out.println("i is"+a);
System.out.println("read false");
}
}
public static void main(String[] args) {
valititelTest a=new valititelTest();
new Thread(() -> a.write()).start();
new Thread(() -> a.read()).start();
}
}
从结果可以看出,一个线程对volitatel变量进行操作另外一个线程能够看到它状态的改变,也允许它的改变。
4.csa
转载介绍csa的博客
[https://blog.csdn.net/qq_32998153/article/details/79529704]
4.1、简介:CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和 A 的值是一样的,那么就将内存里面的值更新成 B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a 线程获取地址里面的值被b 线程修改了,那么 a 线程需要自旋,到下次循环才有可能机会执行。
4.2、ABA问题:银行转账的例子,小陈银行账户有100块钱,取出50,发起了两次、小陈老妈存50.。现在的情况是小陈中一个线程取钱取成公了,小陈老妈存成功之后,另外一个线程发现100然后取出50,现在账户是50块钱。
5.threadlocald
应用场景:变量是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本。这种情况之下ThreadLocal就非常使用。每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal。当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。
应用实例:jdbc数据库连接,session管理