赞
踩
四大口决
摩尔定律:
它是由英特尔创始人之一Gordon Moore(戈登·摩尔)提出来的。其内容为:
当价格不变时,集成电路上可容纳的元器件的数目约每隔18-24个月便会增加一倍,性能也将提升一倍。
换言之,每一美元所能买到的电脑性能,将每隔18-24个月翻一倍以上。这一定律揭示了信息技术进步的速度。
可是从2003年开始CPU主频已经不再翻倍,而是采用多核而不是更快的主频。
摩尔定律失效。
在主频不再提高且核数在不断增加的情况下,要想让程序更快就要用到并行或并发编程。
高并发系统,异步+回调等生产需求
是程序的⼀次执⾏,是系统进⾏资源分配和调度的独⽴单位,每⼀个进程都有它⾃⼰的内存空间和系统资源
在同⼀个进程内⼜可以执⾏多个任务,⽽这每⼀个任务我们就可以看做是⼀个线程(⼀个进程会有1个或多个线程的)
Monitor其实是一种同步机制,他的义务是保证(同一时间)只有一个线程可以访问被保护的数据和代码。
JVM中同步是基于进入和退出监视器对象(Monitor,管程对象)来实现的,每个对象实例都会有一个Monitor对象,
- Object o = new Object();
-
- new Thread(() -> {
- synchronized (o)
- {
-
- }
- },"t1").start();
Monitor对象会和Java对象一同创建并销毁,它底层是由C++语言来实现的。
Java线程分为用户线程和守护线程,线程的daemon属性为true表示是守护线程,false表示是用户线程
是一种特殊的线程,在后台默默地完成一些系统性的服务,比如垃圾回收线程
是系统的工作线程,它会完成这个程序需要完成的业务操作
- package com.atguigu.itdachang;
-
- /**
- * @auther zzyy
- * @create 2020-07-07 15:39
- */
- public class DaemonDemo
- {
- public static void main(String[] args)
- {
- Thread t1 = new Thread(() -> {
- System.out.println(Thread.currentThread().getName()+"\t 开始运行,"+(Thread.currentThread().isDaemon() ? "守护线程":"用户线程"));
- while (true) {
-
- }
- }, "t1");
- //线程的daemon属性为true表示是守护线程,false表示是用户线程
- t1.setDaemon(true);
- t1.start();
- //3秒钟后主线程再运行
- try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
-
- System.out.println("----------main线程运行完毕");
- }
-
- }
Future接口定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。
Callable接口中定义了需要有返回的任务需要实现的方法。
比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,
主线程就去做其他事情了,过了一会才去获取子任务的执行结果。
一旦调用get()方法,不管是否计算完成都会导致阻塞,o(╥﹏╥)o
- package com.zzyy.study.test;
-
- import java.util.concurrent.*;
-
- /**
- * @auther zzyy
- * @create 2020-06-14 17:02
- */
- public class CompletableFutureDemo
- {
- public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException
- {
- FutureTask<String> futureTask = new FutureTask<>(() -> {
- System.out.println("-----come in FutureTask");
- try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
- return ""+ThreadLocalRandom.current().nextInt(100);
- });
-
- Thread t1 = new Thread(futureTask,"t1");
- t1.start();
-
- //3秒钟后才出来结果,还没有计算你提前来拿(只要一调用get方法,对于结果就是不见不散,会导致阻塞)
- //System.out.println(Thread.currentThread().getName()+"\t"+futureTask.get());
-
- //3秒钟后才出来结果,我只想等待1秒钟,过时不候
- System.out.println(Thread.currentThread().getName()+"\t"+futureTask.get(1L,TimeUnit.SECONDS));
-
- System.out.println(Thread.currentThread().getName()+"\t"+" run... here");
-
- }
- }
轮询的方式会耗费无谓的CPU资源,而且也不见得能及时地得到计算结果.
如果想要异步获取结果,通常都会以轮询的方式去获取结果,尽量不要阻塞
- package com.zzyy.study.test;
-
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.FutureTask;
- import java.util.concurrent.ThreadLocalRandom;
- import java.util.concurrent.TimeUnit;
-
- /**
- * @auther zzyy
- * @create 2020-06-16 20:16
- */
- public class CompletableFutureDemo2
- {
- public static void main(String[] args) throws ExecutionException, InterruptedException
- {
- FutureTask<String> futureTask = new FutureTask<>(() -> {
- System.out.println("-----come in FutureTask");
- try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
- return ""+ThreadLocalRandom.current().nextInt(100);
- });
-
- new Thread(futureTask,"t1").start();
-
- System.out.println(Thread.currentThread().getName()+"\t"+"线程完成任务");
-
- /**
- * 用于阻塞式获取结果,如果想要异步获取结果,通常都会以轮询的方式去获取结果
- */
- while(true)
- {
- if (futureTask.isDone())
- {
- System.out.println(futureTask.get());
- break;
- }
- }
-
- }
- }
- public static CompletableFuture<Void> runAsync(Runnable runnable)
- public static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor)
- package com.zzyy.study.test;
-
- import java.util.concurrent.CompletableFuture;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.TimeUnit;
-
- /**
- * @auther zzyy
- * @create 2020-06-16 20:16
- */
- public class CompletableFutureDemo2
- {
- public static void main(String[] args) throws ExecutionException, InterruptedException
- {
- CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
- System.out.println(Thread.currentThread().getName()+"\t"+"-----come in");
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
- System.out.println("-----task is over");
- });
- System.out.println(future.get());
- }
- }
- public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
- public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor)
- package com.zzyy.study.test;
-
- import java.util.concurrent.CompletableFuture;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.ThreadLocalRandom;
- import java.util.concurrent.TimeUnit;
-
- /**
- * @auther zzyy
- * @create 2020-06-16 20:16
- */
- public class CompletableFutureDemo2
- {
- public static void main(String[] args) throws ExecutionException, InterruptedException
- {
- CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
- System.out.println(Thread.currentThread().getName() + "\t" + "-----come in");
- //暂停几秒钟线程
- try {
- TimeUnit.SECONDS.sleep(1);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return ThreadLocalRandom.current().nextInt(100);
- });
-
- System.out.println(completableFuture.get());
- }
- }
没有指定Executor的方法,直接使用默认的ForkJoinPool.commonPool() 作为它的线程池执行异步代码。
如果指定线程池,则使用我们自定义的或者特别指定的线程池执行异步代码
从Java8开始引入了CompletableFuture,它是Future的功能增强版,
可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法
- package com.atguigu.juc.senior.inner.completablefuture;
-
- import java.util.concurrent.CompletableFuture;
- import java.util.concurrent.ThreadLocalRandom;
- import java.util.concurrent.TimeUnit;
-
- /**
- * @auther zzyy
- * @create 2020-06-23 12:59
- */
- public class cfuture4
- {
- public static void main(String[] args) throws Exception
- {
- CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
- System.out.println(Thread.currentThread().getName() + "\t" + "-----come in");
- int result = ThreadLocalRandom.current().nextInt(10);
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
- System.out.println("-----计算结束耗时1秒钟,result: "+result);
- if(result > 6)
- {
- int age = 10/0;
- }
- return result;
- }).whenComplete((v,e) ->{
- if(e == null)
- {
- System.out.println("-----result: "+v);
- }
- }).exceptionally(e -> {
- System.out.println("-----exception: "+e.getCause()+"\t"+e.getMessage());
- return -44;
- });
-
- //主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:暂停3秒钟线程
- try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
- }
- }
-
- 经常出现在等待某条 SQL 执行完成后,再继续执行下一条 SQL ,而这两条 SQL 本身是并无关系的,可以同时进行执行的。
-
- 我们希望能够两条 SQL 同时进行处理,而不是等待其中的某一条 SQL 完成后,再继续下一条。同理,
- 对于分布式微服务的调用,按照实际业务,如果是无关联step by step的业务,可以尝试是否可以多箭齐发,同时调用。
-
- 我们去比同一个商品在各个平台上的价格,要求获得一个清单列表,
- 1 step by step,查完京东查淘宝,查完淘宝查天猫......
-
- 2 all 一口气同时查询。。。。。
- 获取结果
-
- public T get() 不见不散
- public T get(long timeout, TimeUnit unit) 过时不候
- public T getNow(T valueIfAbsent) 没有计算完成的情况下,给我一个替代结果
- 立即获取结果不阻塞 计算完,返回计算完成后的结果;没算完,返回设定的valueIfAbsent值
-
-
- package com.zzyy.study.test;
-
- import java.util.concurrent.CompletableFuture;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.TimeUnit;
-
- /**
- * @auther zzyy
- * @create 2020-06-16 20:16
- */
- public class CompletableFutureDemo2
- {
- public static void main(String[] args) throws ExecutionException, InterruptedException
- {
- CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
- return 533;
- });
-
- //去掉注释上面计算没有完成,返回444
- //开启注释上满计算完成,返回计算结果
- try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
-
- System.out.println(completableFuture.getNow(444));
-
-
- }
- }
-
-
-
- public T join()
-
-
-
- package com.zzyy.study.test;
-
- import java.util.concurrent.CompletableFuture;
- import java.util.concurrent.ExecutionException;
-
- /**
- * @auther zzyy
- * @create 2020-06-16 20:16
- */
- public class CompletableFutureDemo2
- {
- public static void main(String[] args) throws ExecutionException, InterruptedException
- {
- System.out.println(CompletableFuture.supplyAsync(() -> "abc").thenApply(r -> r + "123").join());
- }
- }
-
-
- 主动触发计算
-
- public boolean complete(T value) 是否打断get方法立即返回括号值
-
-
-
- package com.zzyy.study.test;
-
- import java.util.concurrent.CompletableFuture;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.TimeUnit;
-
- /**
- * @auther zzyy
- * @create 2020-06-16 20:16
- */
- public class CompletableFutureDemo2
- {
- public static void main(String[] args) throws ExecutionException, InterruptedException
- {
- CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
- return 533;
- });
-
- //注释掉暂停线程,get还没有算完只能返回complete方法设置的444;暂停2秒钟线程,异步线程能够计算完成返回get
- try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
-
- //当调用CompletableFuture.get()被阻塞的时候,complete方法就是结束阻塞并get()获取设置的complete里面的值.
- System.out.println(completableFuture.complete(444)+"\t"+completableFuture.get());
-
-
- }
- }
-
-
- thenApply 计算结果存在依赖关系,这两个线程串行化
- 由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停。
-
- package com.zzyy.study.test;
-
- import java.util.concurrent.*;
-
- /**
- * @auther zzyy
- * @create 2020-06-16 20:16
- */
- public class CompletableFutureDemo2
- {
- public static void main(String[] args) throws ExecutionException, InterruptedException
- {
- //当一个线程依赖另一个线程时用 thenApply 方法来把这两个线程串行化,
- CompletableFuture.supplyAsync(() -> {
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
- System.out.println("111");
- return 1024;
- }).thenApply(f -> {
- System.out.println("222");
- return f + 1;
- }).thenApply(f -> {
- //int age = 10/0; // 异常情况:那步出错就停在那步。
- System.out.println("333");
- return f + 1;
- }).whenCompleteAsync((v,e) -> {
- System.out.println("*****v: "+v);
- }).exceptionally(e -> {
- e.printStackTrace();
- return null;
- });
-
- System.out.println("-----主线程结束,END");
-
- // 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
- try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
- }
-
- }
-
-
-
- handle 有异常也可以往下一步走,根据带的异常参数可以进一步处理
-
-
- package com.zzyy.study.test;
-
- import lombok.Getter;
- import lombok.Setter;
-
- import java.util.concurrent.CompletableFuture;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.TimeUnit;
-
- /**
- * @auther zzyy
- * @create 2020-06-16 20:16
- */
- public class CompletableFutureDemo2
- {
-
- public static void main(String[] args) throws ExecutionException, InterruptedException
- {
- //当一个线程依赖另一个线程时用 handle 方法来把这两个线程串行化,
- // 异常情况:有异常也可以往下一步走,根据带的异常参数可以进一步处理
- CompletableFuture.supplyAsync(() -> {
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
- System.out.println("111");
- return 1024;
- }).handle((f,e) -> {
- int age = 10/0;
- System.out.println("222");
- return f + 1;
- }).handle((f,e) -> {
- System.out.println("333");
- return f + 1;
- }).whenCompleteAsync((v,e) -> {
- System.out.println("*****v: "+v);
- }).exceptionally(e -> {
- e.printStackTrace();
- return null;
- });
-
- System.out.println("-----主线程结束,END");
-
- // 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
- try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
- }
-
-
- }
- 接收任务的处理结果,并消费处理,无返回结果
-
-
- thenAccept
-
- public static void main(String[] args) throws ExecutionException, InterruptedException
- {
- CompletableFuture.supplyAsync(() -> {
- return 1;
- }).thenApply(f -> {
- return f + 2;
- }).thenApply(f -> {
- return f + 3;
- }).thenApply(f -> {
- return f + 4;
- }).thenAccept(r -> System.out.println(r));
- }
-
-
- 补充
- Code之任务之间的顺序执行
- thenRun thenRun(Runnable runnable) 任务 A 执行完执行 B,并且 B 不需要 A 的结果
- thenAccept thenAccept(Consumer action) 任务 A 执行完执行 B,B 需要 A 的结果,但是任务 B 无返回值
- thenApply thenApply(Function fn) 任务 A 执行完执行 B,B 需要 A 的结果,同时任务 B 有返回值
-
-
- System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenRun(() -> {}).join());
-
-
- System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenAccept(resultA -> {}).join());
-
-
- System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenApply(resultA -> resultA + " resultB").join());
- 谁快用谁
- applyToEither
-
-
- package com.zzyy.study.test;
-
- import java.util.concurrent.CompletableFuture;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.TimeUnit;
-
- /**
- * @auther zzyy
- * @create 2020-06-16 20:16
- */
- public class CompletableFutureDemo2
- {
- public static void main(String[] args) throws ExecutionException, InterruptedException
- {
- CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
- System.out.println(Thread.currentThread().getName() + "\t" + "---come in ");
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
- return 10;
- });
-
- CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
- System.out.println(Thread.currentThread().getName() + "\t" + "---come in ");
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
- return 20;
- });
-
- CompletableFuture<Integer> thenCombineResult = completableFuture1.applyToEither(completableFuture2,f -> {
- System.out.println(Thread.currentThread().getName() + "\t" + "---come in ");
- return f + 1;
- });
-
- System.out.println(Thread.currentThread().getName() + "\t" + thenCombineResult.get());
- }
- }
- 两个CompletionStage任务都完成后,最终能把两个任务的结果一起交给thenCombine 来处理
- 先完成的先等着,等待其它分支任务
-
- thenCombine
-
-
- 1. code标准版,好理解先拆分
- package com.zzyy.study.test;
-
- import java.util.concurrent.CompletableFuture;
- import java.util.concurrent.ExecutionException;
-
- /**
- * @auther zzyy
- * @create 2020-06-16 20:16
- */
- public class CompletableFutureDemo2
- {
- public static void main(String[] args) throws ExecutionException, InterruptedException
- {
- CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
- System.out.println(Thread.currentThread().getName() + "\t" + "---come in ");
- return 10;
- });
-
- CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
- System.out.println(Thread.currentThread().getName() + "\t" + "---come in ");
- return 20;
- });
-
- CompletableFuture<Integer> thenCombineResult = completableFuture1.thenCombine(completableFuture2, (x, y) -> {
- System.out.println(Thread.currentThread().getName() + "\t" + "---come in ");
- return x + y;
- });
-
- System.out.println(thenCombineResult.get());
- }
- }
-
-
- 2. code表达式
- package com.zzyy.study.test;
-
- import java.util.concurrent.CompletableFuture;
- import java.util.concurrent.ExecutionException;
-
- /**
- * @auther zzyy
- * @create 2020-06-16 20:16
- */
- public class CompletableFutureDemo2
- {
- public static void main(String[] args) throws ExecutionException, InterruptedException
- {
- CompletableFuture<Integer> thenCombineResult = CompletableFuture.supplyAsync(() -> {
- System.out.println(Thread.currentThread().getName() + "\t" + "---come in 1");
- return 10;
- }).thenCombine(CompletableFuture.supplyAsync(() -> {
- System.out.println(Thread.currentThread().getName() + "\t" + "---come in 2");
- return 20;
- }), (x,y) -> {
- System.out.println(Thread.currentThread().getName() + "\t" + "---come in 3");
- return x + y;
- }).thenCombine(CompletableFuture.supplyAsync(() -> {
- System.out.println(Thread.currentThread().getName() + "\t" + "---come in 4");
- return 30;
- }),(a,b) -> {
- System.out.println(Thread.currentThread().getName() + "\t" + "---come in 5");
- return a + b;
- });
- System.out.println("-----主线程结束,END");
- System.out.println(thenCombineResult.get());
-
-
- // 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
- try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
- }
- }
认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。
synchronized关键字和Lock的实现类都是悲观锁
适合写操作多的场景,先加锁可以保证写操作时数据正确。显式的锁定之后再操作同步资源
- //=============悲观锁的调用方式
- public synchronized void m1()
- {
- //加锁后的业务逻辑......
- }
-
- // 保证多个线程使用的是同一个lock对象的前提下
- ReentrantLock lock = new ReentrantLock();
- public void m2() {
- lock.lock();
- try {
- // 操作同步资源
- }finally {
- lock.unlock();
- }
- }
-
- //=============乐观锁的调用方式
- // 保证多个线程使用的是同一个AtomicInteger
- private AtomicInteger atomicInteger = new AtomicInteger();
- atomicInteger.incrementAndGet();
乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。
如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作
乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的
适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
乐观锁则直接去操作同步资源,是一种无锁算法,得之我幸不得我命,再抢
乐观锁一般有两种实现方式:
看看JVM中对应的锁在哪里?
作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁;
作用于代码块,对括号里配置的对象加锁。
作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁;
javap -v ***.class文件反编译
-v -verbose 输出附加信息(包括行号、本地变量表,反汇编等详细信息)
javap -c ***.class文件反编译
synchronized同步代码块 实现使用的是monitorenter和monitorexit指令
一定是一个enter两个exit吗?
m1方法里面自己添加一个异常试试
javap -v ***.class文件反编译
调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置。
如果设置了,执行线程会将先持有monitor然后再执行方法,
最后在方法完成(无论是正常完成还是非正常完成)时释放 monitor
ACC_STATIC, ACC_SYNCHRONIZED访问标志区分该方法是否静态同步方法
管程 (英语:Monitors,也称为监视器) 是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。
这些共享资源一般是硬件设备或一群变量。对共享变量能够进行的所有操作集中在一个模块中。(把信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。
在HotSpot虚拟机中,monitor采用ObjectMonitor实现
-
- ObjectMonitor.java→ObjectMonitor.cpp→objectMonitor.hpp
ObjectMonitor中有几个关键属性
_owner 指向持有ObjectMonitor对象的线程
_WaitSet 存放处于wait状态的线程队列
_EntryList 存放处于等待锁block状态的线程队列
_recursions 锁的重入次数
_count 用来记录该线程获取锁的次数
每个对象天生都带着一个对象监视器
- package com.atguigu.juc.senior.test;
-
- import java.util.concurrent.locks.ReentrantLock;
-
- class Ticket
- {
- private int number = 30;
- ReentrantLock lock = new ReentrantLock();
-
- public void sale()
- {
- lock.lock();
- try
- {
- if(number > 0)
- {
- System.out.println(Thread.currentThread().getName()+"卖出第:\t"+(number--)+"\t 还剩下:"+number);
- }
- }catch (Exception e){
- e.printStackTrace();
- }finally {
- lock.unlock();
- }
- }
- }
-
-
- /**
- * @auther zzyy
- * @create 2020-05-14 17:26
- */
- public class SaleTicketDemo
- {
- public static void main(String[] args)
- {
- Ticket ticket = new Ticket();
-
- new Thread(() -> { for (int i = 0; i <35; i++) ticket.sale(); },"a").start();
- new Thread(() -> { for (int i = 0; i <35; i++) ticket.sale(); },"b").start();
- new Thread(() -> { for (int i = 0; i <35; i++) ticket.sale(); },"c").start();
- }
- }
按序排队公平锁,就是判断同步队列是否还有先驱节点的存在(我前面还有人吗?),如果没有先驱节点才能获取锁;
先占先得非公平锁,是不管这个事的,只要能抢获到同步状态就可以
公平锁保证了排队的公平性,非公平锁霸气的忽视这个规则,所以就有可能导致排队的长时间在排队,也没有机会获取到锁,
这就是传说中的 “锁饥饿”
如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了;
否则那就用公平锁,大家公平使用。
是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。
所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
可:可以。
重:再次。
入:进入。
锁:同步锁。
进入同步域(即同步代码块/方法或显式锁锁定的代码)
一个线程中的多个流程可以获取同一把锁,持有这把同步锁可以再次进入。自己可以获取自己的内部锁
- package com.atguigu.juc.senior.prepare;
-
- /**
- * @auther zzyy
- * @create 2020-05-14 11:59
- */
- public class ReEntryLockDemo
- {
- public static void main(String[] args)
- {
- final Object objectLockA = new Object();
-
- new Thread(() -> {
- synchronized (objectLockA)
- {
- System.out.println("-----外层调用");
- synchronized (objectLockA)
- {
- System.out.println("-----中层调用");
- synchronized (objectLockA)
- {
- System.out.println("-----内层调用");
- }
- }
- }
- },"a").start();
- }
- }
-
-
- package com.atguigu.juc.senior.prepare;
-
- /**
- * @auther zzyy
- * @create 2020-05-14 11:59
- * 在一个Synchronized修饰的方法或代码块的内部调用本类的其他Synchronized修饰的方法或代码块时,是永远可以得到锁的
- */
- public class ReEntryLockDemo
- {
- public synchronized void m1()
- {
- System.out.println("-----m1");
- m2();
- }
- public synchronized void m2()
- {
- System.out.println("-----m2");
- m3();
- }
- public synchronized void m3()
- {
- System.out.println("-----m3");
- }
-
- public static void main(String[] args)
- {
- ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
-
- reEntryLockDemo.m1();
- }
- }
-
-
- package com.atguigu.juc.senior.prepare;
-
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
-
- /**
- * @auther zzyy
- * @create 2020-05-14 11:59
- * 在一个Synchronized修饰的方法或代码块的内部调用本类的其他Synchronized修饰的方法或代码块时,是永远可以得到锁的
- */
- public class ReEntryLockDemo
- {
- static Lock lock = new ReentrantLock();
-
- public static void main(String[] args)
- {
- new Thread(() -> {
- lock.lock();
- try
- {
- System.out.println("----外层调用lock");
- lock.lock();
- try
- {
- System.out.println("----内层调用lock");
- }finally {
- // 这里故意注释,实现加锁次数和释放次数不一样
- // 由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待。
- lock.unlock(); // 正常情况,加锁几次就要解锁几次
- }
- }finally {
- lock.unlock();
- }
- },"a").start();
-
- new Thread(() -> {
- lock.lock();
- try
- {
- System.out.println("b thread----外层调用lock");
- }finally {
- lock.unlock();
- }
- },"b").start();
-
- }
- }
- package com.atguigu.juc.senior.prepare;
-
- import java.util.concurrent.TimeUnit;
-
- /**
- * @auther zzyy
- * @create 2020-05-14 10:56
- */
- public class DeadLockDemo
- {
- public static void main(String[] args)
- {
- final Object objectLockA = new Object();
- final Object objectLockB = new Object();
-
- new Thread(() -> {
- synchronized (objectLockA)
- {
- System.out.println(Thread.currentThread().getName()+"\t"+"自己持有A,希望获得B");
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
- synchronized (objectLockB)
- {
- System.out.println(Thread.currentThread().getName()+"\t"+"A-------已经获得B");
- }
- }
- },"A").start();
-
- new Thread(() -> {
- synchronized (objectLockB)
- {
- System.out.println(Thread.currentThread().getName()+"\t"+"自己持有B,希望获得A");
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
- synchronized (objectLockA)
- {
- System.out.println(Thread.currentThread().getName()+"\t"+"B-------已经获得A");
- }
- }
- },"B").start();
-
- }
- }
纯命令
- jps -l
- jstack 进程编号
图形化
jconsole
首先
一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。
所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。
其次
在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。
因此,Java提供了一种用于停止线程的机制——中断。
中断只是一种协作机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。
若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true;
接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程要求这条线程中断,此时究竟该做什么需要你自己写代码实现。
每个线程对象中都有一个标识,用于表示线程是否被中断;该标识位为true表示中断,为false表示未中断;
通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。
在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑。
通过一个volatile变量实现
- package com.atguigu.juc.senior.test;
-
- import java.util.concurrent.TimeUnit;
-
- /**
- * @auther zzyy
- * @create 2020-05-12 14:19
- */
- public class InterruptDemo
- {
- private static volatile boolean isStop = false;
-
- public static void main(String[] args)
- {
- new Thread(() -> {
- while(true)
- {
- if(isStop)
- {
- System.out.println(Thread.currentThread().getName()+"线程------isStop = true,自己退出了");
- break;
- }
- System.out.println("-------hello interrupt");
- }
- },"t1").start();
-
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
- isStop = true;
- }
-
- }
通过AtomicBoolean
- package com.zzyy.study.test;
-
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.atomic.AtomicBoolean;
-
- /**
- * @auther zzyy
- * @create 2020-05-26 23:24
- */
- public class StopThreadDemo
- {
- private final static AtomicBoolean atomicBoolean = new AtomicBoolean(true);
-
- public static void main(String[] args)
- {
- Thread t1 = new Thread(() -> {
- while(atomicBoolean.get())
- {
- try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
- System.out.println("-----hello");
- }
- }, "t1");
- t1.start();
-
- try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
-
- atomicBoolean.set(false);
- }
- }
通过Thread类自带的中断api方法实现
- package com.atguigu.itdachang;
-
- import java.util.concurrent.TimeUnit;
-
- /**
- * @auther zzyy
- * @create 2020-07-10 17:33
- */
- public class InterruptDemo
- {
- public static void main(String[] args)
- {
- Thread t1 = new Thread(() -> {
- while(true)
- {
- if(Thread.currentThread().isInterrupted())
- {
- System.out.println("-----t1 线程被中断了,break,程序结束");
- break;
- }
- System.out.println("-----hello");
- }
- }, "t1");
- t1.start();
-
- System.out.println("**************"+t1.isInterrupted());
- //暂停5毫秒
- try { TimeUnit.MILLISECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
- t1.interrupt();
- System.out.println("**************"+t1.isInterrupted());
- }
- }
具体来说,当对一个线程,调用 interrupt() 时:
① 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。
被设置中断标志的线程将继续正常运行,不受影响。所以, interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
② 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,
那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
- package com.atguigu.juc.senior.test;
-
- import java.util.concurrent.TimeUnit;
-
- /**
- * @auther zzyy
- * @create 2020-05-13 10:25
- */
- public class InterruptDemo2
- {
- public static void main(String[] args) throws InterruptedException
- {
- Thread t1 = new Thread(() -> {
- for (int i=0;i<300;i++) {
- System.out.println("-------"+i);
- }
- System.out.println("after t1.interrupt()--第2次---: "+Thread.currentThread().isInterrupted());
- },"t1");
- t1.start();
-
- System.out.println("before t1.interrupt()----: "+t1.isInterrupted());
- //实例方法interrupt()仅仅是设置线程的中断状态位设置为true,不会停止线程
- t1.interrupt();
- //活动状态,t1线程还在执行中
- try { TimeUnit.MILLISECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
- System.out.println("after t1.interrupt()--第1次---: "+t1.isInterrupted());
- //非活动状态,t1线程不在执行中,已经结束执行了。
- try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
- System.out.println("after t1.interrupt()--第3次---: "+t1.isInterrupted());
- }
- }
中断只是一种协同机制,修改中断标识位仅此而已,不是立刻stop打断
- package com.atguigu.juc.senior.test;
-
- /**
- * @auther zzyy
- * @create 2020-05-12 14:19
- * 作用是测试当前线程是否被中断(检查中断标志),返回一个boolean并清除中断状态,
- * 第二次再调用时中断状态已经被清除,将返回一个false。
- */
- public class InterruptDemo
- {
-
- public static void main(String[] args) throws InterruptedException
- {
- System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted());
- System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted());
- System.out.println("111111");
- Thread.currentThread().interrupt();
- System.out.println("222222");
- System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted());
- System.out.println(Thread.currentThread().getName()+"---"+Thread.interrupted());
- }
- }
方式1:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程
方式2:使用JUC包中Condition的await()方法让线程等待,使用signal()方法唤醒线程
方式3:LockSupport类可以阻塞当前线程以及唤醒指定被阻塞的线程
- package com.atguigu.juc.prepare;
-
- import java.util.concurrent.TimeUnit;
-
- /**
- * @auther zzyy
- * @create 2020-04-13 17:12
- *
- * 要求:t1线程等待3秒钟,3秒钟后t2线程唤醒t1线程继续工作
- *
- * 1 正常程序演示
- *
- * 以下异常情况:
- * 2 wait方法和notify方法,两个都去掉同步代码块后看运行效果
- * 2.1 异常情况
- * Exception in thread "t1" java.lang.IllegalMonitorStateException at java.lang.Object.wait(Native Method)
- * Exception in thread "t2" java.lang.IllegalMonitorStateException at java.lang.Object.notify(Native Method)
- * 2.2 结论
- * Object类中的wait、notify、notifyAll用于线程等待和唤醒的方法,都必须在synchronized内部执行(必须用到关键字synchronized)。
- *
- * 3 将notify放在wait方法前面
- * 3.1 程序一直无法结束
- * 3.2 结论
- * 先wait后notify、notifyall方法,等待中的线程才会被唤醒,否则无法唤醒
- */
- public class LockSupportDemo
- {
-
- public static void main(String[] args)//main方法,主线程一切程序入口
- {
- Object objectLock = new Object(); //同一把锁,类似资源类
-
- new Thread(() -> {
- synchronized (objectLock) {
- try {
- objectLock.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- System.out.println(Thread.currentThread().getName()+"\t"+"被唤醒了");
- },"t1").start();
-
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }
-
- new Thread(() -> {
- synchronized (objectLock) {
- objectLock.notify();
- }
-
- //objectLock.notify();
-
- /*synchronized (objectLock) {
- try {
- objectLock.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }*/
- },"t2").start();
-
-
-
- }
- }
wait和notify方法必须要在同步块或者方法里面,且成对出现使用
先wait后notify才OK
- package com.atguigu.juc.prepare;
-
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
-
- /**
- * @auther zzyy
- * @create 2020-04-13 17:55
- */
- public class LockSupportDemo2
- {
- public static void main(String[] args)
- {
- Lock lock = new ReentrantLock();
- Condition condition = lock.newCondition();
-
- new Thread(() -> {
- lock.lock();
- try
- {
- System.out.println(Thread.currentThread().getName()+"\t"+"start");
- condition.await();
- System.out.println(Thread.currentThread().getName()+"\t"+"被唤醒");
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- lock.unlock();
- }
- },"t1").start();
-
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }
-
- new Thread(() -> {
- lock.lock();
- try
- {
- condition.signal();
- } catch (Exception e) {
- e.printStackTrace();
- } finally {
- lock.unlock();
- }
- System.out.println(Thread.currentThread().getName()+"\t"+"通知了");
- },"t2").start();
-
- }
- }
- package com.atguigu.juc.prepare;
-
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
-
- /**
- * @auther zzyy
- * @create 2020-04-13 17:55
- * 异常:
- * condition.await();和condition.signal();都触发了IllegalMonitorStateException异常
- *
- * 原因:调用condition中线程等待和唤醒的方法的前提是,要在lock和unlock方法中,要有锁才能调用
- */
- public class LockSupportDemo2
- {
- public static void main(String[] args)
- {
- Lock lock = new ReentrantLock();
- Condition condition = lock.newCondition();
-
- new Thread(() -> {
- try
- {
- System.out.println(Thread.currentThread().getName()+"\t"+"start");
- condition.await();
- System.out.println(Thread.currentThread().getName()+"\t"+"被唤醒");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- },"t1").start();
-
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }
-
- new Thread(() -> {
- try
- {
- condition.signal();
- } catch (Exception e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName()+"\t"+"通知了");
- },"t2").start();
-
- }
- }
去掉lock/unlock
- package com.atguigu.juc.prepare;
-
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
-
- /**
- * @auther zzyy
- * @create 2020-04-13 17:55
- * 异常:
- * 程序无法运行
- *
- * 原因:先await()后signal才OK,否则线程无法被唤醒
- */
- public class LockSupportDemo2
- {
- public static void main(String[] args)
- {
- Lock lock = new ReentrantLock();
- Condition condition = lock.newCondition();
-
- new Thread(() -> {
- lock.lock();
- try
- {
- condition.signal();
- System.out.println(Thread.currentThread().getName()+"\t"+"signal");
- } catch (Exception e) {
- e.printStackTrace();
- }finally {
- lock.unlock();
- }
- },"t1").start();
-
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }
-
- new Thread(() -> {
- lock.lock();
- try
- {
- System.out.println(Thread.currentThread().getName()+"\t"+"等待被唤醒");
- condition.await();
- System.out.println(Thread.currentThread().getName()+"\t"+"被唤醒");
- } catch (Exception e) {
- e.printStackTrace();
- }finally {
- lock.unlock();
- }
- },"t2").start();
-
- }
- }
先signal后await
Condtion中的线程等待和唤醒方法之前,需要先获取锁
一定要先await后signal,不要反了
线程先要获得并持有锁,必须在锁块(synchronized或lock)中
必须要先等待后唤醒,线程才能够被唤醒
通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作
阻塞 park() /park(Object blocker)
阻塞当前线程/阻塞传入的具体线程
唤醒 unpark(Thread thread)
正常+无锁块要求
- package com.atguigu.juc.prepare;
-
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.LockSupport;
-
- /**
- * @auther zzyy
- * @create 2020-04-13 20:30
- */
- public class LockSupportDemo3
- {
- public static void main(String[] args)
- {
- //正常使用+不需要锁块
- Thread t1 = new Thread(() -> {
- System.out.println(Thread.currentThread().getName()+" "+"1111111111111");
- LockSupport.park();
- System.out.println(Thread.currentThread().getName()+" "+"2222222222222------end被唤醒");
- },"t1");
- t1.start();
-
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
-
- LockSupport.unpark(t1);
- System.out.println(Thread.currentThread().getName()+" -----LockSupport.unparrk() invoked over");
-
- }
- }
之前错误的先唤醒后等待,LockSupport照样支持
- package com.zzyy.study.test;
-
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.LockSupport;
-
- /**
- * @auther zzyy
- * @create 2020-05-06 17:07
- */
- public class T1
- {
- public static void main(String[] args)
- {
- Thread t1 = new Thread(() -> {
- try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
- System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis());
- LockSupport.park();
- System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis()+"---被叫醒");
- },"t1");
- t1.start();
-
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
-
- LockSupport.unpark(t1);
- System.out.println(Thread.currentThread().getName()+"\t"+System.currentTimeMillis()+"---unpark over");
- }
- }
成双成对要牢记
- 你知道什么是Java内存模型JMM吗?
- JMM与volatile它们两个之间的关系?(下一章详细讲解)
- JMM有哪些特性or它的三大特性是什么?
- 为什么要有JMM,它为什么出现?作用和功能是什么?
- happens-before先行发生原则你有了解过吗?
JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念并不真实存在它仅仅描述的是一组约定或规范,通过这组规范定义了程序中(尤其是多线程)各个变量的读写访问方式并决定一个线程对共享变量的写入何时以及如何变成对另一个线程可见,关键技术点都是围绕多线程的原子性、可见性和有序性展开的。
原则:
JMM的关键技术点都是围绕多线程的原子性、可见性和有序性展开的
能干嘛?
1 通过JMM来实现线程和主内存之间的抽象关系。
2 屏蔽各个硬件平台和操作系统的内存访问差异以实现让Java程序在各种平台下都能达到一致的内存访问效果。
是指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道该变更 ,JMM规定了所有的变量都存储在主内存中。
Java中普通的共享变量不保证可见性,因为数据修改被写入内存的时机是不确定的,多线程并发下很可能出现"脏读",所以每个线程都有自己的工作内存,线程自己的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值等 )都必需在线程自己的工作内存中进行,而不能够直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成
主内存中有变量 x,初始值为 0
线程 A 要将 x 加 1,先将 x=0 拷贝到自己的私有内存中,然后更新 x 的值
线程 A 将更新后的 x 值回刷到主内存的时间是不固定的
刚好在线程 A 没有回刷 x 到主内存时,线程 B 同样从主内存中读取 x,此时为 0,和线程 A 一样的操作,最后期盼的 x=2 就会变成 x=1
指一个操作是不可中断的,即多线程环境下,操作不能被其他线程干扰
读取过程:
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到的线程自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如下图:
JMM定义了线程和主内存之间的抽象关系
1 线程之间的共享变量存储在主内存中(从硬件角度来说就是内存条)
2 每个线程都有一个私有的本地工作内存,本地工作内存中存储了该线程用来读/写共享变量的副本(从硬件角度来说就是CPU的缓存,比如寄存器、L1、L2、L3缓存等)
在JMM中,如果一个操作执行的结果需要对另一个操作可见性或者 代码重排序,那么这两个操作之间必须存在happens-before关系。
如果Java内存模型中所有的有序性都仅靠volatile和synchronized来完成,那么有很多操作都将会变得非常啰嗦,但是我们在编写Java并发代码的时候并没有察觉到这一点。
我们没有时时、处处、次次,添加volatile和synchronized来完成程序,这是因为Java语言中JMM原则下
有一个“先行发生”(Happens-Before)的原则限制和规矩
这个原则非常重要:
它是判断数据是否存在竞争,线程是否安全的非常有用的手段。依赖这个原则,我们可以通过几条简单规则一揽子解决并发环境下两个操作之间是否可能存在冲突的所有问题,而不需要陷入Java内存模型苦涩难懂的底层编译原理之中。
一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作;
前一个操作的结果可以被后续的操作获取。讲白点就是前面一个操作把变量X赋值为1,那后面一个操作肯定能知道X已经变成了1。
一个unLock操作先行发生于后面((这里的“后面”是指时间上的先后))对同一个锁的lock操作;
- package com.zzyy.study.test;
- /**
- * @auther zzyy
- * @create 2020-06-11 16:01
- */
- public class HappenBeforeDemo
- {
- static Object objectLock = new Object();
-
- public static void main(String[] args) throws InterruptedException
- {
- //对于同一把锁objectLock,threadA一定先unlock同一把锁后B才能获得该锁,
- // A 先行发生于B
- synchronized (objectLock)
- {
-
- }
- }
- }
对一个volatile变量的写操作先行发生于后面对这个变量的读操作,
前面的写对后面的读是可见的,这里的“后面”同样是指时间上的先后。
如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
Thread对象的start()方法先行发生于此线程的每一个动作
线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread::join()方法是否结束、Thread::isAlive()的返回值等手段检测线程是否已经终止执行。
一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始
说人话。对象没有完成初始化之前,是不能调用finalized()方法的
什么是finalized()方法
finalize()方法是Object类中提供的一个方法,在GC准备释放对象所占用的内存空间之前,它将首先调用finalize()方法
protected void finalize() throws Throwable { }
与C++的析构函数(对象在清除之前析构函数会被调用)不同,在Java中,由于GC的自动回收机制,因而并不能保证finalize方法会被及时地执行(垃圾对象的回收时机具有不确定性),也不能保证它们会被执行(程序由始至终都未触发垃圾回收)。
finalize()方法中一般用于释放非Java 资源(如打开的文件资源、数据库连接等),或是调用非Java方法(native方法)时分配的内存(比如C语言的malloc()系列函数)。
首先,由于finalize()方法的调用时机具有不确定性,从一个对象变得不可到达开始,到finalize()方法被执行,所花费的时间这段时间是任意长的。我们并不能依赖finalize()方法能及时的回收占用的资源,可能出现的情况是在我们耗尽资源之前,gc却仍未触发,因而通常的做法是提供显示的close()方法供客户端手动调用。
另外,重写finalize()方法意味着延长了回收对象时需要进行更多的操作,从而延长了对象回收的时间。
利用finalize()方法最多只会被调用一次的特性,我们可以实现延长对象的生命周期。
特点:可见性和有序性
没有管控,顺序难保
设定规则,禁止乱序 上海南京路步行街武警“人墙”当红灯
内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。内存屏障其实就是一种JVM指令,Java内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile实现了Java内存模型中的可见性和有序性,但volatile无法保证原子性。
内存屏障之前的所有写操作都要回写到主内存,
内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)。
因此重排序时,不允许把内存屏障之后的指令重排序到内存屏障之前。
一句话:对一个 volatile 域的写, happens-before 于任意后续对这个 volatile 域的读,也叫写后读。
内存屏障 (Memory Barriers / Fences)
上一章讲解过happens-before先行发生原则,类似接口规范,落地?
IDEA工具里面找Unsafe.class
Unsafe.java
Unsafe.cpp
OrderAccess.hpp
orderAccess_linux_x86.inline.hpp
写!!!
1. 在每个 volatile 写操作的前⾯插⼊⼀个 StoreStore 屏障
2. 在每个 volatile 写操作的后⾯插⼊⼀个 StoreLoad 屏障
读!!!
3. 在每个 volatile 读操作的后⾯插⼊⼀个 LoadLoad 屏障
4. 在每个 volatile 读操作的后⾯插⼊⼀个 LoadStore 屏障
保证不同线程对这个变量进行操作时的可见性,即变量一旦改变所有线程立即可见
- package com.zzyy.study.juc;
-
- import java.util.concurrent.TimeUnit;
-
- /**
- * @auther zzyy
- * @create 2020-06-30 11:29
- */
- public class VolatileSeeDemo
- {
- static boolean flag = true; //不加volatile,没有可见性
- //static volatile boolean flag = true; //加了volatile,保证可见性
-
- public static void main(String[] args)
- {
- new Thread(() -> {
- System.out.println(Thread.currentThread().getName()+"\t come in");
- while (flag)
- {
-
- }
- System.out.println(Thread.currentThread().getName()+"\t flag被修改为false,退出.....");
- },"t1").start();
-
- //暂停2秒钟后让main线程修改flag值
- try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
-
- flag = false;
-
- System.out.println("main线程修改完成");
- }
- }
不加volatile,没有可见性,程序无法停止
加了volatile,保证可见性,程序可以停止
Java内存模型中定义的8种工作内存与主内存之间的原子操作
read(读取)→load(加载)→use(使用)→assign(赋值)→store(存储)→write(写入)→lock(锁定)→unlock(解锁)
read: 作用于主内存,将变量的值从主内存传输到工作内存,主内存到工作内存
load: 作用于工作内存,将read从主内存传输的变量值放入工作内存变量副本中,即数据加载
use: 作用于工作内存,将工作内存变量副本的值传递给执行引擎,每当JVM遇到需要该变量的字节码指令时会执行该操作
assign: 作用于工作内存,将从执行引擎接收到的值赋值给工作内存变量,每当JVM遇到一个给变量赋值字节码指令时会执行该操作
store: 作用于工作内存,将赋值完毕的工作变量的值写回给主内存
write: 作用于主内存,将store传输过来的变量值赋值给主内存中的变量
由于上述只能保证单条指令的原子性,针对多条指令的组合性原子保证,没有大面积加锁,所以,JVM提供了另外两个原子指令:
lock: 作用于主内存,将一个变量标记为一个线程独占的状态,只是写时候加锁,就只是锁了写变量的过程。
unlock: 作用于主内存,把一个处于锁定状态的变量释放,然后才能被其他线程占用
volatile变量的复合操作(如i++)不具有原子性
- package com.zzyy.study.juc;
-
- import java.util.concurrent.TimeUnit;
-
- class MyNumber
- {
- volatile int number = 0;
-
- public void addPlusPlus()
- {
- number++;
- }
- }
-
- public class VolatileNoAtomicDemo
- {
- public static void main(String[] args) throws InterruptedException
- {
- MyNumber myNumber = new MyNumber();
-
- for (int i = 1; i <=10; i++) {
- new Thread(() -> {
- for (int j = 1; j <= 1000; j++) {
- myNumber.addPlusPlus();
- }
- },String.valueOf(i)).start();
- }
-
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
- System.out.println(Thread.currentThread().getName() + "\t" + myNumber.number);
- }
- }
原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程影响。
- public void add()
- {
- i++; //不具备原子性,该操作是先读取值,然后写回一个新值,相当于原来的值加上1,分3步完成
- }
如果第二个线程在第一个线程读取旧值和写回新值期间读取i的域值,那么第二个线程就会与第一个线程一起看到同一个值,
并执行相同值的加1操作,这也就造成了线程安全失败,因此对于add方法必须使用synchronized修饰,以便保证线程安全.
volatile主要是对其中部分指令做了处理
要use(使用)一个变量的时候必需load(载入),要载入的时候必需从主内存read(读取)这样就解决了读的可见性。
写操作是把assign和store做了关联(在assign(赋值)后必需store(存储))。store(存储)后write(写入)。
也就是做到了给一个变量赋值的时候一串关联指令直接把变量值写到主内存。
就这样通过用的时候直接从主内存取,在赋值到直接写回主内存做到了内存可见性。注意蓝色框框的间隙。。。。。。o(╥﹏╥)o
读取赋值一个volatile变量的情况
read-load-use 和 assign-store-write 成为了两个不可分割的原子操作,但是在use和assign之间依然有极小的一段真空期,有可能变量会被其他线程读取,导致写丢失一次...o(╥﹏╥)o
但是无论在哪一个时间点主内存的变量和任一工作内存的变量的值都是相等的。这个特性就导致了volatile变量不适合参与到依赖当前值的运算,如i = i + 1; i++;之类的那么依靠可见性的特点volatile可以用在哪些地方呢? 通常volatile用做保存某个状态的boolean值or int值。
《深入理解Java虚拟机》提到:
重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,有时候会改变程序语句的先后顺序
不存在数据依赖关系,可以重排序;
存在数据依赖关系,禁止重排序
但重排后的指令绝对不能改变原有的串行语义!这点在并发设计中必须要重点考虑!
编译器优化的重排序: 编译器在不改变单线程串行语义的前提下,可以重新调整指令的执行顺序
指令级并行的重排序: 处理器使用指令级并行技术来讲多条指令重叠执行,若不存在数据依赖性,处理器可以改变语句对应机器指令的执行顺序
内存系统的重排序: 由于处理器使用缓存和读/写缓冲区,这使得加载和存储操作看上去可能是乱序执行
数据依赖性:若两个操作访问同一变量,且这两个操作中有一个为写操作,此时两操作间就存在数据依赖性。
- 1. 在每一个volatile写操作前面插入一个StoreStore屏障
- StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作都已经刷新到主内存中。
- 2. 在每一个volatile写操作后面插入一个StoreLoad屏障
- StoreLoad屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序
- 3. 在每一个volatile读操作后面插入一个LoadLoad屏障
- LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序。
- 4. 在每一个volatile读操作后面插入一个LoadStore屏障
- LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序。
- package com.atguigu.juc.prepare;
-
- import java.util.concurrent.TimeUnit;
-
- /**
- * @auther zzyy
- * @create 2020-04-14 18:11
- *
- * 使用:作为一个布尔状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或任务结束
- * 理由:状态标志并不依赖于程序内任何其他状态,且通常只有一种状态转换
- * 例子:判断业务是否结束
- */
- public class UseVolatileDemo
- {
- private volatile static boolean flag = true;
-
- public static void main(String[] args)
- {
- new Thread(() -> {
- while(flag) {
- //do something......
- }
- },"t1").start();
-
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(2L); } catch (InterruptedException e) { e.printStackTrace(); }
-
- new Thread(() -> {
- flag = false;
- },"t2").start();
- }
- }
- public class UseVolatileDemo
- {
- /**
- * 使用:当读远多于写,结合使用内部锁和 volatile 变量来减少同步的开销
- * 理由:利用volatile保证读取操作的可见性;利用synchronized保证复合操作的原子性
- */
- public class Counter
- {
- private volatile int value;
-
- public int getValue()
- {
- return value; //利用volatile保证读取操作的可见性
- }
- public synchronized int increment()
- {
- return value++; //利用synchronized保证复合操作的原子性
- }
- }
- }
- package com.atguigu.itdachang;
-
- /**
- * @auther zzyy
- * @create 2020-07-13 15:14
- */
- public class SafeDoubleCheckSingleton
- {
- private static SafeDoubleCheckSingleton singleton;
- //私有化构造方法
- private SafeDoubleCheckSingleton(){
- }
- //双重锁设计
- public static SafeDoubleCheckSingleton getInstance(){
- if (singleton == null){
- //1.多线程并发创建对象时,会通过加锁保证只有一个线程能创建对象
- synchronized (SafeDoubleCheckSingleton.class){
- if (singleton == null){
- //隐患:多线程环境下,由于重排序,该对象可能还未完成初始化就被其他线程读取
- singleton = new SafeDoubleCheckSingleton();
- }
- }
- }
- //2.对象创建完毕,执行getInstance()将不需要获取锁,直接返回创建对象
- return singleton;
- }
- }
单线程看问题代码
由于存在指令重排序......
多线程看问题代码
加volatile修饰
- //现在比较好的做法就是采用静态内部内的方式实现
-
- public class SingletonDemo
- {
- private SingletonDemo() { }
-
- private static class SingletonDemoHandler
- {
- private static SingletonDemo instance = new SingletonDemo();
- }
-
- public static SingletonDemo getInstance()
- {
- return SingletonDemoHandler.instance;
- }
- }
atomic
因为volatile可以解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。
所以使用该类实现:
- AtomicInteger count = new AtomicInteger();
- count.addAndGet(1);
-
- 如果是jdk8,推荐使用LongAdder对象,比AtomicLong性能更好(减少乐观锁的重试次数)
AtomicInteger
AtomicBoolean
AtomicLong
- package com.atguigu.juc.senior.test2;
-
- import lombok.Getter;
-
- import java.util.concurrent.CountDownLatch;
- import java.util.concurrent.atomic.AtomicInteger;
-
- class MyNumber
- {
- @Getter
- private AtomicInteger atomicInteger = new AtomicInteger();
- public void addPlusPlus()
- {
- atomicInteger.incrementAndGet();
- }
- }
-
- /**
- * @auther zzyy
- * @create 2020-07-03 17:16
- */
- public class AtomicIntegerDemo
- {
- public static void main(String[] args) throws InterruptedException
- {
- MyNumber myNumber = new MyNumber();
- CountDownLatch countDownLatch = new CountDownLatch(100);
-
- for (int i = 1; i <=100; i++) {
- new Thread(() -> {
- try
- {
- for (int j = 1; j <=5000; j++)
- {
- myNumber.addPlusPlus();
- }
- }finally {
- countDownLatch.countDown();
- }
- },String.valueOf(i)).start();
- }
-
- countDownLatch.await();
-
- System.out.println(myNumber.getAtomicInteger().get());
- }
- }
-
-
- tsleep→countDownLatch
- 不再使用睡眠堵塞, 不能保证睡眠时间!!!!
- 改为countDownLatch
- 100个线程跑完一个少一个,等到100个线程都跑完了之后再去获取结果
- 不会因为时间的空转导致吞吐量变小
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
- package com.atguigu.juc.prepare;
-
- import java.util.concurrent.atomic.AtomicIntegerArray;
-
- /**
- * @auther zzyy
- * @create 2020-04-16 14:59
- */
- public class AtomicIntegerArrayDemo
- {
- public static void main(String[] args)
- {
- AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);
- //AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5);
- //AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1,2,3,4,5});
-
- for (int i = 0; i <atomicIntegerArray.length(); i++) {
- System.out.println(atomicIntegerArray.get(i));
- }
- System.out.println();
- System.out.println();
- System.out.println();
- int tmpInt = 0;
-
- tmpInt = atomicIntegerArray.getAndSet(0,1122);
- System.out.println(tmpInt+"\t"+atomicIntegerArray.get(0));
- atomicIntegerArray.getAndIncrement(1);
- atomicIntegerArray.getAndIncrement(1);
- tmpInt = atomicIntegerArray.getAndIncrement(1);
- System.out.println(tmpInt+"\t"+atomicIntegerArray.get(1));
- }
- }
- package com.atguigu.Interview.study.thread;
-
- import lombok.AllArgsConstructor;
- import lombok.Getter;
- import lombok.ToString;
-
- import java.util.concurrent.atomic.AtomicReference;
-
- @Getter
- @ToString
- @AllArgsConstructor
- class User
- {
- String userName;
- int age;
- }
-
- /**
- * @auther zzyy
- * @create 2018-12-31 17:22
- */
- public class AtomicReferenceDemo
- {
- public static void main(String[] args)
- {
- User z3 = new User("z3",24);
- User li4 = new User("li4",26);
-
- AtomicReference<User> atomicReferenceUser = new AtomicReference<>();
-
- atomicReferenceUser.set(z3);
- System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
- System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
- }
- }
-
-
- true 设计模式.creatation.prototype.User@677327b6
- false 设计模式.creatation.prototype.User@677327b6
自旋锁SpinLockDemo
- package com.atguigu.Interview.study.thread;
-
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.atomic.AtomicReference;
-
- /**
- * @auther zzyy
- * @create 2018-12-28 17:57
- * 题目:实现一个自旋锁
- * 自旋锁好处:循环比较获取没有类似wait的阻塞。
- *
- * 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒钟,B随后进来后发现
- * 当前有线程持有锁,不是null,所以只能通过自旋等待,直到A释放锁后B随后抢到。
- */
- public class SpinLockDemo
- {
- AtomicReference<Thread> atomicReference = new AtomicReference<>();
-
- public void myLock()
- {
- Thread thread = Thread.currentThread();
- System.out.println(Thread.currentThread().getName()+"\t come in");
- while(!atomicReference.compareAndSet(null,thread))
- {
-
- }
- }
-
- public void myUnLock()
- {
- Thread thread = Thread.currentThread();
- atomicReference.compareAndSet(thread,null);
- System.out.println(Thread.currentThread().getName()+"\t myUnLock over");
- }
-
- public static void main(String[] args)
- {
- SpinLockDemo spinLockDemo = new SpinLockDemo();
-
- new Thread(() -> {
- spinLockDemo.myLock();
- //暂停一会儿线程
- try { TimeUnit.SECONDS.sleep( 5 ); } catch (InterruptedException e) { e.printStackTrace(); }
- spinLockDemo.myUnLock();
- },"A").start();
- //暂停一会儿线程,保证A线程先于B线程启动并完成
- try { TimeUnit.SECONDS.sleep( 1 ); } catch (InterruptedException e) { e.printStackTrace(); }
-
- new Thread(() -> {
- spinLockDemo.myLock();
- spinLockDemo.myUnLock();
- },"B").start();
-
- }
- }
携带版本号的引用类型原子类,可以解决ABA问题
解决修改过几次!!!!
- package com.atguigu.juc.cas;
-
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.atomic.AtomicInteger;
- import java.util.concurrent.atomic.AtomicStampedReference;
-
- /**
- * @auther zzyy
- * @create 2021-03-18 15:34
- */
- public class ABADemo
- {
- static AtomicInteger atomicInteger = new AtomicInteger(100);
- static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);
-
- public static void main(String[] args)
- {
- abaProblem();
- abaResolve();
- }
-
- public static void abaResolve()
- {
- new Thread(() -> {
- int stamp = atomicStampedReference.getStamp();
- System.out.println("t3 ----第1次stamp "+stamp);
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
- atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);
- System.out.println("t3 ----第2次stamp "+atomicStampedReference.getStamp());
- atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
- System.out.println("t3 ----第3次stamp "+atomicStampedReference.getStamp());
- },"t3").start();
-
- new Thread(() -> {
- int stamp = atomicStampedReference.getStamp();
- System.out.println("t4 ----第1次stamp "+stamp);
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
- boolean result = atomicStampedReference.compareAndSet(100, 20210308, stamp, stamp + 1);
- System.out.println(Thread.currentThread().getName()+"\t"+result+"\t"+atomicStampedReference.getReference());
- },"t4").start();
- }
-
- public static void abaProblem()
- {
- new Thread(() -> {
- atomicInteger.compareAndSet(100,101);
- atomicInteger.compareAndSet(101,100);
- },"t1").start();
-
- try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
-
- new Thread(() -> {
- atomicInteger.compareAndSet(100,20210308);
- System.out.println(atomicInteger.get());
- },"t2").start();
- }
- }
-
- t2 ------20210308
- t4 ----第1次stamp 1
- t3 ----第1次stamp 1
- t3 ----第2次stamp 2
- t3 ----第3次stamp 3
- t4 false 100
-
- CAS问题引出的ABA问题,第一次将1修改成了2,第二次又把2改成了3,第三次把3改成1
- 表面上没有错,但是其中出现改了好几次
- 所以每次修改都会有版本号。t3和t4第一次都拿到了版本号是1。但是t3提前争到了修改成101
- 版本号就改变了。所以当t4要修改时,虽然结果都是100.但是版本号已经是对不上了!!!
原子更新带有标记位的引用类型对象
解决是否修改过!!!
它的定义就是将状态戳简化为true|false
- package com.atguigu.juc.senior.inner.atomic;
-
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.atomic.AtomicInteger;
- import java.util.concurrent.atomic.AtomicMarkableReference;
- import java.util.concurrent.atomic.AtomicStampedReference;
-
- /**
- * @auther zzyy
- * @create 2020-05-23 10:56
- */
- public class ABADemo
- {
- static AtomicInteger atomicInteger = new AtomicInteger(100);
- static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1);
- static AtomicMarkableReference<Integer> markableReference = new AtomicMarkableReference<>(100,false);
-
- public static void main(String[] args)
- {
- new Thread(() -> {
- atomicInteger.compareAndSet(100,101);
- atomicInteger.compareAndSet(101,100);
- System.out.println(Thread.currentThread().getName()+"\t"+"update ok");
- },"t1").start();
-
- new Thread(() -> {
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
- atomicInteger.compareAndSet(100,2020);
- },"t2").start();
-
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
-
- System.out.println(atomicInteger.get());
-
- System.out.println();
- System.out.println();
- System.out.println();
-
- System.out.println("============以下是ABA问题的解决,让我们知道引用变量中途被更改了几次=========================");
- new Thread(() -> {
- System.out.println(Thread.currentThread().getName()+"\t 1次版本号"+stampedReference.getStamp());
- //故意暂停200毫秒,让后面的t4线程拿到和t3一样的版本号
- try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
-
- stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
- System.out.println(Thread.currentThread().getName()+"\t 2次版本号"+stampedReference.getStamp());
- stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
- System.out.println(Thread.currentThread().getName()+"\t 3次版本号"+stampedReference.getStamp());
- },"t3").start();
-
- new Thread(() -> {
- int stamp = stampedReference.getStamp();
- System.out.println(Thread.currentThread().getName()+"\t =======1次版本号"+stamp);
- //暂停2秒钟,让t3先完成ABA操作了,看看自己还能否修改
- try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
- boolean b = stampedReference.compareAndSet(100, 2020, stamp, stamp + 1);
- System.out.println(Thread.currentThread().getName()+"\t=======2次版本号"+stampedReference.getStamp()+"\t"+stampedReference.getReference());
- },"t4").start();
-
- System.out.println();
- System.out.println();
- System.out.println();
-
- System.out.println("============AtomicMarkableReference不关心引用变量更改过几次,只关心是否更改过======================");
-
- new Thread(() -> {
- boolean marked = markableReference.isMarked();
- System.out.println(Thread.currentThread().getName()+"\t 1次版本号"+marked);
- try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
- markableReference.compareAndSet(100,101,marked,!marked);
- System.out.println(Thread.currentThread().getName()+"\t 2次版本号"+markableReference.isMarked());
- markableReference.compareAndSet(101,100,markableReference.isMarked(),!markableReference.isMarked());
- System.out.println(Thread.currentThread().getName()+"\t 3次版本号"+markableReference.isMarked());
- },"t5").start();
-
- new Thread(() -> {
- boolean marked = markableReference.isMarked();
- System.out.println(Thread.currentThread().getName()+"\t 1次版本号"+marked);
- //暂停几秒钟线程
- try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
- markableReference.compareAndSet(100,2020,marked,!marked);
- System.out.println(Thread.currentThread().getName()+"\t"+markableReference.getReference()+"\t"+markableReference.isMarked());
- },"t6").start();
- }
- }
-
-
- t1 update ok
- 2020
-
-
-
- ============以下是ABA问题的解决,让我们知道引用变量中途被更改了几次=========================
- t3 1次版本号1
-
-
-
- t4 =======1次版本号1
- ============AtomicMarkableReference不关心引用变量更改过几次,只关心是否更改过======================
- t5 1次版本号false
- t6 1次版本号false
- t5 2次版本号true
- t6 101 true
- t5 3次版本号false
- t3 2次版本号2
- t3 3次版本号3
- t4 =======2次版本号3 100
原子更新对象中int类型字段的值
原子更新对象中Long类型字段的值
原子更新引用类型字段的值
以一种线程安全的方式操作非线程安全对象内的某些字段
- package com.atguigu.itdachang;
-
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
-
-
- class BankAccount
- {
- private String bankName = "CCB";//银行
- public volatile int money = 0;//钱数
- AtomicIntegerFieldUpdater<BankAccount> accountAtomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class,"money");
-
- //不加锁+性能高,局部微创
- public void transferMoney(BankAccount bankAccount)
- {
- accountAtomicIntegerFieldUpdater.incrementAndGet(bankAccount);
- }
- }
-
- /**
- * @auther zzyy
- * @create 2020-07-14 18:06
- * 以一种线程安全的方式操作非线程安全对象的某些字段。
- * 需求:
- * 1000个人同时向一个账号转账一元钱,那么累计应该增加1000元,
- * 除了synchronized和CAS,还可以使用AtomicIntegerFieldUpdater来实现。
- */
- public class AtomicIntegerFieldUpdaterDemo
- {
-
- public static void main(String[] args)
- {
- BankAccount bankAccount = new BankAccount();
-
- for (int i = 1; i <=1000; i++) {
- int finalI = i;
- new Thread(() -> {
- bankAccount.transferMoney(bankAccount);
- },String.valueOf(i)).start();
- }
-
- //暂停毫秒
- try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
-
- System.out.println(bankAccount.money);
-
- }
- }
-
- package com.atguigu.juc.atomics;
-
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
-
- class MyVar
- {
- public volatile Boolean isInit = Boolean.FALSE;
- AtomicReferenceFieldUpdater<MyVar,Boolean> atomicReferenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(MyVar.class,Boolean.class,"isInit");
-
-
- public void init(MyVar myVar)
- {
- if(atomicReferenceFieldUpdater.compareAndSet(myVar,Boolean.FALSE,Boolean.TRUE))
- {
- System.out.println(Thread.currentThread().getName()+"\t"+"---init.....");
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
- System.out.println(Thread.currentThread().getName()+"\t"+"---init.....over");
- }else{
- System.out.println(Thread.currentThread().getName()+"\t"+"------其它线程正在初始化");
- }
- }
-
-
- }
-
-
- /**
- * @auther zzyy
- * @create 2021-03-18 17:20
- * 多线程并发调用一个类的初始化方法,如果未被初始化过,将执行初始化工作,要求只能初始化一次
- */
- public class AtomicIntegerFieldUpdaterDemo
- {
- public static void main(String[] args) throws InterruptedException
- {
- MyVar myVar = new MyVar();
-
- for (int i = 1; i <=5; i++) {
- new Thread(() -> {
- myVar.init(myVar);
- },String.valueOf(i)).start();
- }
- }
- }
AtomicReferenceFieldUpdater
LongAdder只能用来计算加法,且从零开始计算
LongAccumulator提供了自定义的函数操作
-
- long类型的聚合器,需要传入一个long类型的二元操作,可以用来计算各种聚合操作,包括加乘等
-
- package com.atguigu.juc.senior.inner.atomic;
-
- import java.util.concurrent.atomic.LongAccumulator;
- import java.util.concurrent.atomic.LongAdder;
- import java.util.function.LongBinaryOperator;
-
- /**
- * @auther zzyy
- * @create 2020-05-30 13:51
- */
- public class LongAccumulatorDemo
- {
-
- LongAdder longAdder = new LongAdder();
- public void add_LongAdder()
- {
- longAdder.increment();
- }
-
- //LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y,0);
- LongAccumulator longAccumulator = new LongAccumulator(new LongBinaryOperator()
- {
- @Override
- public long applyAsLong(long left, long right)
- {
- return left - right;
- }
- },777);
-
- public void add_LongAccumulator()
- {
- longAccumulator.accumulate(1);
- }
-
- public static void main(String[] args)
- {
- LongAccumulatorDemo demo = new LongAccumulatorDemo();
-
- demo.add_LongAccumulator();
- demo.add_LongAccumulator();
- System.out.println(demo.longAccumulator.longValue());
- }
- }
-
-
LongAdderAPIDemo
-
- package com.atguigu.juc.atomics;
-
- import java.util.concurrent.atomic.LongAccumulator;
- import java.util.concurrent.atomic.LongAdder;
-
- /**
- * @auther zzyy
- * @create 2021-03-19 15:59
- */
- public class LongAdderAPIDemo
- {
- public static void main(String[] args)
- {
- LongAdder longAdder = new LongAdder();
-
- longAdder.increment();
- longAdder.increment();
- longAdder.increment();
-
- System.out.println(longAdder.longValue());
-
- LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x * y,2);
-
- longAccumulator.accumulate(1);
- longAccumulator.accumulate(2);
- longAccumulator.accumulate(3);
-
- System.out.println(longAccumulator.longValue());
-
- }
- }
LongAdder高性能对比Code演示
- package com.zzyy.study.day524;
-
- import java.util.concurrent.CountDownLatch;
- import java.util.concurrent.atomic.AtomicLong;
- import java.util.concurrent.atomic.LongAccumulator;
- import java.util.concurrent.atomic.LongAdder;
-
- class ClickNumberNet
- {
- int number = 0;
- public synchronized void clickBySync()
- {
- number++;
- }
-
- AtomicLong atomicLong = new AtomicLong(0);
- public void clickByAtomicLong()
- {
- atomicLong.incrementAndGet();
- }
-
- LongAdder longAdder = new LongAdder();
- public void clickByLongAdder()
- {
- longAdder.increment();
- }
-
- LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x + y,0);
- public void clickByLongAccumulator()
- {
- longAccumulator.accumulate(1);
- }
- }
-
- /**
- * @auther zzyy
- * @create 2020-05-21 22:23
- * 50个线程,每个线程100W次,总点赞数出来
- */
- public class LongAdderDemo2
- {
- public static void main(String[] args) throws InterruptedException
- {
- ClickNumberNet clickNumberNet = new ClickNumberNet();
-
- long startTime;
- long endTime;
- CountDownLatch countDownLatch = new CountDownLatch(50);
- CountDownLatch countDownLatch2 = new CountDownLatch(50);
- CountDownLatch countDownLatch3 = new CountDownLatch(50);
- CountDownLatch countDownLatch4 = new CountDownLatch(50);
-
-
- startTime = System.currentTimeMillis();
- for (int i = 1; i <=50; i++) {
- new Thread(() -> {
- try
- {
- for (int j = 1; j <=100 * 10000; j++) {
- clickNumberNet.clickBySync();
- }
- }finally {
- countDownLatch.countDown();
- }
- },String.valueOf(i)).start();
- }
- countDownLatch.await();
- endTime = System.currentTimeMillis();
- System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickBySync result: "+clickNumberNet.number);
-
- startTime = System.currentTimeMillis();
- for (int i = 1; i <=50; i++) {
- new Thread(() -> {
- try
- {
- for (int j = 1; j <=100 * 10000; j++) {
- clickNumberNet.clickByAtomicLong();
- }
- }finally {
- countDownLatch2.countDown();
- }
- },String.valueOf(i)).start();
- }
- countDownLatch2.await();
- endTime = System.currentTimeMillis();
- System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByAtomicLong result: "+clickNumberNet.atomicLong);
-
- startTime = System.currentTimeMillis();
- for (int i = 1; i <=50; i++) {
- new Thread(() -> {
- try
- {
- for (int j = 1; j <=100 * 10000; j++) {
- clickNumberNet.clickByLongAdder();
- }
- }finally {
- countDownLatch3.countDown();
- }
- },String.valueOf(i)).start();
- }
- countDownLatch3.await();
- endTime = System.currentTimeMillis();
- System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByLongAdder result: "+clickNumberNet.longAdder.sum());
-
- startTime = System.currentTimeMillis();
- for (int i = 1; i <=50; i++) {
- new Thread(() -> {
- try
- {
- for (int j = 1; j <=100 * 10000; j++) {
- clickNumberNet.clickByLongAccumulator();
- }
- }finally {
- countDownLatch4.countDown();
- }
- },String.valueOf(i)).start();
- }
- countDownLatch4.await();
- endTime = System.currentTimeMillis();
- System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t clickByLongAccumulator result: "+clickNumberNet.longAccumulator.longValue());
-
-
- }
- }
AtomicLong
N个线程CAS操作修改线程的值,每次只有一个成功过,其它N - 1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,一下子cpu就打高了。
LongAdder vs AtomicLong Performance
Java 8 Performance Improvements: LongAdder vs AtomicLong | Palomino Labs Blog
LongAdder
恶心的大厂面试题
- ThreadLocal中ThreadLocalMap的数据结构和关系?
- ThreadLocal的key是弱引用,这是为什么?
- ThreadLocal内存泄露问题你知道吗?
- ThreadLocal中最后为什么要加remove方法?
按照总销售额统计,方便集团公司做计划统计(群雄逐鹿起纷争)
- package com.atguigu.juc.tl;
-
-
- import java.util.concurrent.TimeUnit;
-
- class MovieTicket
- {
- int number = 50;
-
- public synchronized void saleTicket()
- {
- if(number > 0)
- {
- System.out.println(Thread.currentThread().getName()+"\t"+"号售票员卖出第: "+(number--));
- }else{
- System.out.println("--------卖完了");
- }
- }
- }
-
- /**
- * @auther zzyy
- * @create 2021-03-23 15:03
- * 三个售票员卖完50张票务,总量完成即可,吃大锅饭,售票员每个月固定月薪
- */
- public class ThreadLocalDemo
- {
- public static void main(String[] args)
- {
- MovieTicket movieTicket = new MovieTicket();
-
- for (int i = 1; i <=3; i++) {
- new Thread(() -> {
- for (int j = 0; j <20; j++) {
- movieTicket.saleTicket();
- try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
- }
- },String.valueOf(i)).start();
- }
- }
- }
上述需求变化了...
---------------------------------------------------
不参加总和计算,希望各自分灶吃饭,
各凭销售本事提成,按照出单数各自统计
比如某找房软件,每个中介销售都有自己的销售额指标,自己专属自己的,不和别人掺和
---------------------------------------------------
上述需求该如何处理???
- package com.atguigu.juc.tl;
-
-
- class MovieTicket
- {
- int number = 50;
-
- public synchronized void saleTicket()
- {
- if(number > 0)
- {
- System.out.println(Thread.currentThread().getName()+"\t"+"号售票员卖出第: "+(number--));
- }else{
- System.out.println("--------卖完了");
- }
- }
- }
-
- class House
- {
- ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);
-
- public void saleHouse()
- {
- Integer value = threadLocal.get();
- value++;
- threadLocal.set(value);
- }
- }
-
- /**
- * @auther zzyy
- * @create 2021-03-23 15:03
- * 1 三个售票员卖完50张票务,总量完成即可,吃大锅饭,售票员每个月固定月薪
- *
- * 2 分灶吃饭,各个销售自己动手,丰衣足食
- */
- public class ThreadLocalDemo
- {
- public static void main(String[] args)
- {
- /*MovieTicket movieTicket = new MovieTicket();
-
- for (int i = 1; i <=3; i++) {
- new Thread(() -> {
- for (int j = 0; j <20; j++) {
- movieTicket.saleTicket();
- try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
- }
- },String.valueOf(i)).start();
- }*/
-
- //===========================================
- House house = new House();
-
- new Thread(() -> {
- try {
- for (int i = 1; i <=3; i++) {
- house.saleHouse();
- }
- System.out.println(Thread.currentThread().getName()+"\t"+"---"+house.threadLocal.get());
- }finally {
- house.threadLocal.remove();//如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题
- }
- },"t1").start();
-
- new Thread(() -> {
- try {
- for (int i = 1; i <=2; i++) {
- house.saleHouse();
- }
- System.out.println(Thread.currentThread().getName()+"\t"+"---"+house.threadLocal.get());
- }finally {
- house.threadLocal.remove();
- }
- },"t2").start();
-
- new Thread(() -> {
- try {
- for (int i = 1; i <=5; i++) {
- house.saleHouse();
- }
- System.out.println(Thread.currentThread().getName()+"\t"+"---"+house.threadLocal.get());
- }finally {
- house.threadLocal.remove();
- }
- },"t3").start();
-
-
- System.out.println(Thread.currentThread().getName()+"\t"+"---"+house.threadLocal.get());
- }
- }
一般而言JDK8按照默认情况下,new一个对象占多少内存空间
对象布局、GC回收和后面的锁升级就是对象标记MarkWord里面标志位的变化
- 谈谈你对Synchronized的理解
- Synchronized的锁升级你聊聊
- Synchronized的性能是不是一定弱于Lock
synchronized锁:由对象头中的Mark Word根据锁标志位的不同而被复用及锁升级策略
重量级锁,假如锁的竞争比较激烈的话,性能下降。
Java5之前,用户态和内核态之间的切换
markOop.hpp
Monitor(监视器锁)
Java 6之后,为了减少获得锁和释放锁所带来的性能消耗,引入了轻量级锁和偏向锁
需要有个逐步升级的过程,别一开始就捅到重量级锁
synchronized用的锁是存在Java对象头里的Mark Word中锁升级功能主要依赖MarkWord中锁标志位和释放偏向锁标志位
64位标记图再看
- package com.atguigu.juc.senior.inner.object;
-
- import org.openjdk.jol.info.ClassLayout;
-
- /**
- * @auther zzyy
- * @create 2020-06-13 11:24
- */
- public class MyObject
- {
- public static void main(String[] args)
- {
- Object o = new Object();
-
- System.out.println("10进制hash码:"+o.hashCode());
- System.out.println("16进制hash码:"+Integer.toHexString(o.hashCode()));
- System.out.println("2进制hash码:"+Integer.toBinaryString(o.hashCode()));
-
- System.out.println( ClassLayout.parseInstance(o).toPrintable());
- }
- }
程序不会有锁的竞争
a. 主要作用
当一段同步代码一直被同一个线程多次访问,由于只有一个线程那么该线程在后续访问时便会自动获得锁
同一个老顾客来访,直接老规矩行方便
Hotspot 的作者经过研究发现,大多数情况下:
多线程的情况下,锁不仅不存在多线程竞争,还存在锁由同一线程多次获得的情况,
偏向锁就是在这种情况下出现的,它的出现是为了解决只有在一个线程执行同步时提高性能。
b. 64位标记图再看
通过CAS方式修改markword中的线程ID
c. 偏向锁的持有
理论落地:
在实际应用运行过程中发现,“锁总是同一个线程持有,很少发生竞争”,也就是说锁总是被第一个占用他的线程拥有,这个线程就是锁的偏向线程。
那么只需要在锁第一次被拥有的时候,记录下偏向线程ID。这样偏向线程就一直持有着锁(后续这个线程进入和退出这段加了同步锁的代码块时,不需要再次加锁和释放锁。而是直接比较对象头里面是否存储了指向当前线程的偏向锁)。
如果相等表示偏向锁是偏向于当前线程的,就不需要再尝试获得锁了,直到竞争发生才释放锁。以后每次同步,检查锁的偏向线程ID与当前线程ID是否一致,如果一致直接进入同步。无需每次加锁解锁都去CAS更新对象头。如果自始至终使用锁的线程只有一个,很明显偏向锁几乎没有额外开销,性能极高。
假如不一致意味着发生了竞争,锁已经不是总是偏向于同一个线程了,这时候可能需要升级变为轻量级锁,才能保证线程间公平竞争锁。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程是不会主动释放偏向锁的。
技术实现:
一个synchronized方法被一个线程抢到了锁时,那这个方法所在的对象就会在其所在的Mark Word中将偏向锁修改状态位,同时还会有占用前54位来存储线程指针作为标识。若该线程再次访问同一个synchronized方法时,该线程只需去对象头的Mark Word 中去判断一下是否有偏向锁指向本身的ID,无需再进入 Monitor 去竞争对象了。
偏向锁JVM命令
java -XX:+PrintFlagsInitial |grep BiasedLock*
- package com.atguigu.juc.senior.inner.object;
-
- import org.openjdk.jol.info.ClassLayout;
-
- /**
- * @auther zzyy
- * @create 2020-06-13 11:24
- */
- public class MyObject
- {
- public static void main(String[] args)
- {
- Object o = new Object();
-
- new Thread(() -> {
- synchronized (o){
- System.out.println(ClassLayout.parseInstance(o).toPrintable());
- }
- },"t1").start();
- }
- }
无效果!!!!
因为参数系统默认开启
关闭延时参数,启用该功能
偏向锁使用一种等到竞争出现才释放锁的机制,只有当其他线程竞争锁时,持有偏向锁的原来线程才会被撤销。
撤销需要等待全局安全点(该时间点上没有字节码正在执行),同时检查持有偏向锁的线程是否还在执行:
① 第一个线程正在执行synchronized方法(处于同步块),它还没有执行完,其它线程来抢夺,该偏向锁会被取消掉并出现锁升级。
此时轻量级锁由原持有偏向锁的线程持有,继续执行其同步代码,而正在竞争的线程会进入自旋等待获得该轻量级锁。
② 第一个线程执行完成synchronized方法(退出同步块),则将对象头设置成无锁状态并撤销偏向锁,重新偏向 。
如果关闭偏向锁,就可以直接进入轻量级锁
-XX:-UseBiasedLocking
有大量的线程参与锁的竞争,冲突性很高
synchronized锁升级过程总结:一句话,就是先自旋,不行再阻塞。
实际上是把之前的悲观锁(重量级锁)变成在一定条件下使用偏向锁以及使用轻量级(自旋锁CAS)的形式
synchronized在修饰方法和代码块在字节码上实现方式有很大差异,但是内部实现还是基于对象头的MarkWord来实现的。
JDK1.6之前synchronized使用的是重量级锁,JDK1.6之后进行了优化,拥有了无锁->偏向锁->轻量级锁->重量级锁的升级过程,而不是无论什么情况都使用重量级锁。
偏向锁:适用于单线程适用的情况,在不存在锁竞争的时候进入同步方法/代码块则使用偏向锁。
轻量级锁:适用于竞争较不激烈的情况(这和乐观锁的使用范围类似), 存在竞争时升级为轻量级锁,轻量级锁采用的是自旋锁,如果同步方法/代码块执行时间很短的话,采用轻量级锁虽然会占用cpu资源但是相对比使用重量级锁还是更高效。
重量级锁:适用于竞争激烈的情况,如果同步方法/代码块执行时间很长,那么使用轻量级锁自旋带来的性能消耗就比使用重量级锁更严重,这时候就需要升级为重量级锁。
Just In Time Compiler,一般翻译为即时编译器
- package com.atguigu.itdachang;
-
- /**
- * 锁消除
- * 从JIT角度看相当于无视它,synchronized (o)不存在了,这个锁对象并没有被共用扩散到其它线程使用,
- * 极端的说就是根本没有加这个锁对象的底层机器码,消除了锁的使用
- */
- public class LockClearUPDemo
- {
- static Object objectLock = new Object();//正常的
-
- public void m1()
- {
- //锁消除,JIT会无视它,synchronized(对象锁)不存在了。不正常的
- Object o = new Object();
-
- synchronized (o)
- {
- System.out.println("-----hello LockClearUPDemo"+"\t"+o.hashCode()+"\t"+objectLock.hashCode());
- }
- }
-
- public static void main(String[] args)
- {
- LockClearUPDemo demo = new LockClearUPDemo();
-
- for (int i = 1; i <=10; i++) {
- new Thread(() -> {
- demo.m1();
- },String.valueOf(i)).start();
- }
- }
- }
- package com.atguigu.itdachang;
-
- /**
- * 锁粗化
- * 假如方法中首尾相接,前后相邻的都是同一个锁对象,那JIT编译器就会把这几个synchronized块合并成一个大块,
- * 加粗加大范围,一次申请锁使用即可,避免次次的申请和释放锁,提升了性能
- */
- public class LockBigDemo
- {
- static Object objectLock = new Object();
-
-
- public static void main(String[] args)
- {
- new Thread(() -> {
- synchronized (objectLock) {
- System.out.println("11111");
- }
- synchronized (objectLock) {
- System.out.println("22222");
- }
- synchronized (objectLock) {
- System.out.println("33333");
- }
- },"a").start();
-
- new Thread(() -> {
- synchronized (objectLock) {
- System.out.println("44444");
- }
- synchronized (objectLock) {
- System.out.println("55555");
- }
- synchronized (objectLock) {
- System.out.println("66666");
- }
- },"b").start();
-
- }
- }
抽象的队列同步器
是用来构建锁或者其它同步器组件的重量级基础框架及整个JUC体系的基石,通过内置的FIFO队列来完成资源获取线程的排队工作,并通过一个int类变量。表示持有锁的状态
定义了程序员和锁交互的使用层API,隐藏了实现细节,你调用即可。
比如Java并发大神DougLee,提出统一规范并简化了锁的实现,
屏蔽了同步状态管理、阻塞线程排队和通知、唤醒机制等。
有阻塞就需要排队,实现排队必然需要队列
AQS使用一个volatile的int类型的成员变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作将每条要去抢占资源的线程封装成一个Node节点来实现锁的分配,通过CAS完成对State值的修改。
对比公平锁和非公平锁的 tryAcquire()方法的实现代码,其实差别就在于非公平锁获取锁时比公平锁中少了一个判断 !hasQueuedPredecessors()
hasQueuedPredecessors() 中判断了是否需要排队
导致公平锁和非公平锁的差异如下:
公平锁:公平锁讲究先来先到,线程在获取锁时,如果这个锁的等待队列中已经有线程在等待,那么当前线程就会进入等待队列中;
非公平锁:不管是否有等待队列,如果可以获取锁,则立刻占有锁对象。也就是说队列的第一个排队线程在unpark(),之后还是需要竞争锁(存在线程竞争的情况下)
无锁→独占锁→读写锁→邮戳锁
读写锁说明
『读写锁』意义和特点
code演示ReentrantReadWriteLockDemo
- package com.atguigu.juc.rwlock;
-
-
- import java.util.HashMap;
- import java.util.Map;
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- import java.util.concurrent.locks.ReentrantReadWriteLock;
-
- class MyResource
- {
- Map<String,String> map = new HashMap<>();
- //=====ReentrantLock 等价于 =====synchronized
- Lock lock = new ReentrantLock();
- //=====ReentrantReadWriteLock 一体两面,读写互斥,读读共享
- ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
-
- public void write(String key,String value)
- {
- rwLock.writeLock().lock();
- try
- {
- System.out.println(Thread.currentThread().getName()+"\t"+"---正在写入");
- map.put(key,value);
- //暂停毫秒
- try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
- System.out.println(Thread.currentThread().getName()+"\t"+"---完成写入");
- }finally {
- rwLock.writeLock().unlock();
- }
- }
- public void read(String key)
- {
- rwLock.readLock().lock();
- try
- {
- System.out.println(Thread.currentThread().getName()+"\t"+"---正在读取");
- String result = map.get(key);
- //后续开启注释修改为2000,演示一体两面,读写互斥,读读共享,读没有完成时候写锁无法获得
- //try { TimeUnit.MILLISECONDS.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
- System.out.println(Thread.currentThread().getName()+"\t"+"---完成读取result:"+result);
- }finally {
- rwLock.readLock().unlock();
- }
- }
- }
-
-
-
-
- /**
- * @auther zzyy
- * @create 2021-03-28 11:04
- */
- public class ReentrantReadWriteLockDemo
- {
- public static void main(String[] args)
- {
- MyResource myResource = new MyResource();
-
- for (int i = 1; i <=10; i++) {
- int finalI = i;
- new Thread(() -> {
- myResource.write(finalI +"", finalI +"");
- },String.valueOf(i)).start();
- }
-
- for (int i = 1; i <=10; i++) {
- int finalI = i;
- new Thread(() -> {
- myResource.read(finalI +"");
- },String.valueOf(i)).start();
- }
-
- //暂停几秒钟线程
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
-
- //读全部over才可以继续写
- for (int i = 1; i <=3; i++) {
- int finalI = i;
- new Thread(() -> {
- myResource.write(finalI +"", finalI +"");
- },"newWriteThread==="+String.valueOf(i)).start();
- }
- }
- }
《Java 并发编程的艺术》中关于锁降级的说明:
锁降级:将写入锁降级为读锁(类似Linux文件读写权限理解,就像写权限要高于读权限一样)
可以降级
锁降级是为了让当前线程感知到数据的变化,目的是保证数据可见性
如果有线程在读,那么写线程是无法获取写锁的,是悲观锁的策略
- package com.atguigu.juc.rwlock;
-
- import java.util.concurrent.locks.ReentrantReadWriteLock;
-
- /**
- * @auther zzyy
- * @create 2021-03-28 10:18
- * 锁降级:遵循获取写锁→再获取读锁→再释放写锁的次序,写锁能够降级成为读锁。
- *
- * 如果一个线程占有了写锁,在不释放写锁的情况下,它还能占有读锁,即写锁降级为读锁。
- */
- public class LockDownGradingDemo
- {
- public static void main(String[] args)
- {
- ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
-
- ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
- ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
-
-
- writeLock.lock();
- System.out.println("-------正在写入");
-
-
- readLock.lock();
- System.out.println("-------正在读取");
-
- writeLock.unlock();
-
- }
- }
写锁和读锁是互斥的(这里的互斥是指线程间的互斥,当前线程可以获取到写锁又获取到读锁,但是获取到了读锁不能继续获取写锁),这是因为读写锁要保持写操作的可见性。
因为,如果允许读锁在被获取的情况下对写锁的获取,那么正在运行的其他读线程无法感知到当前写线程的操作。
因此,
分析读写锁ReentrantReadWriteLock,会发现它有个潜在的问题:
读锁全完,写锁有望;写锁独占,读写全堵;
如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁,见前面Case《code演示LockDownGradingDemo》
即ReadWriteLock读的过程中不允许写,只有等待线程都释放了读锁,当前线程才能获取写锁,
也就是写入必须等待,这是一种悲观的读锁,o(╥﹏╥)o,人家还在读着那,你先别去写,省的数据乱。
================================后续讲解StampedLock时再详细展开=======================
分析StampedLock(后面详细讲解),会发现它改进之处在于:
读的过程中也允许获取写锁介入(相当牛B,读和写两个操作也让你“共享”(注意引号)),这样会导致我们读的数据就可能不一致!
所以,需要额外的方法来判断读的过程中是否有写入,这是一种乐观的读锁,O(∩_∩)O哈哈~。
显然乐观锁的并发效率更高,但一旦有小概率的写入导致读取的数据不一致,需要能检测出来,再读一遍就行。
Oracle公司ReentrantWriteReadLock源码总结
锁降级 下面的示例代码摘自ReentrantWriteReadLock源码中:
ReentrantWriteReadLock支持锁降级,遵循按照获取写锁,获取读锁再释放写锁的次序,写锁能够降级成为读锁,不支持锁升级。
解读在最下面:
ReentrantReadWriteLock实现了读写分离,但是一旦读操作比较多的时候,想要获取写锁就变得比较困难了,
假如当前1000个线程,999个读,1个写,有可能999个读取线程长时间抢到了锁,那1个写线程就悲剧了
因为当前有可能会一直存在读锁,而无法获得写锁,根本没机会写,o(╥﹏╥)o
ReentrantReadWriteLock
允许多个线程同时读,但是只允许一个线程写,在线程获取到写锁的时候,其他写操作和读操作都会处于阻塞状态,
读锁和写锁也是互斥的,所以在读的时候是不允许写的,读写锁比传统的synchronized速度要快很多,
原因就是在于ReentrantReadWriteLock支持读并发
StampedLock横空出世
ReentrantReadWriteLock的读锁被占用的时候,其他线程尝试获取写锁的时候会被阻塞。
但是,StampedLock采取乐观获取锁后,其他线程尝试获取写锁时不会被阻塞,这其实是对读锁的优化,
所以,在获取乐观读锁后,还需要对结果进行校验。
StampedLock有三种访问模式
①Reading(读模式):功能和ReentrantReadWriteLock的读锁类似
②Writing(写模式):功能和ReentrantReadWriteLock的写锁类似
③Optimistic reading(乐观读模式):无锁机制,类似于数据库中的乐观锁,
支持读写并发,很乐观认为读取时没人修改,假如被修改再实现升级为悲观读模式
- package com.atguigu.itdachang;
-
- import java.util.concurrent.TimeUnit;
- import java.util.concurrent.locks.StampedLock;
-
- /**
- * @auther zzyy
- * @create 2020-07-22 16:03
- */
- public class StampedLockDemo
- {
- static int number = 37;
- static StampedLock stampedLock = new StampedLock();
-
- public void write()
- {
- long stamp = stampedLock.writeLock();
- System.out.println(Thread.currentThread().getName()+"\t"+"=====写线程准备修改");
- try
- {
- number = number + 13;
- }catch (Exception e){
- e.printStackTrace();
- }finally {
- stampedLock.unlockWrite(stamp);
- }
- System.out.println(Thread.currentThread().getName()+"\t"+"=====写线程结束修改");
- }
-
- //悲观读
- public void read()
- {
- long stamp = stampedLock.readLock();
- System.out.println(Thread.currentThread().getName()+"\t come in readlock block,4 seconds continue...");
- //暂停几秒钟线程
- for (int i = 0; i <4 ; i++) {
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
- System.out.println(Thread.currentThread().getName()+"\t 正在读取中......");
- }
- try
- {
- int result = number;
- System.out.println(Thread.currentThread().getName()+"\t"+" 获得成员变量值result:" + result);
- System.out.println("写线程没有修改值,因为 stampedLock.readLock()读的时候,不可以写,读写互斥");
- }catch (Exception e){
- e.printStackTrace();
- }finally {
- stampedLock.unlockRead(stamp);
- }
- }
-
- //乐观读
- public void tryOptimisticRead()
- {
- long stamp = stampedLock.tryOptimisticRead();
- int result = number;
- //间隔4秒钟,我们很乐观的认为没有其他线程修改过number值,实际靠判断。
- System.out.println("4秒前stampedLock.validate值(true无修改,false有修改)"+"\t"+stampedLock.validate(stamp));
- for (int i = 1; i <=4 ; i++) {
- try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
- System.out.println(Thread.currentThread().getName()+"\t 正在读取中......"+i+
- "秒后stampedLock.validate值(true无修改,false有修改)"+"\t"
- +stampedLock.validate(stamp));
- }
- if(!stampedLock.validate(stamp)) {
- System.out.println("有人动过--------存在写操作!");
- stamp = stampedLock.readLock();
- try {
- System.out.println("从乐观读 升级为 悲观读");
- result = number;
- System.out.println("重新悲观读锁通过获取到的成员变量值result:" + result);
- }catch (Exception e){
- e.printStackTrace();
- }finally {
- stampedLock.unlockRead(stamp);
- }
- }
- System.out.println(Thread.currentThread().getName()+"\t finally value: "+result);
- }
-
- public static void main(String[] args)
- {
- StampedLockDemo resource = new StampedLockDemo();
-
- new Thread(() -> {
- resource.read();
- //resource.tryOptimisticRead();
- },"readThread").start();
-
- // 2秒钟时乐观读失败,6秒钟乐观读取成功resource.tryOptimisticRead();,修改切换演示
- //try { TimeUnit.SECONDS.sleep(6); } catch (InterruptedException e) { e.printStackTrace(); }
-
- new Thread(() -> {
- resource.write();
- },"writeThread").start();
- }
- }
LockSupport是基于Unsafe类,由JDK提供的线程操作工具类,主要作用就是挂起线程,唤醒线程。
- LockSupport.park
- LockSupport.unpark
线程在Object.wait之后必须等到Object.notify才能唤醒
LockSupport可以先unpark线程,等线程执行LockSupport.park是不会挂起的,可以继续执行
volatile+cas机制实现的锁模板,保证了代码的同步性和可见性,而AQS封装了线程阻塞等待挂起,解锁唤醒其他线程的逻辑。AQS子类只需根据状态变量,判断是否可获取锁,是否释放锁,使用LockSupport挂起、唤醒线程即可
- //AbstractQueuedSynchronizer.java
- public class AbstractQueuedSynchronizer{
- //线程节点
- static final class Node {
- volatile Node prev;
- volatile Node next;
- volatile Thread thread;
- ...
- }
- //head 等待队列头尾节点
- private transient volatile Node head;
- private transient volatile Node tail;
- private volatile int state; // The synchronization state. 同步状态
- ...
- //提供CAS操作,状态具体的修改由子类实现
- protected final boolean compareAndSetState(int expect, int update) {
- return STATE.compareAndSet(this, expect, update);
- }
- }
AQS内部维护一个同步队列,元素就是包装了线程的Node。
同步队列中首节点是获取到锁的节点,它在释放锁的时会唤醒后继节点,后继节点获取到锁的时候,会把自己设为首节点。
线程会先尝试获取锁,失败则封装成Node,CAS加入同步队列的尾部。在加入同步队列的尾部时,会判断前驱节点是否是head结点,并尝试加锁(可能前驱节点刚好释放锁),否则线程进入阻塞等待。
当使用ThreadLocal声明变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本
Semaphore(信号量)是用来控制同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。很多年以来,我都觉得从字面上很难理解Semaphore所表达的含义,只能把它比作是控制流量的红绿灯,比如XX马路要限制流量,只允许同时有一百辆车在这条路上行使,其他的都必须在路口等待,所以前一百辆车会看到绿灯,可以开进这条马路,后面的车会看到红灯,不能驶入XX马路,但是如果前一百辆中有五辆车已经离开了XX马路,那么后面就允许有5辆车驶入马路,这个例子里说的车就是线程,驶入马路就表示线程在执行,离开马路就表示线程执行完成,看见红灯就表示线程被阻塞,不能执行。
说了这么多我们来个实际的例子看一看,比如我们去停车场停车,停车场总共只有5个车位,但是现在有8辆汽车来停车,剩下的3辆汽车要么等其他汽车开走后进行停车,或者去找别的停车位?
- public class SemaphoreTest {
-
- public static void main(String[] args) throws InterruptedException {
-
- // 初始化五个车位
-
- Semaphore semaphore = new Semaphore(5);
-
- // 等所有车子
-
- final CountDownLatch latch = new CountDownLatch(8);
-
- for (int i = 0; i < 8; i++) {
-
- int finalI = i;
-
- if (i == 5) {
-
- Thread.sleep(1000);
-
- new Thread(() -> {
-
- stopCarNotWait(semaphore, finalI);
-
- latch.countDown();
-
- }).start();
-
- continue;
-
- }
-
- new Thread(() -> {
-
- stopCarWait(semaphore, finalI);
-
- latch.countDown();
-
- }).start();
-
- }
-
- latch.await();
-
- log("总共还剩:" + semaphore.availablePermits() + "个车位");
-
- }
-
-
-
- private static void stopCarWait(Semaphore semaphore, int finalI) {
-
- String format = String.format("车牌号%d", finalI);
-
- try {
-
- semaphore.acquire(1);
-
- log(format + "找到车位了,去停车了");
-
- Thread.sleep(10000);
-
- } catch (Exception e) {
-
- e.printStackTrace();
-
- } finally {
-
- semaphore.release(1);
-
- log(format + "开走了");
-
- }
-
- }
-
-
-
- private static void stopCarNotWait(Semaphore semaphore, int finalI) {
-
- String format = String.format("车牌号%d", finalI);
-
- try {
-
- if (semaphore.tryAcquire()) {
-
- log(format + "找到车位了,去停车了");
-
- Thread.sleep(10000);
-
- log(format + "开走了");
-
- semaphore.release();
-
- } else {
-
- log(format + "没有停车位了,不在这里等了去其他地方停车去了");
-
- }
-
- } catch (Exception e) {
-
- e.printStackTrace();
-
- }
-
-
-
- }
-
-
-
- public static void log(String content) {
-
- // 格式化
-
- DateTimeFormatter fmTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
-
- // 当前时间
-
- LocalDateTime now = LocalDateTime.now();
-
- System.out.println(now.format(fmTime) + " "+content);
-
- }
-
- }
-
-
-
-
- 2021-03-01 18:54:57 车牌号0找到车位了,去停车了
-
- 2021-03-01 18:54:57 车牌号3找到车位了,去停车了
-
- 2021-03-01 18:54:57 车牌号2找到车位了,去停车了
-
- 2021-03-01 18:54:57 车牌号1找到车位了,去停车了
-
- 2021-03-01 18:54:57 车牌号4找到车位了,去停车了
-
- 2021-03-01 18:54:58 车牌号5没有停车位了,不在这里等了去其他地方停车去了
-
- 2021-03-01 18:55:07 车牌号7找到车位了,去停车了
-
- 2021-03-01 18:55:07 车牌号6找到车位了,去停车了
-
- 2021-03-01 18:55:07 车牌号2开走了
-
- 2021-03-01 18:55:07 车牌号0开走了
-
- 2021-03-01 18:55:07 车牌号3开走了
-
- 2021-03-01 18:55:07 车牌号4开走了
-
- 2021-03-01 18:55:07 车牌号1开走了
-
- 2021-03-01 18:55:17 车牌号7开走了
-
- 2021-03-01 18:55:17 车牌号6开走了
-
- 2021-03-01 18:55:17 总共还剩:5个车位
从输出结果我们可以看到车牌号5这辆车看见没有车位了,就不在这个地方傻傻的等了,而是去其他地方了,但是车牌号6和车牌号7分别需要等到车库开出两辆车空出两个车位后才停进去。这就体现了Semaphore 的acquire 方法如果没有获取到凭证它就会阻塞,而tryAcquire方法如果没有获取到凭证不会阻塞的。
Java高并发编程基础三大利器之Semaphore-腾讯云开发者社区-腾讯云
CountDownLatch是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就减1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上(调用await方法的线程)等待的线程就可以恢复工作了。
CountDownLatch可以用来干什么呢?有什么应用场景?实际项目中有应用的场景吗?这应该才是大家比较关心的。我们先来看看官网提供的例子是如何进行应用的
CountDownLatch (Java Platform SE 8 )
官方提供了两个demo我直接把它转成了图片顺带推荐下这个代码转图片的网址https://www.dute.org/code-snapshot 还挺好用的。
★The first is a start signal that prevents any worker from proceeding until the driver is ready for them to proceed; The second is a completion signal that allows the driver to wait until all workers have completed. ”
★Another typical usage would be to divide a problem into N parts, describe each part with a Runnable that executes that portion and counts down on the latch, and queue all the Runnables to an Executor. When all sub-parts are complete, the coordinating thread will be able to pass through await. ”
另一种典型的用法就是把一个大任务拆分N个部分,让多个线程(Worker)执行,每个线程(Worker)执行完自己的部分计数器就减1,当所有子部分都完成后,Driver 才继续向下执行才继续执行。就好比富士康手机加工的流水线一样,组装一步手机需要一条条的流水线来相互配合完成。一条条流水线(Worker),每条线都干自己的活。有的流水线是贴膜的,有的流水线是打螺丝的,有的流水线是质检的、有的流水线充电的、有的流水线贴膜的。等这些流水线都干完了就把一部手机组装完成了。
Java高并发编程基础三大利器之CountDownLatch-腾讯云开发者社区-腾讯云
前面一篇文章我们《Java高并发编程基础三大利器之CountDownLatch》它有一个缺点,就是它的计数器只能够使用一次,也就是说当计数器(state)减到为 0的时候,如果 再有线程调用去 await() 方法,该线程会直接通过,不会再起到等待其他线程执行结果起到同步的作用。为了解决这个问题CyclicBarrier就应运而生了。
CyclicBarrier是什么?把它拆开来翻译就是循环(Cycle)和屏障(Barrier)
它的主要作用其实和CountDownLanch差不多,都是让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障会被打开,所有被屏障阻塞的线程才会继续执行,不过它是可以循环执行的,这是它与CountDownLanch最大的不同。CountDownLanch是只有当最后一个线程把计数器置为0的时候,其他阻塞的线程才会继续执行。
我们首先先来看下关于使用CyclicBarrier的一个demo:比如游戏中有个关卡的时候,每次进入下一关的时候都需要进行加载一些地图、特效背景音乐什么的只有全部加载完了才能够进行游戏:
- /**demo 来源https://blog.csdn.net/lstcui/article/details/107389371
- * 公众号【java金融】
- */
- public class CyclicBarrierExample {
- static class PreTaskThread implements Runnable {
- private String task;
- private CyclicBarrier cyclicBarrier;
-
- public PreTaskThread(String task, CyclicBarrier cyclicBarrier) {
- this.task = task;
- this.cyclicBarrier = cyclicBarrier;
- }
-
- @Override
- public void run() {
- for (int i = 0; i < 4; i++) {
- Random random = new Random();
- try {
- Thread.sleep(random.nextInt(1000));
- System.out.println(String.format("关卡 %d 的任务 %s 完成", i, task));
- cyclicBarrier.await();
- } catch (InterruptedException | BrokenBarrierException e) {
- e.printStackTrace();
- }
- }
- }
-
- public static void main(String[] args) {
- CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
- System.out.println("本关卡所有的前置任务完成,开始游戏... ...");
- });
- new Thread(new PreTaskThread("加载地图数据", cyclicBarrier)).start();
- new Thread(new PreTaskThread("加载人物模型", cyclicBarrier)).start();
- new Thread(new PreTaskThread("加载背景音乐", cyclicBarrier)).start();
- }
- }
- }
我们可以看到每次游戏开始都会等当前关卡把游戏的人物模型,地图数据、背景音乐加载完成后才会开始进行游戏。并且还是可以循环控制的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。