1、JUC四大口诀
2、start线程
private native void start0();
start0是一个native方法
java线程是通过start的方法启动执行的,主要内容在native方法start0中
native关键字修饰的方法,表示通过jvm调用底层操作系统的函数方法——C语言
JNI = java native interface
3、多线程相关概念
JVM中同步是基于进入和退出监视器对象(Monitor,管程对象)来实现的,每个对象实例都会有一个Monitor对象,Monitor对象会和Java对象一同创建并销毁
执行线程的流程:持有管程->执行方法->执行完成后释放管程(执行期间,其他线程不能获取该管程)
面试题:为什么多线程重要?
4、用户线程和守护线程
线程的daemon
属性为true表示是守护线程,false表示是用户线程
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线程运行完毕");
}
}
注意:
start()
方法之前进行Future接口:定义操作异步任务
执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等
Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力的复杂业务
Callable接口:定义需要有返回值的任务要实现的方法
有个目的:异步多线程任务执行且有返回结果,三个特点:多线程/有返回/异步任务
Future接口相关架构:(alt+ctrl+u
)
FutureTask
继承了RunnableFuture
接口,在构造方法中实现了Callable
接口(有返回值、可抛出异常),实现了Runnable
接口
Runnable & Callable
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception{
FutureTask<String> futureTask = new FutureTask<>(new MyThread());
Thread t1 = new Thread(futureTask, "t1");
t1.start();
System.out.println(futureTask.get()); // 接收返回值
}
}
class MyThread implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("----come in call()");
return "hello";
}
}
future+线程池异步多线程任务配合,能显著提高程序的执行效率。
1、问题:3个任务,开启多个异步任务线程
处理,耗时多少?400ms左右
假如每次new一个Thread,太浪费资源,会有GC这些工作,所以推荐使用线程池
ExecutorService threadPool = Executors.newFixedThreadPool(3);
2、问题:3个任务,目前只有一个线程
main处理,耗时多少?1130ms左右
public class FutureThreadPollDemo {
public static void main(String[] args) {
// 1、问题:3个任务,开启多个异步任务线程处理,耗时多少?
// 线程池
ExecutorService threadPool = Executors.newFixedThreadPool(3);
long startTime = System.currentTimeMillis();
FutureTask<String> futureTask1 = new FutureTask<String>(() -> {
try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
return "task1 over";
});
// 假如每次new一个Thread,太浪费资源,会有GC这些工作,所以推荐使用线程池
// Thread t1 = new Thread(futureTask1, "t1");
// t1.start();
threadPool.submit(futureTask1);
FutureTask<String> futureTask2 = new FutureTask<String>(() -> {
try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
return "task2 over";
});
threadPool.submit(futureTask2);
FutureTask<String> futureTask3 = new FutureTask<String>(() -> {
try { TimeUnit.MILLISECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
return "task3 over";
});
threadPool.submit(futureTask3);
long endTime = System.currentTimeMillis();
System.out.println((endTime - startTime) + "ms");
threadPool.shutdown();
}
public static void m1() {
// 2、问题:3个任务,目前只有一个线程main处理,耗时多少?
long startTime = 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 endTime = System.currentTimeMillis();
System.out.println((endTime - startTime) + "ms");
System.out.println(Thread.currentThread().getName() + "====end");
}
}
缺点:
get()阻塞:一旦调用get()方法,不管是否计算完成,都会导致阻塞
isDone()轮询:利用if(futureTask.isDone())
的方式使得其在结束之后才get()
,但是也会消耗cpu
如果想要异步获取结果,通常都会以轮询的方式去获取结果
不要用阻塞,尽量用轮询CAS替代阻塞
public class FutureAPIDemo {
public static void main(String[] args) throws Exception {
FutureTask<String> futureTask = new FutureTask<String>(()->{
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-------主线程忙其他任务了");
//1------- System.out.println(futureTask.get(3,TimeUnit.SECONDS));//只愿意等3秒,过了3秒直接抛出异常
//2-------更健壮的方式-------轮询方法---等副线程拿到才去get()
//但是也会消耗cpu资源
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应用现状
简单的应用场景可以使用Future
回调通知
回调函数
,在Future结束时自动调用该回调函数创建异步任务
多个任务前后依赖可以组合处理
对计算速度选最快完成的(并返回结果)
核心的四个静态方法,来创建一个异步操作
(1)runAsync()
无返回值
public class CompletableFutureBuildDemo {
public static void main(String[] args) throws Exception{
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture completableFuture = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
// 暂停几秒
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("task over");
}, threadPool);
System.out.println(completableFuture.get());
threadPool.shutdown();
// ForkJoinPool.commonPool-worker-9 默认ForkJoinPool
// pool-1-thread-1 使用线程池
// task over
// null
}
}
(2)supplyAsync()
有返回值
public static void main(String[] args) throws Exception{
ExecutorService threadPool = Executors.newFixedThreadPool(3);
CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
// 暂停几秒
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
return "hello supplyAsync";
}, threadPool);
System.out.println(completableFuture.get());
threadPool.shutdown();
// pool-1-thread-1
// hello supplyAsync
}
CompletableFuture日常使用
CompletableFuture
可以完成Future
的功能private static void future1() throws InterruptedException, ExecutionException {
CompletableFuture completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
int result = ThreadLocalRandom.current().nextInt(10);
try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); }
System.out.println("======1s后出结果:" + result);
return result;
});
System.out.println(Thread.currentThread().getName() + "线程先去忙其他业务");
System.out.println(completableFuture.get());
}
whenComplete
public static void main(String[] args) throws Exception{
ExecutorService threadPool = Executors.newFixedThreadPool(3);
try {
CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName());
int result = ThreadLocalRandom.current().nextInt(10); // 产生随机数
try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { e.printStackTrace(); }
System.out.println("======1s后出结果:" + result);
return result;
}, threadPool).whenComplete((v, e) -> {
if (e == null) {
System.out.println("=====计算完成,更新值:" + v);
}
}).exceptionally(e -> {
e.printStackTrace();
System.out.println("异常情况" + e.getCause() + "\t" + e.getMessage());
return null;
});
System.out.println(Thread.currentThread().getName() + "线程先去忙其他任务");
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown();
}
// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:暂停3s
// try {TimeUnit.SECONDS.sleep(3);} catch (Exception e) {e.printStackTrace();}
}
CompletableFuture优点
自动回调
某个对象的方法;自动回调
某个对象的方法函数式编程
任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
Runnable
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
Function
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
Consumer
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
Supplier
@FunctionalInterface
public interface Supplier<T> {
T get();
}
Biconsumer
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
}
函数式接口名称 | 方法名称 | 参数 | 返回值 |
---|---|---|---|
Runnable | run | 无参数 | 无返回值 |
Function | apply | 1个参数 | 有返回值 |
Consume | accept | 1个参数 | 无返回值 |
Supplier | get | 没有参数 | 有返回值 |
Biconsumer | accept | 2个参数 | 无返回值 |
// join与get作用相同,区别是编译时是否报错
System.out.println(completableFuture.join());
需求
1、需求说明
1.1同一款产品,同时搜索出同款产品在各大电商平台的售价;
1.2同一款产品,同时搜索出本产品在同一个电商平台下,各个入驻卖家售价是多少
2、输出返回:
出来结果希望是同款产品的在不同地方的价格清单列表, 返回一个List
《mysql》in jd price is 88.05
《mysql》in dang dang price is 86.11
《mysql》in tao bao price is 90.43
3、解决方案,比对同一个商品在各个平台上的价格,要求获得一个清单列表
1 stepbystep , 按部就班, 查完京东查淘宝, 查完淘宝查天猫......
2 all in ,万箭齐发,一口气多线程异步任务同时查询。。。
public class CompletableFutureMallDemo {
static List<NetMall> list = Arrays.asList(
new NetMall("jd"),
new NetMall("dangdang"),
new NetMall("taobao")
);
// 1.一家家搜
public static List<String> getPrice(List<NetMall> list, String productName) {
return list
.stream()
.map(netMall -> String.format(productName + " in %s price is %.2f",
netMall.getNetMallName(),
netMall.calcPrice(productName)))
.collect(Collectors.toList());
}
// 2.优化 List =====> List ===> List
public static List<String> getPriceByCompletableFuture(List<NetMall> list, String productName) {
return list
.stream()
.map(netMall -> CompletableFuture.supplyAsync(() -> String.format(productName + " in %s price is %.2f",
netMall.getNetMallName(),
netMall.calcPrice(productName))))
.collect(Collectors.toList())
.stream()
.map(s -> s.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(endTime - startTime + "ms"); // 3046ms
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(endTime2 - startTime2 + "ms"); // 1005ms
}
}
@AllArgsConstructor
class NetMall {
@Getter
private String netMallName;
public double calcPrice(String productName) {
// 暂停几秒
try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0);
}
}
获得结果和触发计算
public T get()
public T get(long timeout, TimeUnit unit)
public T getNow(T valueIfAbsent)
立即获取结果不阻塞,计算完,返回计算完成后的结果;没算完,返回设定的valueIfAbsent
值
public T join()
与get作用相同,但是不抛异常
public boolean complete(T value)
是否打断get方法,立即返回括号值
对计算结果进行处理
thenApply
由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停
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
有异常也可以往下一步走,根据异常参数可以进一步处理
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(); }
}
}
//-----异常情况
//111
//333
//异常,可以看到多走了一步333
whenComplete
执行当前任务的线程继续执行当前任务
whenCompleteAsync
把任务继续提交给线程池
来进行执行
对计算结果进行消费
thenAccept
接收任务的处理结果,并消费处理,无返回结果
区别:
thenRun(Runnable runnable)
任务 A 执行完执行 B,并且 B 不需要 A 的结果
thenAccept(Consumer action)
任务 A 执行完执行 B,B 需要 A 的结果,但是任务 B 无返回值
thenApply(Function fn)
任务 A 执行完执行 B,B 需要 A 的结果,同时任务 B 有返回值
System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenRun(() -> {}).join());
//null
System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenAccept(resultA -> {}).join());
//resultA打印出来的 null因为没有返回值
System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenApply(resultA -> resultA + " resultB").join());
//resultA resultB 返回值
对计算速度进行选用
applyToEither
public class CompletableFutureFastDemo {
public static void main(String[] args) {
CompletableFuture<String> playA = CompletableFuture.supplyAsync(() -> {
System.out.println("A com in");
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
return "playA";
});
CompletableFuture<String> playB = CompletableFuture.supplyAsync(() -> {
System.out.println("B com 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() + "============" + result.join());
}
}
// A com in
// B com in
// main============playA is winer
对计算结果进行合并
thenCombine
两个CompletionStage任务都完成后,最终能把两个任务的结果一起交给thenCombine 来处理,先完成的先等着,等待其它分支任务
以thenRun
和thenRunAsync
为例,有什么区别?
ForkJoinPool
悲观锁:在获取数据的时候会先加锁
,确保数据不会被别的线程修改
synchronized
关键字和Lock
的实现类都是悲观锁
适合写操作多
的场景,先加锁可以保证写操作时数据正确
显式的锁定之后再操作同步资源
乐观锁:认为自己在使用数据时不会有别的线程修改数据
适合读操作多
的场景,不加锁的特点能够使其读操作的性能大幅提升
乐观锁则直接去操作同步资源,是一种无锁算法
两种实现方式:采用版本号
机制、CAS
(Compare-and-Swap,即比较并替换)
/**
* - 题目:谈谈你对多线程锁的理解,8锁案例说明
* - 口诀:线程 操作 资源类
* 1. 标准访问有ab两个线程,请问先打印邮件还是短信?邮件
* 2. a里面故意停3秒?邮件
* 3. 添加一个普通的hello方法,请问先打印邮件还是hello?hello
* 4. 有两部手机,请问先打印邮件(这里有个3秒延迟)还是短信?短信
* 5.有两个静态同步方法(synchroized前加static,3秒延迟也在),有1部手机,先打印邮件还是短信?邮件
* 6.两个手机,有两个静态同步方法(synchroized前加static,3秒延迟也在),有1部手机,先打印邮件还是短信?邮件
* 7.一个静态同步方法,一个普通同步方法,请问先打印邮件还是手机?短信
* 8.两个手机,一个静态同步方法,一个普通同步方法,请问先打印邮件还是手机?短信
*/
public class Lock8Demo {
public static void main(String[] args) {
Phone phone = new Phone();
Phone phone2 = new Phone();
new Thread(() -> {
phone.sendEmail();
}, "a").start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
// phone.senMSM();
// phone.hello();
phone2.senMSM();
}, "b").start();
}
}
class Phone { // 资源类
public synchronized void sendEmail() {
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("sendEmail");
}
public synchronized void senMSM() {
System.out.println("senMSM");
}
public void hello() {
System.out.println("hello");
}
}
8锁原理
1.2中
某一个时刻内,只能有唯一的一个线程去访问这些synchronized方法,锁的是当前对象this,被锁定后,其它的线程都不能 进入到当前对象的其他synchronized方法
3中
hello并未和其他synchronized修饰的方法产生争抢
4 中
锁在两个不同的对象/两个不同的资源上,不产生竞争条件
5.6中
static+synchronized - 类锁 phone = new Phone();中 加到了左边的Phone上
对于普通同步方法,锁的是当前实例对象,通常指this,具体的一部部手机,所有的普通同步方法用的都是同一把锁→实例对象本身。
对于静态同步方法,锁的是当前类的Class对象,如Phone,class唯一的一个模板。
对于同步方法块,锁的是synchronized括号内的对象 synchronized(o)
7.8中
一个加了对象锁,一个加了类锁,不产生竞争条件
【p32】
synchronized的3种作用方式
实例方法
对当前实例加锁,进入同步代码前要获得当前实例的锁
调用指令将会检查方法的ACC_SYNCHRONIZED
访问标志是否被设置
如果设置了,执行线程会先持有monitor,然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放 monitor
代码块
对括号里配置的对象加锁
实现使用的是monitorenter
和monitorexit
指令
一般是一个enter两个exit,极端情况是一个enter一个exit
静态方法
对当前类加锁,进去同步代码前要获得当前类对象的锁
ACC_STATIC
, ACC_SYNCHRONIZED
访问标志区分该方法是否静态同步方法
字节码分析:javap -c ***.class
文件反编译
反编译synchronized锁的是什么
什么是管程
monitor、监视器
(把信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序
执行线程就要求先成功持有管程,然后才能执行方法,最后当方法完成(无论是正常完成还是非正常完成)时释放管理。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程
为什么任何一个对象都可以成为一个锁?
Object
类是所有类的父类,即Java 的所有类都继承了 Object,子类可以使用 Object 的所有方法ObjectMonitor.java
→ObjectMonitor.cpp
→objectMonitor.hpp
ReentrantLock lock = new ReentrantLock(true);
面试题
为什么会有公平锁/非公平锁的设计?为什么默认非公平?
使⽤公平锁会有什么问题?
什么时候用公平?什么时候用非公平?
内层方法会自动获取锁
(前提,锁对象得是同一个对象),不会因为之前已经获取过还没释放而阻塞ReentrantLock
和synchronized
都是可重入锁,可重入锁的一个优点是可一定程度避免死锁
*******隐式锁synchronized
public class ReEntryLockDemo {
// 同步方法
public synchronized void m1() {
System.out.println(Thread.currentThread().getName() + "---m1");
m2();
System.out.println(Thread.currentThread().getName() + "---m1 end");
}
public synchronized void m2() {
System.out.println(Thread.currentThread().getName() + "---m2");
m3();
}
public synchronized void m3() {
System.out.println(Thread.currentThread().getName() + "---m3");
}
public static void main(String[] args) {
ReEntryLockDemo reEntryLockDemo = new ReEntryLockDemo();
new Thread(() -> {
reEntryLockDemo.m1();
}, "t1").start();
}
// t1---m1
// t1---m2
// t1---m3
// t1---m1 end
// 同步代码块
public class ReEntryLockDemo {
public static void main(String[] args) {
final Object object = new Object();
new Thread(() -> {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "外层调用");
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "中层调用");
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "内层调用");
}
}
}
}, "t1").start();
}
}
// t1外层调用
// t1中层调用
// t1内层调用
显示锁ReentrantLock
注意:lock
和unlock
一定要配对
假如lock
unlock
不成对,单线程情况下问题不大,但多线程下出问题
public class ReEntryLockDemo {
static Lock lock = new ReentrantLock();
public static void main(String[] args) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t外层调用");
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\t内层调用");
} finally {
lock.unlock();
}
} finally {
//lock.unlock();//-------------------------不成对|多线程情况
// 由于加锁次数和释放次数不一样,第二个线程始终无法获取到锁,导致一直在等待
}
}, "t1").start();
new Thread(() -> {
lock.lock();
try
{
System.out.println("t2 ----外层调用lock");
}finally {
lock.unlock();
}
},"t2").start();
}
}
//t1 ----外层调用
//t1 ------内层调用
//(t2 ----外层调用lock 假如不成对,这句话就不显示了)
死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待
的现象,若无外力干涉那它们都将无法推进下去
如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁
写一个死锁case
public class DeadLockDemo {
public static void main(String[] args) {
final Object objectA = new Object();
final Object objectB = new Object();
new Thread(() -> {
synchronized (objectA) {
System.out.println(Thread.currentThread().getName() + "自己持A锁,希望获得B锁");
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (objectB) {
System.out.println(Thread.currentThread().getName() + "成功获得B锁");
}
}
}, "A").start();
new Thread(() -> {
synchronized (objectB) {
System.out.println(Thread.currentThread().getName() + "自己持B锁,希望获得A锁");
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
synchronized (objectA) {
System.out.println(Thread.currentThread().getName() + "成功获得A锁");
}
}
}, "B").start();
}
}
死锁的排查
方法一:控制台
jps -l
查看端口号,类似linux中的ps -ef|grep xxx
jstack 77860
查看进程编号的栈信息方法二:图形化界面(通用)
win
+ r
输入jconsole
,打开图形化工具,打开线程
,点击 检测死锁
锁总结
指针指向monitor
对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个monitor与之关联,当一个monitor被某个线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor
实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp
,C++实现的)
什么是中断?如何停止、中断一个运行中的线程?
public void interrupt()
: 实例方法,设置中断状态为true,不会停止线程public static boolean interrupted()
: 静态方法,Thread.interrupted();
判断线程是否被中断,并清除当前中断状态
public boolean isInterrupted()
: 实例方法,判断当前线程是否被中断(通过检查中断标志位)面试题1:如何使用中断标识停止线程?
在需要中断的线程中不断监听中断状态,一旦发生中断,就执行相应的中断处理业务逻辑
方法:
通过一个volatile
变量实现
通过AtomicBoolean
通过Thread类自带的中断api
方法实现
public class InterruptDemo {
static volatile boolean isStop = false;
static AtomicBoolean atomicBoolean = new AtomicBoolean(false);
// interrupt api
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
if (Thread.currentThread().isInterrupted()) {
System.out.println(Thread.currentThread().getName() + " isInterrupt 被修改为true,程序停止");
break;
}
System.out.println("t1========hello interrupt api");
}
}, "t1");
t1.start();
try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
// t2向t1发出协商,将t1的中断标志位设为true,希望t1停下来
t1.interrupt(); // ***
}, "t2").start();
}
// AtomicBoolean原子类
private static void m2_atomicBoolean() {
new Thread(() -> {
while (true) {
if (atomicBoolean.get()) {
System.out.println(Thread.currentThread().getName() + " atomicBoolean 被修改为true,程序停止");
break;
}
System.out.println("t1========hello atomicBoolean");
}
}, "t1").start();
try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
atomicBoolean.set(true);
}, "t2").start();
}
// violate
private static void m1_violate() {
new Thread(() -> {
while (true) {
if (isStop) {
System.out.println(Thread.currentThread().getName() + " isStop被修改为true,程序停止");
break;
}
System.out.println("t1========hello volatile");
}
}, "t1").start();
try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
isStop = true;
}, "t2").start();
}
}
当对一个线程,调用 interrupt()
时:
正常活动状态
,那么会将该线程的中断标志设置为 true,仅此而已。 interrupt() 并不能真正的中断线程
,需要被调用的线程自己进行配合才行。被阻塞状态
(例如处于sleep, wait, join 等状态),在别的线程中调用当前线程对象的interrupt方法,那么线程将立即退出被阻塞状态,并抛出一个InterruptedException
异常。面试题2:当前线程的中断标识为true,是不是就立刻停止?
不会
1、如果线程处于正常活动状态,会将线程的中断标识设置为true,被中断标志的线程继续运行,不受影响
中断不活动的线程不会产生任何影响
public class InterruptDemo2 {
public static void main(String[] args) {
// 实例方法interrupt()仅设置线程的中断状态为true,不会停止线程
Thread t1 = new Thread(() -> {
for (int i = 1; i <= 300; i ++) {
System.out.println("======:" + i);
}
System.out.println("after t1 interrupt() 02:" + Thread.currentThread().isInterrupted()); // true
}, "t1");
t1.start();;
System.out.println("t1默认的中断标识:" + t1.isInterrupted()); // false
try { TimeUnit.MILLISECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
t1.interrupt(); // true
System.out.println("after t1 interrupt() 01:" + t1.isInterrupted()); // true
// 中断不活动的线程不会产生任何影响
try { TimeUnit.MILLISECONDS.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("after t1 interrupt() 03:" + t1.isInterrupted()); // false
}
}
后手案例(重要)
2、如果线程处于被阻塞状态(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() +
"中断标志位" + Thread.currentThread().isInterrupted() + "程序终止");
break;
}
System.out.println("interrupt demo 03");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// 为什么要在异常处再调用一次?
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
}, "t1");
t1.start();
try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> t1.interrupt(), "t2").start();
}
}
1 中断标志位 默认false
2 t2向t1发出了中断协商,t2调用t1.interrupt(),中断标志位true
3 中断标志位true,正常情况下,程序终止
4 中断标志位true,异常情况下,将会把中断状态清除,并收到InterruptedException异常,中断标志位false,导致无限循环
5 在catch块中,需要再次给中断标志位设置为true,即2次调用interrupt()停止程序
面试题3:静态方法Thread.interrupted(),谈谈你的理解
Thread.interrupted():判断线程是否被中断,并清除当前中断状态
做两件事:
1、返回当前线程的中断状态,测试当前线程是否已被中断
2、将当前线程的中断状态清零并重新设为false,清除线程的中断状态
public class InterruptDemo4 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted()); // main false
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted()); // main false
System.out.println("---1");
Thread.currentThread().interrupt();
System.out.println("---2");
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted()); // main false
System.out.println(Thread.currentThread().getName() + "\t" + Thread.interrupted()); // main true
}
}
区别:
实例方法:Thread.currentThread().isInterrupted();
底层:
isInterrupted(false);
isInterrupted(boolean ClearInterrupted);
静态方法:Thread.interrupted();
底层:
currentThread().isInterrupted(true);
isInterrupted(boolean ClearInterrupted);
中断状态将会根据传入的ClearInterrupted参数值确定是否重置
静态方法将会清除中断状态,传入的参数是true;实例方法不会,传入的参数是false
总结
interrupt():实例方法,通知目标线程中断,设置目标线程的中断标志位true
isInterrupted():实例方法:判断当前线程是否被中断
interrupted():返回当前线程中断值,并且清零置false
java.util.concurrent.locks.LockSupport
用来创建锁和其他同步类的基本线程阻塞原语
方法:park()
和unpark()
分别是阻塞线程和解除阻塞线程
线程等待唤醒机制
三种让线程等待和唤醒的方法:
synchronized
wait
notify
正常情况下:
public class LockSupportDemo {
public static void main(String[] args) {
Object objectLock = new Object();
new Thread(() -> {
synchronized (objectLock) {
System.out.println(Thread.currentThread().getName() + "\t --- come in");
try {
objectLock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t ---被唤醒");
}
}, "t1").start();
try { TimeUnit.MILLISECONDS.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
synchronized (objectLock) {
objectLock.notify();
System.out.println(Thread.currentThread().getName() + "\t ---发出通知");
}
}, "t2").start();
}
}
// t1 --- come in
// t2 ---发出通知
// t1 ---被唤醒
异常1:wait()和notify()方法,两个都去掉同步代码块
异常2:将notify放在wait方法前面,程序将无法执行,无法唤醒
总结:
1、wait()和notify()方法必须在synchronized代码块里面,并且成对出现使用
2、先wait后notify
Lock Condition
await
signal
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 ---come in");
condition.await();
System.out.println(Thread.currentThread().getName() + "\t ---被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}, "t1").start();
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(() -> {
lock.lock();
try {
condition.signal();
System.out.println(Thread.currentThread().getName() + "\t ---发出通知");
} finally {
lock.unlock();
}
}, "t2").start();
}
}
总结:Condition中的线程等待和唤醒方法,需要先获取锁,要先await后signal
LockSupport
park() 等待
permit许可证默认没有不能放行,所以一开始调用park()方法,当前线程就会阻塞,直到别的线程给当前线程发放permit,park方法才会被唤醒
unpark() 唤醒
调用之后,会将thread线程的许可证permit发放,自动唤醒park线程,即之前的阻塞中LockSupport.park()方法会立即返回
p54
许可证最多只有一个
LockSupport是一个线程阻塞的工具类
定义
作用
三大特性
原子性:指一个操作是不可中断的,即多线程环境下,操作不能被其他线程干扰
有序性:为了性能,编译器和处理器会对指令序列进行重新排序
指令重排可以保证串行语义一致,但没有义务保证多线程间的语义也一致
处理器在进行重排序时必须要考虑指令之间的数据依赖性
计算机存储体系:磁盘->主存->CPU缓存
多级缓存:CPU的运行并不是直接操作内存而是先把内存里边的数据读到缓存,而内存的读和写操作的时候就会造成不一致的问题
多线程对变量的读写过程
我们定义的所有共享变量都储存在物理主内存中
每个线程都有自己独立的工作内存,保存该线程使用到的变量的副本(主内存中该变量的一份拷贝)
线程对共享变量所有的操作都必须先在线程自己的工作内存中进行后写回主内存,不能直接从主内存中读写(不能越级)
不同线程之间也无法直接访问其他线程的工作内存中的变量,线程间变量值的传递需要通过主内存来进行(同级不能相互访问)
多线程先行发生原则之happens-before
如果一个操作执行的结果需要对另一个操作可见性或代码重排序,那么这两个操作之间必须存在happens-before关系
作用:判断数据是否存在竞争,线程是否安全
happens-before总原则
如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,
而且第一个操作的执行顺序排在第二个操作之前
两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行
如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法
happens-before之8条
1、次序规则:一个线程内,按照代码顺序,写在前面的操作先行发生于写在后面的操作
2、锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作(前一个线程unlock后,后一个线程才能lock)
3、volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的(先写后读且可见)
4、传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
5、线程启动规则:Thread对象的**start()**方法先行发生于此线程的每一个动作
6、线程中断规则:对线程**interrupt()**方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
Thread.interrupted()
:是否发生中断
7、线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测
Thread::join()
、Thread::isAlive()
:是否终止
8、对象终结规则:对象没有完成初始化之前,是不能调用finalized()方法的
举例
private int value;
public void setValue(int value) {
this.value = value;
}
public int getValue() {
return value;
}
有A、B两线程,A先调用setValue(1),B调用同一个对象的getValue(),B的返回值?
不确定,这段代码不安全
1 由于两个方法是由不同的线程调用,不在同一个线程中,所以肯定不满足程序次序规则;
2 两个方法都没有使用锁,所以不满足锁定规则;
3 变量不是用volatile修饰的,所以volatile变量规则不满足;
4 传递规则肯定不满足;
修复:
1 把getter/setter方法都定义为synchronized方法
2 把value定义为volatile变量
并行流会有线程安全问题,慎用!
Stream
volatile
定义:一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作,避免代码重排序
内存屏障之前的所有写操作都要回写到主内存
内存屏障之后的所有读操作都能获得内存屏障之前的所有写操作的最新结果(实现了可见性)
**重排序时,不允许把内存屏障之后的指令重排序到内存屏障之前**
volatile为什么能保证可见性和有序性?——**内存屏障 (Memory Barriers / Fences)**
4类内存屏障指令
happens-before 之 volatile 变量规则
当第一个操作为volatile读时,不论第二个操作是什么,都不能重排序——保证volatile读之后的操作不会被重排到volatile读之前
当第二个操作为volatile写时,不论第一个操作是什么,都不能重排序——保证volatile写之前的操作不会被重排到volatile写之后
当第一个操作为volatile写时,第二个操作为volatile读时,不能重排
内存屏障四种插入策略
在每个 volatile 写操作的前⾯插⼊⼀个 StoreStore 屏障——保证在volatile写之前,其前面的所有普通写操作都已经刷新到主内存中
在每个 volatile 写操作的后⾯插⼊⼀个 StoreLoad 屏障——避免volatile写与后面可能有的volatile读/写操作重排序
读
3. 在每个 volatile 读操作的后⾯插⼊⼀个 LoadLoad 屏障——禁止处理器把上面的volatile读与下面的普通读重排序
read(读取)→load(加载)→use(使用)→assign(赋值)→store(存储)→write(写入)→lock(锁定)→unlock(解锁)
read: 作用于主内存,将变量的值从主内存传输到工作内存,主内存到工作内存
load: 作用于工作内存,将read从主内存传输的变量值放入工作内存变量副本中,即数据加载
use: 作用于工作内存,将工作内存变量副本的值传递给执行引擎,每当JVM遇到需要该变量的字节码指令时会执行该操作
assign: 作用于工作内存,将从执行引擎接收到的值赋值给工作内存变量,每当JVM遇到一个给变量赋值字节码指令时会执行该操作
store: 作用于工作内存,将赋值完毕的工作变量的值写回给主内存
write: 作用于主内存,将store传输过来的变量值赋值给主内存中的变量
由于上述只能保证单条指令的原子性,针对多条指令的组合性原子保证,没有大面积加锁,所以,JVM提供了另外两个原子指令:
lock: 作用于主内存,将一个变量标记为一个线程独占的状态,只是写时候加锁,就只是锁了写变量的过程
unlock: 作用于主内存,把一个处于锁定状态的变量释放,然后才能被其他线程占用
P31在听一次
没有原子性
volatile变量的复合操作(如i++
)不具有原子性
i++
三步操作:读取值,+1操作,写回新值为什么不能保证原子性?
volatile主要是对其中部分指令做了处理
read-load-use 和 assign-store-write 成为了两个不可分割的原子操作,但是在use和assign之间依然有极小的一段真空期,有可能变量会被其他线程读取,导致写丢失一次
要use(使用)一个变量的时候必需load(载入),要载入的时候必需从主内存read(读取)这样就解决了读的可见性
写操作是把assign和store做了关联( 在assign(赋值)后必需store(存储)),store(存储)后write(写入 )
也就是给一个变量赋值的时候一串关联指令直接把变量值写到主内存
用的时候直接从主内存取,再赋值到直接写回主内存做到了内存可见性
小结
boolean
值or int
值指令重排
volatile的底层实现是通过内存屏障
如何正确使用volatile?
单一赋值可以,but含复合运算赋值不可以(i++之类)
状态标志,判断业务是否结束
/**
* 使用:作为一个布尔状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或任务结束
* 理由:状态标志并不依赖于程序内任何其他状态,且通常只有一种状态转换
* 例子:判断业务是否结束
*/
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保证复合操作的原子性
}
}
}
DCL双端锁的发布
单线程下,初始化实例:
1.分配对象的内存空间
2.初始化对象
3.指针指向刚分配的内存地址
多线程下,如果重排序,会导致2、3乱写,最后线程得到的是null而不是完成初始化的对象
解决办法:
1.加volatile修饰
public class SafeDoubleCheckSingleton
{
// 通过volatile声明,实现线程安全的延迟初始化。
private volatile static SafeDoubleCheckSingleton singleton;
// 私有化构造方法
private SafeDoubleCheckSingleton(){
}
// 双重锁设计
public static SafeDoubleCheckSingleton getInstance(){
if (singleton == null){
// 1.多线程并发创建对象时,会通过加锁保证只有一个线程能创建对象
synchronized (SafeDoubleCheckSingleton.class){
if (singleton == null){
// 隐患:多线程环境下,由于重排序,该对象可能还未完成初始化就被其他线程读取
// 原理:利用volatile,禁止 "初始化对象"(2) 和 "设置singleton指向内存空间"(3) 的重排序
singleton = new SafeDoubleCheckSingleton();
}
}
}
// 2.对象创建完毕,执行getInstance()将不需要获取锁,直接返回创建对象
return singleton;
}
}
// 现在比较好的做法就是采用静态内部内的方式实现
public class SingletonDemo {
private SingletonDemo() { }
private static class SingletonDemoHandler {
private static SingletonDemo instance = new SingletonDemo();
}
public static SingletonDemo getInstance() {
return SingletonDemoHandler.instance;
}
}
1、内存屏障是什么?能干什么?
2、内存屏障的四大指令
3、volatile与内存屏障的关系
4、volatile的特性
volatile与内存屏障的关系
关键字
添加了一个ACC_VOLATILE指令。当JVM把字节码生成机器码时,发现是volatile变量,就根据JMM要求,在相应的位置插入内存屏障指令