1.JMM
问题1:请你谈谈你对Volatile的理解
Volatile 是java虚拟机提供轻量级的同步机制
1. 保证可见性
2. 不保证原子性
3. 禁止指令重排
1.1、什么是JMM
JMM Java内存模型 不存在的东西,概念!约定 !
1.2、关于JMM的一些同步的约定:
1、线程解锁前,必须把共享变量立刻刷回主存,
2、线程枷锁前,必须读取主存中的最新值到工作内存中
3、加锁和解锁的是同一把锁
线程工作内存,主内存。store和write换一个位置
2.Volatile
2.1.保证可见性
package com.kuang.tvolatile;
import java.util.concurrent.TimeUnit;
public class Demo01 {
//不加volatile ,程序就会陷入死循环,加了保证主内存的可见性
public volatile static int num=0;
public static void main(String[] args) {
new Thread(()->{//线程对主内存的num值的变化是不知道的
while (num==0){
}
System.out.println(num);
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
}
2.2不保证原子性
package com.kuang.tvolatile;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//不保证原子性
public class Demo02 {
//volatile不保证原子性
private volatile static int num=0;
//改变就会重新加载主存的值,不改变就存一次到工作内存,工作内存相当于线程私有的栈帧,先这么理解
public static void add(){
num++;
}
// public void add1(){
// lock.lock();
// try {
// num++;
// } catch (Exception e) {
// e.printStackTrace();
// } finally {
// lock.unlock();
// }
// }
public static void main(String[] args) {
//理论上num结果应该为两万
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
// Demo02 demo02 = new Demo02();
// for (int i = 0; i < 10; i++) {
// new Thread(()->{
// for (int j = 0; j < 1000; j++) {
// demo02.add1();
// }
// }).start();
// }
while (Thread.activeCount()>2){//main gc
Thread.yield();
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
如果不加lock和synchronized,怎么样保证原子性?
使用原子类,解决原子性问题
package com.kuang.tvolatile;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//不保证原子性
public class Demo02 {
//volatile不保证原子性
// private volatile static int num=0;
public volatile static AtomicInteger num=new AtomicInteger();
//改变就会重新加载主存的值,不改变就存一次到工作内存,工作内存相当于线程私有的栈帧,先这么理解
public static void add(){
num.getAndIncrement();//AtomicInterger+1方法,CAS
}
// public void add1(){
// lock.lock();
// try {
// num++;
// } catch (Exception e) {
// e.printStackTrace();
// } finally {
// lock.unlock();
// }
// }
public static void main(String[] args) {
//理论上num结果应该为两万
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
// Demo02 demo02 = new Demo02();
// for (int i = 0; i < 10; i++) {
// new Thread(()->{
// for (int j = 0; j < 1000; j++) {
// demo02.add1();
// }
// }).start();
// }
while (Thread.activeCount()>2){//main gc
Thread.yield();//Thread.yield() 方法是Java中的一个静态方法,用于提示调度程序当前线程愿意放弃其当前时间片。
// 换句话说,它建议线程愿意暂停执行并为同一优先级的其他线程提供机会。
}
System.out.println(Thread.currentThread().getName()+" "+num);
}
}
这些类的底层都直接和操作系统直接挂钩 !在内存中修改值!!!Unsafe类是一个很特殊的存在!
2.3 、指令重排
什么是 指令重排: 你写的程序,计算机并不是按照你写的那样去执行的。
源代码->编译器优化的重排-->指令并行也可能会重排-->内存系统也会重排-->执行
volatile 是可以保持可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
问:volatile在哪里避免指令重排的现象用的最多,在单例模式!!!
3.彻底玩转单例模式
饿汉式,DCL懒汉式
饿汉式
package com.kuang.single;
//饿汉式
public class Hungry {
private byte[] bytes1=new byte[1024*1024];
private byte[] bytes2=new byte[1024*1024];
private byte[] bytes3=new byte[1024*1024];
private byte[] bytes4=new byte[1024*1024];
private Hungry(){
}
private final static Hungry HUNGRY= new Hungry();
public static Hungry getHungry(){
return HUNGRY;
}
}
DCL懒汉式 DCL:双重检测模式
原子性
package com.kuang.single;
//懒汉式
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"ok");
}
//双重检测锁,volatile内存屏障,防止指令重排,
private volatile static LazyMan lazyMan;
//双重检测该模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan=new LazyMan();//不是一个原子性操作
/**
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向这个空间
*
* 2和3的顺序,在指令重排的时候
*/
}
//外面那一层判断空 是因为可以减少同步的时间,不会全部同步在锁当中,而一部分代码会因为不为空而直接获取对象。
}
}
return lazyMan;
}
//多线程并发
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
静态内部类
package com.kuang.single;
//静态内部类
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER=new Holder();
}
}
进阶
package com.kuang.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
//懒汉式
//道高一尺魔高一丈
public class LazyMan {
private static boolean qingjiang=false;
private LazyMan(){
synchronized (LazyMan.class){
if (qingjiang==false){
qingjiang=true;
}else {
throw new RuntimeException("不要尝试反射破坏单例模式");
}
}
}
//双重检测锁,原子性,volatile内存屏障,防止指令重排,
private volatile static LazyMan lazyMan;
//双重检测该模式的 懒汉式单例 DCL懒汉式
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if (lazyMan==null){
lazyMan=new LazyMan();//不是一个原子性操作
/**
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向这个空间
*
* 2和3的顺序,在指令重排的时候
*/
}
//外面那一层判断空 是因为可以减少同步的时间,不会全部同步在锁当中,而一部分代码会因为不为空而直接获取对象。
}
}
return lazyMan;
}
//多线程并发
// public static void main(String[] args) {
// for (int i = 0; i < 10; i++) {
// new Thread(()->{
// LazyMan.getInstance();
// }).start();
// }
// }
public static void main(String[] args) throws Exception {
// LazyMan instance = LazyMan.getInstance();
Field qingjiang = LazyMan.class.getDeclaredField("qingjiang");
qingjiang.setAccessible(true);
Constructor<? extends LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
LazyMan instance2 = constructor.newInstance();
qingjiang.set(constructor,false);
LazyMan instance = constructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
反射无法破坏枚举
枚举在运行时没有无参构造,但是有一个有参构造,反射拿无参,会抛没有更多的方法
反射拿有参构造,则会抛出,
throw new IllegalArgumentException("Cannot reflectively create enum objects");
4.深入理解CAS
Unsafe类
5.原子引用
ABA问题
package com.kuang.cas;
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
//CAS atomicInteger.compareAndSet() CAS就是compareAndSet的缩写:比较并交换!
public static void main(String[] args){
AtomicInteger atomicInteger=new AtomicInteger(2020);
//对于我们平时写的SQL:乐观锁!
//expect期望 update更新
//public final boolean compareAndSet(int expect, int update)
//如果我期望的值达到了,那么就更新,否则,就不更新,CAS 是CPU的并发原语!
//捣乱的线程
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println(atomicInteger.get());
// atomicInteger.getAndIncrement();
//期望的线程
System.out.println(atomicInteger.compareAndSet(2020, 6666));
System.out.println(atomicInteger.get());
}
}
6.各种锁的理解
package com.kuang.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
public class CASDemo {
//解决ABA问题,引入原子引用
//CAS atomicInteger.compareAndSet() CAS就是compareAndSet的缩写:比较并交换!
public static void main(String[] args){
// AtomicInteger atomicInteger=new AtomicInteger(2020);
AtomicStampedReference<Integer> atomic = new AtomicStampedReference<>(1, 1);
new Thread(()->{
int stamp = atomic.getStamp();//获取当前版本号
System.out.println("a1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("a2=>"+atomic.compareAndSet(1, 2, atomic.getStamp(), atomic.getStamp() + 1));
System.out.println("a3=>"+atomic.compareAndSet(2, 1, atomic.getStamp(), atomic.getStamp() + 1));
System.out.println("a4=>"+atomic.getStamp());
},"a").start();
//乐观锁原理是一样的
new Thread(()->{
int stamp = atomic.getStamp();//获取当前版本号
System.out.println("b1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("b1=>"+(atomic.compareAndSet(1, 6, stamp, stamp + 1)));
System.out.println("b2=>"+atomic.getStamp());
},"b").start();
//
// //对于我们平时写的SQL:乐观锁!
//
//
// //expect期望 update更新
// //public final boolean compareAndSet(int expect, int update)
// //如果我期望的值达到了,那么就更新,否则,就不更新,CAS 是CPU的并发原语!
// //捣乱的线程
// System.out.println(atomicInteger.compareAndSet(2020, 2021));
// System.out.println(atomicInteger.get());
//
// System.out.println(atomicInteger.compareAndSet(2021, 2020));
// System.out.println(atomicInteger.get());
//
atomicInteger.getAndIncrement();
//
// //期望的线程
// System.out.println(atomicInteger.compareAndSet(2020, 6666));
// System.out.println(atomicInteger.get());
}
}
对应的思想就是:乐观锁
6.1、公平锁与非公平锁
6.2、可重入锁
package com.kuang.reelock;
//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");
}
}
package com.kuang.reelock.reelock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//Synchronized
public class Demo02{
public static void main(String[] args) {
Phone1 phone = new Phone1();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone1{
Lock lock =new ReentrantLock();
public void sms(){
lock.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();
}
}
}
6.3、自旋锁
自定义锁
package com.kuang.spinlock;
import java.util.concurrent.atomic.AtomicReference;
//自旋锁
public class SpinLockDemo {
/**
* int 0
* Thread null
*/
AtomicReference<Thread> atomicReference=new AtomicReference<>();
//加锁
public void lock(){
Thread thread = Thread.currentThread();
System.out.println("加锁的是"+thread.getName());
//第一次进来 为null 成功改变,while条件不成立,不自旋,但是条件没有释放,第二次进来,他不是null了。返回false但是取反为true
//自旋等待解锁
while (!atomicReference.compareAndSet(null,thread)){
}
}
//解锁
public void unlock(){
Thread thread = Thread.currentThread();
System.out.println("解锁的是"+thread.getName());
atomicReference.compareAndSet(thread,null);
}
//
}
自定义锁测试
package com.kuang.spinlock;
import java.util.concurrent.TimeUnit;
public class TestDemo {
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.lock();
try {
System.out.println(Thread.currentThread().getName()+"执行");
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinLockDemo.unlock();
}
},"A").start();
new Thread(()->{
spinLockDemo.lock();
try {
System.out.println(Thread.currentThread().getName()+"执行");
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
} finally {
spinLockDemo.unlock();
}
},"B").start();
}
}
6.4、死锁
死锁是多线程或多进程并发执行时可能遇到的一种问题,它具有以下四个必要条件,也被称为死锁的四个特性:
1. 互斥条件(Mutual Exclusion):至少有一个资源被多个线程或进程竞争使用,且一次只能被一个线程或进程占用。当一个线程或进程占用了资源,其他线程或进程必须等待该资源释放。
2. 请求与保持条件(Hold and Wait):线程或进程在持有至少一个资源的同时,又请求其他线程或进程持有的资源。即线程或进程在等待其他资源时,仍然保持已经占有的资源。
3. 不可剥夺条件(No Preemption):已经被一个线程或进程占用的资源不能被其他线程或进程强制性地抢占,只能由占有者主动释放。
4. 循环等待条件(Circular Wait):存在一个线程或进程的资源请求序列,使得每个线程或进程都在等待下一个线程或进程所持有的资源。形成一个闭环,导致循环等待。
当以上四个条件同时满足时,就可能发生死锁。在死锁发生时,线程或进程无法继续执行,导致系统无法正常运行。因此,死锁的四个特性是死锁问题的根本原因,需要通过合理的资源分配和调度策略来避免死锁的发生。
package com.kuang.sisuo;
import java.util.concurrent.TimeUnit;
public class DeadLockDemo {
public static void main(String[] args) {
String A="A";
String B="B";
new Thread(new MyThread(A,B)).start();
new Thread(new MyThread(B,A)).start();
}
}
class MyThread implements Runnable {
private String A;
private String B;
public MyThread(String a, String b) {
A = a;
B = b;
}
@Override
public void run() {
synchronized (A){
System.out.println(A+"获取"+B);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B){
System.out.println(B+"获取"+A);
}
}
}
}
如何解决问题
栈:jstack
堆: jprofile
调优