Thread和Synchronized锁

Thread 的基本介绍和 Synchronized 常见注意事项

一、State 类型

1、操作系统线程 State

  • 新建(new):创建了一个新的线程对象

  • 就绪(runnable):调用线程的 start()方法,处于就绪状态

  • 运行(running):获得了 CPU 时间片,执行程序代码,就绪状态是进入到运行状态的唯一入口

  • 阻塞(block): 线程放弃对 CPU 的使用权,停止执行,直到进入就绪状态在有可能再次被 CPU 调度

    等待阻塞:运行状态的线程执行 wait 方法,JVM 会把线程放在等待队列中,使本线程进入阻塞状态。
    同步阻塞:线程在获得 synchronized 同步锁失败,JVM 会把线程放入锁池中,线程进入同步阻塞。对于锁池和等待池,可以看这篇文章
    其他阻塞:调用线程的 sleep()或者 join()后,线程会进入道阻塞状态,当 sleep 超时或者 join 终止或超时,线程重新转入就绪状态

  • 死亡(dead):线程 run()、main()方法执行结束,或者因为异常退出了 run()方法,则该线程结束生命周期

2、Java 线程 State

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 enum State {
/**
* 线程被构建,但是还没有调用start方法
*/
NEW,

/**
* Java线程把操作系统中就绪和运行两种状态统一称为“运行中”
*/
RUNNABLE,

/**
* 表示线程进入等待状态,也就是线程因为某种原因放弃了CPU的使用权,阻塞也分为几种情况(当一个线程试图获取一个内部的对象锁(非java.util.concurrent库中的锁),而该锁被其他线程持有,则该线程进入阻塞状态。)
* 等待阻塞:运行的线程执行了Thread.sleep、wait、join等方法,JVM会把当前线程设置为等待状态,当sleep结束,join线程终止或者线程被唤醒后,该线程从等待状态进入阻塞状态,重新占用锁后进行线程恢复
* 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程锁占用了,那么JVM会把当前项城放入到锁池中
* 其他阻塞:发出I/O请求,JVM会把当前线程设置为阻塞状态,当I/O处理完毕则线程恢复
*/
BLOCKED,

/**
* 等待状态,没有超时时间(无限等待),要被其他线程或者有其他的中断操作执行wait、join、LockSupport.park()
*/
WAITING,

/**
* 与等待不同的是,不是无限等待,超时后自动返回,执行sleep,带参数的wait等可以实现
*/
TIMED_WAITING,

/**
* 代表线程执行完毕
*/
TERMINATED;
}

二、创建线程的方法

1、继承 Thread

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 ThreadDemo{
public static void main(String[] args) {
//4.创建Thread类的子类对象
MyThread myThread=new MyThread();
//5.调用start()方法开启线程
//[ 会自动调用run方法这是JVM做的事情,源码看不到 ]
myThread.start();
for (int i = 0; i < 100; i++) {
System.out.println("我是主线程"+i);
}
}
}
class MyThread extends Thread{
//2.重写run方法
public void run(){
//3.将要执行的代码写在run方法中
for(int i=0;i<100;i++){
System.out.println("我是线程"+i);
}
}
}

2、实现 Runable

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 class RunnableDemo {
public static void main(String[] args) {
//4.创建Runnable的子类对象
MyRunnale mr=new MyRunnale();
//5.将子类对象当做参数传递给Thread的构造函数,并开启线程
//MyRunnale taget=mr; 多态
new Thread(mr).start();
for (int i = 0; i < 1000; i++) {
System.out.println("我是主线程"+i);
}
}
}

//1.定义一个类实现Runnable
class MyRunnale implements Runnable{
//2.重写run方法
@Override
public void run() {
//3.将要执行的代码写在run方法中
for (int i = 0; i < 1000; i++) {
System.out.println("我是线程"+i);
}
}
}

3、实现 Callable

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
/*
创建线程的方式三: 实现callable接口 ---JDK 5.0 新增
1.创建一个实现Callable接口的实现类
2.实现call方法,将此线程需要执行的操作声明在call()中
3.创建callable接口实现类的对象
4.将此callable的对象作为参数传入到FutureTask构造器中,创建FutureTask的对象
5.将FutureTask对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用star
6.获取callable接口中call方法的返回值
* */
public class ThreadNew {
public static void main(String[] args) {
//3.创建callable接口实现类的对象
NumThead m=new NumThead();
//4.将此callable的对象作为参数传入到FutureTask构造器中,创建FutureTask的对象

FutureTask futureTask = new FutureTask(m);
//5.将FutureTask对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法
//FutureTask类继承了Runnable接口
//new Runnable = futrueTask;
new Thread(futureTask).start();

//6.获取callable接口中call方法的返回值
try {
//get()方法返回值即为FutureTask构造器参数callable实现类重写的call方法的返回值
Object sum = futureTask.get();
System.out.println("总和是:"+sum);
} catch (Exception e) {
e.printStackTrace();
}
}

}
//1.创建一个实现Callable接口的实现类
class NumThead implements Callable{
// class NumThead implements Callable<Integer>{
//2.实现call方法,将此线程需要执行的操作声明在call()中
@Override
public Object call() throws Exception {
//public Integer call() throws Exception {
int sum=0;
for(int i=1;i<=100;i++){
System.out.println(i);
sum+=i;
}
return sum;
}
}

4、线程池

  • 后续做

三、同步问题及解决

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
public class SellTicket implements Runnable {
//定义一个成员变量表示有100张票
private int tickets=100;
public void run(){
while (true){
if(tickets>0){
try {
//通过sleep()方法来等待
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"张票");
}else{
//System.out.println("");
}
}
}
}
@SuppressWarnings("all")
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t1 = new Thread(st, "窗口1");
Thread t2 = new Thread(st, "窗口2");
Thread t3 = new Thread(st, "窗口3");
t1.start();
t2.start();
t3.start();
}
}

2、同步锁

synchronized 同步锁,对 this 和 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
public class SellTicket implements Runnable {
private int tickets = 100;
private Object obj = new Object();
private int x = 0;

@Override
public void run() {
while (true) {
if (x % 2 == 0) {
synchronized (this) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
} else {
sellTicket();
}
x++;
}
}

private synchronized void sellTicket() {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
  • synchorized 高阶操作

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
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
package github.polarisink.sync;

/**
* @author hzsk
*/
public class Test {
public static void main(String[] args) {
Food noodles = new RegularNoodles();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
noodles.makeNoodles();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "厨师A").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
noodles.makeNoodles();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "厨师B").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
noodles.eatNoodles();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "食客甲").start();
new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
noodles.eatNoodles();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "食客乙").start();
}
}


/**
* 普通面
*/
class RegularNoodles implements Food {
private int num = 0;
@Override
public synchronized void makeNoodles() throws InterruptedException {
//如果面的数量不为0,则等待食客吃完面再做面
if (num != 0) {
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "做好了一份面,当前有" + num + "份面");
//面做好后,唤醒食客来吃
this.notifyAll();
}
@Override
public synchronized void eatNoodles() throws InterruptedException {
//如果面的数量为0,则等待厨师做完面再吃面
if (num == 0) {
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "吃了一份面,当前有" + num + "份面");
//吃完则唤醒厨师来做面
this.notifyAll();
}
}
/**
* if改while可以不再虚假唤醒
* 手擀面
*/
class HandRolled implements Food {
private int num = 0;
@Override
public synchronized void makeNoodles() throws InterruptedException {
//如果面的数量不为0,则等待食客吃完面再做面
while (num != 0) {
this.wait();
}
num++;
System.out.println(Thread.currentThread().getName() + "做好了一份面,当前有" + num + "份面");
//面做好后,唤醒食客来吃
this.notifyAll();
}
@Override
public synchronized void eatNoodles() throws InterruptedException {
//如果面的数量为0,则等待厨师做完面再吃面
while (num == 0) {
this.wait();
}
num--;
System.out.println(Thread.currentThread().getName() + "吃了一份面,当前有" + num + "份面");
//吃完则唤醒厨师来做面
this.notifyAll();
}
}
interface Food {
/**
* 做面
*
* @throws InterruptedException
*/
void makeNoodles() throws InterruptedException;
/**
* 吃面
*
* @throws InterruptedException
*/
void eatNoodles() throws InterruptedException;
}

Thread和Synchronized锁
https://polarisink.github.io/20221009/yuque/Thread和Synchronized锁/
作者
Areis
发布于
2022年10月9日
许可协议