线程

概述

  1. 即使程序编码时没有主动自定义线程,运行时仍然会有一些默认线程,如主线程【main()】、gc线程【jvm提供的垃圾回收】。
  2. 多线程操作同一份资源,存在资源抢夺,需要加入并发控制。(每个线程在自己的工作内存交互,内存控制不当会造成数据不一致。)。每个拿到资源的线程都上一把锁,就算是Thread.sleep()也不会释放这把锁。保证安全性。
  3. 线程的运行由调度器安排,调度器与操作系统紧密相关,因此线程运行的先后顺序无法人为干预。
  4. 额外性能开销:CPU调度时间、并发控制开销

image-20201012215342413

程序、进程、线程关系

  1. 程序:没有运行的含义,静态的代码
  2. 进程Process:运行的程序【系统分配的】
  3. 线程Thread:一个进程中包含>=1个线程,真正程序的执行由线程来完成【由CPU调度和执行】

多线程类别

  • 真正的多线程,有多个CPU(多核)
  • 模拟的多线程,仅一个CPU,(在同一时间点CPU仍然只能做一件事),但因为cpu执行的任务切换的非常快,从而产生了同时执行的错觉。

线程创建

三种方式:

  1. Thread类

  2. Runnable接口

    • Thread类实际上是实现了Runnable接口。
    • 建议使用,接口实现
    1
    class Thread implements Runnable {}
  3. Callable接口

Thread

  1. 继承Thread类
  2. 重写run()方法,run方法为线程执行体
  3. 调用start()方法,启动线程。
  4. 线程不一定立即执行,由CPU安排调度。线程调用顺序不是同步一次执行(多线程是同时执行的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 定义
public class SoutTest extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("SoutTest i:" + i);
}
}
}
// 调用
public class ThreadMainTest {
public static void main(String[] args) {
SoutTest soutTest = new SoutTest();
soutTest.start();

for (int i = 0; i < 1000; i++) {
System.out.println("ThreadMainTest i:" + i);
//观察结果可知,SoutTest和ThreadMainTest出现的时机无序
}
}
}

下载文件案例

使用依赖包commons-io实现文件下载。如果是maven项目,直接引入依赖;否则,手动引入。

1
2
3
4
5
6
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.8.0</version>
</dependency>

image-20201017113300379

可以看到已经成功引入。

image-20201017113613211

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
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

class FileDownloadTest extends Thread {
private String url;
private String fileName;

public FileDownloadTest(String url, String fileName) {
this.url = url;
this.fileName = fileName;
}

@Override
public void run() {
Downloader downloader = new Downloader();
downloader.download(url, fileName);
System.out.println("download:" + fileName);
}

public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("70迈4K行车记录仪.jpg", "http://s1.dgtle.com/dgtle_img/article/2020/09/28/fc6567a9b2d4f176c11bae1c6d226f05.jpg");
map.put("360G580行车记录.jpeg", "http://s1.dgtle.com/dgtle_img/article/2020/08/23/7c707202008231518243883_1800_500.jpeg?imageMogr2/auto-orient");
map.put("小米行车记录仪2-2k版.jpeg", "http://s1.dgtle.com/dgtle_img/sale/2020/10/121388f202010121029499586_1800_500.jpeg");

for (String key : map.keySet()) {
FileDownloadTest f = new FileDownloadTest(map.get(key), key);
f.start();
}
}
}

class Downloader {
public void download(String url, String fileName) {
try {
// commons.io处理文件
FileUtils.copyURLToFile(new URL(url), new File(fileName));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,download方法出现问题");
}
}
}

Runnable

  1. 实现Runnable接口
  2. 实现run()方法
  3. 借助Thread类,调用start()方法,启动线程。
  4. 线程不一定立即执行,由CPU安排调度。线程调用顺序不是同步一次执行(多线程是同时执行的)
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 RunnableTest implements Runnable {
private int id;

public RunnableTest(int value) {
this.id = value;
}

public void run() {
for (int i = 0; i < 200; i++) {
System.out.println(id + "-" + i + ":线程");
}
}

public static void main(String[] args) {
// 创建实现类实例
RunnableTest runnableTest = new RunnableTest(1);
// 创建线程对象,还是通过Thread实例启动(start)线程
new Thread(runnableTest).start();

RunnableTest runnableTest1 = new RunnableTest(132);
new Thread(runnableTest1,"线程名").start();
}
}

Callable

  1. 实现Callable<数据类型>接口
  2. 实现call(数据类型)方法
  3. 启动线程
    • 创建执行服务 ExecutorService executorService = Executors.newFixedThreadPool(1);//线程个数
    • 提交执行 Future future = executorService.submit(f); // 每个线程一次
    • 获取结果 Boolean r1 = future.get(); // 每个线程一次
    • 关闭服务 executorService.shutdownNow(); // //所有线程关闭
  4. 线程不一定立即执行,由CPU安排调度。线程调用顺序不是同步一次执行(多线程是同时执行的)
  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
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
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;

class CallableFileDownloadTest implements Callable<Boolean> {
private String url;
private String fileName;

public CallableFileDownloadTest(String url, String fileName) {
this.url = url;
this.fileName = fileName;
}

// 重点
@Override
public Boolean call() {
CDownloader downloader = new CDownloader();
downloader.download(url, fileName);
System.out.println("download:" + fileName);
return true;
}

public static void main(String[] args) {
Map<String, String> map = new HashMap<>();
map.put("./src/com/thread/img/70迈4K行车记录仪.jpg", "http://s1.dgtle.com/dgtle_img/article/2020/09/28/fc6567a9b2d4f176c11bae1c6d226f05.jpg");
map.put("./src/com/thread/img//360G580行车记录.jpeg", "http://s1.dgtle.com/dgtle_img/article/2020/08/23/7c707202008231518243883_1800_500.jpeg?imageMogr2/auto-orient");
map.put("./src/com/thread/img/小米行车记录仪2-2k版.jpeg", "http://s1.dgtle.com/dgtle_img/sale/2020/10/121388f202010121029499586_1800_500.jpeg");

// 创建执行服务,线程池有3个线程
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (String key : map.keySet()) {
CallableFileDownloadTest f = new CallableFileDownloadTest(map.get(key), key);
//提交执行
Future<Boolean> future = executorService.submit(f);
// FutureTask<Boolean> future = new FutureTask<Boolean>(new CallableFileDownloadTest()); new Thread(future).start();
try {
//获取结果
Boolean r1 = future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
//关闭服务
executorService.shutdownNow();
}

}

class CDownloader {
public void download(String url, String fileName) {
try {
FileUtils.copyURLToFile(new URL(url), new File(fileName));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,download方法出现问题");
}
}
}

线程生命周期

生命周期

image-20201025143842137

环节 描述 拟人
创建状态(新生状态) Thread thread = new Thread() 线程对象实例创建。尚未启动。 来了
就绪状态 thread.start() 线程变为就绪状态。 准备工作了
运行状态 真正执行线程体、真正工作。 在工作
阻塞状态 thread.sleep() / thread.wait() / 同步锁定时,代码不再执行。阻塞事件解除后,重新进入就绪状态,等待cpu调度。 午休
死亡状态 线程中断或结束。一旦死亡不能再次启动。 离开了

线程状态

https://docs.oracle.com/javase/8/docs/api/index.html

Thread.state,一个线程在给定时间点,必定处于一个状态。这个状态是虚拟机状态,而不能反映操作系统线程状态。

使用方式:

  1. Thread.State.TERMINATED
  2. thread.getState()
状态 描述
NEW 线程创建,但尚未启动
RUNNABLE 线程正在运行
BLOCKED 线程被阻塞
WAITING 正等待另一线程执行特定动作
TIMED_WAITING 正等待另一线程执行特定动作,且并已经到了指定的等待时间
TERMINATED 线程已退出

案例

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
public class ThreadStateTest {
public static void main(String[] args) {
final Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-------------");
}
});
// --------------------------------------
/* lamda表达式,等价如下内容

final Runnable runnable = () -> {
// 代码执行体
};
Thread thread = new Thread(runnable);
*/
// --------------------------------------

// 初始化但尚未启动
Thread.State state = thread.getState();
System.out.println(state); // NEW

// 启动后
thread.start();
System.out.println(thread.getState()); // RUNNABLE

// 休眠一下
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getState()); // TIMED_WAITING

// 结束了
while (!Thread.State.TERMINATED.equals(thread.getState())) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getState()); // 最后一个状态是TERMINATED
}
}
}

线程优先级

优先级低,只是意味着,获得调度的概率比较低

Thread类:

  1. public final static int MIN_PRIORITY = 1;最小优先级

  2. public final static int NORM_PRIORITY = 5; 默认优先级

  3. public final static int MAX_PRIORITY = 10;最大优先级

  1. setPriority(int newPriority),建议在start()调度前设置

  2. getPriority()

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 ThreadPriorityTest {
public static void main(String[] args) {
Love love = new Love();
Thread thread1 = new Thread(love, "最大");
Thread thread2 = new Thread(love, "最小");
Thread thread3 = new Thread(love, "c");
Thread thread4 = new Thread(love, "d");
Thread thread5 = new Thread(love, "e");

thread1.setPriority(10);// 优先级最大
thread1.start();

thread2.setPriority(2); // 优先级最小
thread2.start();

thread3.start();
thread4.start();
thread5.start();
}
}

class Love implements Runnable {
@Override
public void run() {
System.out.println("nice:"
+ Thread.currentThread().getName()
+ Thread.currentThread().getPriority()
);
}
}

线程方法

方法 说明
boolean isAlive() 获取是否活动状态
setPriority(int priority) 修改线程优先级
static void yield() Thread.yield() 礼让线程。礼让不一定成功,看CPU。暂停当前执行的线程实例,并执行其他线程。
static void sleep(long millis) Thread.sleep(200) 暂停线程,让线程休眠,毫秒数。sleep能够放大问题的发生性。
void interrupt() 中断线程。不建议使用。
void join() myThread.join() 插队。myThread线程插队先执行。其他线程阻塞。等插队进行执行完成后,再执行其他线程。

线程停止

  1. 【废弃】Thread提供的stop()\destroy()方法不要使用,已废弃@Deprecated。
  2. 【办法】手动设置标志位flag = false,让线程代码不真正运行。或其他手动停止的办法。
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
public class ThreadStopTest implements Runnable {
// 手动flag
private boolean isStopped = false;

@Override
public void run() {
int i = 0;
// flag 停止代码运行
while (!isStopped) {
System.out.println("喝酸奶,身体健康,心情舒畅:" + i++);
}
}

public void stop() {
this.isStopped = true;
}

public static void main(String[] args) {
ThreadStopTest drinkYogurt = new ThreadStopTest();
Thread thread = new Thread(drinkYogurt);
thread.start();

for (int i = 0; i < 1000; i++) {
System.out.println(i);
if (i == 990) {
// 手动停止代码运行
drinkYogurt.stop();
System.out.println("线程已停止");
}
}
}
}

线程休眠

sleep

  1. 当前线程阻塞的毫秒数。
  2. 时间到达后进入就绪状态。
  3. 存在异常InterruptedException。
  4. 可以模拟网络延时、倒计时等。
模拟网络延时
1
2
3
4
5
6
7
8
9
System.out.println("a");
try {
// 延时2秒
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 2秒后再运行
System.out.println("b");
模拟倒计时
1
2
3
4
5
6
7
8
9
10
for (int i = 10; i > 0; i--) {
System.out.println(i);
if(i>1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
打印当前时间
1
2
3
4
5
6
7
8
9
10
11
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
for (int i = 10; i > 0; i--) {
System.out.println(simpleDateFormat.format(new Date()));
if (i > 1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

System.currentTimeMillis()获取当前时间的13位时间戳。new Date(System.currentTimeMillis())。

线程礼让

yield

  1. 礼让,让线程从运行状态,转为就绪状态。
  2. 如,线程a执行到中途,不执行了,变成就绪状态。让线程b执行。
  3. 礼让不一定成功!看CUP的心情。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ThreadYieldTest {
public static void main(String[] args) {
MyYield yield = new MyYield();
new Thread(yield, "曹植").start();
new Thread(yield, "曹丕").start();
}
}

class MyYield implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
Thread.yield(); // 礼让
System.out.println(Thread.currentThread().getName() + "线程停止执行,end");
}
}
// 这个案例不太好,因为线程执行本身是无序的。但通过概率,还是能看出礼让情况。

结果:

1
2
3
4
5
6
7
8
9
10
11
// 礼让,曹植没有一直执行到完毕。而是礼让了一下曹丕。
曹植线程开始执行
曹丕线程开始执行
曹植线程停止执行,end
曹丕线程停止执行,end

// 不礼让
曹植线程开始执行
曹植线程停止执行,end
曹丕线程开始执行
曹丕线程停止执行,end

线程强制执行

join

  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
package com.luxia.thread;

public class ThreadJoinTest implements Runnable {
public static void main(String[] args) {
ThreadJoinTest threadJoinTest = new ThreadJoinTest();
Thread threadJoin = new Thread(threadJoinTest);
threadJoin.start();

for (int i = 0; i < 100; i++) {
if (i == 50) {
//join线程(threadJoin.join)来插队,join线程先执行,然后再执行main
try {
threadJoin.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("main" + i);
}
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("join: " + i);
}
}
}

守护线程daemon

线程的分类:

  1. 用户线程:thread.setDaemon(false)。虚拟机必须确保用户线程执行完毕。main()。
  2. 守护线程:thread.setDaemon(true)。虚拟机不会等待守护线程执行完毕。 后台记录操作日志,监控内存,垃圾回收gc()等待…
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
package com.luxia.thread;

public class ThreadDaemonTest {
public static void main(String[] args) {

ServiceProcess serviceProcess = new ServiceProcess();
Thread thread = new Thread(serviceProcess);

thread.setDaemon(true); // 设为守护进程,只要用户线程执行完毕,虚拟机就不管了(虚拟机关闭需要一点时间,所以还会看到一些)。
thread.start();

new Thread(new MainProcess()).start();
}
}

class ServiceProcess implements Runnable {

@Override
public void run() {
while (true) {
System.out.println("守护进程,为执行线程提供保障");
}
}
}

class MainProcess implements Runnable {

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("我在执行" + i);
}
System.out.println("==========我不执行了==========");
}
}

线程Thread是一种静态代理模式

静态代理模式

  1. 真实对象,和代理对象,都实现了同一个接口。
  2. 代理对象要代理真实对象

new Thread(真实执行实例)。

其中

  1. Thread类,继承了Runnable接口,是代理。
  2. 真实执行类,也继承了Runnable接口,是真正的执行人。
1
2
3
4
5
// 创建实现类实例
RunnableTest runnableTest = new RunnableTest(1);

// 创建线程对象,还是通过Thread实例启动(start)线程
new Thread(runnableTest).start();

一个静态代理示例

消费者要买车,4s店可以代理消费者买车。

都实现了买车这个行为,4s店代理消费者完成。

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 StaticProxy {
public static void main(String[] args) {
Auto4sShop auto4sShop = new Auto4sShop(new BuyCar());
auto4sShop.sale("levin", "lucy");
}
}

// 同一个接口
interface CarLife {
void sale(String carName, String consumer);

void service(String carName, String consumer);

void sparepart(String carName, String consumer);

void survey(String carName, String consumer);
}

// 真实对象
class BuyCar implements CarLife {
public void sale(String carName, String consumer) {
System.out.println(consumer + "买了这个车:" + carName);
}

public void service(String carName, String consumer) {
System.out.println(consumer + "需要服务,车型:" + carName);
}

public void sparepart(String carName, String consumer) {
System.out.println(consumer + "需要零配件,车型:" + carName);
}

public void survey(String carName, String consumer) {
System.out.println(consumer + "反馈了一些问题,车型:" + carName);
}
}

// 代理对象
class Auto4sShop implements CarLife {
private CarLife target;

public Auto4sShop(CarLife target) {
this.target = target;
}

public void sale(String carName, String consumer) {
start();
this.target.sale(carName, consumer);
end();
}

public void service(String carName, String consumer) {
start();
this.target.service(carName, consumer);
end();
}

public void sparepart(String carName, String consumer) {
start();
this.target.sparepart(carName, consumer);
end();
}

public void survey(String carName, String consumer) {
start();
this.target.survey(carName, consumer);
end();
}

private void start() {
System.out.println("欢迎光临!");
}

private void end() {
System.out.println("谢谢惠顾!");
}
}

线程同步

并发问题

同一份资源,需要被多个线程同时操作。

  1. 抢火车票
  2. 同时取钱

但各线程都有自己的内存,当拿到资源之后,都会以为自己处理了资源。导致资源处理出错。

线程a认为还剩1张票,线程b也认为还剩1张票,在各自内存进行了-1操作,导致实际上取了2张票。

同步机制

用来解决并发问题。

线程同步机制,本质是一种【等待】机制,多个想同时访问此对象的线程进入这份资源的等待池,形成【队列】,等待前一个线程使用完毕【锁】,下一个线程再使用。

  1. 访问同一资源的多个线程形成【队列】,等待。
  2. 当时占用资源的线程,上【锁】,保证安全,直到使用完成后释放。

锁机制

  1. synchronized隐式锁。

    • 出了作用域自动释放。

    • 可以锁方法、锁代码块。

  2. lock显式锁。

    • 手动开锁、关锁。

    • 只锁代码块。

    • 性能更好(jvm花费更少时间来调度线程)
    • 扩展性更好(提供了更多子类)

优先级顺序:lock锁 > 同步代码块 > 同步方法

synchronized隐式锁

一个线程一旦获得资源,就获得一把排它锁,独占资源。此时,其他线程必须等待,直到使用完成释放锁。

  1. 一个线程拥有了锁,就会导致其他需要此资源的线程挂起。
  2. 加锁、释放锁,会导致上下文切换和调度延时,引起性能问题。
  3. 优先级高的线程,等待优先级低的线程释放锁,会导致优先级倒置,引起性能问题。

synchronized锁如何修改对象资源?

每个对象资源都有一把锁,谁来访问谁拥有。

因为数据对象已经被private控制,只能通过方法进行修改,所以只需要对方法进行锁机制,就可保证同步。

方法里有需要修改的资源,就需要锁。其他不需要,否则浪费资源。

  1. synchronized方法。将一个大方法申明为synchronized会影响效率。

synchronized方法,默认锁的是this(对象本身,或者反射class)

1
public synchronized void method(int args) {//含修改共享资源的代码}
  1. synchronized块。

synchronized块,需要指定监听器Obj,可以是任何对象,推荐使用共享资源作为监听器。

synchronized方法,其实就是监听的对象本身。

1
synchronized(Obj) {//含修改共享资源的代码}
lock显式锁
  1. jdk5.0开始引入
  2. 拥有与synchronized相同的并发性和内存语义,但更强大。
  3. 显式定义【同步锁对象(Lock对象)】,来实现同步。
  4. 接口:java.util.concurrent.locks.Lock。
  5. Lock接口实现类:ReentrantLock,可以显式加锁、释放锁。
1
2
3
4
5
private ReentrantLock lock = new ReentrantLock();

lock.lock();
/**代码块*/
lock.unlock();

锁示例

取票

不安全取票

买票时候,会拿到0票和-1票。

因为每个线程都把资源数量copy到自己的线程中了,比如,只剩1张票的情况下,小红、小明和黄牛都看到了1张票,都放到了自己的线程内存中,继续对ticketCount进行了处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TicketTest implements Runnable {
private int ticketCount = 10;

public void run() {
while (ticketCount > 0) {
try {
Thread.sleep(200); // 模拟延时,sleep能够放大问题的发生性。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ ",买到了第【" + (ticketCount--) + "】张票");
}
}

public static void main(String[] args) {
TicketTest ticketTest = new TicketTest();
new Thread(ticketTest, "小红").start();
new Thread(ticketTest, "小明").start();
new Thread(ticketTest, "黄牛").start();
}
}
安全锁: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
public class TicketTest implements Runnable {
private int ticketCount = 10;
private boolean flag = true;

public void run() {
while (flag) {
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}

}
}

// synchronized方法,默认锁的是this(对象本身,或者反射class)
private synchronized void buy() throws InterruptedException {
if(ticketCount <= 0) {
flag = false;
System.out.println("票卖完了");
return;
}
Thread.sleep(200); // 模拟延时
System.out.println(Thread.currentThread().getName()
+ ",买到了第【" + (ticketCount--) + "】张票");
}

public static void main(String[] args) {
TicketTest ticketTest = new TicketTest();
new Thread(ticketTest, "小红").start();
new Thread(ticketTest, "小明").start();
new Thread(ticketTest, "黄牛").start();
}
}
安全锁: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
public class TicketTest implements Runnable {
private int ticketCount = 10;
private boolean isFinished = false;

public void run() {
while (!isFinished) {
// synchronized块
synchronized (this) {
if (ticketCount <= 0) {
isFinished = true;
System.out.println("票卖完了");
return;
}
try {
Thread.sleep(200); // 模拟延时
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ ",买到了第【" + (ticketCount--) + "】张票");
}
}
}

public static void main(String[] args) {
TicketTest ticketTest = new TicketTest();
new Thread(ticketTest, "小红").start();
new Thread(ticketTest, "小明").start();
new Thread(ticketTest, "黄牛").start();
}
}
安全锁: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
import java.util.concurrent.locks.ReentrantLock;

public class LockTest {
public static void main(String[] args) {
final BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket, "lu").start();
new Thread(buyTicket, "want").start();
new Thread(buyTicket, "long").start();
}
}

class BuyTicket implements Runnable {
private int count = 10;
// 显式锁
private ReentrantLock lock = new ReentrantLock();

@Override
public void run() {
while (true) {
try {
// 加锁
lock.lock();
if (count > 0) {
try {
Thread.sleep(200); // 模拟延时,sleep能够放大问题的发生性。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(
Thread.currentThread().getName() + "拿到第" + count-- + "张票");
} else {
break;
}
} finally {
// 解锁
lock.unlock();
}
}
}
}

取钱

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
public class BankTest {

public static void main(String[] args) {
final Bank bank = new Bank("工商银行", 200);
final Drawing drawingL = new Drawing(bank, 110, "lu"); // 线程1
final Drawing drawingW = new Drawing(bank, 100, "want"); // 线程2

new Thread(drawingL).start();
new Thread(drawingW).start();
}
}

/**
* 共享资源
*/
class Bank {
private String name;
private Integer money;

public Bank(String name, Integer money) {
this.name = name;
this.money = money;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getMoney() {
return money;
}

public void setMoney(Integer money) {
this.money = money;
}
}

/**
* 操作资源
*/
class Drawing implements Runnable {
/**
* 取款人
*/
private String name;
/**
* 取钱金额
*/
private Integer money;
/**
* 银行资源
*/
private Bank bank;

public Drawing(Bank bank, Integer money, String name) {
this.bank = bank;
this.money = money;
this.name = name;
}

@Override
public void run() {
System.out.println("====" + this.name + ",欢迎来到" + bank.getName() + "====");
// 安全块
synchronized (bank) {
System.out.println("**账户余额:" + bank.getMoney() + "**");
if (bank.getMoney() < money) {
System.out.println(this.name + "想取" + this.money + ",但是余额只有" + bank.getMoney());
return;
}
int left = bank.getMoney() - money;
bank.setMoney(left);
System.out.println(
this.name + "取了" + money);
}
}
}

操作List资源

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
import java.util.ArrayList;
import java.util.List;

/**
* 线程集合
*/
public class ThreadListTest {

public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 10000; i++) {
// lamda表达式匿名类(Runnable是函数式接口)
new Thread(() -> {
// 函数块
synchronized (list) {
list.add(Thread.currentThread(R).getName());
}
}).start();
}

// sleep,防止主线程提前完成
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
}
}
线程安全的方法

CopyOnWriteArrayList相对于List方法,本身就是一种线程安全的方法。自带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
package com.luxia.thread;
// CopyOnWriteArrayList线程安全方法,属于JUC(java.util.concurrent)
import java.util.concurrent.CopyOnWriteArrayList;

public class ThreadSafeMethodTest {
public static void main(String[] args) {
CopyOnWriteArrayList<String> copyOnWriteArrayList =
new CopyOnWriteArrayList<>();

for (int i = 0; i < 10000; i++) {
new Thread(() -> {
copyOnWriteArrayList.add(Thread.currentThread().getName());
}).start();
}

try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(
copyOnWriteArrayList.size());
}
}

死锁

死锁,多个线程互相占着对方需要的资源,形成僵持,导致所有线程都运行停止。

  1. 线程1,占用资源a。但等待线程2释放资源b。
  2. 线程2,占用资源b。但等待线程1释放资源a。

某【一个同步块】(synchronized块),同时拥有【两个以上资源的锁】,就可能发生死锁。

产生死锁的四个必要条件

  1. 资源独占,即每次只能被一个进程调用。
  2. 资源霸占,一个进程绝不主动释放已获资源,
  3. 资源不剥夺,在进程使用完资源前,绝不能强行剥夺资源。
  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
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
/**
* 死锁
* 两个线程,相互拥有对方的资源。
*/
public class LockDeadTest {
public static void main(String[] args) {
MarkUp markUp = new MarkUp("lu", 1); // 线程1
MarkUp markUp1 = new MarkUp("want", 2); // 线程2
new Thread(markUp).start();
new Thread(markUp1).start();
}
}

/**
* 资源1
*/
class LispStick {

}

/**
* 资源2
*/
class Mirror {

}

/**
* 线程行为
*/
class MarkUp implements Runnable {
private static LispStick lispStick = new LispStick(); // 共享资源1
private static Mirror mirror = new Mirror(); // 共享资源2

private String name;
private int flag;

public MarkUp(String name, int flag) {
this.name = name;
this.flag = flag;
}

@Override
public void run() {
if (flag == 1) {
// 锁住lispStick的同时,又试图占用mirror。可是mirror已经被占用,导致线程停止。
synchronized (lispStick) {
System.out.println(this.name + "占用了lispStick");

try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (mirror) {
System.out.println(this.name + "也占用了mirror");
}
}
} else {
// 锁住mirror的同时,又试图占用lispStick。可是lispStick已经被占用,导致线程停止。
synchronized (mirror) {
System.out.println(this.name + "占用了mirror");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lispStick) {
System.out.println(this.name + "也占用了lispStick");
}
}
}
}
}

不死锁写法

用完就释放,而不是一直锁住。

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
/**
* 线程行为
*/
class MarkUp implements Runnable {
private static LispStick lispStick = new LispStick(); // 共享资源1
private static Mirror mirror = new Mirror(); // 共享资源2

private String name;
private int flag;

public MarkUp(String name, int flag) {
this.name = name;
this.flag = flag;
}

@Override
public void run() {
if (flag == 1) {
// 用完释放
synchronized (lispStick) {
System.out.println(this.name + "占用了lispStick");
}
// 再使用其他资源
synchronized (mirror) {
System.out.println(this.name + "也占用了mirror");
}
} else {
synchronized (mirror) {
System.out.println(this.name + "占用了mirror");
}
synchronized (lispStick) {
System.out.println(this.name + "也占用了lispStick");
}
}
}
}

线程协作

目的是实现多线程之间进行通信。

通信方法

是Object类的方法(意味着所有类都有该方法),且只能在同步方法/同步代码块中使用,否则存在异常IIIegalMonitorStateException

方法名 描述
等待 wait() 线程一直等待,直到收到其他线程的通知。
等待 wait(long timeout) 指定毫秒数
唤醒 notify() 唤醒一个处于等待状态的线程
唤醒 notifyAll() 唤醒同一对象上所有调用wait()方法的线程,优先唤醒优先级高的线程

生产者消费者模型的通信方式

  1. 管程法
  2. 信号灯法
graph LR

a(生产者) --> B{数据缓存区}
B --> C(消费者)

生产者消费者模型:

  • 生产者:负责生产数据
  • 消费者:负责处理数据
  • 缓冲区:生产者将数据放入缓冲区,消费者从缓冲区拿出数据
管程法
    1. 缓冲区
    1. notify() / 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
99
100
101
102
103
104
105
106
107
/**
* 线程之间通信之生产者消费者模式
* 管程法
* 1. 缓冲区
* 2. notify() / wait()
*/
public class CooperationPipe {
public static void main(String[] args) {
final BufferData bufferData = new BufferData();

new Thread(new Producer(bufferData)).start();
new Thread(new Consumer(bufferData)).start();
}
}

/**
* 缓存区
*/
class BufferData {
private String[] list = new String[10];

private int count = 0;

// final ReentrantLock lock = new ReentrantLock();

public synchronized void push(String str) {
// try {
// lock.lock();
if (count == list.length) {
// 容器满了,生产者等待
try {
this.wait(); // BufferData
} catch (InterruptedException e) {
e.printStackTrace();
}

}
// 容器未满,进行生产
list[count] = str;
count++;
// 告知消费者
this.notifyAll();
// } finally {
// lock.unlock();
// }
}

public synchronized String pop() {
// try {
// lock.lock();
if (count <= 0) {
// 产品没了,自己等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
count--;
// 通知生产者生产
this.notifyAll();
return list[count];
// } finally {
// lock.unlock();
// }

}
}

/**
* 生产者
*/
class Producer implements Runnable {
private BufferData bufferData;

public Producer(BufferData bufferData) {
this.bufferData = bufferData;
}

@Override
public void run() {
for (int i = 1; i < 100; i++) {
System.out.println("生产了" + i);
bufferData.push(String.valueOf(i));
}

}
}

/**
* 消费者
*/
class Consumer implements Runnable {
private BufferData bufferData;

public Consumer(BufferData bufferData) {
this.bufferData = bufferData;
}

@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("消费了----" + bufferData.pop());
;
}
}
}
信号灯法
  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
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
/**
* 线程之间通信之生产者消费者模式
* 信号灯法
* 1. 标志位
* 2. notify() / wait()
*/
public class CooperationSign {
public static void main(String[] args) {
Sign sign = new Sign();
Producer1 producer = new Producer1(sign);
new Thread(producer).start();

Consumer1 consumer = new Consumer1(sign);
new Thread(consumer).start();
}

}

/**
* 信号
*/
class Sign {
/**
* 生产中
*/
private boolean isProduct = true;
private String produce;

public synchronized void product(String produce) {
if (!isProduct) {
// System.out.println("消费者正在使用,无法生产");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println("生产者生产了" + produce);
this.produce = produce;
this.isProduct = false;
this.notifyAll(); // 唤醒消费者
}

public synchronized void consume() {
if (this.isProduct) {
// System.out.println("生产者正在生产,无法消费");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者消费了" + this.produce);
this.isProduct = true;
this.notifyAll(); // 唤醒生产者
}
}

/**
* 生产者
*/
/**
* 生产者
*/
class Producer1 implements Runnable {

private Sign sign;

public Producer1(Sign sign) {
this.sign = sign;
}

@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.sign.product("苹果");
}
}
}

/**
* 消费者
*/
class Consumer1 implements Runnable {
private Sign sign;

public Consumer1(Sign sign) {
this.sign = sign;
}

@Override
public void run() {
for (int i = 0; i < 20; i++) {
this.sign.consume();
}

}
}

线程池

  1. jdk5.0引入线程池API
  2. 可提前创建很多线程,放在线程池,使用时直接取用,用完放回池中。避免频繁创建销毁线程,实现重复利用。提高响应速度,降低资源消耗,便于线程管理。
  3. ExecutorService:线程池【接口】,常见子类ThreadPoolExecutor。
    • void execute() 执行任务,没有返回值,一般用来执行【Runnable】线程类
    • submit() 执行任务,有返回值,一般用来执行【Callable】线程类
    • void shutdown() 关闭连接池
  4. Executors:工具类、线程池的【工厂类】,用于创建并返回不同类型的线程池。
  5. corePoolSize 核心池大小
  6. maximumPoolSize 最大线程数
  7. keepAliveTime 线程没有任务时,最多保持多长时间终止
1
2
3
4
5
6
7
8
9
10
// 创建
ExecutorService service = Executors.newFixedThreadPool(10);

// 执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());

// 关闭
service.shutdown();
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
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* 线程池
*/
public class ThreadPool {
public static void main(String[] args) {

// 创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);

// 执行线程
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());

// 关闭线程池
service.shutdown();
}

}

class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
-------------Keep It Simple Stupid-------------
0%