初识JUC
JUC
并发编程
简介
//这三个包里的 就是JUC import java.util.concurrent.*; import java.util.concurrent.atomic.*; import java.util.concurrent.locks.*;
java.util 工具包 包、分类
Runnable 没有返回值、效率相比Callable相对较低
进程和线程
进程:一个运行中的程序 .exe 可执行程序的集合
一个进程往往可以包含多个线程,至少包含一个
java默认有几个线程 2个 main、GC
线程:就是再这个进程中可以分开干别的事 就是一个单独的资源类,没有任何附属操作
开启线程:Thread、Runnable、Callable
并发 :多个线程操作同一个资源
- CPU一核,模拟多条线程
并行:多个人一起行走
- CPU多核 ,多个线程可以同时执行,线程池
public static void main(String[] args) { //获取CPU的核数 //CPU 密集型,IO密集型 System.out.println(Runtime.getRuntime().availableProcessors()); }
并发编程的本质:充分利用CPU的资源
线程有几个状态 Thread.State
public enum State { //新生 NEW, //运行 RUNNABLE, //阻塞 BLOCKED, //等待,只能等 WAITING, //超时等待,过时不侯 TIMED_WAITING, //终止 TERMINATED; }
wait 、sleep 区别
-
来自不同的类
- wiat来自Object类
- sleep来自Thread 一般使用TimeUnit进行休眠
-
关于锁的释放
- wait会释放锁
- sleep不会释放
-
使用的范围是不同的
- wait必须再同步代码块中 使用
- sleep可以再任何地方都可以使用
都需要捕获一个中断异常 InterruptedException
Lock锁
Synchroized锁
本质:锁(对象)+队列
public class SaleTicketDemo01 { public static void main(String[] args) { //并发:多个线程操作通过一个资源类 Ticket ticket = new Ticket(); new Thread(() -> { for (int i = 0; i < 60; i++) ticket.sale(); }, "A").start(); new Thread(() -> { for (int i = 0; i < 60; i++) ticket.sale(); }, "B").start(); new Thread(() -> { for (int i = 0; i < 60; i++)ticket.sale(); }, "C").start(); } } //资源类 class Ticket { //属性、方法 private int number = 50; //synchronized 本质:锁+队列 public synchronized void sale() { if (number > 0) { System.out.println(Thread.currentThread().getName() + "卖出了1张票, 剩余: " + (number--) + " 张票"); } } }
Lock锁接口
实现类:
- ReentrantLock(可重入锁)
- ReentrantReadWriteLock.ReadLock (读锁)
- ReentrantReadWriteLock.WriteLock (写锁)
公平锁:先来后到 很公平
非公平锁:可以插队
public class SaleTicketDemo02 { public static void main(String[] args) { //并发:多个线程操作通过一个资源类 Ticket1 ticket = new Ticket1(); new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale(); }, "A").start(); new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale(); }, "B").start(); new Thread(() -> { for (int i = 0; i < 40; i++) ticket.sale(); }, "C").start(); } } //Lock 三部曲 //1、 创建锁 new ReentrantLock(); //2、 上锁 lock.lock(); //3、 解锁 finally->lock.unlock(); class Ticket1 { private int number = 30; Lock lock = new ReentrantLock(); public void sale() { //上锁 lock.lock(); try { //业务逻辑 if (number > 0) { System.out.println(Thread.currentThread().getName() + "卖出了 1 张票, 剩余: " + (number--) + " 张票"); } } catch (Exception e) { e.printStackTrace(); } finally { //解锁 lock.unlock(); } } }
区别
- Synchronized 内置的java关键字; Lock是一个java类
- Synchronized 无法判断获取锁的状态 隐式;Lock可以判断是否获取到了锁 显式
- Synchronized 会自动释放锁 ;Lock必须要手动释放锁 不释放锁就会造成死锁
- Synchronized 线程1 先获得锁之后阻塞了,线程2 就会一直等待;Lock 锁不会一直等待 lock.tryLock(); 尝试获得锁
- Synchronized 可重入锁,不可中断的,非公平 ; Lock:可重入锁,可以判断锁,非公平(可以自己设置)
- Synchronized 适合锁少量的代码同步块;Lock 适合锁大量的代码同步块
生产者和消费者
Synchroized
Synchroized版 使用 wait notify
public class A { public static void main(String[] args) { Data data = new Data(); new Thread(() -> { try { for (int i = 0; i < 10; i++) { data.increment(); } } catch (InterruptedException e) { e.printStackTrace(); } }, "A").start(); new Thread(() -> { try { for (int i = 0; i < 10; i++) { data.decrement(); } } catch (InterruptedException e) { e.printStackTrace(); } }, "B").start(); } } //判断等待、业务、通知 class Data { private int num = 0; public synchronized void increment() throws InterruptedException { if (num != 0) { //等待 this.wait(); } //业务 num++; System.out.println(Thread.currentThread().getName() + "=>" + num); //通知其他线程 this.notifyAll(); } public synchronized void decrement() throws InterruptedException { if (num == 0) { //等待 this.wait(); } //业务 num--; System.out.println(Thread.currentThread().getName() + "=>" + num); //通知其他线程 this.notifyAll(); } }
问题存在,只能是两个线程 多个线程出现 虚假唤醒 用while来判断
//判断等待、业务、通知 class Data { private int num = 0; public synchronized void increment() throws InterruptedException { while (num != 0) { //等待 this.wait(); } //业务 num++; System.out.println(Thread.currentThread().getName() + "=>" + num); //通知其他线程 this.notifyAll(); } public synchronized void decrement() throws InterruptedException { while (num == 0) { //等待 this.wait(); } //业务 num--; System.out.println(Thread.currentThread().getName() + "=>" + num); //通知其他线程 this.notifyAll(); }
Lock
Lock中 有newCondition() 方法 返回一个新Condition绑定到该实例Lock实例
持有当前线程的锁,呼叫Condition.await()
将在等待之前将原子释放锁,并在等待返回之前重新获取锁
Condition因素出Object监视器方法( wait
,notify
和notifyAll
)成不同的对象,以得到具有多个等待集的每个对象,通过将它们与使用任意的组合的效果Lock个实现。 Lock替换synchronized方法和语句的使用, Condition取代了对象监视器方法的使用。
唤醒一个等待的线程 signal() 唤醒所有等待线程 signalAll()
//判断等待、业务、通知 class Data { private int num = 0; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); public void increment() throws InterruptedException { lock.lock(); try { while (num != 0) { //等待 condition.await(); } //业务 num++; System.out.println(Thread.currentThread().getName() + "=>" + num); //通知其他线程 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void decrement() throws InterruptedException { lock.lock(); try { while (num == 0) { //等待 condition.await(); } //业务 num--; System.out.println(Thread.currentThread().getName() + "=>" + num); //通知其他线程 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
Condition
精准的通知和唤醒操作
class Data3 { private Lock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); private int num = 1; //1A 2B 3C public void printA() { lock.lock(); try { //业务,判断到执行到通知 while (num != 1) { condition1.await(); } System.out.println(Thread.currentThread().getName() + "=>AAAAAAAAAAAAA"); num = 2; //唤醒condition2 condition2.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printB() { lock.lock(); try { while (num != 2) { condition2.await(); } System.out.println(Thread.currentThread().getName() + "=>BBBBBBBBBBBB"); num = 3; condition3.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printC() { lock.lock(); try { while (num != 3) { condition3.await(); } System.out.println(Thread.currentThread().getName() + "=>CCCCCCCCCCCCC"); num = 1; condition1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
锁的多种现象
/** * 环境: * 线程:A、B 资源类: 两个同步方法 f1 f2 * 同一个对象调用,A执行f1 B执行f2 f1、f2谁先执行? * #######谁先拿到这个资源谁先执行 ---->锁的是对象的调用者 * * 线程:A、B 资源类: 两个同步方法 f1:sleep(1000) f2 * 同一个对象调用,延迟线程A后,A执行f1 B执行f2 f1、f2谁先执行? * ########先执行f1再执行f2 因为锁的是同一个同一个对象 而sleep不会释放锁 */ /** * 环境: * 线程:A、B 资源类: 同步方法f1:sleep 普通方法f2 * 同一个对象调用 线程A 执行f1, 线程B执行f2 那么先执行f1还是f2? * ########先执行f2再执行f1 因为f2没有上锁不受锁影响 f1先休眠了 * * 线程:A、B 资源类: 两个同步方法f1:sleep f2 * 两个分别对象调用 A执行f1 B执行f2 那么哪个方法先执行? * ########先执行f2 再执行f1 因为是两个对象两把锁 f1也先休眠了 */ /** * 环境: * 线程:A、B 资源类: 两个静态同步方法f1:sleep f2 * 同一个对象调用 线程A 执行f1, 线程B执行f2 那么先执行f1还是f2? * ########先执行f1再执行f2 是因为static 在类加载的时候就有了 锁的对象是Class对象 * ########同样f1即使休眠了也不会释放锁 * * 线程:A、B 资源类: 两个静态同步方法f1:sleep f2 * 两个分别对象调用 A执行f1 B执行f2 那么哪个方法先执行? * ########先执行f1再执行f2 是因为static 在类加载的时候就有了 锁的对象是Class对象 * ########同样f1即使休眠了也不会释放锁 */ /** * 环境: * 线程:A、B 资源类: 静态的同步方法f1:sleep ,普通的同步方法f2 * 同一个对象调用 线程A 执行f1, 线程B执行f2 那么先执行f1还是f2? * ########先执行f2执行再执行f1 这是因为两个不同的锁 * ########一个锁的是Class 一个锁的是调用者 f1会休眠 * * 线程:A、B 资源类: 静态同步方法f1:sleep 普通同步方法 f2 * 两个分别对象调用 A执行f1 B执行f2 那么哪个方法先执行? * ########先执行f2再执行f1 因为这是两把锁 * ########一个锁的是Class 一个锁的是调用者 f1会休眠 */
new关键字 this 具体的一个对象
static 关键字 Class 对象的唯一模板
集合类不安全
List
//java.util.ConcurrentModificationException 并发修改异常 public class ListTest { public static void main(String[] args) { //并发下ArrayList 不安全的 /** * 解决方案: * 1、用jdk1.0的 new Vector<>(); * 2、利用工具类转换成安全的 Collections.synchronizedList(new ArrayList<>()); * 3、List<String> list = new CopyOnWriteArrayList<>(); */ //CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略 //多个线程调用的时候, list 读取的时候,是固定的,写入的存在覆盖问题 //在写入的时候避免覆盖造成的数据问题 //读写分离 MyCat //CopyOnWriteArrayList 使用的lock锁和 Vector使用Synchronized List<String> list = new CopyOnWriteArrayList<>(); for (int i = 1; i <= 10; i++) { new Thread(() -> { list.add(UUID.randomUUID().toString().substring(0, 5)); System.out.println(list); }, "Thread" + i).start(); } } }
set
//java.util.ConcurrentModificationException 并发修改异常 public class SetTest { public static void main(String[] args) { /** * 解决方案: * 1、Set<String> set= Collections.synchronizedSet(new HashSet<>()); * 2、Set<String> set=new CopyOnWriteArraySet<>(); */ //Set<String> set = new HashSet<>(); //Set<String> set= Collections.synchronizedSet(new HashSet<>()); Set<String> set=new CopyOnWriteArraySet<>(); for (int i = 0; i < 10; i++) { new Thread(() -> { set.add(UUID.randomUUID().toString().substring(0, 5)); System.out.println(set); }, String.valueOf(i)).start(); } } }
HashSet原理
/** * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has * default initial capacity (16) and load factor (0.75). */ public HashSet() { map = new HashMap<>(); } //add方法 map key是无法重复的! PRESENT不变的值 public boolean add(E e) { return map.put(e, PRESENT)==null; }
Map
//java.util.ConcurrentModificationException public class MapTest { public static void main(String[] args) { /** * 解决方案 * 1、Map<String,String> map= Collections.synchronizedMap(new HashMap<>()); * 2、Map<String,String> map= new ConcurrentHashMap<>(); */ Map<String,String> map= new ConcurrentHashMap<>(); //ConcurrentHashMap原理 //默认等价于 new HashMap<>(16,0.75) //加载因子 for (int i = 1; i <=30; i++) { new Thread(()->{ map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 5)); System.out.println(map); },String.valueOf(i)).start(); } } }
Callable
可以有返回值、可以抛出异常、方法不同call()
public class CallableTest { public static void main(String[] args) throws ExecutionException, InterruptedException { /** * new Thread(new Runnable()).start();===new Thread(new FutureTask(Callable)).start() */ MyThread thread = new MyThread(); //适配类 FutureTask<String> futureTask = new FutureTask<>(thread); new Thread(futureTask,"A").start(); new Thread(futureTask,"B").start();//结果有缓存 String s = futureTask.get(); System.out.println(s); } } class MyThread implements Callable<String> { @Override public String call() throws Exception { System.out.println("进入call()方法"); //耗时的操作 return "123456"; } }
细节:
- 结果会有缓存
- 结果可能需要等待,会阻塞
ReadWriteLock
读写锁
/** * 独占锁 (写锁) 一次只能被一次线程占有 * 共享锁 (读锁)多个线程同时占有 * ReadWriteLock * 读 - 读 可以共存 * 读 - 写 不可共存 * 写 - 写 不可共存 */ public class ReadWriteLockDemo { public static void main(String[] args) { MyCacheLock myCache = new MyCacheLock(); for (int i = 1; i <= 10; i++) { final int temp = i; new Thread(() -> { myCache.put(temp + "", UUID.randomUUID().toString().substring(0, 6)); }, String.valueOf(i)).start(); } for (int i = 1; i <= 10; i++) { final int temp = i; new Thread(() -> { myCache.get(temp + ""); }, String.valueOf(i)).start(); } } } //小型的读写分离 //自定义缓存 class MyCache { private volatile Map<String, Object> map = new HashMap<>(); //存,写 public void put(String key, Object value) { System.out.println(Thread.currentThread().getName() + "写入 " + key); map.put(key, value); System.out.println(Thread.currentThread().getName() + "写入完毕 " ); } //取,读 public void get(String key) { System.out.println(Thread.currentThread().getName() + "读取 " + key); map.get(key); System.out.println(Thread.currentThread().getName() + "读取完毕"); } } class MyCacheLock { //读写锁;更加细粒度控制 private ReentrantReadWriteLock rwLock=new ReentrantReadWriteLock(); private volatile Map<String, Object> map = new HashMap<>(); //存,写 ,写入的时候只希望只有一个人线写 public void put(String key, Object value) { rwLock.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + "写入 " + key); map.put(key, value); System.out.println(Thread.currentThread().getName() + "写入完毕 " ); } catch (Exception e) { e.printStackTrace(); } finally { rwLock.writeLock().unlock(); } } //取,读 所有人什么时候都可以读 public void get(String key) { rwLock.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + "读取 " + map.get(key)); System.out.println(Thread.currentThread().getName() + "读取完毕"); } catch (Exception e) { e.printStackTrace(); } finally { rwLock.readLock().unlock(); } } }
异步回调
对将来的某个事件的结果进行建模 异步执行成功返回什么结果失败返回什么 或者没有返回值
/**Ajax * 异步调用 :CompletableFuture * 异步执行 * 成功回调 * 失败回调 */ public class Demo01 { public static void main(String[] args) throws ExecutionException, InterruptedException { test2(); } //没有返回值的runAsync异步回调 public static void test1() throws ExecutionException, InterruptedException { CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "runAsync=Void"); }); System.out.println("111111111111111"); completableFuture.get();//获取执行结果 可能会阻塞 } //有返回值的supplyAsync异步回调 //成功和失败回调 public static void test2() throws ExecutionException, InterruptedException { CompletableFuture<Integer> completableFuture= CompletableFuture.supplyAsync(()->{ System.out.println(Thread.currentThread().getName()+"supplyAsync==>Integer"); int i=1/0; return 1024; }); System.out.println(completableFuture.whenComplete((t, u) -> { System.out.println("t->" + t);// 正常的结果 System.out.println("u->" + u);//错误信息 }).exceptionally((e) -> { System.out.println(e.getMessage()); return 23333; //获取错误的返回结果 }).get()); } }
JMM
Java 内存模型 ,概念性
关于JMM的一些同步的约定:
- 线程解锁前,必须把共享变量立刻刷回主存
- 线程加锁前,必须读取主存中的最新值到工作内存中
- 加锁和解锁是同一把锁
线程:工作内存、主内存
8种操作:
主存-->lock-->read-->load-->use-->assign-->store-->write--> unlock-->主存
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
-
lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
-
unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
-
read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
-
load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
-
use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
-
assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
-
store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
-
write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
-
不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
-
不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
-
不允许一个线程将没有assign的数据从工作内存同步回主内存
-
一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
-
一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
-
如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
-
如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
-
对一个变量进行unlock操作之前,必须把此变量同步回主内存
Volatile
Volatile是Java虚拟机提供轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
保证可见性
//不加volatile就会死循环! //加了volatile 保证了可见性 private volatile static int num = 0; public static void main(String[] args) { new Thread(() -> { //该线程需要知道主内存的num有没有变化 while (num == 0) { } }).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } num=1; System.out.println(num); }
不保证原子性
原子性: 不可分割
线程A在执行任务的时候,不能被打扰的,也不能被分割。要么同时成功,要么同时失败
//不保证原子性 public class Demo02 { private volatile static int num=0; private static void add(){ num++; } public static void main(String[] args) { for (int i = 1; i <=20 ; i++) { new Thread(()->{ for (int j = 0; j <1000 ; j++) { add(); } }).start(); } //判断存活的线程 main gc while (Thread.activeCount()>2){ Thread.yield(); } System.out.println(Thread.currentThread().getName()+"=>"+num); } }
如果不加lock和synchronized 怎么保证原子性?
使用原子类,解决原子性问题
//不保证原子性 public class Demo02 { //AtomicInteger原子类 private volatile static AtomicInteger num=new AtomicInteger(); private static void add(){ //num++; //不是原子性操作 num.getAndIncrement();//+1 方法 CAS } public static void main(String[] args) { for (int i = 1; i <=20 ; i++) { new Thread(()->{ for (int j = 0; j <1000 ; j++) { add(); } }).start(); } //判断存活的线程 main gc while (Thread.activeCount()>2){ Thread.yield(); } System.out.println(Thread.currentThread().getName()+"=>"+num); } }
这些类的底层都和操作系统挂钩的 native 在内存种修改 Unsafe类是一个很特殊的类
禁止指令重排
指令重排:你写的程序,计算机并不是按照你写的那样取执行的
源代码-->编译器优化的重排-->指令并行也可能重排-->内存系统也会重排-->执行
处理器在进行指令重排的时候,考虑:数据之间的依赖性
int x=1; //1 int y=2; //2 x=x+5; //3 y=x*x; //4 1234 2134 1324 4123
可能造成影响的结果:a b x y默认值都是0;
线程A | 线程B |
---|---|
x=a | y=b |
b=1 | a=2 |
正常的结果 x=0 ; y=0 但可能由于指令重排
线程A | 线程B |
---|---|
b=1 | a=2 |
x=a | y=b |
指令重排导致的结果 x=2 ; y=1
Volatile 可以避免指令重排
内存屏障。CPU指令,作用:
- 保证特定的操作的执行顺序
- 可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性 )
理解CAS
CAS: CompareAndSwap 比较并交换
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。
//CAS: CompareAndSwap 是CPU的并发原语 public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2020); /** 期望 更新 * public final boolean compareAndSet(int expect, int update) { * return unsafe.compareAndSwapInt(this, valueOffset, expect, update); * } */ //如果达到期望值到达了就更新 boolean b = atomicInteger.compareAndSet(2020, 2021); System.out.println(atomicInteger.get()); atomicInteger.getAndIncrement(); System.out.println(atomicInteger.compareAndSet(2020, 2021)); }
Unsafe类
CAS:比较当前工作内存中的值,如果这个值是期望的,那么执行操作,如果不是就一直循环
缺点:
- 循环会耗时
- 一次性只能保证一个共享变量的原子性
- ABA问题
ABA问题
有个线程它修改了主内存的值之后又改回原来的值,另一个线程过来认为还是原来的那个之后就进行操作
也就是有个线程把主内存的A值该为B后又改为回A了
原子引用
带版本号的CAS操作解决了ABA问题 相似乐观锁
Integer使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueof获取对象实例,而不是new因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间;
public class CASDemo02 { public static void main(String[] args) { //参数初始值 和一个版本时间戳 //Integer AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(20,1); new Thread(()->{ //获得当前版本号就是时间戳 int stamp=reference.getStamp(); System.out.println("A线程获得当前版本号:"+stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //期望的引用值是20 新的引用值为21 获得当前时间戳,CAS操作后,就让时间戳+1也就是新的时间戳 boolean b = reference.compareAndSet(20, 21, reference.getStamp(), reference.getStamp() + 1); System.out.println("A线程第一次的修改是否成功:"+b); System.out.println("A线程第一次修改后的版本=>"+reference.getStamp()); boolean b1 = reference.compareAndSet(21, 20, reference.getStamp(), reference.getStamp() + 1); System.out.println("A线程第二次的修改是否成功:"+b1); System.out.println("A线程第二次修改后的版本=>"+reference.getStamp()); },"A").start(); new Thread(()->{ //获得当前版本号就是时间戳 int stamp=reference.getStamp(); System.out.println("B线程获得当前版本号:"+stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } int stamp1=reference.getStamp(); System.out.println("B线程睡觉后获得当前版本号:"+stamp1); boolean b = reference.compareAndSet(21, 20, stamp, stamp + 1); System.out.println("B线程的修改是否成功:"+b); System.out.println("B线程修改后的版本号=> "+reference.getStamp()); },"B").start(); } }
多种锁含义
公平锁 :
需要排队获取锁 不能插队 先来后到
public ReentrantLock() { sync = new NonfairSync(); }
非公平锁
不需要排队获取 可以插队 lock默认的方式就是非公平锁
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
可重入锁
(递归锁) 锁上加锁 获得外面一层的锁,就自动获得里面的锁,自动获得
//Synchronized //ReentrantLock public class Demo01 { public static void main(String[] args) { Phone2 phone = new Phone2(); new Thread(() -> { phone.sms(); }, "A").start(); new Thread(() -> { phone.sms(); }, "B").start(); } } class Phone1 { //隐式 public synchronized void sms() { System.out.println(Thread.currentThread().getName() + "发短信"); call();//锁上加锁 } public synchronized void call() { System.out.println(Thread.currentThread().getName() + "打电话"); } } class Phone2 { private Lock lock = new ReentrantLock(); public void sms() { lock.lock(); //显式 需要配对 try { System.out.println(Thread.currentThread().getName() + "发短信"); call();//锁上加锁 } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void call() { lock.lock(); try { System.out.println(Thread.currentThread().getName() + "打电话"); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
自旋锁
(spinLock) 循环
//自旋锁 public class SpinLockDemo04 { AtomicReference<Thread> atomicReference=new AtomicReference<>(); //加锁 public void lock1(){ Thread thread = Thread.currentThread(); //自旋锁 while (!atomicReference.compareAndSet(null,thread)){ System.out.println(Thread.currentThread().getName()+"====> lock1"); } } //解锁 public void unlock1(){ Thread thread = Thread.currentThread(); System.out.println(Thread.currentThread().getName()+"====> unlock1"); atomicReference.compareAndSet(thread,null); } }
测试
public class Test { public static void main(String[] args) { //自旋锁CAS SpinLockDemo04 lock = new SpinLockDemo04(); new Thread(() -> { lock.lock1(); try { TimeUnit.MILLISECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock1(); } }, "A").start(); new Thread(() -> { lock.lock1(); try { TimeUnit.MILLISECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock1(); } }, "B").start(); } }
死锁
死锁问题
public class DeadLockDemo { public static void main(String[] args) { String lockA="lockA"; String lockB="lockB"; new Thread(new MyThead(lockA,lockB),"T1").start(); new Thread(new MyThead(lockB,lockA),"T2").start(); } } class MyThead implements Runnable{ private String lockA; private String lockB; public MyThead(String lockA, String lockB) { this.lockA = lockA; this.lockB = lockB; } @Override public void run() { synchronized(lockA){ System.out.println(Thread.currentThread().getName()+"_lock: "+lockA+"=>"+lockB); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(lockB){ System.out.println(Thread.currentThread().getName()+"_lock: "+lockB+"=>"+lockA); } } } }
排除死锁
使用
jps-l
定位进程号
使用
jstack 进程号
找到进程号
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端