JUC并发编程

小德 2021-12-02 12:57:59
Categories: Tags:

JUC并发编程

1.什么是JUC?

Java.util.Concurrent

Runnable没有返回值,效率比Callable低!

2.线程和进程

进程:一个程序

一个进程往往可以包括多个线程,至少包括一个。

java默认有两个进程:main和GC

线程:开了一个进程,Typora写字,自动保存(线程负责)

对于java而言,Thread、Runnable、Callable

java真的可以开启线程吗?不能,只能通过本地方法去调用底层的C++,无法直接操作。

3.并发、并行

并发、并行

并发(多线程操作同一个资源):

CPU一核,模拟出多条线程,快速交替。

1
2
//获取CPU的核数
System.out.println(Runtime.getRuntime().availableProcessors());

并发编程的本质:充分利用CPU的资源

并行(多个人一起走)

CPU多核,多个线程可以同时执行,线程池。

4.线程有六个状态

创建、运行、阻塞、等待、超时等待、终止

wait/sleep的区别

1.来自不同的类,wait是Object类,sleep是Thread类

2.关于锁的释放,wait会释放锁,sleep不会释放锁

3.使用的范围不同,wait必须在同步代码块中,sleep可以任何地方使用

4.是否需要捕获异常,wait不需要捕获异常,sleep必须捕获异常

5.Lock锁(重点)

传统的synchronized

线程就是一个单独的资源类,没有任何的附属操作
并发:多线程操作同一个资源,把资源丢到线程
jdk1.8 lambda表达式 ()->{}

Lock接口,实现类:可重用锁ReentrantLock、读锁,写锁

ReentrantLock源码:

1
2
3
4
5
6
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

公平锁:先来后到

非公平锁:可以插队(默认)

1.new ReentrantLock

2.加锁 lock.lock()

3.解锁 lock.unlock();

Lock锁和synchronized区别:

1.synchronize是内置的java关键字,Lock是java类

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

3.synchronized会自动释放锁,Lock锁必须要手动释放锁,不释放会导致死锁

4.synchronized线程1(获得锁,阻塞),线程2(等待,傻傻的等),Lock锁的线程2就不一定等待下去

lock.trylock():尝试获取锁

5.synchronized可重入锁,不可中断,非公平的;Lock可重入锁,可以判断锁,非公平(可以自己设置)

6.synchronized适合少量的代码同步问题,Lock适合锁大量的同步代码

锁是什么,如何判断锁的是谁?

6.生产者和消费者问题

面试:单例模式、排序算法、生产者和消费者、死锁

synchronized版本

wait()/notifyAll()

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
//线程间通信问题:生产者和消费者问题,等待唤醒,通知唤醒
//线程交替执行
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 (Exception e){
e.printStackTrace();
}
}
}, "A").start();

new Thread(() -> {
for (int i=0;i<10;i++){
try {
data.decrement();
}catch (Exception e){
e.printStackTrace();
}
}
}, "B").start();
}
}

//等待、业务、通知
class Data { //资源
private int num = 0;

public synchronized void increment() throws InterruptedException {
if (num != 0) {
//等待
this.wait();//wait()方法会释放锁
}
num++;
//通知其他线程,+1完毕了
this.notifyAll();
System.out.println(Thread.currentThread().getName() + "=>" + num);
}

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

四个线程的synchronized

出现问题。

虚假唤醒:

当一个条件满足时,很多线程都被唤醒了,但是只有其中部分是有用的唤醒,其它的唤醒都是无用功

1.比如说买货,如果商品本来没有货物,突然进了一件商品,这是所有的线程都被唤醒了 ,但是只能一个人买,所以其他人都是假唤醒,获取不到对象的锁

被唤醒之后,它是从上次被挂起的地方,继续往下执行

因为if只会执行一次,执行完会接着向下执行if()外边的 而while不会,直到条件满足才会向下执行while()外边的

拿两个加法线程A、B来说,A执行一次后,A再执行,执行时调用了wait方法,那它会等待,此时会释放锁,之后线程B获得锁并且也会执行wait方法,两个加线程一起等待被唤醒。此时减线程中的某一个线程执行完毕并且唤醒了这俩加线程,那么这俩加线程不会一起执行,其中A获取了锁并且加1,执行完毕之后B再执行。如果是if的话,那么A修改完num后,B不会再去判断num的值,直接会给num+1。如果是while的话,A执行完之后,B还会去判断num的值,因此就不会执行。

防止虚假唤醒。if改为while判断。

lock版本

await()/signal()

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 B {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "A").start();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "C").start();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (Exception e) {
e.printStackTrace();
}
}
}, "D").start();
}
}

//等待、业务、通知
class Data2 { //资源
private int num = 0;

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();

public void increment() throws InterruptedException {
lock.lock();
try {
while (num != 0) {
//等待
condition.await();
System.out.println(Thread.currentThread().getName() + "加阻塞");
}
num++;
//通知其他线程,+1完毕了
condition.signalAll();

System.out.println(Thread.currentThread().getName() + "=>" + num);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}

}

//
public void decrement() throws InterruptedException {

lock.lock();
try {
while (num == 0) {
//等待
System.out.println(Thread.currentThread().getName() + "减阻塞");
condition.await();
}
num--;
//通知其他线程,-1完毕了
condition.signalAll();
System.out.println(Thread.currentThread().getName() + "=>" + num);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}

}
}

任何一个新技术,绝对不仅仅只是覆盖原技术,应该有优势和补充。

Condition,精准的通知和唤醒线程

随机状态,需要有序执行

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
//线程A执行完通知线程B,线程B执行完通知C,线程C执行完通知A
public class C {
public static void main(String[] args) {
Data3 data = new Data3();

new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printA();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printB();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.printC();
}
}, "C").start();

}

}

class Data3 { //资源

private int num = 1;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();

public void printA() {

lock.lock();
try {
//业务,判断,执行,通知
while (num != 1) {
//等待
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "=>AAA");
//唤醒指定
num = 2;
condition2.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}

}

public void printB() {

lock.lock();
try {
//业务,判断,执行,通知
while (num != 2) {
//等待
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "=>BBB");
//唤醒指定
num = 3;
condition3.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}

}

public void printC() {

lock.lock();
try {
//业务,判断,执行,通知
while (num != 3) {
//等待
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "=>CCC");
//唤醒指定
num = 1;
condition1.signal();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}

}
}

7.八锁现象

深刻理解锁:8锁:关于锁的八个问题

//8锁,关于锁的八个问题

1.Test1

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
package com.xmx.锁;

import java.util.concurrent.TimeUnit;

//8锁,关于锁的八个问题
//问题1:标准情况下,两个线程打印是先发短信还是先打电话?
//问题2:sendMsg()延迟四秒,两个线程打印是先发短信还是先打电话?
public class Test1 {

public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() -> {
phone.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone.call();
}, "B").start();
}


}

class Phone {
//synchronized 锁的对象是方法的调用者
//两个方法用的是一个锁,谁先拿到谁先执行
public synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");

}

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

2.Test2

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
package com.xmx.锁;

import java.util.concurrent.TimeUnit;

//8锁,关于锁的八个问题
//问题1:标准情况下,两个线程打印是先发短信还是先打电话? 两个方法用的是一个锁,谁先拿到谁先执行
//问题2:sendMsg()延迟四秒,两个线程打印是先发短信还是先打电话? 两个方法用的是一个锁,谁先拿到谁先执行
//问题3:增加了个普通方法hello(),两个线程打印是先发短信还是先hello? 普通方法没有锁,不是同步方法,不受锁的影响
//问题4:两个对象,分别调用sendMsg()和call(),两个线程打印是先发短信还是先打电话? 两把锁,按时间执行
public class Test2 {


public static void main(String[] args) {
Phone2 phone = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(() -> {
phone.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}


}

class Phone2 {
//synchronized 锁的对象是方法的调用者
//两个方法用的是一个锁,谁先拿到谁先执行
public synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}

public synchronized void call() {
System.out.println("打电话");
}
//没有锁,不是同步方法,不受锁的影响
public void hello(){
System.out.println("hello");
}
}

3.Test3

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
package com.xmx.锁;

import java.util.concurrent.TimeUnit;

//8锁,关于锁的八个问题
//问题1:一个对象,标准情况下,两个线程打印是先发短信还是先打电话? synchronized 锁的对象是方法的调用者,两个方法用的是一个锁,谁先拿到谁先执行
//问题2:一个对象,sendMsg()延迟四秒,两个线程打印是先发短信还是先打电话? synchronized 锁的对象是方法的调用者, 两个方法用的是一个锁,谁先拿到谁先执行
//问题3:一个对象,增加了个普通方法hello(),两个线程打印是先发短信还是先hello? 普通方法没有锁,不是同步方法,不受锁的影响
//问题4:两个对象,分别调用sendMsg()和call(),两个线程打印是先发短信还是先打电话? 两把锁,按时间执行
//问题5:一个对象,sendMsg()和call()是静态的同步方法,两个线程打印是先发短信还是先打电话?static类一加载就有了,锁的是class,用的也是同一个锁
//问题6:两个对象,分别调用sendMsg()和call(),是静态的同步方法,两个线程打印是先发短信还是先打电话?static类一加载就有了,锁的是class,用的也是同一个锁
public class Test3 {


public static void main(String[] args) {
Phone3 phone = new Phone3();
Phone3 phone2 = new Phone3();

new Thread(() -> {
phone.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}


}

class Phone3 {
//synchronized 锁的对象是方法的调用者
//两个方法用的是一个锁,谁先拿到谁先执行
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}

public static synchronized void call() {
System.out.println("打电话");
}
//没有锁,不是同步方法,不受锁的影响
public void hello(){
System.out.println("hello");
}
}

4.Test4

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
package com.xmx.锁;

import java.util.concurrent.TimeUnit;

//8锁,关于锁的八个问题
//问题1:一个对象,标准情况下,两个线程打印是先发短信还是先打电话? synchronized 锁的对象是方法的调用者,两个方法用的是一个锁,谁先拿到谁先执行
//问题2:一个对象,sendMsg()延迟四秒,两个线程打印是先发短信还是先打电话? synchronized 锁的对象是方法的调用者, 两个方法用的是一个锁,谁先拿到谁先执行
//问题3:一个对象,增加了个普通方法hello(),两个线程打印是先发短信还是先hello? 普通方法没有锁,不是同步方法,不受锁的影响
//问题4:两个对象,分别调用sendMsg()和call(),两个线程打印是先发短信还是先打电话? 两把锁,按时间执行
//问题5:一个对象,sendMsg()和call()是静态的同步方法,两个线程打印是先发短信还是先打电话?static类一加载就有了,锁的是class,用的也是同一个锁
//问题6:两个对象,分别调用sendMsg()和call(),是静态的同步方法,两个线程打印是先发短信还是先打电话?static类一加载就有了,锁的是class,用的也是同一个锁
//问题7:一个对象,sendMsg()是静态同步方法,call()是非静态的同步方法,两个线程打印是先发短信还是先打电话?两把锁,按时间执行
//问题8:两个对象,分别调用sendMsg()是静态同步方法,call()是非静态的同步方法,两个线程打印是先发短信还是先打电话?两把锁,按时间执行
public class Test4 {

public static void main(String[] args) {
Phone4 phone = new Phone4();
Phone4 phone2 = new Phone4();

new Thread(() -> {
phone.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}


}

class Phone4 {
//锁的是class类模板
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
//锁的调用者
public synchronized void call() {
System.out.println("打电话");
}
//没有锁,不是同步方法,不受锁的影响
public void hello(){
System.out.println("hello");
}
}

小结

new this 具体的一个对象

static 唯一的一个class模板

8.集合类不安全

List不安全

List不安全,并发运行出现ConcurrentModificationException并发修改异常问题

怎么解决?

1.Vector

2.工具类Collections有synchronizedList转换

1
2
3
4
5
6
7
8
9
10
11
12
public class ListTest {
public static void main(String[] args) {
List<String> list = Collections. (new ArrayList<>());
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();

}
}
}

3.CopyOnwriteArrayList 写入时复制,COW,计算机程序设计领域的一种优化策略。

多线程在调用的时候,读取是固定的,写入为了避免覆盖,造成数据异常,就会复制一份。

CopyOnwriteArrayList 比 Vector 更好,只要有synchronized,效率就会比较低。

1
2
3
4
5
6
7
8
9
10
11
12
public class ListTest {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
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不安全

ConcurrentModificationException 同理

解决方式:

1.工具类Collections有synchronizedSet转换

1
2
3
4
5
6
7
8
9
10
11
12
public class SetTest {
public static void main(String[] args) {
Set<String> set = Collections.synchronizedSet(new HashSet<>());
for (int i = 1; i < 10; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();

}
}
}

2.CopyOnWriteArraySet

1
2
3
4
5
6
7
8
9
10
11
12
public class SetTest {
public static void main(String[] args) {
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 1; 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
public HashSet() {
map = new HashMap<>();
}
1
2
3
4
public boolean add(E e) {
//本质是HashMap的Key,无法重复的,PRESENT不变的值
return map.put(e, PRESENT)==null;
}

Map不安全

ConcurrentModificationException

HashMap<String, String> map = new HashMap<>();默认等价HashMap<String, String> map = new HashMap<>(16,0.75f);工作中不这样用HashMap,

1
2
3
initialCapacity加载因子,loadFactor初始化容量
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final float DEFAULT_LOAD_FACTOR = 0.75f;

解决方式:

1.工具类Collections有synchronizedMap转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MapTest {
public static void main(String[] args) {

HashMap<String, String> map =Collections.synchronizedMap(new HashMap<>(16, 0.75f));

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

}
}
}  

2.ConcurrentHashMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MapTest {
public static void main(String[] args) {

Map<String, String> map = new ConcurrentHashMap<>();

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

}
}
}

9.Callable类(简单)

1.可以有返回值

2.可以抛出异常

3.方法不同,run()/call()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//FutureTask是Runnable的实现类
MyThread thread=new MyThread();
FutureTask futureTask=new FutureTask(thread);
new Thread(futureTask,"A").start();
System.out.println(futureTask.get());//获取返回结果
}
}
class MyThread implements Callable<String> {

@Override
public String call() throws Exception {
System.out.println("call()");
return "123";
}
}

10.常用辅助类

1.CountDownLatch减法计数器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//计数器(减法)
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch=new CountDownLatch(6);//总数

for (int i=1;i<=6;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t出去");
countDownLatch.countDown();//-1
},String.valueOf(i)).start();
}

countDownLatch.await();//等待计数器归零,再向下执行

System.out.println("关门");

}
}

当计数为0,就会唤醒countDownLatch.await(),执行下面的代码

2.CyclicBarrier加法计数器

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 CyclicBarrierDemo {
public static void main(String[] args) {
//集齐七颗龙珠召唤神龙

CyclicBarrier cyclicBarrier=new CyclicBarrier(7,()->{
System.out.println("召唤神龙成功");
});

for (int i=1;i<=7;i++){
//lambda表达式拿不到i,新建final变量就行(系统会自带加final关键字)
final int temp=i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t收集了"+ temp +"颗龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}

}
}

3.Semaphore信号量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore=new Semaphore(3);//线程数量(停车位)/限流
for (int i=1;i<=6;i++){
new Thread(()->{
try {
//得到acquire
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放 release
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}

​ semaphore.acquire();获得,假如满了会等待,直到释放为止。

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

作用:多共享资源互斥,限流,控制最大线程数

11.读写锁(独占锁、共享锁)

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
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache cache = new MyCache();
MyCacheLock myCacheLock=new MyCacheLock();
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
// cache.put(temp + "", temp + "");
myCacheLock.put(temp + "", temp + "");
}, String.valueOf(i)).start();
}

for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
// cache.get(temp + "");
myCacheLock.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);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取完成");
}
}

//自定义缓存:加锁
class MyCacheLock {
private volatile Map<String, Object> map = new HashMap<>();
//读写锁,更加细粒度控制
private ReentrantReadWriteLock 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);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取完成");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}

}
}

12.阻塞队列BlockingQueue

不得不阻塞:写入:队列满了。读取:队列空的。

BlockingQueue接口(父类是Collection),Queue和List及Set同级,是Queue的子接口

使用场景:多线程并发处理,线程池

四组API

ArrayBlockingQueue

方式 抛出异常 有返回值 阻塞等待 超时等待
添加 add offer put offer(xxx,2, TimeUnit.SECONDS)
移除 remove poll take poll(xxx,TimeUnit.SECONDS)
判断队首元素 element peek

1.抛出异常

1
2
3
4
5
6
7
8
9
10
11
public static void test1(){
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.add("a"));
System.out.println(arrayBlockingQueue.add("b"));
System.out.println(arrayBlockingQueue.add("c"));
// System.out.println(arrayBlockingQueue.add("d"));
System.out.println("============");
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
//System.out.println(arrayBlockingQueue.remove());}
1
System.out.println(arrayBlockingQueue.element());//查看队首元素

队列满了出现Queue full异常

队列为空出现NoSuchElementException异常

2.有返回值

1
2
3
4
5
6
7
8
9
10
11
12
public static void test2(){
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
// System.out.println(arrayBlockingQueue.offer("d")); //返回false
System.out.println("============");
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
// System.out.println(arrayBlockingQueue.poll()); //返回null
}
1
System.out.println(arrayBlockingQueue.peek());//查看队首元素

队列满了返回false

队列为空返回null

3.阻塞等待

1
2
3
4
5
6
7
8
9
10
11
12
13
//等待,阻塞(一直等待)
public static void test3() throws InterruptedException {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
arrayBlockingQueue.put("a");
arrayBlockingQueue.put("b");
arrayBlockingQueue.put("c");
arrayBlockingQueue.put("d");//队列没位置了,一直等待

arrayBlockingQueue.take();
arrayBlockingQueue.take();
arrayBlockingQueue.take();
arrayBlockingQueue.take();//没有元素,一直等待
}

4.超时等待

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//等待,阻塞(等待超时)
public static void test4() throws InterruptedException {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
System.out.println(arrayBlockingQueue.offer("d",2, TimeUnit.SECONDS));//超过两秒就退出
System.out.println(arrayBlockingQueue.peek());
System.out.println("============");
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll(2,TimeUnit.SECONDS));//超过两秒就退出
}

同步队列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
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName()+"put 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName()+"put 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName()+"put 3");
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 (InterruptedException e) {
e.printStackTrace();
}
}, "T2").start();

}
}

13.线程池(重点)

3大方法、7大参数、4种拒绝策略

池化技术

事先准备好一些资源,需要使用就拿,用完归还。

程序的运行,本质:占用系统的资源

优化资源的使用=》池化技术

线程池、连接池、内存池、对象池。。。

创建和销毁浪费资源。

线程池的好处:

1.降低资源的消耗

2.提高响应的速度

3.方便管理

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

线程池:三大方法

三大方法

1
2
3
4
Executors.newSingleThreadExecutor();
//单个线程的线程池
Executors.newFixedThreadPool(5);//固定的线程池
Executors.newCachedThreadPool();//可伸缩的线程池

源码分析:

1
2
3
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}
1
2
3
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
1
2
3
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}

均调用ThreadPoolExecutor

七个参数

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 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;
}

四种拒绝策略

1.new ThreadPoolExecutor.AbortPolicy()

线程池和阻塞队列都满了,丢掉任务,抛出异常

2.new ThreadPoolExecutor.CallerRunsPolicy()

线程池和阻塞队列都满了,哪来的回哪去(由调用线程处理该任务)

3.new ThreadPoolExecutor.DiscardPolicy()

线程池和阻塞队列都满了,丢掉任务,不会抛出异常

4.new ThreadPoolExecutor.DiscardOldestPolicy()

线程池和阻塞队列都满了,丢弃队列最前面的任务,然后重新提交被拒绝的任务

手动创建线程池

最大承载:max+queue

1
2
超出最大承载
RejectedExecutionException//拒绝策略异常

线程池

最大线程到底该如何定义

最大线程到底该如何定义

1.CPU密集型

几核CPU就定义几,这样可以保持CPU效率最高。

获取CPU核数。

1
Runtime.getRuntime().availableProcessors()

2.IO密集型

判断程序中最耗IO的线程数,设置为它的两倍。

14.四大函数式接口(重点、简单)

程序员必须掌握的

1.lambda表达式

2.链式编程

3.函数式接口

4.Stream流式计算

函数式接口

1
2
3
4
5
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
超多使用,简化编程模型,在新版本框架中大量应用

函数式接口都可以用lambda简写

1.Function函数型接口

有一个输入参数,有一个输出参数。

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
//工具类,输出输入的值
// Function function=new Function<String,String>(){
// @Override
// public String apply(String str) {
// return str;
// }
// };
Function<String,String> function=(str)->{return str;};
System.out.println(function.apply("abc"));
}

2.Predicate断定型接口

有一个输入参数,返回值只能是布尔值。

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) {
//判断字符串是否为空
// Predicate predicate=new Predicate<String>() {
// @Override
// public boolean test(String str) {
// return str.isEmpty();
// }
// };
Predicate<String> predicate=(str)->{return str.isEmpty();};
System.out.println(predicate.test(""));
}

3.Consumer消费型接口

只有输入,没有返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    public static void main(String[] args) {
//打印字符串

// Consumer consumer=new Consumer<String>() {
// @Override
// public void accept(String str) {
// System.out.println(str);
// }
// };
Consumer<String> consumer=(str)->{
System.out.println(str);
};
consumer.accept("aaa");
}

4.Supplier供给型接口

没有参数,只有返回值

1
2
3
4
5
6
7
8
9
10
    public static void main(String[] args) {
// Supplier supplier=new Supplier<Integer>() {
// @Override
// public Integer get() {
// return 1024;
// }
// };
Supplier supplier=()->{return 1024;}
System.out.println(supplier.get());
}

15.Stream流式计算

什么是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
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(5,"e",25);
User u6=new User(6,"F",26);
//集合是存储
List<User> list= Arrays.asList(u1,u2,u3,u4,u5,u6);
//计算交给stream
//ID是偶数
//年龄大于23
//把用户名字都转为大写
//用户名倒置排序
//只输出一个用户

//链式编程
list.stream().filter((u)->{return u.getUid()%2==0;})
.filter((u)->{return u.getAge()>23;})
.map((u)->{return u.getUname().toUpperCase();})
.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
.limit(1)
.forEach(System.out::println);

}

16.ForkJoin

什么是ForkJoin

JDK1.7,并行执行任务,大数据量才能提高效率。

把大任务分成小任务,特点:工作窃取

双端队列

使用ForkJoin

1.ForkJoinPool执行

2.计算任务forkjoinpool.execute(ForkJoinTask task)

3.计算类继承RecursiveTask

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
//求和计算的任务(ForkJoin/Stream并行流)
public class Test1 extends RecursiveTask<Long> {
private Long start;//1
private Long end;//10 0000 0000
private Long temp=10000L;

public Test1(Long start, Long end) {
this.start = start;
this.end = end;
}

//计算方法
@Override
protected Long compute() {
if(end-start>temp){
//分支合并计算
long middle= (start + end) / 2;
Test1 test1 = new Test1(start, middle);
test1.fork();//拆分任务,把任务压入线程队列
Test1 test2 = new Test1(middle+1, end);
test2.fork();
return test1.join()+test2.join();
}else{
Long sum = 0L;
for (Long i = start; i <= end; i++) {
sum += i;
}
return sum;
}
}
}

测试类

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
    //普通程序员
//sum=500000000500000000 时间:10899
@Test
public void a(){
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));
}
//ForkJoin会使用
//sum=500000000500000000时间:5132
@Test
public void b() throws ExecutionException, InterruptedException {
long start=System.currentTimeMillis();
ForkJoinPool forkJoinPool=new ForkJoinPool();
ForkJoinTask<Long> task=new Test1(0L,10_0000_0000L);
// forkJoinPool.execute(task);//执行任务
Long sum=forkJoinPool.submit(task).get();//提交任务
long end=System.currentTimeMillis();
System.out.println("sum="+sum+"时间:"+(end-start));
}

//Steam并行流
//sum=500000000500000000时间:1274
@Test
public void c(){
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+"时间:"+(end-start));
}

17.异步回调

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
    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("11111");
// completableFuture.get();//阻塞获取执行结果

//有返回值得supplyAsync异步回调
//返回错误信息
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread().getName() + "runAsync=>Integer");
int i = 10 / 0;
return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
//正常得返回结果
System.out.println("t=>" + t);
System.out.println("u=>" + u);//错误信息
}).exceptionally((e) -> {
System.out.println(e.getMessage());//获取到错误得返回结果
return 233;
}).get());
}

18.JMM

volatile是什么?

volatile是java虚拟机提供轻量级得同步机制。

1.保证可见性

2.不保证原子性

3.禁止指令重排

JMM

JMM:java内存模型,不存在得东西,概念、约定

关于JMM得一些同步约定:

1.线程解锁前、必须把共享变量立刻刷回主存。

2.线程加锁前,必须读取主存中得最新值到工作内存中。

3.加锁和解锁是同一把锁。

JMM中8种操作

1.read (读取)、2.load(载入)

3.use(使用)、 4.assign(赋值)

5.store(存储)、6.write(写入)

7.lock(锁定)、8.unlock(解锁)

线程A和线程B都读取了主存的值,但线程B修改值并写入主存,线程A还在使用原来的值,和主存的值不一致,相对于线程A,则没有可见性。

需要线程A指定主内存中的值发生了变化了。

volatile

保证可见性

保证可见性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private volatile static int num=0;
public static void main(String[] args) {

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

try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

num=1;
System.out.println(num);
}
不保证原子性

不保证原子性

原子性:不可分割

线程在执行任务的时候,不能被分割,要么同时成功,要么同时失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//不保证原子性
private volatile static int num=0;
public static void add(){//synchronized可以保证原子性
num++;
}
public static void main(String[] args) {
for (int i=1;i<=20;i++){
new Thread(()->{
for (int j=0;j<1000;j++){
add();//理论上nun为2万
}
}).start();
}
while (Thread.activeCount()>2){//main、GC线程默认执行
Thread.yield();//礼让,main线程主动礼让线程给其他线程执行
}
System.out.println(Thread.currentThread().getName()+" "+num);
}

如果没有synchronized或者lock,怎么保证原子性?

使用原子类,解决原子性问题

原子类为什么这么高级?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private volatile static AtomicInteger num=new AtomicInteger();
public static void add(){
//synchronized可以保证原子性
num.getAndIncrement();//CAS,在内存中修改值,加1操作
}
public static void main(String[] args) {
for (int i=1;i<=20;i++){
new Thread(()->{
for (int j=0;j<1000;j++){
add();//理论上nun为2万
}
}).start();
}
while (Thread.activeCount()>2){//main、GC线程默认执行
Thread.yield();//礼让,main线程主动礼让线程给其他线程执行
} System.out.println(Thread.currentThread().getName()+" "+num);
}

这些类的底层直接和操作系统挂钩。

禁止指令重排

禁止指令重排

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

处理器在进行指令重排的时候,会考虑数据之间的依赖性。

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

1
2
3
4
5
int x=1;  //1
int y=2; //2
x=x+5; //3
y=x+x; //4
我们期望的1234,但是可能执行的时候变成2134,1324,不可能是 4123

可能造成影响的结果

线程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指令、作用:

1.保证特定的操作的执行顺序

2.保证某些变量的内存可见性(利用这些特性,volatile实现了可见性)

19.彻底玩转单例模式

1.构造函数私有

2.私有自身对象成员变量

3.公有获取对象方法

饿汉式

饿汉式

1
2
3
4
5
6
7
8
private Hungry(){}
//可能会浪费空间
private byte[] data=new byte[1024*1024];

private final static Hungry Hungry=new Hungry();
public static Hungry getInstance(){
return Hungry;
}

懒汉式

懒汉式

普通单线程,懒汉式
1
2
3
4
5
6
7
8
9
10
11
12
13
14

//单线程是ok的
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懒汉式 :双重检测模式的懒汉单例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private LazyMan() {
System.out.println(Thread.currentThread().getName() + "ok");
}

private static LazyMan lazyMan;

//双重检测模式的懒汉单例,DCL懒汉式
public static LazyMan getInstance() {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
return lazyMan;
}

上面的代码存在问题,需要避免指令重排:volatile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private LazyMan() {
System.out.println(Thread.currentThread().getName() + "ok");
}

private volatile static LazyMan lazyMan;//禁止指令重排

//双重检测模式的懒汉单例,DCL懒汉式
public static LazyMan getInstance() {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();//不是一个原子性操作
//1.分配内存空间
//2.执行构造方法,初始化对象
//3.把这个对象指向这个空间
//正常123
//但如果走成1 3(期间单例B线程插入) 2
}
}
return lazyMan;
}

静态内部类

1
2
3
4
5
6
7
private Holder(){}
public static Holder getInstance(){
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
//enum是什么,本身也是一个class类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) {
EnumSingle instance = EnumSingle.INSTANCE;

}
}

20.深入理解CAS

什么是CAS

1
2
3
4
5
6
7
8
    //原子类的底层是CAS
public static void main(String[] args) {
AtomicInteger atomicInteger=new AtomicInteger(2021);
//如果期望值达到了,就更新
//CAS是CPU的并发原理
atomicInteger.compareAndSet(2021,2022);//比较并交换
System.out.println(atomicInteger.get());
}

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

缺点:

1.循环耗时

2.一次只能保证一个共享变量的原子性

3.ABA问题

ABA问题

狸猫换太子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//原子类的底层是CAS
public static void main(String[] args) {
AtomicInteger atomicInteger=new AtomicInteger(2021);
//如果期望值达到了,就更新
//CAS是CPU的并发原理


//对于SQL来说,会有乐观锁解决

//======================捣乱的线程==============================
atomicInteger.compareAndSet(2021,2022);//比较并交换
System.out.println(atomicInteger.get());
atomicInteger.compareAndSet(2022,2021);//比较并交换
System.out.println(atomicInteger.get());

//=======================期望的线程============================
atomicInteger.compareAndSet(2021,2022);//比较并交换
System.out.println(atomicInteger.get());
}

怎么解决,不被捣乱?

21.原子引用

带版本号的原子!

如果泛型是包装类,要注意对象的引用问题。

Integer使用对象缓存机制,默认范围-128-127,推荐使用静态工厂方法valueof获取对象实例,而不是new,因为valueof使用缓存,而new一定会创建新的对象分配新的内存。

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 static void main(String[] args) {
AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(1,1);

new Thread(()->{
int stamp = atomicInteger.getStamp(); //获得版本号
System.out.println("a=>"+stamp);

System.out.println(atomicInteger.compareAndSet(1, 2, atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
System.out.println("a2=>"+atomicInteger.getStamp());

System.out.println(atomicInteger.compareAndSet(2, 1, atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
System.out.println("a3=>"+atomicInteger.getStamp());

},"a").start();

new Thread(()->{
int stamp = atomicInteger.getStamp(); //获得版本号
System.out.println("b=>"+stamp);

try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println(atomicInteger.compareAndSet(1, 6, atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
System.out.println("b2=>"+atomicInteger.getStamp());

},"b").start();

}

解决ABA问题,B线程把数据改变了又改回来,A线程不知情。

对应思想:乐观锁

22.各种锁的理解

1.公平锁、非公平锁

公平锁:非常公平,不能插队,线程必须先来后到!
非公平锁:非常不公平,可以插队(默认)

1
2
3
4
5
6
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}

2.可重入锁(递归锁)

拿到外面的锁,就可以自动获取里面的锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
    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");
}
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
    public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(() -> {
phone.sms();
}, "A").start();
new Thread(() -> {
phone.sms();
}, "B").start();
}
}

class Phone2 {
Lock lock = new ReentrantLock();

public void sms() {
lock.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();
}
}

3.自旋锁

spinlock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//自旋锁
public class SpinLock1 {


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);
}

}
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

SpinLock1 spinLock = new SpinLock1();

new Thread(() -> {
spinLock.myLock();
try {
TimeUnit.SECONDS.sleep(3);
}catch (Exception e){
e.printStackTrace();
}finally {
spinLock.myUnLock();
}
}, "T1").start();


new Thread(() -> {
spinLock.myLock();
try {
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
e.printStackTrace();
}finally {
spinLock.myUnLock();
}
}, "T2").start();

}

4.死锁

死锁是什么?

线程A抢线程B的锁,线程B抢线程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
29
30
31
32
33
34
   public static void main(String[] args) {

String lockA="lockA";
String lockB="lockB";
new Thread(new MyThread(lockA,lockB),"T1").start();
new Thread(new MyThread(lockA,lockB),"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+"=>get:"+lockB);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lockB){
System.out.println(Thread.currentThread().getName()+"lock:"+lockB+"=>get:"+lockA);
}
}
}

解决问题(死锁排查)

1.使用命令提示符命令,jps -l定位进程号

2.使用 jstack+进程号,找到死锁问题