目录
一、深入单例模式
(这里是在之前笔记的基础上进行补充;)
1、之前的笔记
https://blog.csdn.net/qq_29689343/article/details/105046493
2、双重检测锁的(DCL)懒汉式 + volatile
代码:
package com.zibo.singleton;
//懒汉式
public class LazyMan {
private LazyMan(){
}
private volatile static LazyMan lazyMan;
//双重检测锁的懒汉式 DCL懒汉式 + volatile
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();//不是原子性操作
/*
* 底层操作步骤:
* 1、分配内存空间;
* 2、执行构造方法,初始化对象;
* 3、将这个对象指向这个空间;
* 指令重排会导致这个顺序混乱,导致lazyMan未真正完成初始化,此时就需要再加上volatile关键字
*/
}
}
}
return lazyMan;
}
}
3、使用反射破解单例模式
代码:
package com.zibo.singleton;
import java.lang.reflect.Constructor;
//懒汉式
public class LazyMan {
private LazyMan(){
}
private volatile static LazyMan lazyMan;
//双重检测锁的懒汉式 DCL懒汉式 + volatile
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();//不是原子性操作
/*
* 底层操作步骤:
* 1、分配内存空间;
* 2、执行构造方法,初始化对象;
* 3、将这个对象指向这个空间;
* 指令重排会导致这个顺序混乱,导致lazyMan未真正完成初始化,此时就需要再加上volatile关键字
*/
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws Exception {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor();
constructor.setAccessible(true);
LazyMan instance1 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
结果:
(两个对象)
4、防止单例模式被反射破解
概述:
在构造方法中做判断;
代码:
package com.zibo.singleton;
import java.lang.reflect.Constructor;
//懒汉式
public class LazyMan {
private volatile static LazyMan lazyMan;
private static boolean flag = false;
private LazyMan(){
synchronized (LazyMan.class){
if(flag){
throw new RuntimeException("不要试图使用反射破解单例!");
// if(lazyMan!=null){
// throw new RuntimeException("不要试图使用反射破解单例!");
// //这个时候仍然会出现一个问题,就是两个对象都是用反射创建,也会破解单例模式,设置标志位记录之
// }
}else {
flag = true;
}
}
}
//双重检测锁的懒汉式 DCL懒汉式 + volatile
public static LazyMan getInstance(){
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();//不是原子性操作
/*
* 底层操作步骤:
* 1、分配内存空间;
* 2、执行构造方法,初始化对象;
* 3、将这个对象指向这个空间;
* 指令重排会导致这个顺序混乱,导致lazyMan未真正完成初始化,此时就需要再加上volatile关键字
*/
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws Exception {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor();
constructor.setAccessible(true);
LazyMan instance1 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
结果:
5、但是
道高一尺,魔高一丈!
这样并不是最安全的,可以使用反射将flag的值进行修改,在创建一个对象后,将flag修改成false,然后就可以继续创建对象;
二、CAS
概述:
比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作,否是就一直循环判断(自旋锁);
缺点:
①循环耗时;
②一次只保证一个共享变量的原子性;
③存在ABA问题;
代码:
package com.zibo.cas;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
//CAS compareAndSet:比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//expect期望;update更新;
//public final boolean compareAndSet(int expect, int update)
//如果期望值达到了就更新,否则不更新 CAS是CPU的并发原语
atomicInteger.compareAndSet(2020,2021);//期望是2020,如果是2020,则更新为2021
System.out.println(atomicInteger);
}
}
三、ABA问题
1、问题引出
(有一个线程将1改成3,又改回去了)
2、原子引用
带版本号的原子操作;
代码:
package com.zibo.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class CASDemo {
//CAS compareAndSet:比较并交换
public static void main(String[] args) {
//注意:如果泛型是包装类,注意对象的引用问题
AtomicStampedReference<Integer> reference = new AtomicStampedReference(1,1);
new Thread(()->{
int stamp = reference.getStamp();//获得当前版本号
System.out.println("a1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a_" + reference.compareAndSet(1, 2, reference.getStamp(), reference.getStamp() + 1));
System.out.println("a2=>" + reference.getStamp());
System.out.println("a_" + reference.compareAndSet(2, 1, reference.getStamp(), reference.getStamp() + 1));
System.out.println("a3=>" + reference.getStamp());
}).start();
new Thread(()->{
int stamp = reference.getStamp();//获得当前版本号
System.out.println("b1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("b_" + reference.compareAndSet(1, 2, reference.getStamp(), reference.getStamp() + 1));
System.out.println("b2=>" + reference.getStamp());
}).start();
}
}
结果:
a1=>1
b1=>1
b_false
a_true
b2=>2
a2=>2
a_true
a3=>3
四、可重入锁
1、代码
package com.zibo.lock01;
// Synchronized
public class Demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(phone::sms, "A").start();
new Thread(phone::sms, "B").start();
}
}
class Phone {
public synchronized void sms() {
System.out.println(Thread.currentThread().getName() + "sms");
call(); // 这里也有锁
}
public synchronized void call() {
System.out.println(Thread.currentThread().getName() + "call");
}
}
2、说明
3、Lock 版
package com.zibo.lock01;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(phone::sms, "A").start();
new Thread(phone::sms, "B").start();
}
}
class Phone2 {
Lock lock = new ReentrantLock();
public void sms() {
lock.lock(); // 细节问题:lock.lock(); lock.unlock(); // lock 锁必须配对,否则就会死在里面
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "sms");
call(); // 这里也有锁
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
lock.unlock();
}
}
public void call() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
五、自旋锁
1、引出
(自旋锁:不断尝试,直到成功!)
2、自己写个锁
package com.zibo.lock01;
import java.util.concurrent.atomic.AtomicReference;
/**
* 自旋锁
*/
public class SpinlockDemo {
// Thread null
AtomicReference<Thread> atomicReference = new AtomicReference<>();
// 加锁
public void myLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "==> mylock");
// 自旋锁:如果非空(加锁)的话,就一直循环
while (!atomicReference.compareAndSet(null, thread)) {
}
}
// 解锁
public void myUnLock() {
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "==> myUnlock");
//改为空,上面的自旋锁就结束了
atomicReference.compareAndSet(thread, null);
}
}
3、测试
package com.zibo.lock01;
import java.util.concurrent.TimeUnit;
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
// ReentrantLock reentrantLock = new ReentrantLock();
// reentrantLock.lock();
// reentrantLock.unlock();
// 底层使用的自旋锁CAS
SpinlockDemo lock = new SpinlockDemo();
new Thread(() -> {
lock.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnLock();
}
}, "T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
lock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.myUnLock();
}
}, "T2").start();
}
}
4、测试结果
六、死锁排查
1、图示
2、死锁代码
package com.zibo.lock01;
import java.util.concurrent.TimeUnit;
public class DeadLockDemo {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyThread(lockA, lockB), "T1").start();
new Thread(new MyThread(lockB, lockA), "T2").start();
}
}
class MyThread implements Runnable {
private final String lockA;
private final String lockB;
public MyThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() +
"lock:" + lockA + "=>get" + lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() +
"lock:" + lockB + "=>get" + lockA);
}
}
}
}
3、解决方式1:使用 jps -l 定位进程号
4、解决方式2:使用 jstack 进程号 找到死锁问题
5、面试回答
看面试和看堆栈信息;