1、进程和线程

进程:把一个静态代码装载到内存中运行即可得到一个进程

线程:在进程中的执行单元,进程是最小的资源分配单元,线程是最小的执行单元

Java默认两个线程,一个Main线程,一个GC回收线程

Java实际上并不能开启线程,Thread.start方法最后调用native本地方法开启线程

并发编程:并发、并行

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

CPU 一核 ,模拟出来多条线程,天下武功,唯快不破,快速交替

  • 并行(多个人一起行走)

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

1
2
3
4
5
6
7
8
//获取CPU核数
public class Test1 {
public static void main(String[] args) {
// 获取cpu的核数
// CPU 密集型,IO密集型
System.out.println(Runtime.getRuntime().availableProcessors());
}
}

线程的几种状态(6种)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public enum State {
// 新生
NEW,
// 运行
RUNNABLE,
// 阻塞
BLOCKED,
// 等待,死死地等
WAITING,
// 超时等待
TIMED_WAITING,
// 终止
TERMINATED;
}

wait和sleep区别

  • wait是Object类的方法,sleep是Thread类方法
  • wait会释放锁,sleep不会释放锁
  • wait必须在同步代码块使用,sleep随处可用

2、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
/**
* 真正的多线程开发,公司中的开发,降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作!
* 1、 属性、方法
*/
public class TicketSaleDemo1 {
public static void main(String[] args) {
// 并发:多线程操作同一个资源类, 把资源类丢入线程
Ticket ticket = new Ticket();
//@FunctionalInterface注解支持函数式接口,使用Lambda表达式即可
new Thread(()->{
for (int j = 0; j < 40; j++) {
ticket.sale();
}
},"线程A").start();
new Thread(()->{
for (int j = 0; j < 40; j++) {
ticket.sale();
}
},"线程B").start();
new Thread(()->{
for (int j = 0; j < 40; j++) {
ticket.sale();
}
},"线程C").start();
}
}

class Ticket{
private int num = 30;
//synchronized本质:队列,锁
public synchronized void sale(){
if(num>0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(num--)+"票,剩余"+num);
}
}
}

Lock锁

公平锁:十分公平:可以先来后到,存在性能问题,短作业优先

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

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
/**
* 真正的多线程开发,公司中的开发,降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作!
* 1、 属性、方法
*/
public class TicketSaleDemo2 {
public static void main(String[] args) {
// 并发:多线程操作同一个资源类, 把资源类丢入线程
Ticket2 ticket = new Ticket2();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "B").start();
new Thread(() -> {
for (int i = 0; i < 40; i++) {
ticket.sale();
}
}, "C").start();

}
}

/**
* Lock三部曲
* 1.new ReentrantLock() 底层默认使用非公平锁,非公平锁不排对,可以保证短作业优先
* 2.lock.lock() 加锁
* 3.try catch finally{
* lock.unlock() 解锁
* }
*/

class Ticket2 {
private int num = 30;
Lock lock = new ReentrantLock();//可重入锁

public void sale() {
lock.lock();
try {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了" + (num--) + "票,剩余" + num);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

synchronized和lock的区别

  • synchronized是内置Java关键字,Lock是JUC包下的一个类
  • synchronized无法判断锁的状态,Lock可以判断是否获取到锁
  • synchronized会自动释放锁,Lock需要自己手动释放锁,如果未释放会造成死锁
  • synchronized线程1(获得锁,阻塞),线程2(等待),Lock锁就不一定会等待下去
  • synchronized适合锁少量代码同步问题,Lock适合锁大量同步代码块

3、生产者消费者模式

生产者和消费者问题 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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/**
* 线程之间的通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
* 线程交替执行 A B 操作同一个变量 num = 0
* A num+1
* B num-1
*/
public class Demo1 {
public static void main(String[] args) {
MyClass data = new MyClass();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "B").start();

/**
* 如果再加上两个线程,一个加一个减会出现问题,wait()的虚假唤醒
*/
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "D").start();
}
}

/**
* 生产者消费者模型,传统sync版
* 流程:判断等待,业务,唤醒
*/
class MyClass { //资源类
private int number = 0;

public synchronized void increment() throws InterruptedException {
if (number != 0) {
/**
* 虚假唤醒是指,在多线程编程中,wait方法通常用于使线程进入等待状态,
* 直到被其他线程唤醒。虚假唤醒是指线程在没有被notify或notifyAll方法正确唤醒的情况下,
* 却从wait状态恢复执行的现象。
*
* 如果使用if判断,只判断了一次,则会往下执行,应该使用while判断
*/
wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
notifyAll();
}

public synchronized void decrement() throws InterruptedException {
if (number == 0) {
wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
notifyAll();
}
}

存在虚假唤醒的可能,两个线程不会出问题,四个线程就有问题

虚假唤醒是指,在多线程编程中,wait方法通常用于使线程进入等待状态,
直到被其他线程唤醒。虚假唤醒是指线程在没有被notify或notifyAll方法正确唤醒的情况下,
却从wait状态恢复执行的现象。
如果使用if判断,只判断了一次,则会往下执行,应该使用while判断

  • 底层系统实现差异:不同的操作系统和 JVM(Java 虚拟机)实现对于线程等待和唤醒的机制可能存在差异。在某些情况下,系统可能会产生一些 “伪信号”,导致线程被错误地唤醒。
  • 信号干扰:在复杂的多线程环境中,可能存在多种并发的信号和事件。例如,当一个线程正在等待某个特定条件(如资源可用),但由于其他外部因素(如系统中断、硬件异常等),可能会错误地触发线程从等待状态中恢复。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public synchronized void increment() throws InterruptedException {
while (number != 0) { //使用while排除虚假唤醒
wait();
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
notifyAll();
}

public synchronized void decrement() throws InterruptedException {
while (number == 0) {
wait();
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
notifyAll();
}

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
/**
* 功能描述
* 生产者消费者模式,传统三件套 synchronized wait notifyAll
* 使用Lock锁 Lock lock.newCondition.await() lock.newCondition.signalAll()
*
* @author: jx
* @date: 2024年12月25日 19:59
*/
public class LockDemo {
public static void main(String[] args) {
LockData data = new LockData();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.increment();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.decrement();
}
}, "B").start();

/**
* 如果再加上两个线程,一个加一个减会出现问题,wait()的虚假唤醒
*/
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.increment();
}
}, "C").start();
new Thread(() -> {
for (int i = 0; i < 10; i++) {
data.decrement();
}
}, "D").start();
}
}

class LockData {
private int number = 0;
final Lock lock = new ReentrantLock();
final Condition isZero = lock.newCondition();

/**
* 循环判断等待,业务,唤醒
* <p>
* Lock.lock() 配合try catch业务 finally Lock.unlock()
*/
public void increment() {
lock.lock();
try {
while (number != 0) {
isZero.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "->" + number);
isZero.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void decrement() {
lock.lock();
try {
while (number == 0) {
isZero.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "->" + number);
isZero.signalAll();
} 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
/**
* 功能描述
* 三个线程按照顺序打印A,B,C
* 多个线程操作同一个资源
*
* @author: jx
* @date: 2024年12月26日 15:08
*/
public class ConditionDemo {
public static void main(String[] args) {
ConditionData data = new ConditionData();
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();
}
}

/**
* 创建一个锁
* 创建三个监视器,用于通知对应线程执行操作
* 创建一个业务变量,用于判断是否需要等待
* <p>
* 判断 等待 业务 唤醒
*
* wait调用会释放锁
*/
class ConditionData {
private Lock lock = new ReentrantLock();
private Condition printA = lock.newCondition();
private Condition printB = lock.newCondition();
private Condition printC = lock.newCondition();
private int flag = 1;

public void printA() {
lock.lock();
try {
while (flag != 1) {
printA.await();
}
System.out.println(Thread.currentThread().getName() + "====>" + "AAAAAAAAAAAAAA");
flag++;
printB.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void printB() {
lock.lock();
try {
while (flag != 2) {
printB.await();
}
System.out.println(Thread.currentThread().getName() + "====>" + "BBBBBBBBBBBBB");
flag++;
printC.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

public void printC() {
lock.lock();
try {
while (flag != 3) {
printC.await();
}
System.out.println(Thread.currentThread().getName() + "====>" + "CCCCCCCCCCCCC");
flag = 1;
printA.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}

4、锁的八个问题

锁的对象只有两种,一种是实例锁,锁的是对象实例可以存在多个实例,一种是Class锁,锁的是字节码只有一份

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
/**
* 功能描述
* 关于锁的八个问题
* 1.标准情况下,线程执行顺序是先发消息再打电话
* 2.在发消息的方法中睡五秒 还是先发消息再打电话
* @author: jx
* @date: 2024年12月26日 15:53
*/
public class Demo1 {
public static void main(String[] args) {
//synchronized在方法上锁的是实例对象phone
Phone phone = new Phone();
new Thread(()->{
phone.sendMsg();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
phone.call();
},"B").start();
}
}

/**
* 首先看主线程,线程A先拿到锁
* synchronized在方法上锁的是实例对象phone
* main线程中先开了A线程调用了发消息的方法,然后睡了一秒
* 这个过程中A线程拿到了phone对象的锁,sleep方法不会释放锁,经过了main线程的1秒睡眠,
* B线程启动去获取phone对象锁开启打电话的功能,B线程进入等待队列,由jvm中的monitor维护的等待队列
*/
class Phone{

public synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发消息");
}

public synchronized void call(){
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
41
42
43
44
45
46
47
48
/**
* 功能描述
* 关于锁的八个问题
* 3.增加一个不带锁的普通方法,使用同一个对象的话,B线程则先执行了hello
* 4.创建了两个资源对象,sync锁的是实例对象,两个对象之间互不干扰,b只睡了一秒先执行
* @author: jx
* @date: 2024年12月26日 15:53
*/
public class Demo2 {
public static void main(String[] args) {
//synchronized在方法上锁的是实例对象phone
Phone2 phone1 = new Phone2();
Phone2 phone2 = new Phone2();
new Thread(()->{
phone1.sendMsg();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
phone2.call();
},"B").start();
}
}


class Phone2{

public synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发消息");
}

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

//未在方法上使用sync锁,则不受锁的限制
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
41
42
43
44
45
46
47
48
49
/**
* 功能描述
* 关于锁的八个问题
* 如果在静态方法上加上sync,那么这个锁就不是对象锁了,而是Class锁,全局只有一份,跟对象无关
*
* 5.增加两个静态的同步方法,只有一个对象,先打印 发短信?打电话?
* 6、两个对象!增加两个静态的同步方法, 先打印 发短信?打电话?
* @author: jx
* @date: 2024年12月26日 15:53
*/
public class Demo3 {
public static void main(String[] args) {
Phone3 phone1 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(()->{
phone1.sendMsg();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
phone2.call();
},"B").start();
}
}


class Phone3{

public static synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发消息");
}

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

//未在方法上使用sync锁,则不受锁的限制
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
41
42
43
44
45
46
/**
* 功能描述
* 关于锁的八个问题
* 如果在静态方法上加上sync,那么这个锁就不是对象锁了,而是Class锁,全局只有一份,跟对象无关
*
* 7.增加1个静态的同步方法,1个普通同步方法,只有一个对象,先打印 发短信?打电话? 打电话
* 静态锁的CLass,普通锁的实例,两者没有关系,看具体场景
* 8、两个对象!增加1个静态的同步方法,1个普通同步方法, 先打印 发短信?打电话? 打电话
* @author: jx
* @date: 2024年12月26日 15:53
*/
public class Demo4 {
public static void main(String[] args) {
Phone4 phone1 = new Phone4();
Phone4 phone2 = new Phone4();
new Thread(()->{
phone1.sendMsg();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
phone2.call();
},"B").start();
}
}


class Phone4{

public static synchronized void sendMsg(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("发消息");
}

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

}

实例锁:sync在实例方法上,sync在代码块中sync(this)

类锁:sync在静态方法上,sync在代码块中sync(xxx.class)

5、集合类不安全

List不安全

使用ArrayList在多线程环境下报错:java.util.ConcurrentModificationException 多线程修改异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class UnSafeList {
public static void main(String[] args) {
//List list = new ArrayList(); java.util.ConcurrentModificationException 多线程修改异常
/**
* 解决方案:
* 1.new Vector(); 太老了,性能不好,ArrayList都是在他之后才出来
* 2.Collections.synchronizedList(new ArrayList<>()); sync的效率不如写时复制效率高
* 3.new CopyOnWriteArrayList();
*
* CopyOnWrite写入时复制 COW 计算机设计领域的一种优化策略
* 读写分离,避免写入时覆盖
*/
List list = new CopyOnWriteArrayList();
for (int i = 0; i < 10; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
}).start();
}
}
}

CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为开始读的那一刻已经确定了读的对象是旧对象。CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单等场景。

Set不安全

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 同理可证 : ConcurrentModificationException
* //1、Set<String> set = Collections.synchronizedSet(new HashSet<>());
* //2、
*/
public class SetTest {
public static void main(String[] args) {
// Set<String> set = new HashSet<>();
// Set<String> set = Collections.synchronizedSet(new HashSet<>());
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 1; i <=30 ; 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
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(); // 不变得值!