JavaJUC学习篇

什么是JUC

java.util 工具包、包、分类

线程和进程

进程:一个程序,比如qq,酷我音乐啥的一个进程往往包含一个多个线程。

线程:java默认两个线程一个main,一个GC

java线程实现的方式:

继承Thread,实现Runnable接口,实现callable

并行和并发

并行:多线程操作处理同一个资源,或者说多个人完成同一件事情

并发:为了达到某件事,每个人负责不同的模块然后完成事情。

线程的状态:

创建 —》 就绪 —》 运行 —》阻塞 —>死亡

(我们通常分为五种,但是有些情况下分为7种,加的两种为等待和超时等待)

wait和sleep的区别

首先:

wait来自Object

sleep来自Thread

其次:

wait会释放锁

sleep不会

使用范围不一样:

wait必须放到同步块中

sleep没有要求

Lock和Synchonized

传统的synchonized锁

synchonized(){

}

括号里边一般两种

1对象

2类名.class

还有 private synchonized void name(){}

Lock接口

ReentrantLock: 可重用锁

readLock: 读锁

writeLock:写锁

Synchronized 和 Lock 区别

1、Synchronized 内置的Java关键字, Lock 是一个Java类

2、Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁

3、Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁

4、Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下

去;

5、Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以 判断锁,非公平(可以

自己设置);

6、Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!

生产者消费者问题

生产者和消费者问题Synchronized版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
package com.xh.juc.day02;


/**
* @Title
* @Description
* @author 罗小黑
* 线程之间的通信问题,常用的是生产者和消费者问题。 等待唤醒,通知唤醒。
* 线程交替执行 A B操作同一个变量 num = 0;
* A num + 1
* B num - 1
* @return
* @date 2022/10/2 16:29
* @email 2844135670@qq.com
*/
public class A {
public static void main(String[] args) {
Date date = new Date();

new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();

new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();
}
}

// 生产者消费者: 判断等待 ,业务 , 通知
class Date{
//数字资源锁
private int number = 0;

public synchronized void increment() throws InterruptedException {
if(number != 0){
//等待
this.wait();
}

//通知其他线程我加1完毕了
number ++ ;
System.out.println(Thread.currentThread().getName() + "=>" +number);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
if(number == 0){
//等待
this.wait();
}
number --;
//通知其他线程我-1完毕了
System.out.println(Thread.currentThread().getName() + "=>" +number);
this.notifyAll();
}
}

生产者消费者问题:两个还好,但是到了三个或者四个就会产生问题

面试常问: 单例模式,排序算法,生产者消费者问题,死锁

问题存在:A,B,C,D四个线程的情况下出现问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class B {

public static void main(String[] args) {
Date date = new Date();

//创建线程A进行加
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
date.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();

new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
date.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();

new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"C").start();

new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
date.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"D").start();
}
}

A=>1
C=>0
B=>1
A=>2
B=>3
C=>2
C=>1
C=>0
B=>1
A=>2
B=>3
C=>2
B=>3
A=>4
D=>3
D=>2
D=>1
D=>0
A=>1
D=>0

以上原因是由于线程产生了虚假唤醒,导致出现问题。

==解决办法,我们将if改成while判断==

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Date1{
//数字资源锁
private int number = 0;

public synchronized void increment() throws InterruptedException {
while(number != 0){//if改成while解决线程虚拟唤醒
//等待
this.wait();
}
//通知其他线程我加1完毕了
number ++ ;
System.out.println(Thread.currentThread().getName() + "=>" +number);
this.notifyAll();
}
public synchronized void decrement() throws InterruptedException {
while(number == 0){//改成while解决线程虚拟唤醒
//等待
this.wait();
}
number --;
//通知其他线程我-1完毕了
System.out.println(Thread.currentThread().getName() + "=>" +number);
this.notifyAll();
}
}

Lock解决生产者和消费者问题JUC版本的

基本实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
public class C {
public static void main(String[] args) {
Data data = new Data();

new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"B").start();

new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"C").start();

new Thread(()->{
for (int i = 0; i < 5; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
},"D").start();

}
}

class Data{
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

//加一操作
/* condition.await();//等待相当于wait
condition.signalAll();//唤醒全部*/
public void increment () throws InterruptedException {

try {
lock.lock();
while(number != 0){
condition.await();
}
number ++;

System.out.println(Thread.currentThread().getName()+"=>"+ number);
condition.signalAll();

}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}


}

//减一操作
public void decrement () throws InterruptedException {
try {
lock.lock();
while (number == 0) {
condition.await();
}
number--;

System.out.println(Thread.currentThread().getName() +"=>"+ number);
condition.signalAll();

} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

任何一个新的技术绝对不是仅仅覆盖了一起的技术,一定会在原来的技术上进行相应的拓展

Condtion实现精准唤醒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class D {
public static void main(String[] args) {
Data2 data2 = new Data2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data2.printA();
}
},"A").start();;
new Thread(()->{
for (int i = 0; i < 10; i++) {
data2.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data2.printC();
}
},"C").start();;
}
}

class Data2{
private int number = 1; //1A 2B 3C
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
public void printA(){
lock.lock();
try{
//业务,判断 ->执行 ->通知
while(number != 1){
//等待
condition.await();
}
System.out.println(Thread.currentThread().getName()+ "==>AA" );
number = 2;
condition1.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try{
//业务,判断 ->执行 ->通知
while ( number != 2){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+ "==>BB");
number = 3;
condition2.signal();

}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try{
//业务,判断 ->执行 ->通知
while (number != 3){
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "==>CC");
number =1;
condition.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}

什么是锁,如何判断锁对象(8种锁):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Phone phone =new Phone();
//并不是谁先谁后,而是由于锁的存在,谁先拿到谁执行
new Thread(()->{
try {
phone.sendSms();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"A").start();

TimeUnit.SECONDS.sleep(1);//休眠一秒,比较谁快

new Thread(()->{
phone.sendTel();
},"B").start();
}
}

class Phone{

public synchronized void sendSms() throws InterruptedException {

TimeUnit.SECONDS.sleep(2);
System.out.println("发短信");
}
public synchronized void sendTel(){
System.out.println("打电话");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Test2 {
public static void main(String[] args) throws InterruptedException {
//创建两个不同的对象,每个对象都 持有一把锁,故而有两把锁
Phone2 phone2 =new Phone2();
Phone phone = new Phone();
//并不是谁先谁后,而是由于锁的存在,谁先拿到谁执行
new Thread(()->{
try {
//存在锁 phone2.sendSms();
phone.sendSms();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"A").start();

TimeUnit.SECONDS.sleep(1);//休眠一秒,比较谁快

new Thread(()->{
// phone2.hello();
phone2.sendTel();
},"B").start();
}
}

class Phone2{
//synchronized锁的对象时方法的调用者
public synchronized void sendSms() throws InterruptedException {

TimeUnit.SECONDS.sleep(2);
System.out.println("发短信");
}
public synchronized void sendTel(){
System.out.println("打电话");
}

//没有加锁,普通方法,故而没有收到锁的限制
public void hello(){
System.out.println("说hello");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Test3 {
public static void main(String[] args) throws InterruptedException {
//加了static保证锁住类,而不是之前的对象了
Phone3 phone3 =new Phone3();
Phone phone = new Phone();
new Thread(()->{
try {
//存在锁 phone2.sendSms();
phone.sendSms();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"A").start();

TimeUnit.SECONDS.sleep(1);//休眠一秒,比较谁快

new Thread(()->{
// phone2.hello();
phone3.sendTel();
},"B").start();
}
}

class Phone3{
//synchronized锁的对象时方法的调用者
//static类一加载就有了 此时锁的是类而不是一开始的对象了锁的是Phone3.class
public static synchronized void sendSms() throws InterruptedException {

TimeUnit.SECONDS.sleep(2);
System.out.println("发短信");
}
public static synchronized void sendTel(){
System.out.println("打电话");
}

//没有加锁,普通方法,故而没有收到锁的限制
public void hello(){
System.out.println("说hello");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.util.concurrent.TimeUnit;

public class Test4 {
public static void main(String[] args) {
Phone4 phone4 = new Phone4();
Phone2 phone2 = new Phone2();
new Thread(()->{
try {
phone4.sendSms();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();

new Thread(()->{
phone2.sendTel();
}).start();

}
}

class Phone4{
public static synchronized void sendSms() throws InterruptedException {
TimeUnit.SECONDS.sleep(2);
System.out.println("发短信");
}

public synchronized void call(){
System.out.println("打电话");
}
}

new this 具体的一个手机

static Class 唯一的一个模板

集合类是不安全的

List

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//java.util.ConcurrentModificationException  并发修改异常
public class ListTest {
public static void main(String[] args) {
//并发下ArrayList是不安全的。 java.util.ConcurrentModificationException
/*
解决办法,使用Vector可以保证不报错
List<String> list = new Vector<>();

解决方法二,使用Collection将ArrayList转变成安全的,使用锁机制
List<String> list = Collections.synchronizedList(new ArrayList<>());

解决办法三:使用JUC下的CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>(); //JUC里边自带的解决办法
*/
//copyOnWrite写入时复制 COW计算程序领域的一种优化策略
//多个线程调用的时候,list,读取的时候固定的,写入(覆盖)
//写入时候避免覆盖,造成数据问题
// copyOnWrite 比vector nb在哪?
// List<String> list = new ArrayList<>();
List<String> list = new CopyOnWriteArrayList<>(); //JUC里边自带的解决办法
for (int i = 1; i <= 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}

Set的不安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* @Title java.util.ConcurrentModificationException
* @Description 同理可得还是出现并发修改异常
* @author 罗小黑
* @date 2022/10/5 14:22
* @email 2844135670@qq.com
*/
public class SetTest {
public static void main(String[] args) {
//解决办法,还是如以前一样
// Set<String> set = new HashSet<>();
//方法一使用Collection.synchronizedSet方法解决
// Set<String> set = Collections.synchronizedSet(new HashSet<>());
//方法二使用JUC下的Copy...
//Set<String > set = new CopyOnWriteArraySet<>();
Set<String > set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
}

HashSet的底层就是HashMap

1
2
3
4
5
6
7
8
9
  public HashSet() {
map = new HashMap<>();
}

//add set 本质上就是map key 是无法重复的
public boolean add(E e){
return map.put(e,PRESENT) == null;
}
private static final object PRESENT = new Object(); //不可变的值

HashMap

回顾hashMap的 简单用法加载因子为0.75

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//ConcurrentModificationException存在并发修改异常
public class HashMapTest {
public static void main(String[] args) {
//map 是这样子用的吗?不是,工作中不用HashMap(16,0.75)
//默认等价干什么?

////ConcurrentModificationException存在并发修改异常
//Map<String ,Object> map = new HashMap<>();
//解决办法一,还是使用 Map<String ,String> map = Collections.synchronizedMap(new HashMap<>());
//Map<String ,String> map = Collections.synchronizedMap(new HashMap<>());

//解决办法二
Map<String,String> map = new ConcurrentHashMap<>();

for (int i = 0; i < 10; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}

Callable重点讲解

  1. 有返回值
  2. 可以抛出异常
  3. 方法不同,run()/call()

代码测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class CallabelTest {
//两者对比,看到这里实现的Runnable接口没有返回值
//new Thread (new Runnable()).start();
//new Thread (new FutureTask<V>()).start();
//new Thread (new FutureTask<V>(Callable)).start();
public static void main(String[] args) throws ExecutionException, InterruptedException {
new Thread().start(); //怎么启动Callable
MyThread1 thread = new MyThread1();
FutureTask futureTask = new FutureTask(thread);
// 适配类
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start(); //结果会被缓存,效率会高
Integer o = (Integer)futureTask.get();//获取callbale的返回结果
System.out.println(o);
}
}

//实现Runnable接口
class MyThread implements Runnable{
@Override
public void run() {
}
}

//实现Callable接口
class MyThread1 implements Callable<Integer>{

//对比发现,这里实现的Callable接口是存在返回值的
@Override
public Integer call() throws Exception {
System.out.println("Call()");
return 123;
}
}

细节:

  1. 存在缓存
  2. 结果可能需要等待,会阻塞

常用的辅助类(必须掌握)

CountDownLatch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//计数器
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
//总数是6,必须要执行任务的时候,再使用
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
new Thread( ()->{
System.out.println(Thread.currentThread().getName() + " \t GO OUT");
countDownLatch.countDown(); //数量减1
},String.valueOf(i)).start();
}
countDownLatch.await(); //等待计数器归零,让向下执行
//保证数据全部执行之后才将程序结束
//countDownLatch.countDown(); // -1
System.out.println("关门");
}
}

减法计数器:

原理是数量减一:

1
countDownLatch.countDown(); // -1
1
countDownLatch.await(); //等待计数器归零,让向下执行

每次有线程调用countDown()数量1,假设计数器变为0,countDownLatch.await就会被唤醒,继续执行!

CycliBarrier: 加法计数器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CycliBarrierDemo {
public static void main(String[] args) {
//如果这里的数值没有在for循环中体现的话,就不能结束事件,也就会一直运行,占用程序运行
CyclicBarrier cyclicBarrier = new CyclicBarrier(6,()->{
System.out.println("事件完成");
});
//加法计数器
for (int i = 1; i < 7; i++) {
final int temp = i;
//lambda能操作到i吗 --不能直接操作到
new Thread( ()->{
System.out.println(Thread.currentThread().getName() + "收集"+ temp + "个龙珠"); //我们发现是操作不到i的
try {
cyclicBarrier.await(); //等待
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
}).start();
}
}
}

Semaphore : 信号量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class SemaphoreDemo {
public static void main(String[] args) {
//线程数量;停车位,限流
Semaphore semaphore = new Semaphore(6);

for (int i = 0; i < 6; i++) {
new Thread(()->{
//acquire()得到
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+ "抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+ "离开车位");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
semaphore.release();
}
//release()释放
},String.valueOf(i)).start();
}
}
}

原理:

semaphore.acquire();获得,假如已经满了,等待,等待被释放

semaphore.release();释放,会将当前的信号量释放 +1 ,然后唤醒等待的线程。

读写锁

ReadWriteLock:读可以被多个线程读,但写的时候只能是一个线程写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
public class ReadWriteLockDemo {

public static void main(String[] args) {

MyCacheLock myCache = new MyCacheLock();
for (int i = 0; i < 5; i++) {
final int temp = i; //lamada表达式中不能直接读取i,只能通过这样的转换
//只做写入的操作
new Thread(()->{
myCache.put(temp + "",temp +"");
},String.valueOf(i)).start();
}

//只做读取的操作
for (int i = 0; i < 5; i++) {
final int temp = i; //lamada表达式中不能直接读取i,只能通过这样的转换
//只做写入的操作
new Thread(()->{
myCache.get(temp + "");
},String.valueOf(i)).start();
}

}
}


//未加锁导致,我在写入1的时候由于线程影响可能会产生1没写入完成,
// 二也进行写入了
class MyCache{
private volatile Map<String,Object> map = new HashMap<>();

//这个是一个存的过程
public void put(String key , Object obj){
System.out.println(Thread.currentThread().getName()+ "写入"+ key);
map.put(key,obj);
System.out.println(Thread.currentThread().getName()+"写入成功");
}

//这个是一个取的过程
public void get(String key){
System.out.println(Thread.currentThread().getName()+ "读取"+ key);
Object obj = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取成功");
}
}

class MyCacheLock{
private volatile Map<String,Object> map = new HashMap<>();

//private Lock lock = new ReentrantLock(); //普通锁
//读写锁,存写入的时候,只希望同时只有一个线程写
private ReadWriteLock lock = new ReentrantReadWriteLock();
//这个是一个存的过程
public void put(String key , Object obj){

//这个时候就是进行枷锁了
lock.writeLock().lock();
try{
System.out.println(Thread.currentThread().getName()+ "写入"+ key);
map.put(key,obj);
System.out.println(Thread.currentThread().getName()+"写入成功");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.writeLock().unlock();
}
}

//这个是一个取的过程
public void get(String key){

//加上读锁
lock.readLock().lock();

try{
System.out.println(Thread.currentThread().getName()+ "读取"+ key);
Object obj = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取成功");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.readLock().unlock();
}
}

}

阻塞队列

队列(FIFO)

写入的时候:如果队列是满的,那么久必须阻塞等待。

取出来的时候:如果队列是空的,那么必须阻塞等待生产。

BlockingQueue

在线程下,我们使用的时候需要注意阻塞队列是多线程下并发处理,线程池处理。

学会使用队列

添加,移除

四组API(熟练掌握并使用 )

方式 抛出异常 不会抛出异常 阻塞等待 超时等待
添加 add offer put offer(,,)
移除 remove poll take poll(,)
判断队列首位 element peek
  1. 抛出异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class BlockingQueueDemo {
/* Collections
List
Set
*/
public static void main(String[] args) {
test1();
}
/*
抛出异常
*/
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"));
/*由于我定义的是3个值,故而如果添加四个值的话导致保持
队列满了,这就说明和我们创建一个数组一样,定于一个大小,超出这个大小就好报错
抛出异常 Queue full*/
// System.out.println(blockingQueue.add("d"));
//讲队列的所有添加值全部移除,使得队列为空
// 一次只能移除一个
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
/*
次数队列为空,我们可以发现将会产生问题
java.util.NoSuchElementException
*/
System.out.println(blockingQueue.remove());
}
}
  1. 不会抛出异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
不抛出异常
*/
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(blockingQueue.peek()); //判断队首元素
System.out.println("===========================");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll()); //null
}
  1. 阻塞 , 等待
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
等待,阻塞(一直阻塞)
*/
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());
}

  1. 超时等待
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
超时等待
*/
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());
blockingQueue.poll(2,TimeUnit.SECONDS);//超过2秒钟就退出
}

SynchronousQueue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*
同步队列
和其他的是不一样的,SynchronousQueue不存储元素
put了一个元素,必须从里边先take出来,否则不能在put进去值
*/
public class SynchronousQueueDemo {
public static void main(String[] args) {
//同步队列
SynchronousQueue synchronousQueue = new SynchronousQueue<>();

new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + "put 1" );
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName() + "put 2" );
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName() + "put 3" );
synchronousQueue.put("3");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"T1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() +"=>"+ synchronousQueue.take() );
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName() +"=>"+ synchronousQueue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+ synchronousQueue.take() );
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"T2").start();
}
}

线程池(重点)

池化技术

程序的运行,本质:占用系统的资源,优化资源的使用!=>池化技术

线程池,连接池,内存池,对象池。。。 创建,销毁,十分浪费资源

池化技术,事先准备好一些资源,有人要用,就可以来我这里拿,用完之后还给我

线程池的好处

  1. 降低资源的消耗
  2. 提高响应的速度
  3. 方便管理

线程复用,可以控制最大的并发数,管理线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
Executor 工具类,里边有三大方法
*/
public class Demo1 {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor();//创建一个单例的线程池
ExecutorService threadPool = Executors.newCachedThreadPool();//创建一个缓存的线程池,遇强则强,遇弱则弱
//ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定的线程大小

//TODO 线程使用完毕是要关闭的
try {
for (int i = 0; i < 10; i++) {
final int temp = i;
//new Thread(()->{}).start();//常规的创建线程的方式
//现在线程池的创建方式 threadPool.execute(()->{});
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+ "=>" + "OK");
});
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
threadPool.shutdown();
}
}
}

7大参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
   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>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样 的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors 返回的线程池对象的弊端如下:

1)FixedThreadPool 和 SingleThreadPool:

允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2)CachedThreadPool 和 ScheduledThreadPool:

允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/*
Executor 工具类,里边有三大方法

七大参数
四种拒绝策略
new ThreadPoolExecutor.AbortPolicy() //银行满了,但还有人进去
new ThreadPoolExecutor.CallerRunsPolicy() //哪来的去哪里
new ThreadPoolExecutor.DiscardPolicy() //队列满了不会抛出异常,会丢到任务
new ThreadPoolExecutor.DiscardOldestPolicy()//队列满了,尝试和最早的去竞争
*/
public class Demo1 {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newSingleThreadExecutor();//创建一个单例的线程池
// ExecutorService threadPool = Executors.newCachedThreadPool();//创建一个缓存的线程池,遇强则强,遇弱则弱
//ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定的线程大小
/*
七大参数 ,自定义线程池,一般是工作中使用,因为使用工具了类会出现问题
*/
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy()//队列满了,尝试和最早的去竞争
);
//TODO 线程使用完毕是要关闭的
try {
for (int i = 0; i < 10; i++) {
final int temp = i;
//new Thread(()->{}).start();//常规的创建线程的方式
//现在线程池的创建方式 threadPool.execute(()->{});
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+ "=>" + "OK");
});
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
threadPool.shutdown();
}
}
}

线程池大小如何去设置

CPU密集型和IO密集型(调优)

1
2
3
4
5
6
7
8
/*
最大线程数应该如何定义?
IO密集型 十分占用资源 程序 , 15个类型任务 判断你的程序中判断程序中占用IO的线程
CPU密集型 ,几核就是几,可以保持CPU的效率最高
*/
//输出CPU的核心数
System.out.println(Runtime.getRuntime().availableProcessors());

四大函数接口(必需掌握)

新时代的程序员需要掌握的lambda表达式,链式编程,函数式接口,Stream流式计算

函数式接口:只有一个方法的接口

比如:Runnable

简化开发模型,在新版本中大量使用

比如foreach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
* @Title 函数式接口
* @Description Function
* 有一个输入参数,有一个输出
* 只要是 函数式接口,那么我们都可以用lambda 表达式简化
*
* @author 罗小黑
* @param null
* @return
* @date 2022/10/7 18:38
* @email 2844135670@qq.com
*/
public class FunctionDemo {
public static void main(String[] args) {
Function function = new Function<String,String>(){

//工具类,输出输入的值
@Override
public String apply(String o) {
return o;
}
};
Function function1 = (str)->{ return str;};
System.out.println(function.apply("asd"));
System.out.println(function1.apply("add"));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//断定性接口;有一个输入参数,返回值只能是布尔值
public class PredicateDemo {
public static void main(String[] args) {
Predicate predicate = new Predicate<String >(){

//判断字符串是否为空
@Override
public boolean test(String o) {

return o.isEmpty();
}
};

//注意一下,这里泛型需要定义一下
Predicate<String> predicate1 = (str)->{return str.isEmpty();};
System.out.println(predicate.test("aad"));
System.out.println(predicate1.test(""));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
消费型接口
只有输入,没有返回值
*/
public class ConsumerDemo {
public static void main(String[] args) {
Consumer consumer = new Consumer<String>() {
@Override
public void accept(String o) {
System.out.println(o);
}
};
Consumer<String> consumer1 = (str)->{
System.out.println(str);
};
consumer.accept("你好");
consumer1.accept("我喜欢你");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//供给型函数接口,没有参数,只有返回值
public class SupplierDemo {
public static void main(String[] args) {
Supplier supplier = new Supplier<String>() {
@Override
public String get() {
return "你好";
}
};

Supplier supplier1 = ()->{return "我还是喜欢你";};
System.out.println(supplier.get());
System.out.println(supplier1.get());
}
}

老程序员,泛型,反射,枚举都需要会

新程序员,lambda表达式,函数式接口,Stream流式计算

什么是流式计算

大数据:存储+计算

集合,MySql本质上就是存储东西

计算都应该交给流来处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
流式计算
现有一个题目:要求一分钟内完成此题,只能用一行代码实现
现有五个用户,
1.ID必须为偶数
2.年龄必须要大于23岁
3.用户名必须转换为大写
4.用户名字倒叙排序
5.只输出一个用户
*/
public class Test {
public static void main(String[] args) {
User u1 = new User(1,12,"a");
User u2 = new User(2,25,"b");
User u3 = new User(3,23,"c");
User u4 = new User(4,15,"d");
User u5 = new User(5,16,"e");

//使用List集合存储,将集合类对象转换
List<User> list = Arrays.asList(u1,u2,u3,u4,u5);
System.out.println(list);
//计算交给Steam流
list.stream()
.filter(u->{return u.getId()%2 ==0;})//过滤id为偶数的
.filter(u->{return u.getAge() > 16;}) //过滤年龄小于16的
.map(u->{return u.getName().toUpperCase();}) //通过map函数式接口将名字转换为大写
.sorted((uu1,uu2) -> {return uu1.compareTo(uu2);})//排序并比较
.limit(1)//只显示一条数据
.forEach(System.out::println);//将他遍历出来
}
}

ForkJoin

ForkJoin在JDK1.7中,并行执行任务,提交效率,一般通用于大数据量!

大数据:Map Reduce(把大任务拆分为小任务)

ForkJoin特点

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class Test {

public static void main(String[] args) throws ExecutionException, InterruptedException {
// test1();
//test2();
test3(); //贼强,速度很快一般大型的时候使用
}

//普通程序员
// Sum=时间4099
public static void test1(){
Long start = System.currentTimeMillis();

Long sum = 0L;
for (long i = 1L; i <= 10_0000_0000; i++) {
sum += i;
}
long end = System.currentTimeMillis();

System.out.println("Sum="+sum+"时间\t" + (end -start));
}


//中级程序员
//Sum=时间11302
public static void test2() throws ExecutionException, InterruptedException {
Long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinTask<Long> task = new ForkJoinDemo(1L,10_0000_0000L);
forkJoinPool.execute(task);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);//提交任务
Long sum = submit.get();
long end = System.currentTimeMillis();
System.out.println("Sum="+"时间" + (end -start));

}

//并行流 时间241
public static void test3(){
Long start = System.currentTimeMillis();
Long sum = LongStream.rangeClosed(0L,10_0000_0000L).parallel().reduce(0,Long::sum);
Long end = System.currentTimeMillis();
System.out.println("Sum="+sum + "\n时间" + (end -start));

}
}

异步回调

Future设计初衷,对将来某个事件结果进行建模

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/*
异步调用ajax技术,一般上ajax使用原理就异步调用
异步执行:
成功回调
失败回调

*/
public class FutureDemo {

public static void main(String[] args) throws ExecutionException, InterruptedException {
//发送一个请求 ,没有返回值的异步回调
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "RunAsny=>Void");
});
System.out.println("111");
completableFuture.get();//获取执行结果

System.out.println("=================================");
//有返回值的异步回调
//有返回值的是supplyAsync异步回调
//ajax,成功和失败的回调
//返回的是错误的信息。
CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName() + "supplyAsync=>Integer");
int i = 4/0;
return 1024 ;

});
completableFuture1.whenComplete((t,u)->{
System.out.println( " --> " +t); //正确的返回结果
System.out.println( " -- >" + u); //错误信息

}).exceptionally((e)->{
System.out.println(e.getMessage());
return 444; //可以通过这个标识码获取错误的返回结果
}).get();

}
}

JVM初步理解

谈谈你对Volatile的理解

volatile是javaJVM提供的轻量级的同步机制

  1. 保证可见性
  2. 不保证原子性
  3. 静止指令重排

JMM:java内存模型,不存在的东西,就是一个概念!约定**

关于JMM的一些同步的约定

  1. 线程解锁前,必须把共享变量立刻 刷回主存。
  2. 线程加锁前,必须读取主存中的最新值到工作内存中。
  3. 加锁和解锁是同一把锁。

线程工作内存,主内存

jvm的8种操作:

  1. read - > load
  2. user -> assign
  3. write ->store
  4. lock -> unlock

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类 型的变量来说,load、store、read和write操作在某些平台上允许例外)

lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态

unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量 才可以被其他线程锁定

read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便 随后的load动作使用

load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中

use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机 遇到一个需要使用到变量的值,就会使用到这个指令

assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变 量副本中

store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,

以便后续的write使用write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内 存的变量中

JMM对这八种指令的使用,制定了如下规则

  • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须 write
  • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
  • 不允许一个线程将没有assign的数据从工作内存同步回主内存
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量 实施use、store操作之前,必须经过assign和load操作
  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解 锁
  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前, 必须重新load或assign操作初始化变量的值
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量 对一个变量进行unlock操作之前,必须把此变量同步回主内存

Volatile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
在这个例子中我们发现,当执行线程的时候,num =0是一开始的时候,之后我们将
改成num = 1,但是程序输出1却没有终止
这里原因是,我们更改了num的值,但是线程B知道而线程A却不知道
也就是main线程知道,但是Thread线程却是不知道
*/
public class VolatileDemo {
private static int num = 0;

public static void main(String[] args) throws InterruptedException {

new Thread( ()->{
while (num == 0){

}
}).start();

TimeUnit.SECONDS.sleep(1);
num =1;
System.out.println(num);
}
}

验证特性

  • 保证可见性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
在这个例子中我们发现,当执行线程的时候,num =0是一开始的时候,之后我们将
改成num = 1,但是程序输出1却没有终止
这里原因是,我们更改了num的值,但是线程B知道而线程A却不知道
也就是main线程知道,但是Thread线程却是不知道
*/
public class VolatileDemo {
//不加volatile就会陷入无限循环
private volatile static int num = 0;

public static void main(String[] args) throws InterruptedException {

new Thread( ()->{
while (num == 0){
}
}).start();

TimeUnit.SECONDS.sleep(1);
num =1;
System.out.println(num);
}
}

  • 不保证原子性:不可分割

原子性: 线程A在执行任务的时候,不能被打扰到,也不能被分割,要么同时成功要么同时失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//不保证原子性
public class VolatileDemo2 {
//这个时候我们可以发现,volatile无法保证原子性
private volatile static int num = 0;

//synchronized可保证为结果为20000
public static void add(){
num ++; // 不是原子性
}

public static void main(String[] args) {
//理论上num 为20000
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
/*
java中默认两个线程,第一个main,第二个GC线程
*/
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//不保证原子性
public class VolatileDemo2 {
//这个时候我们可以发现,volatile无法保证原子性

private volatile static int num = 0;
//synchronized可保证为结果为20000

//我们此时发现,lock也可以保证结果为20000
private static Lock lock = new ReentrantLock();

public static void add(){
lock.lock();
try{
num ++;
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}

public static void main(String[] args) {
//理论上num 为20000
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
/*
java中默认两个线程,第一个main,第二个GC线程
*/
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}
}

如果不加Lock和synochronized如何保证原子性。

使用原子类解决原子性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
//不保证原子性
public class VolatileDemo2 {
//这个时候我们可以发现,volatile无法保证原子性

// private volatile static int num = 0;
private volatile static AtomicInteger num = new AtomicInteger();

//synchronized可保证为结果为20000

//我们此时发现,lock也可以保证结果为20000
private static Lock lock = new ReentrantLock();

public static void add(){
/* lock.lock();
try{
num ++;
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}*/
num.getAndIncrement(); //AtomicInteger +1方法


}

public static void main(String[] args) {
//理论上num 为20000
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
/*
java中默认两个线程,第一个main,第二个GC线程
*/
while (Thread.activeCount()>2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + " " + num);
}

这些类的底层都是和操作系统挂钩,在内存中修改中,Unsafe是一个特殊的存在。

指令重排

什么是指令重排:你写的程序,计算机并不是按照你写的程序那样去执行的。

源代码 - — 编译器里边的优化的重排 ——— 指令并行也可能会重排 — 内存系统也会重排——-执行

1
2
3
4
5
int x = 1; //1
int y = 2;//2
x = x+ 5;// 3
y = x +x; // 4
//我们希望的顺序是 1234 可能是 1234 2134 1324 但不可能是 4123

可能造成影响的结果: a b x y 这四个值 默认都是 0

线程A 线程B
x =a y =b
b =1 a = 2

正常的结果 x = 0; y = 0;

volatile可以避免指令重排

内存屏障,CPU指令,作用:

  1. 保证特定的操作执行顺序
  2. 保证某些变量的内存可见性(利用这些特性Volatile实现可见性)

volatile可保证可见性,不能保证原子性,由于内存屏障,可以避免指令重排的现象产生。

内存屏障使用最多的场景是单例模式

单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//饿汉式单例模式
public class SingleDemo {
//饿汉式单例这些数据都没有使用,但是被创建的时候还是占用很多内存
private byte[] d1 = new byte[1024* 1024];
private byte[] d2 = new byte[1024* 1024];
private byte[] d3 = new byte[1024* 1024];
private byte[] d4 = new byte[1024* 1024];
//无参构造 只要是单例,我们都要创建一个私有化构造
private SingleDemo(){
}

//单例模式的调用方式
private static SingleDemo singleDemo = new SingleDemo();

public static SingleDemo getInstance(){
return singleDemo;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/*
* @Title 懒汉式单例模式
* @Description
* @author 罗小黑
* @param null
* @return
* @date 2022/10/9 0:04
* @email 2844135670@qq.com
*/
public class LazyDemo {
//不说二话直接整上构造器,只要是单例模式,构造器私有
private LazyDemo(){
System.out.println(Thread.currentThread().getName() + "\t OK");
}
//先创建一个lazy对象,为保证不会产生指令重排,我们需要在这加入volatile
private volatile static LazyDemo lazyDemo;

/*
* @Title 双重检测模式下的懒汉式单例,DCL懒汉式
* @Description
* @author 罗小黑
* @return com.xh.juc.day06.Single.LazyDemo
* @date 2022/10/9 0:15
* @email 2844135670@qq.com
*/
//重点是getInstance在单例模式中我们直接使用的是getInstance()
public static LazyDemo getInstance(){
//解决办法之一最常用的加锁。

if(lazyDemo == null){
synchronized (LazyDemo.class){
lazyDemo = new LazyDemo(); //不是一个原子性操作
/*
有三步:
1. 分配内存空间
2. 执行构造方法,初始化对象
3.把这个对象指向这个空间
这个时候可能会产生指令重排,会出现故障
*/

}
//如果传如的对象为空,那么我们就需要将对爱心给他赋予
// lazyDemo = new LazyDemo();

}
return lazyDemo;
}
//问题,单线程下没问题,但是多线性下出现问题.
// 我们发现在多线程下会产生不一样的结果,而且也不会创建10个线程
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyDemo.getInstance();
},String.valueOf(i)).start();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
// 静态内部类 
public class Holder {
private Holder(){
}
public static Holder getInstace(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER = new Holder();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//单例不安全,反射
//枚举
// 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();
// NoSuchMethodException: com.kuang.single.EnumSingle.<init>()
System.out.println(instance1);
System.out.println(instance2);
}
}

CAS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CASDemo {
//CAS compareAndSet : 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2022);

//期望,更新
// public final boolean compareAndSet(int expect,int update);
//如果我期望的值达到了,那么就更新,否则不更新 CAS是CPU的调度
//java 无法操作内存,java可以调用C++ native C++可以操作内存,java的后门,可以操作这个类(Unsafe)调度
System.out.println(atomicInteger.compareAndSet(2022, 2123));
System.out.println(atomicInteger.get());

System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}

CAS:比较当前工作内存中的值和主内存的值,如果这个值是期望的,那么执行操作,如果不是就一直循环

缺点:

  1. 循环会耗时
  2. 一次性只能保证一个共享变量的原子性
  3. ABA问题

CAS:ABA问题(狸猫换太子)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CASDemo {
//CAS compareAndSet : 比较并交换
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2022);

//期望,更新
// public final boolean compareAndSet(int expect,int update);
//如果我期望的值达到了,那么就更新,否则不更新 CAS是CPU的调度
//java 无法操作内存,java可以调用C++ native C++可以操作内存,java的后门,可以操作这个类(Unsafe)调度
//(A)
System.out.println(atomicInteger.compareAndSet(2022, 2123));
System.out.println(atomicInteger.get());
// B
System.out.println(atomicInteger.compareAndSet(2123, 2022));
System.out.println(atomicInteger.get());
//A
System.out.println(atomicInteger.compareAndSet(2022, 2123));
System.out.println(atomicInteger.get());

}
}

原子引用(ABA问题)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//乐观锁和悲观锁
public class CASDemo2 {
public static void main(String[] args) {
//注意如果泛型是包装类,需要注意引用类型和范围Integer的范围是-128 到 127
//正常的应用的时候,我们使用的都是对象,而不是直接使用Integer的等,
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(1,1);
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获取版本号
System.out.println( Thread.currentThread().getName()+ " " + atomicStampedReference.getStamp());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(atomicStampedReference.compareAndSet(
1, 5,
atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1));
System.out.println( Thread.currentThread().getName()+ " -A2- " + atomicStampedReference.getStamp());

System.out.println(atomicStampedReference.compareAndSet(
5, 6,
atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1));
System.out.println( Thread.currentThread().getName()+ " -A3- " + atomicStampedReference.getStamp());

},"A").start();


//比较AB两个线程的版本号
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获取版本号
System.out.println( Thread.currentThread().getName()+ " " + atomicStampedReference.getStamp());

try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(atomicStampedReference.compareAndSet(2020, 6666,
stamp, stamp + 1));
System.out.println( Thread.currentThread().getName()+ " -B2- " + atomicStampedReference.getStamp());


},"B").start();
}
}

锁机制的了解

公平锁(包含读写锁)

公平锁:非常公平,不能插队,必须先来先到

非公平锁:java自带默认非公平锁,可以插队

1
2
3
4
5
//java源码
public ReentrantLock() {
//我们发现,这里默认就是不公平锁
sync = new NonfairSync();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class 公平锁 {
//创建一个公平锁
private Lock lock = new ReentrantLock();

//synchronized

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()+"发短信");
call();
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"打电话");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class LockDemo {

private Lock lock = new ReentrantLock();

public static void main(String[] args) {
Phone2 phone2 = new Phone2();
new Thread(()->{
phone2.sms();
},"A").start();

new Thread(()->{
phone2.sms();
},"B").start();

System.out.println("=====================");
Test te = new Test();
new Thread(()->{
te.sms();
},"C").start();

new Thread(()->{
te.sms();
},"D").start();

}


}

class Phone2{
private Lock lock = new ReentrantLock();

public void sms(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+"发消息");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
call();
}

public void call(){
lock.lock();
try{
System.out.println(Thread.currentThread().getName()+ "打电话");
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}

class Test{
public void sms(){
System.out.println(Thread.currentThread().getName()+"发消息");
call();
}
public void call(){
System.out.println(Thread.currentThread().getName()+ "打电话");
}
}
可重入锁

可重入锁是某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。再次获取锁的时候会判断当前线程是否是已经加锁的线程,如果是对锁的次数+1,释放锁的时候加了几次锁,就需要释放几次锁

代码中的锁的递归只是锁的一种表现及证明形式,除了这种形式外,还有另一种表现形式。同一个线程在没有释放锁的情况下多次调用一个加锁方法,如果成功,则也说明是可重入锁。

自旋锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
自旋锁:
我们自己定义一个自旋锁
*/
public class 自旋锁 {
AtomicReference<Thread> atomicStampedReference = new AtomicReference<>();
/*
//加锁
所谓自旋锁
*/
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "=> MY Lock");
//自旋开始
while (!atomicStampedReference.compareAndSet(null,thread)){
}
}
/*
解锁
*/
public void myUnLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + "=> MY UnLock");
atomicStampedReference.compareAndSet(thread,null);
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class Test_Self {
public static void main(String[] args) {

/*
一般情况下使用的方式

ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
reentrantLock.unlock();
*/
/*
使用我们自定义下的方式
我们使用的是CAS比较的也就是compareAndSet
*/
自旋锁 t = new 自旋锁();

new Thread(()->{
t.myLock();

try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
t.myUnLock();
}

},"A").start();

new Thread(()->{
t.myLock();

try {
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
t.myUnLock();
}
},"B").start();

}
}
悲观锁

总是以悲观的角度来看问题,当用户拿到锁之后,认为用户会修改

乐观锁

总是以乐观的方式看待问题,当用户每次拿到锁后,都不会对数据进行修改

死锁

两个线程强制一个资源造成的两个线程处于等待状态,在不经过人的干预的情况之下一直会保持这种状态,我们称之为死锁。

如何去避免死锁(避免死锁产生的条件)

如何去判断线程产生死锁问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class 死锁{
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";

new Thread(new MyTest(lockA,lockB),"A").start();

new Thread(new MyTest(lockB,lockA),"B").start();
}
}

class MyTest implements Runnable{

private String lockA;
private String lockB;


public MyTest(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}

@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName() + "Lock A " + "=> get " + lockB);

//我们让线程A休眠2秒
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

synchronized (lockB){
System.out.println(Thread.currentThread().getName() + "Lock B " + lockB + "=> get " + lockA);
//我们让线程A休眠2秒
}
}
}
}

  1. 使用 jps -l 定位进程号

  2. 使用 jstack 进程号 查看进程的信息 ,找到死锁问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    PS E:\IDEAProgram\juc-learning> jstack 2400
    2022-10-09 21:13:37
    Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.261-b12 mixed mode):

    "DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x000001f454e10800 nid=0x31ec wait
    ing on condition [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE

    "B" #13 prio=5 os_prio=0 tid=0x000001f470d10800 nid=0x3690 waiting for moni
    tor entry [0x000000870e7ff000]
    java.lang.Thread.State: BLOCKED (on object monitor)
    at com.xh.juc.day07.锁.MyTest.run(死锁.java:40)
    - waiting to lock <0x0000000775b9f8a0> (a java.lang.String)
    - locked <0x0000000775b9f8d8> (a java.lang.String)
    at java.lang.Thread.run(Thread.java:748)

    "A" #12 prio=5 os_prio=0 tid=0x000001f470d03000 nid=0x39e0 waiting for moni
    tor entry [0x000000870e6ff000]
    java.lang.Thread.State: BLOCKED (on object monitor)
    at com.xh.juc.day07.锁.MyTest.run(死锁.java:40)
    - waiting to lock <0x0000000775b9f8d8> (a java.lang.String)
    - locked <0x0000000775b9f8a0> (a java.lang.String)
    at java.lang.Thread.run(Thread.java:748)

    "Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000001f470ba4000 nid=0x3
    ea0 runnable [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE

    "C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x000001f470af0000 nid
    =0x3f94 waiting on condition [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE

    "C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000001f470aef000 nid=
    0x3168 waiting on condition [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE

    "C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000001f470ade000 nid=
    0xd1c waiting on condition [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE

    "C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000001f470add000 nid=
    0x1d30 waiting on condition [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE

    "Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000001f470adb000 nid=
    0x268c runnable [0x000000870dffe000]
    java.lang.Thread.State: RUNNABLE
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.socketRead(SocketInputStream.java:116
    )
    at java.net.SocketInputStream.read(SocketInputStream.java:171)
    at java.net.SocketInputStream.read(SocketInputStream.java:141)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
    - locked <0x0000000775c8d1c0> (a java.io.InputStreamReader)
    at java.io.InputStreamReader.read(InputStreamReader.java:184)
    at java.io.BufferedReader.fill(BufferedReader.java:161)
    at java.io.BufferedReader.readLine(BufferedReader.java:324)
    - locked <0x0000000775c8d1c0> (a java.io.InputStreamReader)
    at java.io.BufferedReader.readLine(BufferedReader.java:389)
    at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.
    java:49)

    "Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000001f46edb9800 nid=0xd
    64 waiting on condition [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE

    "Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000001f46ee1e000 nid=0
    x10e8 runnable [0x0000000000000000]
    java.lang.Thread.State: RUNNABLE

    "Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000001f454eb7000 nid=0x2ee0 in
    Object.wait() [0x000000870dcfe000]
    java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x0000000775a08ee0> (a java.lang.ref.ReferenceQueue$L
    ock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
    - locked <0x0000000775a08ee0> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

    "Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000001f454eb0000 nid=
    0x35e0 in Object.wait() [0x000000870dbff000]
    java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x0000000775a06c00> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:502)
    at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
    - locked <0x0000000775a06c00> (a java.lang.ref.Reference$Lock)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

    "VM Thread" os_prio=2 tid=0x000001f454ead800 nid=0x1f50 runnable

    "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x000001f454e26000 nid=0x3f00
    runnable

    "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000001f454e28800 nid=0x3eb0
    runnable

    "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000001f454e29800 nid=0x3e84
    runnable

    "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000001f454e2d000 nid=0x3fb8
    runnable

    "GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000001f454e32000 nid=0x3ca0
    runnable

    "GC task thread#5 (ParallelGC)" os_prio=0 tid=0x000001f454e32800 nid=0x3df8
    runnable

    "GC task thread#6 (ParallelGC)" os_prio=0 tid=0x000001f454e35800 nid=0x3630
    runnable

    "GC task thread#7 (ParallelGC)" os_prio=0 tid=0x000001f454e36800 nid=0x8ec
    runnable

    "VM Periodic Task Thread" os_prio=2 tid=0x000001f470be6000 nid=0x21f4 waiti
    ng on condition

    JNI global references: 12


    Found one Java-level deadlock:
    =============================
    "B":
    waiting to lock monitor 0x000001f454eb3c68 (object 0x0000000775b9f8a0, a
    java.lang.String),
    which is held by "A"
    "A":
    waiting to lock monitor 0x000001f454eb65a8 (object 0x0000000775b9f8d8, a
    java.lang.String),
    which is held by "B"

    Java stack information for the threads listed above:
    ===================================================
    "B":
    at com.xh.juc.day07.???.MyTest.run(死锁.java:40)
    - waiting to lock <0x0000000775b9f8a0> (a java.lang.String)
    - locked <0x0000000775b9f8d8> (a java.lang.String)
    at java.lang.Thread.run(Thread.java:748)
    "A":
    at com.xh.juc.day07.锁.MyTest.run(死锁.java:40)
    - waiting to lock <0x0000000775b9f8d8> (a java.lang.String)
    - locked <0x0000000775b9f8a0> (a java.lang.String)
    at java.lang.Thread.run(Thread.java:748)

    Found 1 deadlock.

    如何检测死锁:

    1. 查看日志
    2. 堆栈信息
轻量锁

① 当一个线程要进入同步块时,首先会创建一个Lock Record(锁记录)对象,该对象包含Displaced Mark Word和Owner。
② 锁记录对象会拷贝对象头中的Mark Word,即Displaced Mark Word。
③ 拷贝成功后,锁记录将owner指向对象头,然后尝试通过cas将对象头的Mark Word更改为指向Lock Record的指针,并且将对象Mark Word的锁标志位设置为“00”。
④ 如果更新成功,则表示该对象处于轻量级锁定状态。如果失败,那么首先检测是否是可重入,如果可重入则进入执行。
⑤ 如果不可重入,则膨胀为重量锁。(有的文章说会进行自旋,有的说不进行自旋只要存在竞争就膨胀为重量锁,美团说当只有一个等待线程时,该线程自旋。当超过一定次数或者超过一个自旋线程时直接膨胀为重量锁)。
⑥ 释放锁时,使用cas对象头的Mark Word恢复,如果cas成功,则解锁成功。如果cas失败,则进入重量锁解锁流程。

重量锁

JDK1.6之前,锁没被优化,synchronized使用的是重量锁。重量锁需要操作系统帮忙,所以需要进行用户态到内核态的切换,同时还会带来线程上下文切换。
原理

1665323626063

Monitor对象分为3部分组成:Owner、EntryList和WaitSet
① JVM会创建一个Monitor对象,然后将锁对象的对象头中MarkWord改变成指向Monitor对象的指针。
② 当其中一个线程抢占到锁后,Monitor的Owner会置为该线程id,只能有一个。
③ 其余没抢到的,会被放置到EntryList中阻塞。
④ 进入WAITING状态的线程,会被放在WaitSet中。

排他锁

排他锁(EXclusive Lock),又称X锁、独占锁、写锁。针对行锁。
当有事务对数据加写锁后,其他事务不能再对锁定的数据加任何锁,又因为InnoDB对select语句默认不加锁,所以其他事务除了不能写操作外,照样是允许读的(尽管不允许加读锁)。

📢主要为了在事务进行写操作时,不允许其他事务修改。

偏向锁

轻量锁每次重入和退出时还是会执行cas操作,并且要创建锁记录对象。如果在大多数情况下,锁总是由一个线程多次获得,不存在多线程竞争,就可以使用偏向锁优化。
原理
① 第一次使用CAS将线程ID设置到对象的Mark Word。
② 以后该线程进入同步块时,不需要CAS进行加锁,只会往当前线程的栈中添加一条Displaced Mark Word为空的Lock Record中,用来统计重入的次数。