多线程
一、线程的创建和使用
1. 方式一:继承Thread类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | public class ThreadTest {
public static void main(String[] args) {
// 3. 创建Thread类子类的对象
MyThread t = new MyThread();
// 4. 通过此对象调用start(): ①启动当前线程;②调用当前线程的run()
t.start();
// 其他代码仍然在main线程中执行
}
}
// 1. 创建继承于Thread类的子类
class MyThread extends Thread {
// 2. 重写Thread类的run方法
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
|
2. 方式二:实现Runnable
接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | package com.example.www;
public class ThreadTest {
public static void main(String[] args) {
// 3. 创建实现类的对象
MyThread myThread = new MyThread();
// 4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t = new Thread(myThread);
// 5. 通过Thread类的对象调用start()
t.start();
}
}
// 1. 创建一个实现了Runnable接口的类
class MyThread implements Runnable {
// 2. 实现类去实现Runnable中的抽象方法run()
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
|
开发中优先使用这种方式
- 实现的方法没有类的单继承性的局限性
- 实现的方法更适合来处理有共享数据的情况
3. 方式三:实现Callable接口
- 可以有返回值
- 方法可以跑出异常
- 支持泛型的返回值
- 需要借助
FutureTask
类,必须获取返回结果。
- 可以对具体
Runnable
、Callable
任务的执行结果进行取消、查询是否完成、获取结果等。
FutureTask
是Future接口的唯一的实现类
FutureTask
同时实现了Runnable
、Future
接口。既可以作为Runnable
被线程执行,又可以作为Future
得到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比Runnable更强大
1. call()可以有返回值
2. call()可以跑出异常,被外面的操作捕获,获取异常的信息
3. Callable是支持泛型的
*/
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest {
public static void main(String[] args) {
// 3. 创建Callable接口实现类的对象
MyThread t = new MyThread();
// 4. 将此Callable接口实现类的对象传递到FutureTask构造器中,创建FutureTask的对象
FutureTask<Integer> futureTask = new FutureTask<Integer>(t);
// 5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
new Thread(futureTask).start();
try {
// get() 返回值是FutureTask构造器参数Callable实现类重写的call()的返回值
// 6. 获取Callable中call方法的返回值
Integer sum = futureTask.get();
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
// 1 创建一个实现Callable的实现类
class MyThread implements Callable<Integer> {
// 2. 实现call()方法,将此线程需要执行的操作声明在call()方法中
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i < 100; i++) {
if (i % 2 == 0){
sum += i;
}
}
return sum; // 可以有返回值
}
}
|
4. 方式四:使用线程池
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 | /*
* 线程池的好处:
* 1. 提高响应速度
* 2. 降低资源消耗
* 3. 便于线程管理
*/
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
ThreadPoolExecutor service = (ThreadPoolExecutor) executorService;
// 设置线程池属性
service.setCorePoolSize(15);
// service.setKeepAliveTime();
// service.execute(); // 适用于Runnable
// service.submit(); // 适用于Callable
service.execute(new MyThread1());
service.shutdown();
}
}
class MyThread1 implements Runnable {
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
|
5. 线程的常用方法
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 | package com.example.www;
public class ThreadMethodTest {
public static void main(String[] args) {
MyThread t = new MyThread();
t.setName("Thread-zero"); // 自定义线程名
t.start();
// 判断当前进程是否存活
System.out.println(t.isAlive()); // true
for (int i = 0; i < 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
if (i == 20) {
try {
// 当前线程进入阻塞状态,直到线程t完全执行完以后
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
// currentThread() 静态方法,返回执行当前代码的线程
// getName() 获取当前线程名
System.out.println(Thread.currentThread().getName() + ":" + i); // Thread-zero 默认是:Thread-0
}
// if (i % 20 == 0) {
// yield(); // 释放当前CPU的执行权
// }
}
}
}
|
6. 线程优先级
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | // MIN_PRIORITY = 1
// NORM_PRIORITY = 5
// MAX_PRIORITY = 10
public class ThreadMethodTest {
public static void main(String[] args) {
MyThread t = new MyThread();
// 设置线程的优先级
t.setPriority(7);
t.start();
// 获取线程的优先级
System.out.println(t.getPriority()); // 7
}
}
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
|
二、线程的生命周期
三、线程同步
买票过程中出现了重票和错票 --> 出现了线程安全问题
在Java中通过同步机制来解决线程的安全问题
操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低。
1. 方法一:同步代码块
1.1 同步代码块处理实现Runnable
的线程安全问题
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 | /*
synchronized(同步监视器){ // 同步监视器:俗称锁,任何一个类的对象都可以充当锁
需要被同步的代码 // 操作共享数据的代码
}
*/
public class TicketWindowTest {
public static void main(String[] args) {
Window window = new Window();
Thread t1 = new Thread(window);
Thread t2 = new Thread(window);
Thread t3 = new Thread(window);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window implements Runnable {
private int ticket = 100;
// new 一个对象充当锁
// Object obj = new Object(); // 多个线程必须要公用一把锁
public void run() {
while (true) {
// synchronized(obj) {
synchronized(this) { // 用当前对象充当锁,此时的this是唯一的Window对象
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
|
1.2 同步代码块处理继承Thread类方式的线程安全问题
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 | public class TicketWindowTest {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window extends Thread {
private static int ticket = 100;
// private static Object obj = new Object();
public void run() {
while (true){
// synchronized (obj) {
synchronized (Window.class) { // Window类只会加载一次,所以可以使用当前类充当锁
if (ticket > 0) {
try {
Thread.sleep(100); // 添加延时,增加出错概率
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":买票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
|
2. 方法一:同步方法
- 同步方法仍然涉及到同步监视器,只是不需要显示声明。
- 非静态的同步方法,同步监视器是
this
。
- 静态的同步方法,同步监视器是当前类本身。
2.1 同步代码块处理实现Runnable
的线程安全问题
如果操作共享数据的代码完整的声明在一个方法中,可以将此方法声明为同步的。
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 | package com.example.www;
public class TicketWindowTest {
public static void main(String[] args) {
Window window = new Window();
Thread t1 = new Thread(window);
Thread t2 = new Thread(window);
Thread t3 = new Thread(window);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window implements Runnable {
private int ticket = 100;
public void run() {
while (ticket > 0) {
ticket();
}
}
public synchronized void ticket() { // 同步方法
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
ticket--;
}
}
}
|
2.2 同步代码块处理实现继承Thread类方式的线程安全问题
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 | package com.example.www;
/*
使用同步方法处理继承Thread的线程安全问题
*/
public class TicketWindowTest {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window extends Thread {
private static int ticket = 100;
public void run() {
while (ticket > 0) {
ticket();
}
}
public static synchronized void ticket() { // 需要包装此方法时静态的, 此时的同步监视器是当前类
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
ticket--;
}
}
}
|
四、死锁
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 | /*
模拟线程死锁
使用同步时要避免死锁
*/
public class LockTest {
public static void main(String[] args) {
StringBuffer str1 = new StringBuffer();
StringBuffer str2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (str1){
str1.append("A");
str2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (str2){
str1.append("B");
str2.append("2");
System.out.println(str1);
System.out.println(str2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (str2){
str1.append("C");
str2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (str1){
str1.append("D");
str2.append("4");
System.out.println(str1);
System.out.println(str2);
}
}
}
}).start();
}
}
|
2. 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 | package com.example.www;
import java.util.concurrent.locks.ReentrantLock;
public class TicketWindowTest {
public static void main(String[] args) {
Window window = new Window();
Thread t1 = new Thread(window);
Thread t2 = new Thread(window);
Thread t3 = new Thread(window);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
class Window implements Runnable {
private int ticket = 100;
// 1. 创建锁
private ReentrantLock lock = new ReentrantLock();
public void run() {
while (true) {
try {
// 2. 调用锁定方法
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
ticket--;
} else {
break;
}
} finally {
// 3. 释放锁
lock.unlock();
}
}
}
}
|
五、线程通信
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 | // 线程通信示例
// 两个线程交替打印1-100
/*
涉及到的方法
wait(): 当前线程进入阻塞状态,并释放同步监视器
notify(): 唤醒一个被wait()的线程
notifyAll(): 唤醒所有被wait()的线程
注意:
1. 只能在同步代码块或同步方法中使用
2. 调用者必须是同步代码块或同步方法中的同步监视器
3. 这三个方法定义在Object中
*/
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.start();
t2.start();
}
}
class Number implements Runnable {
private int number = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
notify();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (number <= 100){
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
wait(); // 使得调用如下wait()方法的线程进入阻塞状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
|
sleep()
和wait()
的异同
相同点:
- 都可以使当前线程进入阻塞状态
不同的:
- 两个方法声明的位置不同:Thread类中声明
sleep()
,Object中声明wait()
- 调用的要求不同:
sleep()
可以在任何需要的场景下调用。wait()
必须使用在同步代码块或同步方法中。
- 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,
sleep()
不会释放锁,wait()
会释放锁。
六、生产者消费者
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 | package com.example.www;
/*
* 线程通信的应用
* 多线程:生产者 消费者
* 数据共享
* 线程安全问题处理
* 线程通信
*/
public class ProductorCustomerTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor p1 = new Productor(clerk);
p1.setName("生产者1");
Customer c1 = new Customer(clerk);
c1.setName("消费者1");
p1.start();
c1.start();
}
}
class Clerk {
private int produceCount = 0;
public synchronized void ProduceProduct() {
if (produceCount < 20) {
produceCount++;
System.out.println(Thread.currentThread().getName() + ":开始生产第" + produceCount + "个产品");
notify();
} else {
// 等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void CustomeProduct() {
if (produceCount > 0) {
System.out.println(Thread.currentThread().getName() + ":开始消费第" + produceCount + "个产品");
produceCount--;
notify();
} else {
//等待
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Productor extends Thread {
private Clerk clerk;
public Productor(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
System.out.println(getName() + ":开始生产产品...");
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.ProduceProduct();
}
}
}
class Customer extends Thread {
private Clerk clerk;
public Customer(Clerk clerk) {
this.clerk = clerk;
}
public void run() {
System.out.println(getName() + ":开始生产产品...");
while (true) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
clerk.CustomeProduct();
}
}
}
|