多线程进阶——JUC并发编程
本文是我学习多线程进阶——JUC并发编程的笔记,其中包含了:
- JUC的介绍
- 线程和进程的回顾
- Lock锁
- 生产者与消费者问题
- 八锁现象
- 集合类不安全
- Callable
- 常用的辅助类
- 读写锁
- 阻塞队列
- 线程池
- 四大接口函数
- Stream流式计算
- ForkJoin详解
- 异步回调
- JMM
- Volatile
- 玩转单例模式
- 理解CAS
- 原子引用
- 各种锁的理解
1.什么是JUC
学习方向:源码+官方文档(高频面试部分)
JUC全称:Java.util.concurrent Java工具类中并发相关的包
Java.util.concurrent.atomic 原子性
Java.util.concurrent.locks Lock锁
java.util工具包:包主要用来分类
业务:普通的线程代码 Thread
Runnable没有返回值、效率相比Callable相对较低!
2.线程和进程
线程和进程。如果不能用一句话说出来的技术说明你不扎实
进程:一个程序,如:QQ.exe、Music.exe 程序的集合;
一个进程中包含多个线程,至少包含一个线程!
java默认有几个线程?2个(main线程和GC线程)
线程:开了一个进程Typora,输入文字和自动保存(线程负责)
对于Java而言创建线程方式:Tread、Runnable、callable
java真的可以开启线程吗?——开不了的,调用了本地的方法。Java是没有权限开启线程的
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 */
}
}
}
//本地方法 调用了底层的C++,Java无法操作硬件,运行在虚拟机之上
private native void start0();
并发、并行
并发编程:并发、并行
并发:多线程同时操作一个资源
- 一核CUP,模拟出来多条线程,(天下武功唯快不破) 快速交换。
并行:多个人一起行走
- 多核CPU,多个线程可以同时执行;线程池
public class Test1 {
public static void main(String[] args) {
//new Thread().start();
//获取CPU的核数
/*
CUP密集型和IO密集型
*/
System.out.println(Runtime.getRuntime().availableProcessors());
}
}
并发编程的本质:充分利用CPU的资源
所有公司都很看重的一个板块
企业:挣钱—>同高效率,裁员 —>找一个厉害的人顶替三个不怎么样的人
人员(减),技术成本(加)
线程有几个状态
public enum State {
//新生
NEW,
//运行
RUNNABLE,
//阻塞
BLOCKED,
//等待(死死的等)
WAITING,
//超时等待
TIMED_WAITING,
//终止
TERMINATED;
}
wait和sleep的区别
-
来自不同的类
wait=>Object
sleep=>Thread
企业中让线程休眠不会用到sleep,
TimeUnit.DAYS.sleep(1);//睡一天 TimeUnit.SECONDS.sleep(1);//睡一秒
-
关于锁的释放
wait会释放锁;sleep抱着锁睡觉,不会释放
-
使用的范围
wait必须在同步代码块中
sleep可以在任何地方谁
-
是否需要捕获异常
wait和sleep都需要捕获异常InterruptedException
3.Lock锁(重点)
传统Synchronized
package com.xiaozhi.demo1;
/**
* 基本的卖票例子
* 真正的多线程开发,公司中的开发,一定要降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作
* 1.属性 2.方法
*/
public class SaleTicketDemo01 {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源类丢入线程
Ticket ticket = new Ticket();
//@FunctionalInterface 函数式接口,jdk1.8之后可以使用lambda表达式 (参数)->{代码}
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 60; i++) {
ticket.sale();
}
},"C").start();
}
}
//资源类 OOP编程(只有属性和方法,不实现继承)
class Ticket{
//属性
private int number=50;
//方法
//卖票的方式
//synchronized本质就是队列,锁
//锁:锁两个东西,对象和class
public synchronized void sale(){
if (number>0){
System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余:" + number + "张票");
}
}
}
锁的作用:锁住对象和class
Lock锁
公平锁:非常公平,可以先来后到
非公平锁:十分不公平,可以插队(默认)
package com.xiaozhi.demo1;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SaleTicketDemo02 {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源类丢入线程
Ticket2 ticket = new Ticket2();
//@FunctionalInterface 函数式接口,jdk1.8之后可以使用lambda表达式 (参数)->{代码}
new Thread(()->{ for (int i = 0; i < 60; i++) ticket.sale(); },"A").start();
new Thread(()->{ for (int i = 0; i < 60; i++) ticket.sale(); },"B").start();
new Thread(()->{ for (int i = 0; i < 60; i++) ticket.sale();},"C").start();
}
}
//Lock锁 三步曲
/*
1.new ReentrantLock();
2.lock.lock();//加锁
3.finally=> lock.unlock();//解锁
*/
class Ticket2{
//属性
private int number=50;
Lock lock = new ReentrantLock();
//方法
public void sale(){
lock.lock();//加锁
try {
//业务代码
if (number>0){
System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余:" + number + "张票");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();//解锁
}
}
}
Synchronized 和 Lock锁的区别
- Synchronized 是内置的Java关键字,Lock是一个Java类
- Synchronized 无法判断获取锁的状态,Lock可以判断是否获取到了锁
- Synchronized 会自动释放锁,Lock必须要手动释放锁!如果不释放锁会造成死锁
- Synchronized 线程1(获得锁,阻塞) 线程2(等待,傻傻的等);Lock锁就不一定会等待下去;
- Synchronized 可重入锁,不可以中断的,非公平的;Lock 可重入锁,可以判断锁,非公平(可以自己设置)
- Synchronized 适合锁少量的代码同步问题;Lock 适合锁大量的同步代码!
锁是什么?如何判断锁的是谁!
4.生产者和消费者问题
笔试手写:单例模式、八大排序算法、生产者和消费者、死锁问题
Synchronized版
package com.xiaozhi.pc;
/**
* 线程之间的通信问题,生产者和消费者问题 等待唤醒,通知唤醒
* 线程交替执行 A B 操作同一个变量 num=0
* A num+1
* B num-1
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
//判断等待、业务、通知
class Data{//数字 资源类
private int number=0;
//+1
public synchronized void increment() throws InterruptedException {
if (number!=0){
//等待操作
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我+1完毕
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
if (number==0){
//等待操作
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我-1完毕
this.notifyAll();
}
}
问题存在,A、B、C、D四个线程!会出现虚假唤醒
解决方案:将if判断改为while判断
package com.xiaozhi.pc;
/**
* 线程之间的通信问题,生产者和消费者问题 等待唤醒,通知唤醒
* 线程交替执行 A B 操作同一个变量 num=0
* A num+1
* B num-1
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//判断等待、业务、通知
class Data{//数字 资源类
private int number=0;
//+1
public synchronized void increment() throws InterruptedException {
while (number!=0){
//等待操作
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我+1完毕
this.notifyAll();
}
//-1
public synchronized void decrement() throws InterruptedException {
while (number==0){
//等待操作
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我-1完毕
this.notifyAll();
}
}
JUC版的生产者与消费者的问题
通过Lock找到Condition
代码实现:
package com.xiaozhi.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程之间的通信问题,生产者和消费者问题 等待唤醒,通知唤醒
* 线程交替执行 A B 操作同一个变量 num=0
* A num+1
* B num-1
* C num+1
* D num-1
*/
public class B {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
//判断等待、业务、通知
class Data2{//数字 资源类
private int number=0;
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
//+1
public void increment() throws InterruptedException {
lock.lock();
try {
//业务代码
while (number!=0){
//等待操作
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我+1完毕
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
//-1
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number==0){
//等待操作
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
//通知其他线程,我-1完毕
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
任何一个新的技术,绝对不是仅仅只是覆盖了原来的技术,一定会有优势和补充
Condition,精准的通知和唤醒线程
代码测试:
package com.xiaozhi.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 要求:执行顺序为A-B-C
* 生产线:思路举例:下单->支付->交易->物流。(举例只是让更好的理解,并不是真实的购物是这样实现的)
*/
public class C {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data3.printC();
}
},"C").start();
}
}
class Data3{//资源类 Lock锁
private Lock lock=new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number=1;//1A 2B 3C
public void printA(){
lock.lock();
try {
while (number!=1){
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "=>AAAAAAAA");
//唤醒指定的人 B
number=2;
condition2.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
//业务:判断->执行->通知
while (number!=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "=>BBBBBBBB");
number=3;
condition3.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (number!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "=>CCCCCCCCCC");
number=1;
condition1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
5.八锁现象
如何判断锁锁的是谁!什么是锁,锁的对象是谁!
深刻理解锁
package com.xiaozhi.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁:就是关于锁的八个问题
* 标准情况:两个线程先打印发短信还是打电话 1.发短信 2.打电话
* 原因不是先后调用的问题,而是锁的问题,因为有锁的存在
* 发短信休息4s:两个线程先打印发短信还是打电话 1.发短信 2.打电话
*
*/
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);//休息1s
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
},"B").start();
}
}
class Phone{
//synchronized,锁的对象是方法的调用者!
//两个方法用的同一把锁(phone的锁),谁先拿到谁执行
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);//休息1s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
package com.xiaozhi.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁:就是关于锁的八个问题
* 一个对象,增加了一个普通方法hello,是先打印hello还是先打印发短信? 1.hello2.发短信
*两个对象,两个同步方法,是先打印打电话还是先打印发短信? 1.打电话 2.发短信
* 根据时间来确定
*
*/
public class Test2 {
public static void main(String[] args) {
//两个对象,两个调用者,两把锁
Phone2 phone = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);//休息1s
} catch (InterruptedException e) {
e.printStackTrace();
}
/*new Thread(()->{
phone.hello();
},"B").start();*/
new Thread(()->{
phone2.call();
},"B").start();
}
}
class Phone2{
//synchronized,锁的对象是方法的调用者!
//两个方法用的同一把锁(phone的锁),谁先拿到谁执行
public synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);//休息1s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
//这里没有锁,不是同步方法,不受锁的影响
public void hello(){
System.out.println("hello");
}
}
package com.xiaozhi.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁:就是关于锁的八个问题
* 增加两个静态方法,只有一个对象,先打印打电话还是发短信? 发短信
* 增加两个静态方法,两个对象,先打印打电话还是发短信? 发短信
*
*/
public class Test3 {
public static void main(String[] args) {
//两个对象的Class类模板只有一个。static,锁的是Class
Phone3 phone = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);//休息1s
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
//Phone3只有唯一的class对象(Class<Phone3> phone3Class = Phone3.class;)
class Phone3{
//synchronized,锁的对象是方法的调用者!
//static 静态方法,类一加载就有了,锁的是class(模板)
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);//休息1s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void call(){
System.out.println("打电话");
}
}
package com.xiaozhi.lock8;
import java.util.concurrent.TimeUnit;
/**
* 8锁:就是关于锁的八个问题
*一个静态同步方法,一个普通同步方法,一个对象,先打印打电话还是发短信?打电话
*一个静态同步方法,一个普通同步方法,两个对象,先打印打电话还是发短信?打电话
*/
public class Test4 {
public static void main(String[] args) {
//两个对象的Class类模板只有一个。static,锁的是Class
Phone4 phone = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{
phone.sendSms();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);//休息1s
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
},"B").start();
}
}
//Phone3只有唯一的class对象(Class<Phone3> phone3Class = Phone3.class;)
class Phone4{
//静态同步方法 锁的class类模板
public static synchronized void sendSms(){
try {
TimeUnit.SECONDS.sleep(4);//休息1s
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//普通同步方法 锁的调用者
public synchronized void call(){
System.out.println("打电话");
}
}
小结
- 锁 锁的是什么?
- new出来的,表示this,具体的一个手机
- static的,class,唯一的一个模板
6.集合类不安全
List不安全
package com.xiaozhi.unsafe;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* 集合的安全
* java.util.ConcurrentModificationException 并发修改异常
* 并发下ArrayList是不安全的
* 解决方案:
* 1.Vector 默认安全,在1.0发行,ArrayList在1.2发行。ArrayList的add方法为扩容,Vector add方法加了synchronized锁
* 2.让ArrayList转变的安全
* 3.JUC包下解决方案
* 3.1 CopyOnWriteArrayList 写入时复制 COW思想,计算机程序设计领域的一种优化策略
* 多个线程操作同一资源时,读取的时候是固定的,写入的时候会有覆盖现象
* 此方法是为了在写入时避免覆盖,造成数据问题
* 此处引发问题:读写分离,
* CopyOnWriteArrayList 比 Vector好在哪里?
* 1. Vector的add方法加有synchronized方法,效率相对较低;CopyOnWriteArrayList用的lock锁
* 3.2
*/
public class ListTest {
public static void main(String[] args) {
//单线程下
List<String> list = Arrays.asList("1", "2", "3");
list.forEach(System.out::println);//遍历集合(forEach中是函数式接口)
System.out.println("===================");
List<String> list1 = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list1.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list1);
}
//多线程下
System.out.println("===================");
//List<String> list2 = new ArrayList<>();//报错
//List<String> list2 = new Vector<>();//解决方案1
//List<String> list2 = Collections.synchronizedList(new ArrayList<>());//解决方案2
List<String> list2 = new CopyOnWriteArrayList<>();//解决方案3.1
for (int i = 1; i <= 20; i++) {
new Thread(()->{
list2.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list2);
},String.valueOf(i)).start();
}
}
}
- CopyOnWriteArrayList 和 Vector 中Add方法对比
-
小智学习方法推荐
- 先会用
- 货比三家(寻找其他解决方案)
- 分析源码
Set 不安全
package com.xiaozhi.unsafe;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* 同理可证(List):ConcurrentModificationException
* 解决方案:
* 1.采用工具类
* 2.写入时复制
*/
public class SetTest {
public static void main(String[] args) {
//HashSet<String> set = new HashSet<>();//报错ConcurrentModificationException
//Set<String> set = Collections.synchronizedSet(new HashSet<>());//解决方案1
CopyOnWriteArraySet<Object> set = new CopyOnWriteArraySet<>();
for (int i = 1; i <= 20; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
-
hashSet的底层原理
-
底层就是HashMap
-
Set的add方法,本质就是map的key,map的key无法重复。PRESENT是一个常量,不变的值
-
HashMap 不安全
回顾Map基本操作
package com.xiaozhi.unsafe;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class MapTest {
public static void main(String[] args) {
//问题:1.map是这样用的吗? 不是,工作中不用HashMap
// 2.默认等价于什么? new HashMap<>(16,0.75);
//Map<String,String>map= new HashMap<>();//报错,ConcurrentModificationException
//Map<String,String>map=Collections.synchronizedMap(new HashMap<>());//解决方案1
//研究ConcurrentHashMap,查看API帮助文档
Map<String,String> map = new ConcurrentHashMap<>();//解决方案2
for (int i =1; i <= 20; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
7.Callable
- Callable可以有返回值
- Callable可以抛出异常
- 方法不同,Runnable为run方法,Callable为call方法
泛型的参数等于方法的返回值
代码测试
package com.xiaozhi.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 1.喜欢探究原理
* 2.觉得自己会用就OK
*/
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//new Thread(new Runnable()).start();
//new Thread(new FutureTask<V>()).start();
//new Thread(new FutureTask<V>(Callable)).start();
new Thread().start();//怎么启动Callable
MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask(thread);//适配类
new Thread(futureTask,"A").start();
Integer o = (Integer) futureTask.get();//获取Callable的返回结果,这个get方法可能会产生阻塞,把它放到最后一行,或异步通信
System.out.println("Callable的返回结果: "+o);
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() {
System.out.println("call()");
return 1024;
}
}
package com.xiaozhi.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 1.喜欢探究原理
* 2.觉得自己会用就OK
*/
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//new Thread(new Runnable()).start();
//new Thread(new FutureTask<V>()).start();
//new Thread(new FutureTask<V>(Callable)).start();
new Thread().start();//怎么启动Callable
MyThread thread = new MyThread();
FutureTask futureTask = new FutureTask(thread);//适配类
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();//结果会被缓存,效率高
Integer o = (Integer) futureTask.get();//获取Callable的返回结果,这个get方法可能会产生阻塞,把它放到最后一行,或异步通信
System.out.println("Callable的返回结果: "+o);
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() {
System.out.println("call()");
return 1024;
}
}
细节问题
- 有缓存
- 结果可能需要等待,有阻塞
8.常用的辅助类(必会)
8.1、CountDownLatch(减法计数器)
代码演示:
package com.xiaozhi.add;
import java.util.concurrent.CountDownLatch;
/**
* 计数器
*/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//总数是 6 必须要执行的任务的时候,再使用
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "Go out");
countDownLatch.countDown();//数量减1
},String.valueOf(i)).start();
}
countDownLatch.await();//等待计数器归零,然后再向下执行
System.out.println("Close Door");
}
}
原理:
- countDownLatch.countDown();//数量减1
- countDownLatch.await();//等待计数器归零,然后再向下执行
每次有线程调用countDown方法数量减 1 ,假设计数器变为 0 ,countDownLatch.await();就会被唤醒,继续执行!
8.2、CyclicBarrier(加法计数器)
加法计数器
package com.xiaozhi.add;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* 集齐七颗龙珠召唤神龙
*/
public class CyclicBarrierDemo {
public static void main(String[] args) {
//召唤龙珠的线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙成功");
});
for (int i = 1; i <= 7; i++) {
final int temp=i;//由于Lambda中无法获得i,所以创建了此中间件
//Lambda能操作到i吗?
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "收集了"+temp+"个龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
8.3、Semaphore
Semaphore:信号量
代码演示:抢车位!(有六辆车,三个停车位)
package com.xiaozhi.add;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreDemo {
public static void main(String[] args) {
//参数:线程数量——>停车位 限流使用
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(()->{
try {
semaphore.acquire();//得到
System.out.println(Thread.currentThread().getName() + "抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();//释放
}
},String.valueOf(i)).start();
}
}
}
- 原理:
- semaphore.acquire();//获得,假设如果已经满了,等待,等到被释放为止。
- semaphore.release();//释放,会将当前的信号量释放+1,然后唤醒等待的线程!
- 作用
- 多个共享资源互斥的使用
- 控制限流,控制最大的线程数
9.读写锁
ReadWriteLock
package com.xiaozhi.rw;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 读写锁(又称为:独占锁(写锁)一次只能被一个线程占有,共享锁(读锁)多个线程可以同时占有)
* ReadWriteLock
* 读和读:可共存!
* 读和写:不能共存!
* 写和写:不能共存!
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
//MyCache myCache = new MyCache();
MyCacheLock myCache = new MyCacheLock();
//写入
for (int i = 1; i <= 5; i++) {
final int temp=i;
new Thread(()->{
myCache.put(temp+"",temp+"");
},String.valueOf(i)).start();
}
//读取
for (int i = 1; i <= 5; i++) {
final int temp=i;
new Thread(()->{
myCache.get(temp+"");
},String.valueOf(i)).start();
}
}
}
/**
* 自定义缓存
*/
class MyCache{
private volatile Map<String,Object>map=new HashMap<>();
//存,写
public void put(String key,Object value){
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入OK");
}
//取,读
public void get(String key){
System.out.println(Thread.currentThread().getName() + "读取" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() + "读取OK");
}
}
/**
* 加锁的
*/
class MyCacheLock{
private volatile Map<String,Object>map=new HashMap<>();
//读写锁:更加细粒度的控制
private ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
//存,写 只希望同时只有一个线程
public void put(String key,Object value){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入OK");
}catch (Exception e){
e.printStackTrace();
}finally {
readWriteLock.writeLock().unlock();
}
}
//取,读 所有人都可以去读
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() + "读取OK");
}catch (Exception e){
e.printStackTrace();
}finally {
readWriteLock.readLock().unlock();
}
}
}
10.阻塞队列
-
阻塞队列
-
阻塞:(不得不阻塞)
- 写入时,队列满了必须阻塞,等待取出
- 取出时,必须阻塞,等待写入
-
队列:
- 特性:FIFO(先进先出)
-
阻塞队列:
BlockingQueue 不是新的东西,和List、Set是同级兄弟
什么时候什么阻塞队列? 多线程并发处理、线程池
学会使用队列:
添加、移除、四组API(抛出异常、不会抛出异常、阻塞等待、超时等待)
四组API:
方式 | 抛出异常 | 不会抛出异常,有返回值 | 阻塞 等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | put() | offer("d",2, TimeUnit.SECONDS) |
移除 | remove() | poll() | take() | poll(2,TimeUnit.SECONDS) |
检测队首元素 | element() | peek() | - | - |
/**
* 抛出异常
*/
public static void test1(){
//队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
//System.out.println(blockingQueue.add("d"));//抛出异常 java.lang.IllegalStateException: Queue full
System.out.println("==================");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());//抛出异常 java.util.NoSuchElementException
}
/**
* 不抛出异常,有返回值
*/
public static void test2(){
//队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("d"));//false 不抛出异常
System.out.println("==================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());//null 不抛出异常
System.out.println("==================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());//null 不抛出异常
}
/**
* 阻塞等待 (一直阻塞)
*/
public static void test3() throws InterruptedException {
//队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
//blockingQueue.put("d");//队列没有位置,一直等待
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());//队列没有元素,一直等待
}
/**
* 阻塞等待 (超时退出)
*/
public static void test4() throws InterruptedException {
//队列的大小
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
blockingQueue.offer("d",2, TimeUnit.SECONDS);//队列没有位置,等待两秒后退出
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));//队列没有元素,等待两秒后退出,如果为空返回null
}
SynchronousQueue 同步队列
不存储元素,没有容量;进去一个元素,必须等待取出之后,才能再放入一个元素!
存:put 取:take
package com.xiaozhi.bq;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* 同步队列
* 和其他的BlokingQueue不一样,SynchronousQueue不存储元素
* put了一个元素,必须从里面先take出来,否则不能再put进去值!
*/
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue();//同步队列
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + "put1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() + "put2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName() + "put3");
blockingQueue.put("3");
}catch (Exception e){
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + ":" + blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + ":" + blockingQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() + ":" + blockingQueue.take());
}catch (Exception e){
e.printStackTrace();
}
},"T2").start();
}
}
学了技术不会用?看得少!
11.线程池(重点)
线程池必会:3大方法、7大参数、4种拒绝策略
池化技术
程序的运行 本质:占用系统的资源!优化资源的使用!=>池化技术
线程池、连接池、内存池、对象池。。。创建和销毁十分浪费资源
池化技术:实现准备好一些资源,有人要用就来我这里拿,用完之后还给我。
线程池的好处:
-
降低资源消耗
-
提高响应速率
-
方便管理
线程复用、可以控制最大并发数、管理线程
线程池:三大方法
OOM:全称“Out Of Memory”,翻译成中文就是“内存用完了”
package com.xiaozhi.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*Executors 工具类:三大方法
*/
public class Demo1 {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
// ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小
ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的,遇强则强,遇弱则弱
try {
for (int i = 0; i < 10; i++) {
//使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + "=>OK");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//线程池用完,程序结束,关闭线程
threadPool.shutdown();
}
}
}
7大参数
源码分析:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,//约等于21亿
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
本质:调用了ThreadPoolExecutor()
public ThreadPoolExecutor(int corePoolSize,//核心线程池大小
int maximumPoolSize,//最大核心线程池大小
long keepAliveTime,//超时了没有人调用就是释放
TimeUnit unit,//超时单位
BlockingQueue<Runnable> workQueue,//阻塞队列
ThreadFactory threadFactory,//线程工厂,创建线程的,一般不用动
RejectedExecutionHandler handler//拒绝策略) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
举例解释:如上图。银行
-
corePoolSize 核心线程池大小
为平时去银行开设的业务窗口数量(在不忙时银行窗口不会全部开)
-
maximumPoolSize 最大核心线程池大小
为银行设有的窗口数量(在客户特别多时,最大可开的窗口数量)
-
keepAliveTime 超时了没有人调用就是释放
当忙完时,等待keepAliveTime 时长,如果不会有人办理业务,将关闭除corePoolSize以外的窗口
-
TimeUnit unit 超时单位
-
BlockingQueue
workQueue 阻塞队列 等候区(设置大小为最大可容多少客户等待)
-
ThreadFactory threadFactory 线程工厂,用于创建线程,一般不用动
-
RejectedExecutionHandler handler 拒绝策略
当业务窗口和等候区人满时,再进入的可会处理策略
手动创建一个线程池
package com.xiaozhi.pool;
import java.util.concurrent.*;
/**
*自定义线程池:七大参数
*/
public class Demo2 {
public static void main(String[] args) {
//自定义线程池,工作中只会用 ThreadPoolExecutor
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
try {
//最大承载= Queue + max
for (int i = 1; i <= 9; i++) {
//使用了线程池之后,使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName() + "=>OK");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//线程用完,程序结束,关闭线程
threadPool.shutdown();
}
}
}
四种拒绝策略
/**
*四大拒绝策略
* new ThreadPoolExecutor.AbortPolicy() 当最大核心线程都被占用,并且阻塞队列也满了。再有进入将不处理此线程,抛出异常java.util.concurrent.RejectedExecutionException
* new ThreadPoolExecutor.CallerRunsPolicy() 当最大核心线程都被占用,并且阻塞队列也满了。再有进入将哪里来的回哪里去
* new ThreadPoolExecutor.DiscardPolicy() 当最大核心线程都被占用,并且阻塞队列也满了。再有进入将丢掉任务,不会抛出异常
* new ThreadPoolExecutor.AbortPolicy() 当最大核心线程都被占用,并且阻塞队列也满了。再有进入将尝试和最早的竞争,最早的结束将被执行,未结束将被丢弃,不会抛出异常
*/
小结和拓展
了解IO密集型和CPU密集型:(调优)
-
线程池的最大线程数(maximumPoolSize)该如何定义?
-
CPU密集型:几核就定义为几,可以保证CPU效率最高
获取CUP核数的方式:
Runtime.getRuntime().availableProcessors()
-
IO密集型:一般设置为你程序十分耗IO的线程数量的2倍
-
12.四大函数式接口(必须掌握)
旧时代的程序员:泛型、枚举、反射、注解
新时代的程序员:lambda表达式、链式编程、函数式接口、stream流式计算
函数式接口:只有一个方法的接口。Runnable接口为典型
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
//FunctionalInterface此注解使用超多
//简化编程模式,在新版本的框架底层大量应用
//foreach(消费者类型的函数式接口)
代码测试:
Function 函数式接口
package com.xiaozhi.function;
import java.util.function.Function;
/**
* Function 函数型接口
* 有一个输入参数,有一个输出
*只要是函数式接口,我们就可以用Lambda表达式简化
*/
public class Demo1 {
public static void main(String[] args) {
//工具类:输出输入的值
/*Function<String, String> function = new Function<String, String>(){
@Override
public String apply(String str) {
return str;
}
};*/
Function<String, String> function =(str)->{return str;};//Lambda表达式简化
System.out.println(function.apply("qwe"));
}
}
Predicate 断定型接口
package com.xiaozhi.function;
import java.util.function.Predicate;
/**
* Predicate:断定型接口
* 有一个输入参数,返回值只能为布尔值
*/
public class Demo2 {
public static void main(String[] args) {
//判断字符串 是否为空
/*Predicate<String> predicate = new Predicate<String>(){
@Override
public boolean test(String o) {
return o.isEmpty();
}
};*/
Predicate<String> predicate = (o)->{ return o.isEmpty(); };
System.out.println(predicate.test(""));
}
}
Consumer 消费型接口
package com.xiaozhi.function;
import java.util.function.Consumer;
/**
* Consumer 消费型接口
* 只有输入没有返回值
*/
public class Demo3 {
public static void main(String[] args) {
/*Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String o) {
System.out.println(o);
}
};*/
Consumer<String> consumer =(o)->{System.out.println(o);};
consumer.accept("asd");
}
}
Supplier 供给型接口
package com.xiaozhi.function;
import java.util.function.Supplier;
/**
* Supplier 供给型接口
*没有参数,只有返回值
*/
public class Demo4 {
public static void main(String[] args) {
/* Supplier<Integer> supplier = new Supplier<Integer>() {
@Override
public Integer get() {
System.out.println("get()");
return 1024;
}
};*/
Supplier<Integer> supplier =()->{
System.out.println("get()");
return 1024;
};
System.out.println(supplier.get());
}
}
13.Stream流式计算
什么是Stream流式计算?
大数据主要:存储+计算
存储:集合、MySQL的本质
计算应该交给流来操作
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
/**
*题目要求:一分钟内完成此题,只能用一行代码实现!
*现在有5个用户!筛选:
*1、ID必须是偶数
*2、年龄必须大子23岁
*3、用户名转为大写字母
*4、用户名字母倒着排序
*5、只输出一个用户!
*/
public class Test {
public static void main(String[] args) {
User u1 = new User(1, "a", 21);
User u2 = new User(2, "b", 22);
User u3 = new User(3, "c", 23);
User u4 = new User(4, "d", 24);
User u5 = new User(6, "e", 25);
//集合就是存储
List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
//计算交给Stream流
Stream<User> stream = list.stream();//将集合转换为stream对象
//用到了 lambda表达式、链式编程、函数式接口、stream流式计算
stream
.filter(u->{return u.getId()%2==0;})
.filter(u->{return u.getAge()>23;})
.map(u->{return u.getName().toUpperCase();})
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
.limit(1)
.forEach(System.out::println);
}
}
package com.xiaozhi.stream;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
//有参、无参构造、get、set、toString方法!
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private int id;
private String name;
private int age;
}
14.ForkJoin详解
什么是ForkJoin
ForkJoin:意思为分支合并
ForkJoin在JDK1.7发行,用于并行执行任务!在大数据量提高效率!一个线程并发成多个操作
大数据:Map Reduce(把大任务拆分为小任务操作做)
ForkJoin特点:工作窃取
这个里面维护的都是双端队列
B执行完会窃取A的任务执行,从而提高效率
ForkJoin操作
package com.xiaozhi.forkJoin;
import java.util.concurrent.RecursiveTask;
/**
* 求和计算的任务
* 人分369等
* 3(常规方法) 6(ForkJoin) 9(Stream并行流)
* 如何使用?
* 1.forkjoinPool通过他执行
* 2.计算任务forkjoinPool.execute(ForkJoinTask<?> task)
* 3.计算类要继承ForkJoinTask
* 4.
*/
public class ForkJoinDemo extends RecursiveTask<Long> {
//ForkJoin方法
private Long start;
private Long end;
//临界值
private Long temp=10000L;
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
//计算方法
@Override
protected Long compute() {
if (end-start<temp){
Long sum=0L;
for (Long i = start; i < end; i++) {
sum+=i;
}
return sum;
}else {//ForkJoin(递归)
//分支合并计算
long middle = (start + end) / 2;//中间值
ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
task1.fork();//拆分任务,把任务压入线程队列
ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
task2.fork();//拆分任务,把任务压入线程队列
long sum = task1.join() + task2.join();//两个分支任务结果相加
return sum;
}
}
}
package com.xiaozhi.forkJoin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;
/**
* 当数据量大时:常规 < ForkJoin < Stream
* 同一任务别人效率高你几十倍
*/
public class Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
test1();//12224
test2();//10038
test3();//153
}
//普通程序员
public static void test1(){
long sum=0L;
long start = System.currentTimeMillis();
for (Long i = 1L; i <= 10_0000_0000; i++) {
sum+=i;
}
long end = System.currentTimeMillis();
System.out.println("test1-->sum" +sum+ "time:" + (end - start));
}
//会使用ForkJoin
public static void test2() throws ExecutionException, InterruptedException {
long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交任务
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("test2-->sum" +sum+ "time:" + (end - start));
}
//Stream并行流
public static void test3(){
long start = System.currentTimeMillis();
long sum = LongStream.rangeClosed(0, 10_0000_0000L).parallel().reduce(0, Long::sum);
long end = System.currentTimeMillis();
System.out.println("test3-->sum" +sum+ "time:" + (end - start));
}
}
15.异步回调
Future 设计的初衷:对将来的某个事件进行建模
package com.xiaozhi.future;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
/**
* 异步调用:CompletableFuture
* 异步执行
* 成功回调
* 失败回调
*
*/
public class Demo1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//没有返回值的 runAsync 异步回调
/*CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "runAsync=>Void");
});
System.out.println("1111");
completableFuture.get();//阻塞获取执行结果*/
//有返回值的 supplyAsync 异步回调
//ajax 成功和失败的回调
//返回的是错误信息
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "supplyAsync=>Integer");
int i=1/0;
return 1024;
});
completableFuture.whenComplete((t,u)->{//编译成功时
System.out.println("t=>" + t);//正常的返回结果
System.out.println("u=>" + u);//错误信息
}).exceptionally((e)->{//编译失败时
System.out.println(e.getMessage());//获取异常详细信息
return 233;//可以获得错误的返回结果
}).get();
}
}
16.JMM
请你谈谈Volatile理解
Volatile时Java虚拟机提供轻量级的同步机制(和synchronized差不多,但是没有synchronized强大)
- 保证可见性
- 不保证原子性
- 禁止指令重排
聊到可见性就谈到JMM
JMM:Java内存模型,不存在的东西,就是一个概念(约定)!
关于JMM同步的约定:
- 线程解锁前,必须把共享变量立刻刷回主存
- 线程加锁前,必须读取主存中的最新值到工作内存中
- 必须保证加锁和解锁是同一把锁
线程分为工作内存和主内存
JMM八种操作:
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
- lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
- unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
- use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
- assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
- store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
- write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存
问题:程序不知道主内存的值已经被修改过了
Volatile可解决此问题
代码:
package tvolatile;
import java.util.concurrent.TimeUnit;
public class JMMDemo {
private static int num=0;
public static void main(String[] args) {//main线程
new Thread(()->{//线程 1
while (num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
/**
* 问题:线程1无法获得main线程修改num后的值。所以导致程序无法停止
*/
}
17.Volatile
验证Volatile三大特性—保证可见性
package tvolatile;
import java.util.concurrent.TimeUnit;
public class JMMDemo {
//不加volatile线程1就会死循环,因为感知不到主内存的变化,加了volatile可以保证可见性
private volatile static int num=0;
public static void main(String[] args) {//main线程
new Thread(()->{//线程 1 对主内存变化不知道
while (num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
}
验证Volatile三大特性—不保证原子性
原子性:ACID原则之一,意为不可分割
线程A 执行任务时,不能被打扰和分割,要么同时成功,要么同时失败。JDBC和MySQL中详细讲到ACID原则
package tvolatile;
/**
* 不保证原子性
*/
public class VDmeo2 {
/*private static int num=0;
public static void add(){
num++;
}
public static void main(String[] args) {
//理论上是2万
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){//main线程 和 GC线程
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "" + num);//main19654
}*/
private volatile static int num=0;//不保证原子性
public static void add(){
num++;
}
public static void main(String[] args) {
//理论上是2万
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){//main线程 和 GC线程
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "" + num);//main19000
}
/*private static int num=0;//保证原子性
public synchronized static void add(){
num++;
}
public static void main(String[] args) {
//理论上是2万
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){//main线程 和 GC线程
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "" + num);//main20000
}*/
}
如果不加lock或synchronized怎么样保证原子性
使用原子类,解决原子性问题(锁更耗费资源)
//源自类操作
private volatile static AtomicInteger num=new AtomicInteger();//保证原子性
public static void add(){
num.getAndIncrement();//AtomicInteger+1,Increment为+1方法,并不是简单的+1操作,用的是CAS(CPU的并发原理效率极高)
}
public static void main(String[] args) {
//理论上是2万
for (int i = 1; i <= 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){//main线程 和 GC线程
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "" + num);//main19000
}
这些类的底层都直接和操作系统挂钩!在内存中修改值!Unsafe类是个很特殊的存在
验证Volatile三大特性—禁止指令重排
什么是指令重排:你写的程序,计算机并不是按照你写的那样执行的
源代码—>编译器优化的重排—>指令并行也可能会重排—>内存系统也会重排—>执行
处理器在进行指令重排的时候,考虑:数据之间的依赖性
int x=1; //1
int y=2; //2
x=x+5; //3
y=x*x; //4
我们期望的执行顺序是1234 但是可能执行的时候会变成2134 1324
但不可能是4123!
可能造成影响的结果:a b x y 这四个值默认都为0;
线程A | 线程B |
---|---|
x=a | y=b |
b=1 | a=2 |
正常结果:x=0; y=0; 但是可能由于指令重排造成:
线程A | 线程B |
---|---|
b=1 | a=2 |
x=a | y=b |
指令重排导致的诡异结果:x=2;y=1;
volatile可以避免指令重排:
- 内存屏障、CPU指令,作用:
-
保证特定的操作的执行顺序
-
可以保证某些变量的内存可见性(利用这些特性,volatile实现了可见性)
只要加了Volatile就会在前后加油一道内存屏障
Volatile是可以保证可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
Volatile内存屏障在单例模式中使用的最多
18.彻底玩转单例模式
- 饿汉式
- 懒汉式
- DCL懒汉式用到了内存屏障
搞懂单例模式、搞懂枚举(可以避免单例模式被破坏)
饿汉式
package com.xiaozhi.single;
/**
* 饿汉式(一上来就把这个对象进行加载)
*/
public class Hungry {
//由于一上来就加载,可能浪费空间
private byte[]data1=new byte[1024*1024];
private byte[]data2=new byte[1024*1024];
private byte[]data3=new byte[1024*1024];
private byte[]data4=new byte[1024*1024];
//私有构造器,别人无法new此对象,保证内存中只有一个对象
private Hungry(){
}
//不管怎么样先new对象
private final static Hungry HUNGRY=new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式
package com.xiaozhi.single;
/**
* 懒汉式单例模式
* 单线程下线程安全,并发情况有问题
* 不是一上来就加载对象,而实用到时才会加载
*/
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName() + "OK");
}
/*//不安全的懒汉式单例模式
private static LazyMan lazyMan;
public static LazyMan getInstance(){
if (lazyMan==null){
lazyMan=new LazyMan();
}
return lazyMan;
}*/
//双重检测锁模式的懒汉式单例 DCL懒汉式
private volatile static LazyMan lazyMan;//需要避免指令重排
public static LazyMan getInstance(){
//加锁
if (lazyMan==null){
synchronized (LazyMan.class){//锁当前对象,使对象只有一个
if (lazyMan==null){
lazyMan=new LazyMan();//不是原子性操作
/*
1.分配内存空间
2.执行构造方法,初始化对象
3.把这个对象指向这个空间
理想123顺序
可能132顺序 A线程,如果有B线程进入,B会判断为!=null,没完成构造时返回
*/
}
}
}
return lazyMan;
}
//多线程并发
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
通过反射破获加锁的懒汉式单例模式(并发安全)
package com.xiaozhi.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* 通过反射破解安全的懒汉式
* 任何代码和关键字在反射面前都是不安全的
*/
public class Demo4 {
private Demo4(){
//解决方案
synchronized (Demo4.class){
if (lazyMan!=null){
throw new RuntimeException("不要试图通过反射破坏");
}
}
}
//双重检测锁模式的懒汉式单例 DCL懒汉式
private volatile static Demo4 lazyMan;//需要避免指令重排
public static Demo4 getInstance(){
//加锁
if (lazyMan==null){
synchronized (LazyMan.class){//锁当前对象,使对象只有一个
if (lazyMan==null){
lazyMan=new Demo4();//不是原子性操作
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Demo4 instance = Demo4.getInstance();
Constructor<Demo4> declaredConstructor = Demo4.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//无视私有
Demo4 instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
当不用Demo4.getInstance();方式创建对象时,用declaredConstructor.newInstance();创建对象将出现新的问题
解决方案
package com.xiaozhi.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* 通过反射破解安全的懒汉式
* 任何代码和关键字在反射面前都是不安全的
*/
public class Demo5 {
//解决方案:添加红绿灯
private static boolean xiaozhi=false;
private Demo5(){
if (xiaozhi==false){
xiaozhi=true;
}else {
throw new RuntimeException("不要试图通过反射破坏");
}
}
//双重检测锁模式的懒汉式单例 DCL懒汉式
private volatile static Demo5 lazyMan;//需要避免指令重排
public static Demo5 getInstance(){
//加锁
if (lazyMan==null){
synchronized (LazyMan.class){//锁当前对象,使对象只有一个
if (lazyMan==null){
lazyMan=new Demo5();//不是原子性操作
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
/*Demo4 instance = Demo4.getInstance();*/
Constructor<Demo5> declaredConstructor = Demo5.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//无视私有
Demo5 instance = declaredConstructor.newInstance();
Demo5 instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
若通过反编译将其破解则不安全
package com.xiaozhi.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
/**
* 通过反射破解安全的懒汉式
* 任何代码和关键字在反射面前都是不安全的
* 道高一尺魔高一丈
*/
public class Demo5 {
//解决方案:添加红绿灯
private static boolean xiaozhi=false;
private Demo5(){
if (xiaozhi==false){
xiaozhi=true;
}else {
throw new RuntimeException("不要试图通过反射破坏");
}
}
//双重检测锁模式的懒汉式单例 DCL懒汉式
private volatile static Demo5 lazyMan;//需要避免指令重排
public static Demo5 getInstance(){
//加锁
if (lazyMan==null){
synchronized (LazyMan.class){//锁当前对象,使对象只有一个
if (lazyMan==null){
lazyMan=new Demo5();//不是原子性操作
}
}
}
return lazyMan;
}
//反射
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Field xiaozhi = Demo5.class.getDeclaredField("xiaozhi");//获取字段
xiaozhi.setAccessible(true);//无视私有
Constructor<Demo5> declaredConstructor = Demo5.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//无视私有
Demo5 instance = declaredConstructor.newInstance();
xiaozhi.set(instance,false);//更改值
Demo5 instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
反射无法攻破枚举验证
package com.xiaozhi.single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* enum 是一个什么?本身也是一个class类
*/
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
枚举的最终反编译源码:
// Decompiled by Jad v1.5.8e2. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://kpdus.tripod.com/jad.html
// Decompiler options: packimports(3) fieldsfirst ansi space
// Source File Name: EnumSingle.java
package com.xiaozhi.single;
public final class EnumSingle extends Enum
{
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/xiaozhi/single/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
静态内部类
package com.xiaozhi.single;
/**
* 静态内部类
*/
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER=new Holder();
}
}
19.深入理解CAS
什么是CAS
大厂必须深入研究底层!有所突破!修内功!操作系统、计算机网络框架
CAS为compareAndSet的缩写。
package com.xiaozhi.cas;
import java.util.concurrent.atomic.AtomicInteger;
/**
* CAS:CPU的并发原语
* 原子类的底层是CAS
*/
public class CASDemo {
public static void main(String[] args) {
//atomicInteger是原子类的Integer
AtomicInteger atomicInteger=new AtomicInteger(2020);//初始值
//public final boolean compareAndSet(int expect, int update)
//我期望的值是2020,如果初始值达到则更新为2021,否则不更新
atomicInteger.compareAndSet(2020,2021);//比较并交换!,返回值为boolean值
System.out.println(atomicInteger.get());
atomicInteger.getAndIncrement();
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
Unsafe类
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望值,那么则执行操作(更新值)!如果不是就一直循环!
缺点:
- 由于底层是自旋锁,会一直循环,耗时
- 一次性只能保证一个共享变量的原子性
- ABA问题
CAS:ABA问题(狸猫换太子)
ABA问题:有两条线程都获取到初始值为1,B线程经过两次cas操作后,A线程在进行cas操作时并不知道,现在A的值是B经过修改的值(1—>3—>1),还以为是最初A本身的1
package com.xiaozhi.cas;
import java.util.concurrent.atomic.AtomicInteger;
/**
* CAS:CPU的并发原语
* 原子类的底层是CAS
*/
public class CASDemo1 {
public static void main(String[] args) {
//atomicInteger是原子类的Integer
AtomicInteger atomicInteger=new AtomicInteger(2020);//初始值
//public final boolean compareAndSet(int expect, int update)
//我期望的值是2020,如果初始值达到则更新为2021,否则不更新
//=================捣乱的线程=========================
atomicInteger.compareAndSet(2020,2021);//比较并交换!,返回值为boolean值
System.out.println(atomicInteger.get());
atomicInteger.compareAndSet(2021,2020);//比较并交换!,返回值为boolean值
System.out.println(atomicInteger.get());
//====================期望的线程=======================
atomicInteger.getAndIncrement();
System.out.println(atomicInteger.compareAndSet(2020, 666));
System.out.println(atomicInteger.get());
}
}
解决方案:运用原子引用
20.原子引用
解决ABA问题,引入原子引用,对应的思想就是一个乐观锁
带版本号的原子操作!
package com.xiaozhi.cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* 原子引用:和乐观锁的原理相同
* 注意:如果泛型是一个包装类,注意对象的引用问题
* 正常在业务操作中,比较的是一个对象,而不是Integer
*/
public class CASDemo2 {
public static void main(String[] args) {
AtomicStampedReference<Integer> atom = new AtomicStampedReference<>(1, 1);
new Thread(()->{
int stamp = atom.getStamp();//获得目前的版本号(initialStamp)
System.out.println("A1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atom.compareAndSet(1, 2
, atom.getStamp(), atom.getStamp() + 1));
System.out.println("A2=>" + atom.getStamp());
System.out.println(atom.compareAndSet(2, 1
, atom.getStamp(), atom.getStamp() + 1));
System.out.println("A3=>" + atom.getStamp());
},"A").start();
new Thread(()->{
int stamp = atom.getStamp();//获得目前的版本号(initialStamp)
System.out.println("B1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atom.compareAndSet(1, 6
, stamp, stamp + 1));
System.out.println("B2=>" + atom.getStamp());//打印最新版本号
},"B").start();
}
}
注意:
Integer使用了对象缓存机制,默认范围是 -128—127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间
21.各种锁的理解
1.公平锁、非公平锁
公平锁:非常公平,不能插队,必须先来后到
//代码
Lock lock = new ReentrantLock(true);
//源码
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
非公平锁:非常不公平,可插队,(默认都是非公平)例如:两个任务耗时:3s,3h,这种情况3s任务是可以插队的
//代码
Lock lock = new ReentrantLock();
//源码
public ReentrantLock() {
sync = new NonfairSync();
}
公平锁和非公平锁—在Lock锁章节有讲解
2.可重入锁
所有的锁都是可重入锁(递归锁)
synchronized版
package com.xiaozhi.lock;
/**
* synchronized 版可重入锁
*/
public class Demo1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
//可重入锁
public synchronized void sms(){
System.out.println(Thread.currentThread().getName() + "sms");
call();//这里有锁
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName() + "call");
}
}
Lock版
package com.xiaozhi.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Lock版可重入锁
* 在使用lock锁时,锁必须配对,否则容易发生死锁现象
*/
public class Demo2 {
public static void main(String[] args) {
Phone2 phone2 = new Phone2();
new Thread(()->{
phone2.sms();
},"A").start();
new Thread(()->{
phone2.sms();
},"B").start();
}
}
class Phone2{
Lock lock=new ReentrantLock();
//可重入锁 //与synchronized相比,这里是两把锁
public void sms(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "sms");
call();//这里有锁
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "call");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
注:在使用lock锁时,锁必须配对,否则容易发生死锁现象
3.自旋锁(spinLock)
自旋锁:不断地循环迭代,直到成功为止
自定义自旋锁
package com.xiaozhi.lock;
import java.util.concurrent.atomic.AtomicReference;
/**
* 自旋锁
*/
public class SpinLockDemo {
AtomicReference<Thread>atomicReference=new AtomicReference<>();
//加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "==> myLock");
//自旋锁
while (!atomicReference.compareAndSet(null,thread)){
}
}
//解锁
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "myUnLock");
atomicReference.compareAndSet(thread,null);
}
}
测试自定义自旋锁
package com.xiaozhi.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
/*ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
reentrantLock.unlock();*/
//底层使用的自旋锁,CAS实现
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
spinLockDemo.myUnLock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
spinLockDemo.myUnLock();
}
},"T2").start();
}
}
运行结果:
4.死锁
死锁是什么?
相当于两个线程互相抢夺资源
死锁测试,怎么排除死锁?
package com.xiaozhi.lock;
import java.util.concurrent.TimeUnit;
/**
* 死锁
*/
public class DeadLockDemo {
public static void main(String[] args) {
String lockA="lockA";
String lockB="lockB";
new Thread(new MyThread(lockA,lockB),"T1").start();
new Thread(new MyThread(lockB,lockA),"T2").start();
}
}
class MyThread implements Runnable{
private String lockA;
private String lockB;
public MyThread(String lockA,String lockB){
this.lockA=lockA;
this.lockB=lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName() + "lock:" + lockA + "=>" + lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName() + "lock:" + lockB + "=>" + lockA);
}
}
}
}
解决问题
-
使用jps-l 定位进程号
-
使用jstack 进程号找到死锁问题
面试或者工作中排查问题方法:
- 看日志
- 堆栈信息
死锁产生的4个必要条件:
- 互斥: 某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
- 占有且等待: 一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
- 不可抢占: 别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
- 循环等待: 存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。
当以上四个条件均满足,必然会造成死锁,发生死锁的进程无法进行下去,它们所持有的资源也无法释放。这样会导致CPU的吞吐量下降。所以死锁情况是会浪费系统资源和影响计算机的使用性能的。那么,解决死锁问题就是相当有必要的了。
注:本文是我在再次温习注解和反射的过程中记下的笔记。谢谢大家的阅读。如有不足之处,请留言,QQ:825888114!