本篇章是边学习边记录尚硅谷周阳老师的juc视频写下来的笔记前篇
后篇
public static void main(String[] args) throws InterruptedException
{
Thread t1 = new Thread(() -> {
}, "t1");
t1. start();
}
t1.start()里面的方法是
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
而其中的start0()方法
private native void start0();
native调用了第三方C语言接口。而Java底层是用c++实现的。
所以native start0()调用了 thread.c ,jvm.cpp,thread.cpp方法
3个文件具体路径为
即最后由
os::start_ thread(thread);
这行代码来调用系统分配一个原生的基础线程。
synchronized
简单的说,在系统中运行的一一个应用程序就是-一个进程,每一个进程都有它自己的内存空间和系统资源。
也被称为轻量级进程,在同一个进程内会有1个或多个线程,是大多数操作系统进行时序调度的基本单元。
Monitor(监视器),也就是我们平时所说的锁
Monitor其实是一种同步机制, 他的义务是保证(同- -时间)只有一一个线程可以访问被保护的数据和代码。
JVM中同步是基于进入和退出监视器对象(Monitor,管程对象)来实现的,每个对象实例都会有-.个Monitor对象,
Object o = new Object();
new Thread(()->{
synchronized (o){
}
},"t2").start();
Monitor对象会和Java对象-同创建并销毁,它底层是由C++语言来实现的。
JVM第三版
是系统的工作线程,它会完成这个程序需要完成的业务操作。例如:main线程
守护线程(Daemon Thread)
是一种特殊的线程为其它线程服务的,在后台默默地完成–些系统性的服务,比如垃圾回收线程就是最典型的例子
守护线程作为一个服务线程,没有服务对象就没有必要继续运行了,如果用户线程全部结束了,意味着程序 需要完成的业务操作已经结束了,系统可以退出了。所以假如当系统只剩下守护线程的时候,java虚拟机会自动退出。
未开启
public class DaemonDemo {
public static void main(String[] args) {
Thread thread = new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t 开始执行,"+(Thread.currentThread().isDaemon()?"守护线程":"用户线程"));
while(true){
}
},"t1");
// thread.setDaemon(true);
thread.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "\t ---end主线程");
}
}
执行结果:
开启守护线程(setDaemon要在start方法之前,不然会报错)
public class DaemonDemo {
public static void main(String[] args) {
Thread thread = new Thread(()->{
System.out.println(Thread.urrentThread().getName()+"\t 开始执行,"+(Thread.currentThread().isDaemon()?"守护线程":"用户线程"));
while(true){
}
},"t1");
thread.setDaemon(true);
thread.start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "\t ---end主线程");
}
}
如果用户线程全部结束意味着程序需要完成的业务操作已经结束了,守护线程随着JVM一同结束工作setDaemon(true)方法必须在start()之前设置,否则报llelgalIThreadStateException异常
总结一句话:Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力的复杂业务。
Future是Java5新加的一个接口,它提供了一种异步并行计算的功能。
Runnable接口
如果主线程需要执行一个很耗时的计算任务,我们就可以通过future把这个任务放到异步线程中执行。
主线程继续处理其他任务或者先行结束,再通过Future获取计算结果。
代码说话:
Callable接口
Future接口和
Future Task实现类
目的:异步多线程任务执行且返回有结果,三个特点:多线程/有返回/异步任务
(班长为老师去买水作为新启动的异步多线程任务且买到水有结果返回)
案例:
import java.util.concurrent.Callable;
public class CompletableFutureDemo {
public static void main(String[] args) {
}
}
/**
* 没有返回值
*/
class MyThread implements Runnable {
@Override
public void run() {
}
}
/**
* 有返回值
*/
class MyThread2 implements Callable<String> {
@Override
public String call() throws Exception {
return null;
}
}
案例:
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new MyThread1());
Thread t1 = new Thread(futureTask,"t1");
t1.start();
System.out.println(futureTask.get());
}
}
class MyThread1 implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("---come in call()");
return "hello Callable";
}
}
import java.util.concurrent.*;
public class FutureThreadPollDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//设置3个线程池
ExecutorService threadPool= Executors.newFixedThreadPool(3);
long l = System.currentTimeMillis();
FutureTask<String > futureTask1=new FutureTask<>(()->{
try{TimeUnit.MILLISECONDS.sleep(500);}catch (InterruptedException e){e.printStackTrace();}
return "task1 over";
});
threadPool.submit(futureTask1);
FutureTask<String > futureTask2=new FutureTask<>(()->{
try{TimeUnit.MILLISECONDS.sleep(300);}catch (InterruptedException e){e.printStackTrace();}
return "task2 over";
});
threadPool.submit(futureTask2);
FutureTask<String > futureTask3=new FutureTask<>(()->{
try{TimeUnit.MILLISECONDS.sleep(300);}catch (InterruptedException e){e.printStackTrace();}
return "task3 over";
});
threadPool.submit(futureTask3);
System.out.println(futureTask1.get());
System.out.println(futureTask2.get());
long l1 = System.currentTimeMillis();
System.out.println("--------costTime:"+(l1-l)+"毫秒");
System.out.println(Thread.currentThread().getName()+"\t---end");
threadPool.shutdown();
System.out.println("------------");
m1();
}
private static void m1() {
long l = System.currentTimeMillis();
try{TimeUnit.MILLISECONDS.sleep(500);}catch (InterruptedException e){e.printStackTrace();}
try{TimeUnit.MILLISECONDS.sleep(300);}catch (InterruptedException e){e.printStackTrace();}
try{TimeUnit.MILLISECONDS.sleep(300);}catch (InterruptedException e){e.printStackTrace();}
long l1 = System.currentTimeMillis();
System.out.println("--------costTime:"+(l1-l)+"毫秒");
System.out.println(Thread.currentThread().getName()+"\t---end");
}
}
执行结果:
FutureTask<String> futureTask = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + "\t---------come in");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "task over";
});
Thread t1 = new Thread(futureTask, "t1");
t1.start();
System.out.println(futureTask.get());
System.out.println(Thread.currentThread().getName() + "\t------------忙其他任务了");
运行结果
稍微解决一点:
在get后加参数
System.out.println(futureTask.get(3,TimeUnit.SECONDS));
运行结果:
轮询的方式会耗费无谓的CPU资源,而且也不见得能及时地得到计算结果.
如果想要异步获取结果,通常都会以轮询的方式去获取结果尽量不要阻塞
FutureTask<String> futureTask = new FutureTask<>(() -> {
System.out.println(Thread.currentThread().getName() + "\t---------come in");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "task over";
});
Thread t1 = new Thread(futureTask, "t1");
t1.start();
System.out.println(Thread.currentThread().getName() + "\t------------忙其他任务了");
// System.out.println(futureTask.get(3,TimeUnit.SECONDS));
while (true) {
if (futureTask.isDone()) {
System.out.println(futureTask.get());
break;
} else {
//暂停毫秒
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在处理中,不要再催了,越催越慢,再催熄火");
}
}
结果:
Future对于结果的获取不是很友好,只能通过阻塞或轮询的方式得到任务的结果。
代表异步计算过程中的某一个阶段,一 个阶段完成以后可能会触发另外-个阶段, 有些类似Linux系统的管道分隔符传参数。
无返回值
有返回值
从Java8开始引入了CompletableFuture,它是Future的功能增强版,减少阻塞和轮询
可以传入回调对象,当异步任务完成或者发生异常时自动调用回调对象的回调方法
CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "------com in");
int result = ThreadLocalRandom.current().nextInt(10);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-------1秒钟后出结果:" + result);
return result;
}).whenComplete((v,e)->{
if (e==null) {
System.out.println("----计算完成,更新系统updateVa:"+v);
}
}).exceptionally(e->{
e.printStackTrace();
System.out.println("异常情况:"+e.getCause()+"\t"+e.getMessage());
return null;
});
System.out.println(Thread.currentThread().getName()+"线程先去忙别的事情");
该方法下默认线程池关闭,自定义线程池记得要关闭
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
或者加入Executor
ExecutorService threadPool = Executors.newFixedThreadPool(3);
函数式接口名称 | 方法名称 | 参数 | 返回值 |
---|---|---|---|
Runnable | run | 无参数 | 无返回值 |
Function | apply | 1个参数 | 无返回值 |
consume | accept | 1个参数 | 无返回值 |
sippier | get | 没有参数 | 有返回值 |
BiConsumer | accept | 2个参数 | 无返回值 |
join不用在运行时检查异常。get要
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
return "hello 1234";
});
// System.out.println(completableFuture.get());
System.out.println(completableFuture.join());
运行结果:
大厂需求是先功能–>完美
1.1同一款产品,同时搜索出同款产品在各大电商平台的售价;
1.2同一-款产品,同时搜索出本产品在同一个电商平台下,各个入驻卖家售价是多少.
出来结果希望是同款产品的在不同地方的价格清单列表,返回一个List
《mysq|l》in jd price is 88.05
《mysq|》in dangdang price is 86.11
《mysq|》in taobao price is 90.43
public class CompletableFutureMallDemo {
static List<NetMail> list= Arrays.asList(
new NetMail("jd"),
new NetMail("dangdang"),
new NetMail("taobao"),
new NetMail("pdd"),
new NetMail("tmall")
);
/**
* 一家家搜
*
* */
public static List<String> getPrice( List<NetMail> list, String productName){
return list
.stream()
.map(netMail -> String.format(productName + " in %s price is %.2f", netMail.getNetMallName(),
netMail.calcPrice(productName)))
.collect(Collectors.toList());
}
/**
*并发搜索
* */
public static List<String> getPriceByCompletableFuture(List<NetMail> list,String productName){
return list.stream().map(netMail ->
CompletableFuture.supplyAsync(() -> String.format(productName + "in %s price is %.2f",
netMail.getNetMallName(),
netMail.calcPrice(productName))))
.collect(Collectors.toList())
.stream().map(CompletableFuture::join)
.collect(Collectors.toList());
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
List<String> list1 = getPrice(list, "mysql");
for (String element: list1){
System.out.println(element);
}
long endTime = System.currentTimeMillis();
System.out.println("----costTime:"+(endTime-startTime)+"毫秒");
System.out.println("------------");
long startTime2 = System.currentTimeMillis();
List<String> list2 = getPriceByCompletableFuture(list, "mysql");
for (String element: list2){
System.out.println(element);
}
long endTime2 = System.currentTimeMillis();
System.out.println("----costTime:"+(endTime2-startTime2)+"毫秒");
}
}
class NetMail{
@Getter
private String netMallName;
public NetMail(String netMallName) {
this.netMallName = netMallName;
}
public double calcPrice(String productName){
try{TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e){e.printStackTrace();}
return ThreadLocalRandom.current().nextDouble()*2+productName.charAt(0);
}
}
运行结果
mysql in jd price is 110.71
mysql in dangdang price is 110.30
mysql in taobao price is 110.73
mysql in pdd price is 109.10
mysql in tmall price is 109.91
----costTime:5100毫秒
------------
mysqlin jd price is 110.15
mysqlin dangdang price is 110.19
mysqlin taobao price is 109.42
mysqlin pdd price is 109.37
mysqlin tmall price is 109.92
----costTime:1017毫秒
获取结果
public T get()
不见不散
public T get(long timeout, TimeUnit unit)
过时不候
public T join()
与get方法类似,但是不会在运行前检查异常
public T getNow(T valuelfAbsent)
没有计算完成的情况下,给我一个替代结果
立即获取结果不阻塞
计算完,返回计算完成后的结果
没算完,返回设定的valuelfAbsent值
// throws ExecutionException, InterruptedException, TimeoutException
{
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "abc";
});
// System.out.println(completableFuture.get());
// System.out.println(completableFuture.get(2,TimeUnit.SECONDS));
// System.out.println(completableFuture.join());
try{TimeUnit.SECONDS.sleep(3);}catch(InterruptedException e){e.printStackTrace();}
System.out.println(completableFuture.getNow("xxx"));
}
主动触发计算
System.out.println(completableFuture.complete("completeValue")+"\t"+completableFuture.join());
计算结果存在依赖关系,两个线程串行化
异常相关
计算结果存在依赖关系,两个线程串行化
异常相关
CompletableFuture.supplyAsync(()-> 1).thenApply(f-> f+2).thenApply(f-> f+3).thenAccept(System.out::println);
thenRun
thenRun(Runnable runnable)
任务A执行完执行B,并且B不需要A的结果
thenAccept
thenApply
ExecutorService service = Executors.newFixedThreadPool(5);
try {
CompletableFuture<Void> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1号任务" + "\t" + Thread.currentThread().getName());
return "abcd";
},service).thenRunAsync(() -> {
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2号任务" + "\t" + Thread.currentThread().getName());
}).thenRunAsync(() -> {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3号任务" + "\t" + Thread.currentThread().getName());
}).thenRunAsync(() -> {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4号任务" + "\t" + Thread.currentThread().getName());
});
System.out.println(completableFuture.get(2L, TimeUnit.SECONDS));
} catch (ExecutionException | InterruptedException | TimeoutException e) {
e.printStackTrace();
} finally {
service.shutdown();
}
如果你执行第一一个任务的时候,传入了一个自定义线程地:
有可能处理太快,系统优化切换原则,直接使用main线程处理
其它如: thenAccept 和thenAcceptAsync,thenApply 和thenApplyAsync等,它们之间的区别也是同理
CompletableFuture<String> playA = CompletableFuture.supplyAsync(() -> {
System.out.println("A come in");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "playA";
});
CompletableFuture<String> playB = CompletableFuture.supplyAsync(() -> {
System.out.println("B come in");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "playB";
});
CompletableFuture<String> result = playA.applyToEither(playB, f -> {
return f + " is winner";
});
System.out.println(Thread.currentThread().getName()+"\t"+"----"+result.join());
code标准版
CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t ----启动");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 10;
});
CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t ----启动");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 20;
});
CompletableFuture<Integer> completableFuture = completableFuture1.thenCombine(completableFuture2, (x, y) -> {
System.out.println("---两个值合并");
return x + y;
});
System.out.println(completableFuture.join());
code表达式(合并版本)
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t ----启动");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 10;
}).thenCombine(CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "\t ----启动");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 20;
}),(x,y)->{
System.out.println("---两个值合并");
return x + y;
});
// CompletableFuture completableFuture2 = CompletableFuture.supplyAsync(() -> {
// System.out.println(Thread.currentThread().getName() + "\t ----启动");
// try {
// TimeUnit.SECONDS.sleep(1);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// return 20;
// });
// CompletableFuture completableFuture = completableFuture1.thenCombine(completableFuture2, (x, y) -> {
// System.out.println("---两个值合并");
// return x + y;
// });
System.out.println(completableFuture.join());
}
悲观锁:
乐观锁
class Phone{
public static synchronized void sendEmail(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----sendEmail");
}
public synchronized void sendSMS(){
System.out.println("----sendSMS");
}
public void Hello(){
System.out.println("---hello");
}
}
/**
* 题目:谈谈你对多线程锁的理解, 8锁案例说明
* 口诀:线程 操作 资源美
* 8锁案例说明:
* 1.标准访问有ab两个线程,请问先打印邮件还是短信 邮件
* 2.sendEmail 方法钟加入暂停3秒钟,请问先打印邮件还是短信 邮件
* 3.添加一个普通的Hello方法,请问先打印邮件还是Hello Hello
* 4.有两部手机,请问先打印邮件还是短信 短信
* 5.有两个静态同步方法,有1部手机,请问先打印邮件还是短信 邮件
* 6.有两个静态同步方法,有2部手机,请问先打印邮件还是短信 邮件
* 7.有1个静态同步方法,有1个普通同步方法,有1部手机,请问先打印邮件还是短信 短信
* 8.有1个静态同步方法,有1个普通同步方法,有2部手机,请问先打印邮件还是短信 短信
*
* 总结:
* 1-2
* 一个对象里面如果有多个synchronized方法, 某一 个时刻内,只要一 .个线程去调用其中的一个synchronized方法了,
* 其它的线程都只能等待,换句话说,某一 -个时刻内,只能有唯的一 个线程去访问这些synchronized方法
* 锁的是当前对象this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized方法
* 3-4
* 加个普通方法后发现和同步锁无关
* 换成两个的象后,不是同把锁了,情况立刻变化,
*
* 5-6都换成静态同步方法后,情况又变化
* 三种synchronized锁的内容有一些差别:
* 对于普通同步方法,锁的是当前实例对象,通常指this, 具体的一部部手机,所有的普通同步方法用的都是同一把锁-> 实例对象本身
* 对于静态同步方法,锁的是当前类的Class对象,如Phone.class唯一的一个模板
* 对于同步方法块,锁的是synchronized括号内的对象
*
* 7-8
* 当个线程试图访时同步代码时它 首先必 须得到锁,正常退出或抛出异常时必须释放锁。
* 所有的普通同步 方法用的都是同一把锁一实例对象 本身,就是new来的具体实例对象本身,本this
* 也就是说如果一 个实例对象的 普通同步方法获取锁后,该实例对象的其他普通同步方法必须等待获取锁的方法释放锁后才能获取锁。
* 所有的静态同步方法用的也是同一把锁一类对象本身, 就是我们说过的唯一模板class
* 具体实例对象this和唯一模板class, 这两把锁是两个不同的对象,所以静态同步方法与普通同步方法之间是不会有竞态条件的
* 但是一旦一-个静态同步方法获取锁后, 其他的静态同步方法都必须等待该方法释放锁后才能获取锁。
* */
public class Lock8Demo {
public static void main(String[] args) { //一切程序的入口
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(phone::,"a").start();
try {
TimeUnit.MILLISECONDS.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(phone2::,"b").start();
}
}
作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁;
public synchronized void sendEmail(){
作用于代码块,对括号里配置的对象加锁。
synchronized (this){}
作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁;
public static synchronized void sendSMS(){}
javap -c .\LockSyncDemo.class
Compiled from "LockSyncDemo.java"
public class locks.LockSyncDemo {
java.lang.Object object;
public locks.LockSyncDemo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: aload_0
5: new #2 // class java/lang/Object
8: dup
9: invokespecial #1 // Method java/lang/Object."":()V
12: putfield #3 // Field object:Ljava/lang/Object;
15: return
public void m1();
Code:
0: aload_0
1: getfield #3 // Field object:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #5 // String ----hello synchronized code block
12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: aload_1
16: monitorexit
17: goto 25
20: astore_2
21: aload_1
22: monitorexit
23: aload_2
24: athrow
25: return
Exception table:
from to target type
7 17 20 any
20 23 20 any
public static void main(java.lang.String[]);
Code:
0: return
}
一般情况就是1个enter对应2个exit
极端
m1方法添加一个异常
javap -c .\LockSyncDemo.class
Compiled from "LockSyncDemo.java"
public class locks.LockSyncDemo {
public locks.LockSyncDemo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public synchronized void m2();
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String ----hello synchronized code block
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public static void main(java.lang.String[]);
Code:
0: return
}
调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置。如果设置了,执行线程会将先持有monitor锁,然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放monitor
javap -c .\LockSyncDemo.class
Compiled from "LockSyncDemo.java"
public class locks.LockSyncDemo {
public locks.LockSyncDemo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public synchronized void m2();
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String ----hello synchronized code block m2
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public synchronized void m3();
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5 // String ----hello synchronized code block m3
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
public static void main(java.lang.String[]);
Code:
0: return
}
class Ticket //资源类,馆拟3个售票员卖50张票
{
private int number = 50;
ReentrantLock lock = new ReentrantLock(true);
public void sale() {
lock.lock();
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出第: \t" + (number--) + "\t还剩下:" + number);
}
} finally {
lock.unlock();
}
}
}
public class SaleTicketDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{for (int i = 0; i < 55; i++) ticket.sale();},"a").start();
new Thread(()->{for (int i = 0; i < 55; i++) ticket.sale();},"b").start();
new Thread(()->{for (int i = 0; i < 55; i++) ticket.sale();},"c").start();
}
}
锁的类型 | 描述 |
---|---|
公平锁 | 是指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买后来的人在队尾排着,这是公平的Lock lock = new Reentrantl ock(true);//true表示公平锁,先来先得 |
非公平锁 | 是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转或者饥饿的状态(某个线程一直 得不到锁)Lock lock = new ReentrantL ock(alse);//false表示非公平锁,后来的也可能先获得锁 |
可重入锁又名递归锁
是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞。
如果是1个有synchronized修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚。
所以Java中ReentrantL ock和synchronized都是可重入锁,可重入锁的一一个优点是可一定程度避免死锁。
进入什么
一句话
隐式锁( 即synchronized关键字使用的锁)默认是可重入锁
同步块
同步方法
public class ReEntryLockDemo {
public synchronized void m1() {
//指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。
System.out.println(Thread.currentThread().getName() + "\t-----come in");
m2();
System.out.println(Thread.currentThread().getName() + "\t-----end m1");
}
public synchronized void m2() {
System.out.println(Thread.currentThread().getName() + "\t-----come in");
m3();
// System.out.println(Thread.currentThread().getName() + "\t-----end m2");
}
public synchronized void m3() {
System.out.println(Thread.currentThread().getName() + "\t-----come in");
}
public static void main(String[] args) {
ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
new Thread(reEntryLockDemo::m1,"t1").start();
}
private static void reEntryM1() {
final Object object = new Object();
new Thread(() -> {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "\t-----外层调用");
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "\t-----中层调用");
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "\t-----内层调用");
}
}
}
}, "t1").start();
}
}
Synchronized的重入的实现机理
每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。
当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虛拟机会将该锁对象的持有线程设置为当前线程,I并且将其计数器加1
在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么Java虛拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。
当执行monitorexit时,Java虛拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。
为什么任何一个对象都可以成为一个锁.
objectMonitor.hpp
显式锁(即Lock)也有ReentrantL ock这样的可重入锁。
new Thread(()->{
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t-----come in外层调用");
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t-----come in内层调用");
}finally {
// lock.unlock();
}
}finally {
lock.unlock();
}
},"t1").start();
new Thread(()->{
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t-----come in外层调用");
lock.lock();
}finally {
lock.unlock();
}
},"t2").start();
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
final Object objectA = new Object();
final Object objectB = new Object();
new Thread(() -> {
synchronized (objectA) {
System.out.println(Thread.currentThread().getName() + "\t 自己持有A锁,希望获得B锁");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objectB) {
System.out.println(Thread.currentThread().getName() + "\t 成功获得B锁");
}
}
},"A").start();
new Thread(() -> {
synchronized (objectB) {
System.out.println(Thread.currentThread().getName() + "\t 自己持有B锁,希望获得A锁");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objectA) {
System.out.println(Thread.currentThread().getName() + "\t 成功获得A锁");
}
}
},"B").start();
运行结果:
首先
一个线程不应该由具他线程强制中断或停止,而是应该由线程自己自行停止,自己来决定自己的命运。所以,Thread.stop, Thread. suspend, Thread.resume都已经被废弃了。
其次
在Java中没有办法立即停止一条线程,然而停止线程却显得尤为重要,如取消一个耗时操作。因此,Java提供了一种用于停止线程的协商机制一一中断,也即中断标识协商机制。中断只是一种协作协商机制,Java没有给中断增加任何语法,中断的过程完全需要程序员自己实现。若要中断一个线程,你需要手动调用该线程的interrupt方法,该方法也仅仅是将线程对象的中断标识设成true;
接着你需要自己写代码不断地检测当前线程的标识位,如果为true,表示别的线程请求这条线程中断,此时究竞该做什么需要你自己写代码实现。
每个线程对象中都有一个中断标识位,用于表示线程是否被中断;该标识位为true表示中断,为false表 示未中断;
通过调用线程对象的interrupt方法将该线程的标识位设为true;可以在别的线程中调用,也可以在自己的线程中调用。.
static volatile boolean isStop = false;
public static void main(String[] args) {
new Thread(()->{
while(true){
if(isStop){
System.out.println(Thread.currentThread().getName()+"\t isStop被修改为true,程序停止");
break;
}
System.out.println("----hello volatile");
}
},"t1").start();
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
isStop=true;
},"t2").start();
}
new Thread(()->{
while(true){
if(atomicBoolean.get()){
System.out.println(Thread.currentThread().getName()+"\t isStop被修改为true,程序停止");
break;
}
System.out.println("----hello volatile");
}
},"t1").start();
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
atomicBoolean.set(true);
},"t2").start();
在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑stop线程
Thread t1 = new Thread(() -> {
while(true){
if(Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName()+"\t isStop被修改为true,程序停止");
break;
}
System.out.println("----hello interrupt api");
}
}, "t1");
t1.start();
try {
TimeUnit.MILLISECONDS.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.isInterrupted();
源码分析
源码分析
具体来说,当对一个线程,调用interrupt()时:
①如果线程处于正常活动状态,那么会将该线程的中断标志设置为true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。
所以,interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
②如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。
public class InterruptDemo3 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + "\t" +
"中断标志位" + Thread.currentThread().isInterrupted() + "程序停止");
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();//为什么要在异常处,再调用一遍interrupt方法
e.printStackTrace();
}
System.out.println("-----hello InterruptDemo3");
}
}, "t1");
t1.start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(t1::interrupt,"t2").start();
}
}
/**
* 1 中断标志位,默认false
* 2 t2 ----> 发出了终端协商,t2调用t1.interrupt(),中断标志位
* 3中断标志位true,正常情况,程序停止,^_ ^
* 4中断标志位true,异常情况,InterruptedException, 将会把中断状态将被清除,并且将收到InterruptedException。中断标志位false
* 5在catch块中,需要再次给中断标志位设置为true, 2次调用停止程序才ok
* */
中断只是一种协商机制,修改中断标识位仅此而已,不是立刻stop打断
静态方法interrupted将会 清除中断状态(传入的参数ClearInterrupted为true),
实例方法isInterrupted则不会(传入的参数Clearlnterrupted为false)
线程中断相关的方法:
public void interrupt() , interrupt()方法是-一个实例方法
它通知目标线程中断,也仅是设置目标线程的中断标志位为true。
public boolean isInterrupted(), isInterrupted()方法也是一个实例方法
它判断当前线程是否被中断(通过检查中断标志位)并获取中断标志,
public static boolean interrupted(), Thread类的静态方法interrupted()
返回当前线程的中断状态真实值(boolean类型)后会将当前线程的中断状态设为false,此方法调用之后会清除当前线程的中断标志位的
状态(将中断标志置为false了),返回当前值并清零置false
用于创建锁和其他同步类的基本线程阻塞原语,不是构造方法
/**
*
* 要求: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();
}
}
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();
}
},"t2").start();
}
}
/**
* 要求:t1线程等待3秒钟,3秒钟后t2线程唤醒t1线程继续工作
* 以下异常情况:
* 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)。
*/
public class LockSupportDemo
{
public static void main(String[] args)//main方法,主线程一切程序入口
{
Object objectLock = new Object(); //同一把锁,类似资源类
new Thread(() -> {
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(() -> {
objectLock.notify();
},"t2").start();
}
}
wait方法和notify方法,两个都去掉同步代码块
/**
*
* 要求:t1线程等待3秒钟,3秒钟后t2线程唤醒t1线程继续工作
*
* 3 将notify放在wait方法前先执行,t1先notify了,3秒钟后t2线程再执行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) {
objectLock.notify();
}
System.out.println(Thread.currentThread().getName()+"\t"+"通知了");
},"t1").start();
//t1先notify了,3秒钟后t2线程再执行wait方法
try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
synchronized (objectLock) {
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"\t"+"被唤醒了");
},"t2").start();
}
}
将notify放在wait方法前面
程序无法执行,无法唤醒
wait和notify方法必须要在同步块或者方法里面,且成对出现使用
先wait后notify才OK
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();
}
}
/**
* 异常:
* 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
condition.await();和 condition.signal();都触发了 IllegalMonitorStateException异常。
结论: lock、unlock对里面才能正确调用调用condition中线程等待和唤醒的方法
/**
* 异常:
* 程序无法运行
*
* 原因:先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,不要反了
通过park()和unpark(thread)方法来实现阻塞和唤醒线程的操作
LockSupport是用来创建锁和其他同步类的基本线程阻塞原语。
LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能, 每个线程都有一个许可(permit), permit只有两个值1和零,默认是零。 可以把许可看成是一种(0,1)信号量(Semaphore),但与 Semaphore 不同的是,许可的累加上限是1。
park() /park(Object blocker)
阻塞当前线程/阻塞传入的具体线程
unpark(Thread thread)
唤醒处于阻塞状态的指定线程
正常+无锁块要求
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照样支持
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");
}
}
因为unpark获得了一个凭证,之后再调用park方法,就可以名正言顺的凭证消费,故不会阻塞。先发放了凭证后续可以畅通无阻。
因为凭证的数量最多为1,连续调用两次unpark和调用-次unpark效果-样,只会增加一个凭证:而调用两次park却需要消费两个凭证,证不够,不能放行。
计算机存储结构,从本地磁盘到主存到CPU缓存,也就是从硬盘到内存,到CPU。一般对应的程序的操作就是从数据库查数据到内存然后到CPU进行计算
因为有这么多级的缓存(cpu和物理主内存的速度不一致的),CPU的运行并不是直接操作内存而是先把内存里边的数据读到缓存,而内存的读和写操作的时候就会造成不一致的问题
Java虚拟机规范中试图定义一种Java内存模型(java Memory Model,简称JMM) 来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。推导出我们需要知道JMM
JMM(Java内存模型Java Memory Model,简称JMM)本身是一种抽象的概念并不真实存在它仅仅描述的是一组约定或规范,通过这组规范定义了程序中(尤其是多线程)各个变量的读写访问方式并决定一个线程对共享变量的写入何时以及如何变成对另一个线程可见,关键技术点都是围绕多线程的原子性、可见性和有序性展开的。
原则:
JMM的关键技术点都是围绕多线程的原子性、可见性和有序性展开的
能干嘛?
1 通过JMM来实现线程和主内存之间的抽象关系。
2 屏蔽各个硬件平台和操作系统的内存访问差异以实现让Java程序在各种平台下都能达到一致的内存访问效果。
Java中普通的共享变量不保证可见性,因为数据修改被写入内存的时机是不确定的,多线程并发下很可能出现"脏读",所以每个线程都有自己的工作内存,线程自己的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作(读取,赋值等 )都必需在线程自己的工作内存中进行,而不能够直接读写主内存中的变量。不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成
线程脏读:如果没有可见性保证
指一个操作是不可中断的,即多线程环境下,操作不能被其他线程干扰
对于一个线程的执行代码而言,我们总是习惯性认为代码的执行总是从上到下,有序执行。 但为了提供性能,编译器和处理器通常会对指令序列进行重新排序。 指令重排可以保证串行语义一致,但没有义务保证多线程间的语义也一致,即可能产生"脏读",简单说, 两行以上不相干的代码在执行的时候有可能先执行的不是第一条,不见得是从上到下顺序执行,执行顺序会被优化。
单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。 处理器在进行重排序时必须要考虑指令之间的数据依赖性 多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的,结果无法预测
在JMM中,如果一个操作执行的结果需要对另一个操作可见性或者 代码重排序,那么这两个操作之间必须存在happens-before(先行发生)原则。逻辑上的先后关系。
如果Java内存模型中所有的有序性都仅靠volatile和synchronized来完成,那么有很多操作都将会变得非常啰嗦, 但是我们在编写Java并发代码的时候并没有察觉到这一点。
我们没有时时、处处、次次,添加volatile和synchronized来完成程序,这是因为Java语言中JMM原则下有一个“先行发生”(Happens-Before)的原则限制和规矩
这个原则非常重要: 它是判断数据是否存在竞争,线程是否安全的非常有用的手段。依赖这个原则,我们可以通过几条简单规则一揽子解决并发环境下两个操 作之间是否可能存在冲突的所有问题,而不需要陷入Java内存模型苦涩难懂的底层编译原理之中。
一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作;
前一个操作的结果可以被后续的操作获取。讲白点就是前面一个操作把变量X赋值为1,那后面一个操作肯定能知道X已经变成了1
一个unLock操作先行发生于后面((这里的“后面”是指时间上的先后))对同一个锁的lock操作;
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()方法先行发生于此线程的每一个动作
对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
可以通过Thread.interrupted()检测到是否发生中断
线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread::join()方法是否结束、 Thread::isAlive()的返回值等手段检测线程是否已经终止执行。
一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始
对象没有完成初始化之前,是不能调用finalized()方法的
在Java语言里面,Happens-Before 的语义本质上是- -种可见性
AHappens-BeforeB意味着A发生过的事情对B来说是可见的,无论A事件和B事件是否发生在同一个线程里.
JMM的设计分为两部分:
一部分是面向我们程序员提供的,也就是happens-before规则,它通俗易懂的向我们程序员阐述了一一个强内存模型,我们只要理解happens-before规则,就可以编写并发安全的程序了。
另一部分是针对JVM实现的,为了尽可能少的对编译器和处理器做约束从而提高性能,JMM在不影响程序执行结果的前提下对其不做要求,即允许优化重排序。我们只需要关注前者就好了,也就是理解happens-before规则即可,其它繁杂的内容有JMM规范结合操作系统给我们搞定,我们只写好代码即可。
内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。内存屏障其实就是一种JVM指令,Java内存模型的重排规则会要求Java编译器在生成JVM指令时插入特定的内存屏障指令,通过这些内存屏障指令,volatile实现了Java内存模型中的可见性和有序性,但volatile无法保证原子性。
内存屏障之前的所有写操作都要回写到主内存,内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)。
因此重排序时,不允许把内存屏障之后的指令重排序到内存屏障之前。 一句话:对一个 volatile 域的写, happens-before 于任意后续对这个 volatile 域的读,也叫写后读。
在读指令之前插入读屏障,让工作内存或CPU高速缓存当中的缓存数据失效,重新回到主内存中获取最新数据
在写指令之后插入写屏障,强制把写缓冲区的数据刷回到主内存中
Unsafe.class
Unsafe.cpp
1重排序有可能影响程序的执行和实现,因此,我们有时候希望告诉JVM你别“自作聪明”给我重排序,我这里不需要排序,听主人的。
2对于编译器的重排序,JMM会根据重排序的规则,禁止特定型的编译器重排序。
3对于处理器的重排序,Java编译器在生成指令序列的适当位置,插入内存屏障指令,来禁止特定类型的处理器排序。
保证不同线程对这个变量进行操作时的可见性,即变量一旦改变所有线程立即可见
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线程修改完成");
}
}
线程t1中为何看不到被主线程main修改为false的flag的值?
问题可能:
1. 主线程修改了flag之后没有将其刷新到主内存,所以t1线程看不到。
2. 主线程将flag刷新到了主内存,但是t1一直读取的是自己工作内存中flag的值,没有去主内存中更新获取flag最新的值。
我们的诉求:
1.线程中修改了工作内存中的副本之后,立即将其刷新到主内存;
2.工作内存中每次读取共享变量时,都去主内存中重新读取,然后拷贝到工作内存。
解决:
使用volatile修饰共享变量,就可以达到上面的效果,被volatile修改的变量有以下特点:
1. 线程中读取的时候,每次读取都会去主内存中读取共享变量最新的值,然后将其复制到工作内存
2. 线程中修改了工作内存中变量的副本,修改之后会立即刷新到主内存
Java内存模型中定义的8种工作内存与主内存之间的原子操作 read(读取)→load(加载)→use(使用)→assign(赋值)→store(存储)→write(写入)→lock(锁定)→unlock(解锁)
read: 作用于主内存,将变量的值从主内存传输到工作内存,主内存到工作内存
load: 作用于工作内存,将read从主内存传输的变量值放入工作内存变量副本中,即数据加载
use: 作用于工作内存,将工作内存变量副本的值传递给执行引擎,每当JVM遇到需要该变量的字节码指令时会执行该操作
assign: 作用于工作内存,将从执行引擎接收到的值赋值给工作内存变量,每当JVM遇到一个给变量赋值字节码指令时会执行该操作
store: 作用于工作内存,将赋值完毕的工作变量的值写回给主内存
write: 作用于主内存,将store传输过来的变量值赋值给主内存中的变量 由于上述只能保证单条指令的原子性,针对多条指令的组合性原子保证,没有大面积加锁,所以,JVM提供了另外两个原子指令:
lock: 作用于主内存,将一个变量标记为一个线程独占的状态,只是写时候加锁,就只是锁了写变量的过程。
unlock: 作用于主内存,把一个处于锁定状态的变量释放,然后才能被其他线程占用
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修饰,以便保证线程安全.
多线程环境下,"数据计算"和"数据赋值"操作可能多次出现,即操作非原子。若数据在加载之后,若主内存count变量发生修改之后,由于线程工作内存中的值在此前已经加载,从而不会对变更操作做出相应变化,即私有内存和公共内存中变量不同步,进而导致数据不一致
对于volatile变量,JVM只是保证从主内存加载到线程工作内存的值是最新的,也就是数据加载时是最新的。
由此可见volatile解决的是变量读时的可见性问题,但无法保证原子性,对于多线程修改共享变量的场景必须使用加锁同步
当线程1对主内存对象发起read操作到write操作第一套流程的时间里,线程2随时都有可能对这个主内存对象发起第二套操作
volatile主要是对其中部分指令做了处理
要use(使用)一个变量的时候必需load(载入),要载入的时候必需从主内存read(读取)这样就解决了读的可见性。
写操作是把assign和store做了关联(在assign(赋值)后必需store(存储))。store(存储)后write(写入)。
也就是做到了给一个变量赋值的时候一串关联指令直接把变量值写到主内存。
就这样通过用的时候直接从主内存取,在赋值到直接写回主内存做到了内存可见性。注意蓝色框框的间隙。。。。。。o(╥﹏╥)o
read-load-use 和 assign-store-write 成为了两个不可分割的原子操作,但是在use和assign之间依然有极小的一段真空期,有可能变量会被其他线程读取,导致写丢失一次...o(╥﹏╥)o
但是无论在哪一个时间点主内存的变量和任一工作内存的变量的值都是相等的。这个特性就导致了volatile变量不适合参与到依赖当前值的运算,如i = i + 1; i++;之类的那么依靠可见性的特点volatile可以用在哪些地方呢? 通常volatile用做保存某个状态的boolean值or int值。
《深入理解Java虚拟机》提到:
JVM的字节码,i++分成三步,间隙期不同步非原子操作(i++)
重排序
重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,有时候会改变程序语句的先后顺序
不存在数据依赖关系,可以重排序;
存在数据依赖关系,禁止重排序
但重排后的指令绝对不能改变原有的串行语义!这点在并发设计中必须要重点考虑!
重排序的分类和执行流程
重排序
重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段,有时候会改变程序语句的先后顺序
不存在数据依赖关系,可以重排序;
存在数据依赖关系,禁止重排序
但重排后的指令绝对不能改变原有的串行语义!这点在并发设计中必须要重点考虑!
重排序的分类和执行流程
重排前 | 重排后 |
---|---|
int a = 1; //1 int b = 20; //2 int c = a + b; //3 |
int b = 20; //1 int a = 1; //2 int c = a + b; //3 |
结论:编译器调整了语句的顺序,但是不影响程序的最终结果。 | 重排序OK |
存在数据依赖关系,禁止重排序===> 重排序发生,会导致程序运行结果不同。
编译器和处理器在重排序时,会遵守数据依赖性,不会改变存在依赖关系的两个操作的执行,但不同处理器和不同线程之间的数据性不会被编译器和处理器考虑,其只会作用于单处理器和单线程环境,下面三种情况,只要重排序两个操作的执行顺序,程序的执行结果就会被改变。
当第一个操作为volatile读时,不论第二个操作是什么,都不能重排序。这个操作保证了volatile读之后的操作不会被重排到volatile读之前。
当第二个操作为volatile写时,不论第一个操作是什么,都不能重排序。这个操作保证了volatile写之前的操作不会被重排到volatile写之后。
当第一个操作为volatile写时,第二个操作为volatile读时,不能重排。
四大屏障的插入情况
//模拟一个单线程,什么顺序读?什么顺序写?
public class VolatileTest {
int i = 0;
volatile boolean flag = false;
public void write(){
i = 2;
flag = true;
}
public void read(){
if(flag){
System.out.println("---i = " + i);
}
}
}
volatile int a = 10
volatile boolean flag = false
/**
*
* 使用:作为一个布尔状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或任务结束
* 理由:状态标志并不依赖于程序内任何其他状态,且通常只有一种状态转换
* 例子:判断业务是否结束
*/
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保证复合操作的原子性
}
}
}
问题代码
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;
}
}
单线程看问题代码
单线程环境下(或者说正常情况下),在"问题代码处",会执行如下操作,保证能获取到已完成初始化的实例
由于存在指令重排序…
多线程看问题代码
隐患:多线程环境下,在"问题代码处",会执行如下操作,由于重排序导致2,3乱序,后果就是其他线程得到的是null而不是完成初始化的对象
解决01
加volatile修饰
面试题,反周志明老师的案例,你还有不加volatile的方法吗
解决02 – 采用静态内部类的方式实现
//现在比较好的做法就是采用静态内部内的方式实现
public class SingletonDemo
{
private SingletonDemo() { }
private static class SingletonDemoHandler
{
private static SingletonDemo instance = new SingletonDemo();
}
public static SingletonDemo getInstance()
{
return SingletonDemoHandler.instance;
}
}
内存屏障:是一种屏障指令,它使得CPU或编译器对屏障指令的前和后所发出的内存操作执行一个排序的约束 。也叫内存栅栏 或栅栏指令
阻止屏障两边的指令重排序
写数据时加入屏障,强制将线程私有工作内存的数据刷回主物理内存
读数据时加入屏障,线程私有工作内存的数据失效,重新到主物理内存中获取最新数据
在每一个volatile写操作前面插入一个StoreStore屏障
在每一个volatile写操作后面插入一个StoreLoad屏障
在每一个volatile读操作后面插入一个LoadLoad屏障
在每一个volatile读操作后面插入一个LoadStore屏障
java.util.concurrent. atomic
compareandswap的缩写,中文翻译成比较并交换,实现并发算法时常用到的一种技术。
它包含三个操作数--内存位置、预期原值及更新值。
执行CAS操作的时候,将内存位置的值与预期原值比较:
如果相匹配,那么处理器会自动将该位置值更新为新值,
如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功。
CAS有3个操作数,位置内存值V,旧的预期值A,要修改的更新值B。
当且仅当旧的预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做或重来
当它重来重试的这种行为成为—自旋! !
AtomicInteger atomicInteger = new AtomicInteger(5);
System.out.println(atomicInteger.compareAndSet(5, 2022)+"\t"+atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(5, 2022)+"\t"+atomicInteger.get());
CAS是JDK提供的非阻塞原子性操作,它通过硬件保证了比较-更新的原子性。
它是非阻塞的且自身具有原子性,也就是说这玩意效率更高且通过硬件保证,说明这玩意更可靠。
CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的
CAS方法( 如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。
执行cmpxchg指令的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会
对总线加锁成功,加锁成功之后会执行cas操作,也就是说CAS的原子性实际上是CPU实现独占的,
比起用synchronized重量级锁,这 里的排他时间要短很多,所以在 多线程情况下性能会比较好。
Unsafe 是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。 注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务
变量value用volatile修饰,保证了多线程之间的内存可见性。
CAS的全称为Compare-And-Swap,它是一条CPU并发原语。 它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。 AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
假设线程A和线程B两个线程同时执行getAndAddInt操作(分别跑在不同CPU上):
native修饰的方法代表是底层方法
Unsafe类中的compareAndSwapInt,是一个本地方法,该方法的实现位于unsafe.cpp中
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
// 先想办法拿到变量value在内存中的地址,根据偏移量valueOffset,计算 value 的地址
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
// 调用 Atomic 中的函数 cmpxchg来进行比较交换,其中参数x是即将更新的值,参数e是原内存的值
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
(Atomic::cmpxchg(x, addr, e)) == e;
cmpxchg
// 调用 Atomic 中的函数 cmpxchg来进行比较交换,其中参数x是即将更新的值,参数e是原内存的值
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
unsigned Atomic::cmpxchg(unsigned int exchange_value,volatile unsigned int* dest, unsigned int compare_value) {
assert(sizeof(unsigned int) == sizeof(jint), "more work to do");
/*
* 根据操作系统类型调用不同平台下的重载函数,这个在预编译期间编译器会决定调用哪个平台下的重载函数*/
return (unsigned int)Atomic::cmpxchg((jint)exchange_value, (volatile jint*)dest, (jint)compare_value);
}
在不同的操作系统下会调用不同的cmpxchg重载函数,本次用的是win10系统
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
//判断是否是多核CPU
int mp = os::is_MP();
__asm {
//三个move指令表示的是将后面的值移动到前面的寄存器上
mov edx, dest
mov ecx, exchange_value
mov eax, compare_value
//CPU原语级别,CPU触发
LOCK_IF_MP(mp)
//比较并交换指令
//cmpxchg: 即“比较并交换”指令
//dword: 全称是 double word 表示两个字,一共四个字节
//ptr: 全称是 pointer,与前面的 dword 连起来使用,表明访问的内存单元是一个双字单元
//将 eax 寄存器中的值(compare_value)与 [edx] 双字内存单元中的值进行对比,
//如果相同,则将 ecx 寄存器中的值(exchange_value)存入 [edx] 内存单元中
cmpxchg dword ptr [edx], ecx
}
}
到这里应该理解了CAS真正实现的机制了,它最终是由操作系统的汇编指令完成的
你只需要记住:CAS是靠硬件实现的从而在硬件层面提升效率,最底层还是交给硬件来保证原子性和可见性
实现方式是基于硬件平台的汇编指令,在intel的CPU中(X86机器上),使用的是汇编指令cmpxchg指令。
核心思想就是:比较要更新变量的值V和预期值E(compare),相等才会将V的值设为新值N(swap)如果不相等自旋再来。
AtomicInteger原子整型,可否有其它原子类型?
@Getter
@ToString
@AllArgsConstructor
class User
{
String userName;
int age;
}
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());
}
}
自旋锁(spinlock)
是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁, 当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
我们可以看到getAndAddInt方法执行时,有个do while
如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
CAS会导致“ABA问题”。
CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B,
然后线程two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功。
尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
public class ABADemo
{
static AtomicInteger atomicInteger = new AtomicInteger(100);
static AtomicStampedReference atomicStampedReference = new AtomicStampedReference(100,1);
public static void main(String[] args)
{
new Thread(() -> {
atomicInteger.compareAndSet(100,101);
atomicInteger.compareAndSet(101,100);
},"t1").start();
new Thread(() -> {
//暂停一会儿线程
try { Thread.sleep( 500 ); } catch (InterruptedException e) { e.printStackTrace(); }; System.out.println(atomicInteger.compareAndSet(100, 2019)+"\t"+atomicInteger.get());
},"t2").start();
//暂停一会儿线程,main彻底等待上面的ABA出现演示完成。
try { Thread.sleep( 2000 ); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("============以下是ABA问题的解决=============================");
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 首次版本号:"+stamp);//1
//暂停一会儿线程,
try { Thread.sleep( 1000 ); } catch (InterruptedException e) { e.printStackTrace(); }
atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 2次版本号:"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 3次版本号:"+atomicStampedReference.getStamp());
},"t3").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 首次版本号:"+stamp);//1
//暂停一会儿线程,获得初始值100和初始版本号1,故意暂停3秒钟让t3线程完成一次ABA操作产生问题
try { Thread.sleep( 3000 ); } catch (InterruptedException e) { e.printStackTrace(); }
boolean result = atomicStampedReference.compareAndSet(100,2019,stamp,stamp+1);
System.out.println(Thread.currentThread().getName()+"\t"+result+"\t"+atomicStampedReference.getReference());
},"t4").start();
}
}
public class AtomicIntegerDemo {
public static final int SIZE = 50;
public static void main(String[] args) throws InterruptedException
{
MyNumber myNumber = new MyNumber();
CountDownLatch countDownLatch = new CountDownLatch(SIZE);
for (int i = 1; i <=SIZE; i++) {
new Thread(() -> {
try {
for (int j = 1; j <=1000; j++) {
myNumber.addPlusPlus();
}
} finally {
countDownLatch.countDown();
}
},String.valueOf(i)).start();
}
//等待上面50个线程全部计算完成后,再去获得最终值
//暂停几秒钟线程
//try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t"+"result: "+myNumber.atomicInteger.get());
}
}
class MyNumber
{
AtomicInteger atomicInteger = new AtomicInteger();
public void addPlusPlus()
{
atomicInteger.getAndIncrement();
}
}
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));
}
}
@Getter
@ToString
@AllArgsConstructor
class User
{
String userName;
int age;
}
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());
}
}
自旋锁SpinLockDemo
/**
* 题目:实现一个自旋锁
* 自旋锁好处:循环比较获取没有类似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();
}
}
ABADemo
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();
}
}
状态戳(true/false)原子引用
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();
}
}
以一种线程安全的方式操作非线程安全对象内的某些字段
更新的对象属性必须使用 public volatile 修饰符。
因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
AtomicReferenceFieldUpdater
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);
}
}
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"+"------其它线程正在初始化");
}
}
}
/**
* 多线程并发调用一个类的初始化方法,如果未被初始化过,将执行初始化工作,要求只能初始化一次
*/
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();
}
}
}
热点商品点赞计算器,点赞数加加统计,不要求实时精确
一个很大的ist,里面都是int类型,如何实现加加,说说思路
官网说明和阿里要求
LongAdder是Striped64的子类
Striped64
striped64中的一些变量或者方法的定义
Cell
LongAdder为什么这么快
底层分散热点。
小总结
LongAdder在 无竞争的情况,跟AtomicLong- -样,对同一 .个base进行操作, 当出现竞争关系时则是采用化整为零分散热点的做法,用空间换时间,用一个数组cells, 将- -个value拆分进这个数组cells。 多个线程需要同时对value进行操作时候,可以对线程id进行hash得到hash值, 再根据hash值映射到这个数组ells的某个下标,再对该下标所对应的值进行自增操作。当所有线程操作完毕,将数组ells的所有值和base都加起来作为最终结果。
longAdder.increment()
longAccumulate入参说明
Striped64中一些变量或者方法的定义
线程hash值:probe
总纲
上述代码首先给当前线程分配一个hash值,然后进入一个for(;;)自旋,这个自旋分为三个分支:
CASE1:Cell[]数组已经初始化
CASE2:Cell[]数组未初始化(首次新建)
CASE3:Cell[]数组正在初始化中
刚刚要初始化Cell[]数组(首次新建)
未初始化过Cell[]数组,尝试占有锁并首次初始化cells数组
如果上面条件都执行成功就会执行数组的初始化及赋值操作, Cell[] rs = new Cell[2]表示数组的长度为2,
rs[h & 1] = new Cell(x) 表示创建一个新的Cell元素,value是x值,默认为1。
h & 1类似于我们之前HashMap常用到的计算散列桶index的算法,通常都是hash & (table.len - 1)。同hashmap一个意思。
兜底
多个线程尝试CAS修改失败的线程会走到这个分支
该分支实现直接操作base基数,将值累加到base上,也即其它线程正在初始化,多个线程正在更新base的值。
Cell数组不再为空且可能存在Cell数组扩容
多个线程同时命中一个cell的竞争
上面代码判断当前线程hash后指向的数据位置元素是否为空,
如果为空则将Cell数据放入数组中,跳出循环。
如果不空则继续循环。
说明当前线程对应的数组中有了数据,也重置过hash值,
这时通过CAS操作尝试对当前数中的value值进行累加x操作,x默认为1,如果CAS成功则直接跳出循环。
sum()会将所有Cell数组中的value和base累加作为返回值。 核心的思想就是将之前AtomicLong一个value的更新压力分散到多个value中去,从而降级更新热点。
为啥在并发情况下sum的值不精确
sum执行时,并没有限制对base和cells的更新(一句要命的话)。所以LongAdder不是强一致性的,它是最终一致性的。
首先,最终返回的sum局部变量,初始被复制为base,而最终返回时,很可能base已经被更新了,而此时局部变量sum不会更新,造成不一致。 其次,这里对cell的读取也无法保证是最后一次写入的值。所以,sum方法在没有并发的情况下,可以获得正确的结果
主要解决S让每个线程绑定目己的值,通过使用get()和set()方法,获取默认值或将其值更改为当前线程所存的副本的值从而避免了线程安全问题,比如我们之前讲解的8锁案例,资源类是使用同一部手机, 多个线程抢夺同一部手机使用,假如人手-份是不是天下太平? ?
5个销售卖房子,集团高层只关心销售总量的准确统计数,按照总销售额统计,方便集团公司给部分发送奖金
上述需求该如何处理???
code1
public class ThreadLocalDemo {
public static void main(String[] args) throws InterruptedException {
House house = new House();
for (int i = 1; i <= 5; i++) {
new Thread(() -> {
int size = new Random().nextInt(5) + 1;
try {
for (int j = 1; j <= size; j++) {
house.saleHouse();
house.saleVolumeByThreadLocal();
}
System.out.println(Thread.currentThread().getName() + "\t" + "号销售卖出:" + house.saleVolume.get());
} finally {
house.saleVolume.remove();
}
}, String.valueOf(i)).start();
}
;
//暂停毫秒
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "共计卖出多少套: " + house.saleCount);
}
}
class House {
int saleCount = 0;
public synchronized void saleHouse() {
++saleCount;
}
ThreadLocal<Integer> saleVolume = ThreadLocal.withInitial(() -> 0);
public void saleVolumeByThreadLocal() {
saleVolume.set(1 + saleVolume.get());
}
}
code2
public class ThreadLocalDemo2 {
public static void main(String[] args) throws InterruptedException{
MyData myData = new MyData();
ExecutorService threadPool = Executors.newFixedThreadPool(3);
try
{
for (int i = 0; i < 10; i++) {
threadPool.submit(() -> {
try {
Integer beforeInt = myData.threadLocalField.get();
myData.add();
Integer afterInt = myData.threadLocalField.get();
System.out.println(Thread.currentThread().getName()+"\t"+"beforeInt:"+beforeInt+"\t afterInt: "+afterInt);
} finally {
myData.threadLocalField.remove();
}
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool.shutdown();
}
}
}
class MyData
{
ThreadLocal<Integer> threadLocalField = ThreadLocal.withInitial(() -> 0);
public void add()
{
threadLocalField.set(1 + threadLocalField.get());
}
}
一句话
如何才能不争抢
加入synchronized或者Lock控制资源的访问顺序
人手一份,大家各自安好,没必要抢夺+
threadLocalMap实际上就是一个以threadLocal实例为key,任意对象为value的Entry对象。 当我们为threadLocal变量赋值,实际上就是以当前threadLocal实例为key,值为value的Entry往这个threadLocalMap中存放
近似的可以理解为: ThreadLocalMap从字面上就可以看出这是一个保存ThreadLocal对象的map(其实是以ThreadLocal为Key),不过是经过了两层包装的ThreadLocal对象:
JVM内部维护了一个线程版的Map
不再会被使用的对象或者变量占用的内存不能被回收,就是内存泄露。
强引用、软引用、弱引用、虚引用分别是什么?
ThreadLocalMap从字面上就可以看出这是一个保存ThreadLocal对象的map(其实是以它为Key),不过是经过了两层包装的ThreadLocal对象: (1)第一层包装是使用 WeakReference
每个Thread对象维护着一个ThreadLocalMap的引用
ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储
调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值Value是传递进来的对象
调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象
ThreadLocal本身并不存储值,它只是自己作为一个key来让线程从ThreadLocalMap获取value,正因为这个原理,所以ThreadLocal能够实现“数据隔离”,获取当前线程的局部变量值,不受其他线程影响~
public void function01()
{
ThreadLocal tl = new ThreadLocal<Integer>(); //line1
tl.set(2021); //line2
tl.get(); //line3
}
//line1新建了一个ThreadLocal对象,t1 是强引用指向这个对象;
//line2调用set()方法后新建一个Entry,通过源码可知Entry对象里的k是弱引用指向这个对象。
当function01方法执行完毕后,栈帧销毁强引用 tl 也就没有了。但此时线程的ThreadLocalMap里某个entry的key引用还指向这个对象,若这个key引用是强引用,就会导致key指向的ThreadLocal对象及v指向的对象不能被gc回收,造成内存泄漏;若这个key引用是弱引用就大概率会减少内存泄漏的问题(还有一个key为null的雷)。使用弱引用,就可以使ThreadLocal对象在方法执行完毕后顺利被回收且Entry的key引用指向为null。
此后我们调用get,set或remove方法时,就会尝试删除key为null的entry,可以释放value对象所占用的内存。
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话(比如正好用在线程池),这些key为null的Entry的value就会一直存在一条强引用链。
虽然弱引用,保证了key指向的ThreadLocal对象能被及时回收,但是v指向的value对象是需要ThreadLocalMap调用get、set时发现key为null时才会去回收整个entry、value,因此弱引用不能100%保证内存不泄露。我们要在不使用某个ThreadLocal对象后,手动调用remoev方法来删除它,尤其是在线程池中,不仅仅是内存泄露的问题,因为线程池中的线程是重复使用的,意味着这个线程的ThreadLocalMap对象也是重复使用的,如果我们不手动调用remove方法,那么后面的线程就有可能获取到上个线程遗留下来的value值,造成bug。
set()
get()
remove()
结论
从前面的set,getEntry,remove方法看出,在threadLocal的生命周期里,针对threadLocal存在的内存泄漏的问题, 都会通过expungeStaleEntry,cleanSomeSlots,replaceStaleEntry这三个方法清理掉key为null的脏entry。
用完记得手动remove
需要解决的问题
ThreadLocal
、InheritableThreadLocal
,这样印象会深刻一些。目前java开发web系统一般有3层,controller、service、dao,请求到达controller,controller调用service,service调用dao,然后进行处理。
我们写一个简单的例子,有3个方法分别模拟controller、service、dao。代码如下:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Demo1 {
static AtomicInteger threadIndex = new AtomicInteger(1);
//创建处理请求的线程池子
static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,
3,
60,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(),
r -> {
Thread thread = new Thread(r);
thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());
return thread;
});
//记录日志
public static void log(String msg) {
StackTraceElement stack[] = (new Throwable()).getStackTrace();
System.out.println("****" + System.currentTimeMillis() + ",[线程:" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);
}
//模拟controller
public static void controller(List<String> dataList) {
log("接受请求");
service(dataList);
}
//模拟service
public static void service(List<String> dataList) {
log("执行业务");
dao(dataList);
}
//模拟dao
public static void dao(List<String> dataList) {
log("执行数据库操作");
//模拟插入数据
for (String s : dataList) {
log("插入数据" + s + "成功");
}
}
public static void main(String[] args) {
//需要插入的数据
List<String> dataList = new ArrayList<>();
for (int i = 0; i < 3; i++) {
dataList.add("数据" + i);
}
//模拟5个请求
int requestCount = 5;
for (int i = 0; i < requestCount; i++) {
disposeRequestExecutor.execute(() -> {
controller(dataList);
});
}
disposeRequestExecutor.shutdown();
}
}
****1565338891286,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.controller(Demo1.java:36):接受请求
****1565338891286,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.controller(Demo1.java:36):接受请求
****1565338891287,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.service(Demo1.java:42):执行业务
****1565338891287,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.service(Demo1.java:42):执行业务
****1565338891287,[线程:disposeRequestThread-3],com.itsoku.chat24.Demo1.controller(Demo1.java:36):接受请求
****1565338891287,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:48):执行数据库操作
****1565338891287,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据0成功
****1565338891287,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据1成功
****1565338891287,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:48):执行数据库操作
****1565338891287,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据2成功
****1565338891287,[线程:disposeRequestThread-3],com.itsoku.chat24.Demo1.service(Demo1.java:42):执行业务
****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.controller(Demo1.java:36):接受请求
****1565338891287,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据0成功
****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.service(Demo1.java:42):执行业务
****1565338891288,[线程:disposeRequestThread-3],com.itsoku.chat24.Demo1.dao(Demo1.java:48):执行数据库操作
****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:48):执行数据库操作
****1565338891288,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据1成功
****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据0成功
****1565338891288,[线程:disposeRequestThread-3],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据0成功
****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据1成功
****1565338891288,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据2成功
****1565338891288,[线程:disposeRequestThread-1],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据2成功
****1565338891288,[线程:disposeRequestThread-3],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据1成功
****1565338891288,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.controller(Demo1.java:36):接受请求
****1565338891288,[线程:disposeRequestThread-3],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据2成功
****1565338891288,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.service(Demo1.java:42):执行业务
****1565338891289,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:48):执行数据库操作
****1565338891289,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据0成功
****1565338891289,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据1成功
****1565338891289,[线程:disposeRequestThread-2],com.itsoku.chat24.Demo1.dao(Demo1.java:51):插入数据数据2成功
代码中调用controller、service、dao 3个方法时,来模拟处理一个请求。main方法中循环了5次模拟发起5次请求,然后交给线程池去处理请求,dao中模拟循环插入传入的dataList数据。
问题来了:开发者想看一下哪些地方耗时比较多,想通过日志来分析耗时情况,想追踪某个请求的完整日志,怎么搞?
上面的请求采用线程池的方式处理的,多个请求可能会被一个线程处理,通过日志很难看出那些日志是同一个请求,我们能不能给请求加一个唯一标志,日志中输出这个唯一标志,当然可以。
如果我们的代码就只有上面示例这么简单,我想还是很容易的,上面就3个方法,给每个方法加个traceId参数,log方法也加个traceId参数,就解决了,代码如下:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Demo2 {
static AtomicInteger threadIndex = new AtomicInteger(1);
//创建处理请求的线程池子
static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,
3,
60,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(),
r -> {
Thread thread = new Thread(r);
thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());
return thread;
});
//记录日志
public static void log(String msg, String traceId) {
StackTraceElement stack[] = (new Throwable()).getStackTrace();
System.out.println("****" + System.currentTimeMillis() + "[traceId:" + traceId + "],[线程:" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);
}
//模拟controller
public static void controller(List<String> dataList, String traceId) {
log("接受请求", traceId);
service(dataList, traceId);
}
//模拟service
public static void service(List<String> dataList, String traceId) {
log("执行业务", traceId);
dao(dataList, traceId);
}
//模拟dao
public static void dao(List<String> dataList, String traceId) {
log("执行数据库操作", traceId);
//模拟插入数据
for (String s : dataList) {
log("插入数据" + s + "成功", traceId);
}
}
public static void main(String[] args) {
//需要插入的数据
List<String> dataList = new ArrayList<>();
for (int i = 0; i < 3; i++) {
dataList.add("数据" + i);
}
//模拟5个请求
int requestCount = 5;
for (int i = 0; i < requestCount; i++) {
String traceId = String.valueOf(i);
disposeRequestExecutor.execute(() -> {
controller(dataList, traceId);
});
}
disposeRequestExecutor.shutdown();
}
}
****1565339559773[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.controller(Demo2.java:36):接受请求
****1565339559773[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.controller(Demo2.java:36):接受请求
****1565339559773[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.controller(Demo2.java:36):接受请求
****1565339559774[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.service(Demo2.java:42):执行业务
****1565339559774[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.service(Demo2.java:42):执行业务
****1565339559774[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:48):执行数据库操作
****1565339559774[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.service(Demo2.java:42):执行业务
****1565339559774[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据0成功
****1565339559774[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:48):执行数据库操作
****1565339559774[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据1成功
****1565339559774[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.dao(Demo2.java:48):执行数据库操作
****1565339559774[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据2成功
****1565339559774[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据0成功
****1565339559775[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.controller(Demo2.java:36):接受请求
****1565339559775[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据0成功
****1565339559775[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.service(Demo2.java:42):执行业务
****1565339559775[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据1成功
****1565339559775[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:48):执行数据库操作
****1565339559775[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据1成功
****1565339559775[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据0成功
****1565339559775[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据2成功
****1565339559775[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据1成功
****1565339559775[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据2成功
****1565339559775[traceId:3],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据2成功
****1565339559775[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.controller(Demo2.java:36):接受请求
****1565339559776[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.service(Demo2.java:42):执行业务
****1565339559776[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:48):执行数据库操作
****1565339559776[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据0成功
****1565339559776[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据1成功
****1565339559776[traceId:4],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo2.dao(Demo2.java:51):插入数据数据2成功
上面我们通过修改代码的方式,把问题解决了,但前提是你们的系统都像上面这么简单,功能很少,需要改的代码很少,可以这么去改。但事与愿违,我们的系统一般功能都是比较多的,如果我们都一个个去改,岂不是要疯掉,改代码还涉及到重新测试,风险也不可控。那有什么好办法么?
ThreadLocal
还是拿上面的问题,我们来分析一下,每个请求都是由一个线程处理的,线程就相当于一个人一样,每个请求相当于一个任务,任务来了,人来处理,处理完毕之后,再处理下一个请求任务。人身上是不是有很多口袋,人刚开始准备处理任务的时候,我们把任务的编号放在处理者的口袋中,然后处理中一路携带者,处理过程中如果需要用到这个编号,直接从口袋中获取就可以了。那么刚好java中线程设计的时候也考虑到了这些问题,Thread对象中就有很多口袋,用来放东西。Thread类中有这么一个变量:
ThreadLocal.ThreadLocalMap threadLocals = null;
如何来操作Thread中的这些口袋呢,java为我们提供了一个类ThreadLocal
,ThreadLocal对象用来操作Thread中的某一个口袋,可以向这个口袋中放东西、获取里面的东西、清除里面的东西,这个口袋一次性只能放一个东西,重复放东西会将里面已经存在的东西覆盖掉。
常用的3个方法:
//向Thread中某个口袋中放东西
public void set(T value);
//获取这个口袋中目前放的东西
public T get();
//清空这个口袋中放的东西
public void remove()
我们使用ThreadLocal来改造一下上面的代码,如下:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Demo3 {
//创建一个操作Thread中存放请求任务追踪id口袋的对象
static ThreadLocal<String> traceIdKD = new ThreadLocal<>();
static AtomicInteger threadIndex = new AtomicInteger(1);
//创建处理请求的线程池子
static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,
3,
60,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(),
r -> {
Thread thread = new Thread(r);
thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());
return thread;
});
//记录日志
public static void log(String msg) {
StackTraceElement stack[] = (new Throwable()).getStackTrace();
//获取当前线程存放tranceId口袋中的内容
String traceId = traceIdKD.get();
System.out.println("****" + System.currentTimeMillis() + "[traceId:" + traceId + "],[线程:" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);
}
//模拟controller
public static void controller(List<String> dataList) {
log("接受请求");
service(dataList);
}
//模拟service
public static void service(List<String> dataList) {
log("执行业务");
dao(dataList);
}
//模拟dao
public static void dao(List<String> dataList) {
log("执行数据库操作");
//模拟插入数据
for (String s : dataList) {
log("插入数据" + s + "成功");
}
}
public static void main(String[] args) {
//需要插入的数据
List<String> dataList = new ArrayList<>();
for (int i = 0; i < 3; i++) {
dataList.add("数据" + i);
}
//模拟5个请求
int requestCount = 5;
for (int i = 0; i < requestCount; i++) {
String traceId = String.valueOf(i);
disposeRequestExecutor.execute(() -> {
//把traceId放入口袋中
traceIdKD.set(traceId);
try {
controller(dataList);
} finally {
//将tranceId从口袋中移除
traceIdKD.remove();
}
});
}
disposeRequestExecutor.shutdown();
}
}
****1565339644214[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.controller(Demo3.java:41):接受请求
****1565339644214[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.controller(Demo3.java:41):接受请求
****1565339644214[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.controller(Demo3.java:41):接受请求
****1565339644214[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.service(Demo3.java:47):执行业务
****1565339644214[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.service(Demo3.java:47):执行业务
****1565339644214[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:53):执行数据库操作
****1565339644214[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.service(Demo3.java:47):执行业务
****1565339644214[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据0成功
****1565339644214[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:53):执行数据库操作
****1565339644214[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.dao(Demo3.java:53):执行数据库操作
****1565339644215[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据0成功
****1565339644215[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据1成功
****1565339644215[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据1成功
****1565339644215[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据0成功
****1565339644215[traceId:0],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据2成功
****1565339644215[traceId:2],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据2成功
****1565339644215[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据1成功
****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.controller(Demo3.java:41):接受请求
****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.controller(Demo3.java:41):接受请求
****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.service(Demo3.java:47):执行业务
****1565339644215[traceId:1],[线程:disposeRequestThread-2],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据2成功
****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:53):执行数据库操作
****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.service(Demo3.java:47):执行业务
****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据0成功
****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:53):执行数据库操作
****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据1成功
****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据0成功
****1565339644215[traceId:4],[线程:disposeRequestThread-3],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据2成功
****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据1成功
****1565339644215[traceId:3],[线程:disposeRequestThread-1],com.itsoku.chat24.Demo3.dao(Demo3.java:56):插入数据数据2成功
可以看出输出和刚才使用traceId参数的方式结果一致,但是却简单了很多。不用去修改controller、service、dao代码了,风险也减少了很多。
代码中创建了一个ThreadLocal traceIdKD
,这个对象用来操作Thread中一个口袋,用这个口袋来存放tranceId。在main方法中通过traceIdKD.set(traceId)
方法将traceId放入口袋,log方法中通traceIdKD.get()
获取口袋中的traceId,最后任务处理完之后,main中的finally中调用traceIdKD.remove();
将口袋中的traceId清除。
ThreadLocal的官方API解释为:
“该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。”
InheritableThreadLocal
继续上面的实例,dao中循环处理dataList的内容,假如dataList处理比较耗时,我们想加快处理速度有什么办法么?大家已经想到了,用多线程并行处理dataList
,那么我们把代码改一下,如下:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Demo4 {
//创建一个操作Thread中存放请求任务追踪id口袋的对象
static ThreadLocal<String> traceIdKD = new ThreadLocal<>();
static AtomicInteger threadIndex = new AtomicInteger(1);
//创建处理请求的线程池子
static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,
3,
60,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(),
r -> {
Thread thread = new Thread(r);
thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());
return thread;
});
//记录日志
public static void log(String msg) {
StackTraceElement stack[] = (new Throwable()).getStackTrace();
//获取当前线程存放tranceId口袋中的内容
String traceId = traceIdKD.get();
System.out.println("****" + System.currentTimeMillis() + "[traceId:" + traceId + "],[线程:" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);
}
//模拟controller
public static void controller(List<String> dataList) {
log("接受请求");
service(dataList);
}
//模拟service
public static void service(List<String> dataList) {
log("执行业务");
dao(dataList);
}
//模拟dao
public static void dao(List<String> dataList) {
CountDownLatch countDownLatch = new CountDownLatch(dataList.size());
log("执行数据库操作");
String threadName = Thread.currentThread().getName();
//模拟插入数据
for (String s : dataList) {
new Thread(() -> {
try {
//模拟数据库操作耗时100毫秒
TimeUnit.MILLISECONDS.sleep(100);
log("插入数据" + s + "成功,主线程:" + threadName);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}).start();
}
//等待上面的dataList处理完毕
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//需要插入的数据
List<String> dataList = new ArrayList<>();
for (int i = 0; i < 3; i++) {
dataList.add("数据" + i);
}
//模拟5个请求
int requestCount = 5;
for (int i = 0; i < requestCount; i++) {
String traceId = String.valueOf(i);
disposeRequestExecutor.execute(() -> {
//把traceId放入口袋中
traceIdKD.set(traceId);
try {
controller(dataList);
} finally {
//将tranceId从口袋中移除
traceIdKD.remove();
}
});
}
disposeRequestExecutor.shutdown();
}
}
看一下上面的输出,有些traceId为null,这是为什么呢?这是因为dao中为了提升处理速度,创建了子线程来并行处理,子线程调用log的时候,去自己的存放traceId的口袋中拿去东西,肯定是空的了。
那有什么办法么?可不可以这样?
父线程相当于主管,子线程相当于干活的小弟,主管让小弟们干活的时候,将自己兜里面的东西复制一份给小弟们使用,主管兜里面可能有很多牛逼的工具,为了提升小弟们的工作效率,给小弟们都复制一个,丢到小弟们的兜里,然后小弟就可以从自己的兜里拿去这些东西使用了,也可以清空自己兜里面的东西。
Thread
对象中有个inheritableThreadLocals
变量,代码如下:
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
inheritableThreadLocals相当于线程中另外一种兜,这种兜有什么特征呢,当创建子线程的时候,子线程会将父线程这种类型兜的东西全部复制一份放到自己的inheritableThreadLocals
兜中,使用InheritableThreadLocal
对象可以操作线程中的inheritableThreadLocals
兜。
InheritableThreadLocal
常用的方法也有3个:
//向Thread中某个口袋中放东西
public void set(T value);
//获取这个口袋中目前放的东西
public T get();
//清空这个口袋中放的东西
public void remove()
使用InheritableThreadLocal
解决上面子线程中无法输出traceId的问题,只需要将上一个示例代码中的ThreadLocal
替换成InheritableThreadLocal
即可,代码如下:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Demo4 {
//创建一个操作Thread中存放请求任务追踪id口袋的对象,子线程可以继承父线程中内容
static InheritableThreadLocal<String> traceIdKD = new InheritableThreadLocal<>();
static AtomicInteger threadIndex = new AtomicInteger(1);
//创建处理请求的线程池子
static ThreadPoolExecutor disposeRequestExecutor = new ThreadPoolExecutor(3,
3,
60,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(),
r -> {
Thread thread = new Thread(r);
thread.setName("disposeRequestThread-" + threadIndex.getAndIncrement());
return thread;
});
//记录日志
public static void log(String msg) {
StackTraceElement stack[] = (new Throwable()).getStackTrace();
//获取当前线程存放tranceId口袋中的内容
String traceId = traceIdKD.get();
System.out.println("****" + System.currentTimeMillis() + "[traceId:" + traceId + "],[线程:" + Thread.currentThread().getName() + "]," + stack[1] + ":" + msg);
}
//模拟controller
public static void controller(List<String> dataList) {
log("接受请求");
service(dataList);
}
//模拟service
public static void service(List<String> dataList) {
log("执行业务");
dao(dataList);
}
//模拟dao
public static void dao(List<String> dataList) {
CountDownLatch countDownLatch = new CountDownLatch(dataList.size());
log("执行数据库操作");
String threadName = Thread.currentThread().getName();
//模拟插入数据
for (String s : dataList) {
new Thread(() -> {
try {
//模拟数据库操作耗时100毫秒
TimeUnit.MILLISECONDS.sleep(100);
log("插入数据" + s + "成功,主线程:" + threadName);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
countDownLatch.countDown();
}
}).start();
}
//等待上面的dataList处理完毕
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//需要插入的数据
List<String> dataList = new ArrayList<>();
for (int i = 0; i < 3; i++) {
dataList.add("数据" + i);
}
//模拟5个请求
int requestCount = 5;
for (int i = 0; i < requestCount; i++) {
String traceId = String.valueOf(i);
disposeRequestExecutor.execute(() -> {
//把traceId放入口袋中
traceIdKD.set(traceId);
try {
controller(dataList);
} finally {
//将tranceId从口袋中移除
traceIdKD.remove();
}
});
}
disposeRequestExecutor.shutdown();
}
}