JMM
Java内存模型,是一个概念,不存在的东西,概念、约定
关于JMM的一些同步约定:
- 线程解锁前,必须把共享变量刷回主存
- 线程加锁前,必须读取主存中的最新值到工作内存中
- 加锁和解锁是同一把锁
内存交互八种操作
- lock
- unlock
- read
- load
- use
- assign
- store
- write
规则:
- 不允许read和load、store和write单独出现
- 不允许线程丢弃最近的assign操作,工作变量改变,必须告诉主存
- 一个线程将没有assign的数据同步回主存
- 一个新的变量必须从主存中诞生,不允许工作内存直接使用一个未初始化的变量。
- 一个变量同一时间只有一个线程对其进行lock,必须执行相同次数的unlock
- 对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎是由这个变量前,必须重新load或assign操作初始化变量的值
- 对一个变量进行unlock操作之前,必须把变量同步回主内存。
Volatile
是Java虚拟机提供的轻量级的同步机制
-
保证可见性,线程A对主存中变量的修改,线程B会知道,给变量加上volatile
-
不保证原子性,原子性是不可分割。
private static volatile AtomicInteger num =new AtomicInteger();
public static void add(){
num.getAndIncrement();
}
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " "+num);
}
如果不加lock和synchronized,怎么保证原子性,java.util.concurrent.atomic
private static volatile AtomicInteger num =new AtomicInteger();
public static void add(){
num.getAndIncrement();
}
-
禁止指令重排
- 指令重排,计算机并不是按照写的程序顺序执行的。
- 源代码->编译器优化的重排->指令并行可能重排->内存系统也会重排->执行
- 指令重排会考虑数据之间的依赖性的。
a,b,x,y=0
正常结果:x=0,y=0
指令重排之后:x=2, y=1
Volatile可以避免指令重排:
内存屏障(禁止上面和下面指令顺序交换),CPU指令:
- 保证特定的操作的执行顺序
- 可以保证某些变量的内存可见性,利用这些特性,volatile实现了可见性
单例模式
-
饿汉式
package com.clay.single;
public class Hungry {
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
public static void main(String[] args) {
}
}
-
懒汉式
package com.clay.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class LazyMan {
private static boolean flag = false;
private LazyMan(){
synchronized (LazyMan.class){
if(flag == false){
flag = true;
}else {
throw new RuntimeException("不要试图通过反射来破坏");
}
}
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan == null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> constructor = LazyMan.class.getConstructor(null);
constructor.setAccessible(true);
LazyMan lazyMan = constructor.newInstance();
}
}
-
静态内部类
package com.clay.single;
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
-
枚举
反射无法破坏枚举的单例
CAS比较并交换
比较当前工作内存中的值和主内存的值,如果这个值是期望的,则执行操作,如果不是则继续循环(自旋锁),CAS是CPU原语,速度很快。
缺点:循环会耗时、一次性只能保证一个共享变量的原子性、存在ABA问题。
compareAndSet源码:(compareAndSwap)
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2021);
atomicInteger.compareAndSet(2021,2022);
System.out.println(atomicInteger.get());
}
ABA问题:A换成B又换成A
原子引用(带版本号的Atomic),解决ABA问题
AtomicReference 和AtomicStampReference
乐观锁原理,每一次改变操作,版本号加1
各种锁
-
公平锁和非公平锁
- 公平锁:不能插队,线程先来后到
- 非公平锁:可以插队,默认一般都是非公平的
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
-
可重入锁
可重入锁(递归锁):拿到了外层的锁,自动也就获取了内部的锁
class Phone{ Lock lock = new ReentrantLock();
-
自旋锁
不断尝试,直到成功,是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
public class SpinLock {
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 unMyLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"unmylock");
atomicReference.compareAndSet(thread,null);
}
}
-
死锁
两个线程互相抢夺对方手里的资源
死锁长生的四个必要条件:
- 互斥
- 占有且等待:该进程本身占有资源,且同时还有资源未得到满足,正在等待其他进程释放该资源。
- 不可抢占:不能抢占对方占有的你的资源
- 循环等待:存在一个进程链,使得每个进程都占有下一个进程所需要的只要一种资源
package com.clay.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
public class DeadLock {
public static void main(String[] args) {
String LockA = "LockA";
String LockB = "LockB";
new Thread(new MyThread(LockA,LockB)).start();
new Thread(new MyThread(LockB,LockA)).start();
}
}
class MyThread implements Runnable{
private String LockA;
private String LockB;
public MyThread(String lockA, String lockB) {
LockA = lockA;
LockB = lockB;
}
@Override
public void run() {
synchronized (LockA){
System.out.println(Thread.currentThread().getName()+"拿到"+LockA+"想要"+LockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (LockB){
System.out.println(Thread.currentThread().getName()+"拿到"+LockB+"想要"+LockA);
}
}
}
}
通过jps -l定位进程号
通过jstak 进程号来找到死锁。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)