线程、进程、多线程

  • 进程:进程是程序的一次执行过程,是一个动态的概念,是系统资源分配的单位
  • 线程:通常在一个进程中可以包含若干个线程,一个进程中至少有一个线程,不然没有存在的意义,线程是CPU调度和执行的单位
  • 多线程:真正的多线程是有多个CUP,同时执行,如果在只有一个CPU的情况下,同一时间只能执行一个代码,因为切换速度很快,造成了同时执行的假象
  1. 线程就是独立的执行路径
  2. 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程、gc线程
  3. main()称为主线程,为系统入口,用于执行整个程序
  4. 在一个线程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不可人为干预的
  5. 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
  6. 线程会带来额外的开销,如CPU调度时间(排队时间),并发控制开销
  7. 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

线程的实现

方式一:继承Thread类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 继承Thread类,重写run()方法,在run()方法体内编写业务代码
public class Test extends Thread{

@Override
public void run() {
for (int i=0; i<100; i++) {
System.out.println("我在吃饭-------------------");
}
}

public static void main(String[] args) throws IOException {
Test test = new Test();
// 调用start()开启线程
test.start();
// 由执行结果可以知道,多个线程是交替执行的,具体执行先后顺序是靠调度器调度的,无法人为干预
for (int i=0; i<1000 ;i++) {
System.out.println("我在睡觉");
}

}
}

方式二:继承Runnable接口实现(常用)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Test implements Runnable{

@Override
public void run() {
for (int i=0; i<100; i++) {
System.out.println("我在吃饭-------------------");
}
}

public static void main(String[] args) throws IOException {
Test test = new Test();

new Thread(test).start();

for (int i=0; i<1000 ;i++) {
System.out.println("我在睡觉");
}

}
}

方式三:继承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
// 继承Callable接口,重写call()方法,方法体里面编写业务代码
public class Test implements Callable<Boolean> {

@Override
public Boolean call() throws Exception {
for (int i=0; i<100; i++) {
System.out.println("我在吃饭-------------------");
}
return true;
}


public static void main(String[] args) throws ExecutionException, InterruptedException {
Test test = new Test();

// 创建服务
ExecutorService ser = Executors.newFixedThreadPool(1);

// 提交执行(开启线程)
Future<Boolean> result = ser.submit(test);

// 用get获取返回值,但是get()会阻塞线程
//System.out.println(result.get());

// 关闭服务
ser.shutdownNow();

for (int i=0; i<1000 ;i++) {
System.out.println("我在睡觉");
}
}
}

初识并发问题

并发问题:多个线程操作同时操作共享数据所导致的

Demo:抢票

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 Test implements Runnable{
private int tecikNums = 10;

@Override
public void run() {
while(true) {
if (tecikNums != 0) {
try {
tecikNums--;
System.out.println(Thread.currentThread().getName() + "==>" + "拿到了第" + tecikNums + "张票");
// 线程休眠,模拟延时
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}

public static void main(String[] args) throws IOException {
Test test = new Test();

new Thread(test,"黄牛").start();
new Thread(test,"小明").start();
new Thread(test,"小红").start();
}

}

Demo:龟兔赛跑

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
public class Test implements Runnable{
private static String winner;

@Override
public void run() {

for (int i=0; i<=100; i++) {
if (gameOver(i)) {
break;
}

// 通过线程名选择对应操作
if (Thread.currentThread().getName().equals("乌龟")) {
// 乌龟每一步都比兔子慢10毫秒
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("乌龟走了第" + i + "步");
}else if (Thread.currentThread().getName().equals("兔子")) {
// 兔子走到第50步的时候,模拟兔子睡觉
if (i==50) {
try {
Thread.sleep(1700);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("兔子走了第" + i + "步");
}
}
}

// 开启乌龟和兔子两个线程
public static void main(String[] args) {
Test test = new Test();

new Thread(test,"乌龟").start();
new Thread(test,"兔子").start();
}

// 判断比赛是否继续
public boolean gameOver(int step) {
if (winner != null) {
return true;
}else if (step == 100) {
winner = Thread.currentThread().getName();
System.out.println("胜利者:" + winner);
return true;
}
return false;
}
}

线程方法

  • 获取当前线程名字
1
Thread.currentThread().getName()
  • 线程休眠
1
2
3
// 每个对象都有一个锁,sleep不会释放锁
// 休眠1秒
Thread.Sleep(1000);
  • 线程礼让
1
2
// 线程礼让是让当前进程暂停,转为就绪状态,让CUP重新调度,所以礼让不一定成功,主要看CPU怎么调度
Thread.yield();
  • 线程强制执行
1
2
3
4
// 调用join方法会让主线程处于阻塞状态,先将线程内的内容执行完毕,再次开始执行主线程
Demo demo = new Demo();
Thread thread = new Thread(demo);
thread.join();
  • 线程优先级:优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,还是得看调度器的调度
1
2
3
4
5
6
7
8
9
10
11
12
// 线程的优先级用数字表示,范围1~10
Thread.MIN_PRIORITY = 1;
Thread.MAX_PRIORITY = 10;
Thread.NORM_PRIORITY = 5;
// 获取线程优先级
Demo demo = new Demo();
Thread thread = new Thread(demo);
thread.getPriority();
// 设置线程优先级
thread.setPriority(xxx);

// 线程优先级的实现原理,类似于买彩票,买一张中奖概率小,那就买100张,其实这里设置优先级就是增加提供给调度器的线程数量,数量越大,就越容易被调度

Lamda表达式

函数式接口:只包含一个方法的接口就是函数式接口,也叫功能性接口

Lamda简化了匿名内部类,方法引用简化了lamda

基本语法:接口 对象 = (参数表) -> {代码实现};

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
// 定义一个函数式接口
interface Demo {
void test(int i);
}

// Lamda表达式用法
public class Lamda {
public static void main(String[] args) {
// 基本写法
Demo demo01 = (int i) -> {
System.out.println("Hello World" + i);
};
demo01.test(10);

// 简化一:参数类型可省略
Demo demo02 = (i) -> {
System.out.println("Hello World" + i);
};
demo02.test(10);

// 简化二:括号可省略
Demo demo03 = i -> {
System.out.println("Hello World" + i);
};
demo03.test(10);

// 简化三:如果只有一行业务代码,那么花括号可省略
Demo demo04 = i -> System.out.println("Hello World" + i);
demo04.test(10);
}
}

Lamda表达式在多线程中的运用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
public static void main(String[] args) throws IOException {
// 用Lamda表达式+匿名类的方式实现了Runnable接口
new Thread(() -> {
for (int i=0; i<100; i++) {
System.out.println("我在吃饭-------------------");
};
}).start();

// 由执行结果可以知道,多个线程是交替执行的,具体是靠CUP调度,无法人为干预
for (int i=0; i<1000 ;i++) {
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
public class Test implements Runnable{

@Override
public void run() {
for (int i=0; i<5; i++) {
try {
Thread.sleep(1000); //TIMED_WAITING
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程终止了");
}

public static void main(String[] args) throws InterruptedException {

Test test = new Test();
Thread thread = new Thread(test);

Thread.State state = thread.getState();
System.out.println(state); //NEW

thread.start();
state = thread.getState();
System.out.println(state); //RUNNABLE

// 只要线程不终止,就一直打印线程状态
while (Thread.State.TERMINATED != state) {
Thread.sleep(200);
state = thread.getState();
System.out.println(state);
}
}
}

线程停止

JDK提供了stop()和destroy()方法来停止线程,但是这两个方法都已经废除,不推荐使用

最好的做法是,自己创建一个标志位来控制线程的停止,达到某一条件,就自动停止

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 class Test extends Thread{
private static boolean flag = false;


@Override
public void run() {
for (int i=0; i<100; i++) {
if (flag) {
break;
}
System.out.println("我在吃饭-------------------");
}
}

public static void main(String[] args) throws IOException {
Test test = new Test();
// 调用start()开启线程
test.start();
// 由执行结果可以知道,多个线程是交替执行的,具体是靠CUP调度,无法人为干预
for (int i=0; i<1000 ;i++) {
if (i==50) {
flag = true;
System.out.println("线程停止了");
}
System.out.println("我在睡觉");
}

}
}

线程休眠

  • sleep时间制定当前线程阻塞的毫秒数
  • sleep存在异常InterruptedException
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时等
  • 每一个对象都有一个锁,sleep不会释放锁
1
2
3
4
5
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}

线程礼让

  • 线程礼让,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态重新转为就绪状态,等待调度器调度
  • 礼让不一定成功,主要还是看调度器的调度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Test implements Runnable{

@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
// 线程礼让
Thread.yield();
System.out.println(Thread.currentThread().getName() + "线程停止执行");
}

public static void main(String[] args) {
Test test = new Test();

new Thread(test,"A").start();
new Thread(test,"B").start();
}
}

线程强制执行

join合并线程,待此线程执行完成后,再执行其他线程,其他线程会阻塞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Test implements Runnable{

@Override
public void run() {
for (int i=0; i<=500; i++) {
System.out.println("插队线程正在执行" + i);
}

}

public static void main(String[] args) throws InterruptedException {
Test test = new Test();
Thread thread = new Thread(test);
thread.start();

for (int i=0; i<=100; i++) {
if (i == 50) {
// 主线程执行到50的时候,插队线程开始执行,因为阻塞,一直要到插队线程执行完毕,主线程才会接着执行
thread.join();
}
System.out.println("主线程在执行" + i);
}
}
}

线程优先级

Java提供一个线程调度器来监控处于就绪状态的所有线程,线程调度器按照优先级觉得线程执行顺序的先后,优先级低也不代表一定后执行,主要还是调度器控制

1
2
3
4
5
6
// 最小优先级
public final static int MIN_PRIORITY = 1;
// 默认优先级
public final static int NORM_PRIORITY = 5;
// 最大优先级
public final static int MAX_PRIORITY = 10;

获取、设置优先级,最好在线程开启之前进行设置

1
2
3
4
5
6
7
8
9
10
Test test = new Test();
Thread thread = new Thread(test);
// 获取
thread.getPriority();

// 设置
thread.setPriority(4);

// 线程开启
thread.start();

守护线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
1
2
3
4
Test test = new Test();
Thread thread = new Thread(test);

thread.setDaemon(true); //默认为false,表示用户线程,一般创建的都为用户线程,true为守护线程

线程同步

  • 形成条件:队列+锁
  • 线程同步是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池,形成队列,前面的线程使用完毕,下一个线程再使用
  • 由于同一进程的多个线程共享同一块存储空间,为了避免访问冲突,加入了锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后再释放锁

使用锁存在一些问题:

  1. 一个线程持有锁会导致其他所有需要此锁的线程挂起
  2. 在多线程竞争下,加锁、释放锁会导致较多的上下文切换和调度延时,引起性能问题
  3. 一个优先级高的线程等待一个优先级低的线程时,会导致优先级倒置,引起性能问题

线程锁

  • 线程同步是依靠锁实现的,锁又分为同步方法和同步代码块两种

  • 对于普通同步方法,锁的是当前实例对象。 如果有多个实例 那么锁的对象必然不同,就无法实现同步。

  • 对于静态同步方法,锁的是当前类的Class对象。有多个实例 但是锁对象是相同的 可以实现同步。

  • 对于同步代码块,锁的是Synchonized括号里的对象。对象最好是线程操作的公共资源

1
2
3
4
5
6
7
8
9
// 同步方法:多个线程时,为保证一个方法被一个线程执行时不被影响,需要锁住此方法,一般对于增删改操作才上锁,默认锁住的是当前方法的所在类的实例对象
private synchronized void buy(){

}

// 同步块:写在方法中,多个对象使用同一共享资源时,为了不被影响,需要锁住此资源
synchronized(Obj){

}

Lock与Synchonized功能相似,显式定义了锁,配合异常使用,一般在finally里面关闭

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
public class Lock implements Runnable{
private int ticks = 10;

ReentrantLock lock = new ReentrantLock();

@Override
public void run() {
while (true) {
try {
lock.lock();
if (ticks>0) {
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() + "拿到了第" + ticks + "张票");
ticks--;
}else {
break;
}
} catch (Exception e) {

}finally {
lock.unlock();
}
}
}

public static void main(String[] args) {
Lock lock = new Lock();

new Thread(lock,"线程一").start();
new Thread(lock,"线程二").start();
new Thread(lock,"线程三").start();
}
}

Lock与Synchonized的区别:

  • Lock是显式锁(手动开启与关闭),Synchonized是隐式锁,出作用域自动关闭
  • Lock只有代码块锁,Synchonized有代码块锁和方法锁
  • Lock性能好
  • 使用顺序: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
public class Test extends Thread{
// 玩具刀和玩具枪都只有一份
static Knife knife = new Knife();
static Gun gun = new Gun();

@Override
public void run() {

if (Thread.currentThread().getName().equals("小明")) {
synchronized (knife) {
// 小明在获得玩具枪的同时,还想去获取玩具刀,但是玩具刀在小黄那里,无法获取,于是小明就等待,小黄也在等待小明使用完玩具枪这个资源,两个线程互相等待,就形成了死锁现象
System.out.println("小明得到了玩具枪");
synchronized (gun) {
System.out.println("小明得到了玩具刀");
}
}
}else {
synchronized (gun) {
System.out.println("小黄得到了玩具刀");
synchronized (knife) {
System.out.println("小明得到了玩具刀");
}
}
}
}

public static void main(String[] args) {
Test test = new Test();

Thread thread1 = new Thread(test,"小明");
Thread thread2 = new Thread(test,"小黄");

thread1.start();
thread2.start();

}
}

// 刀
class Knife {

}

// 枪
class Gun {

}

解决:在抱有资源的情况下,尽量不要去抢夺资源

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
public class Test extends Thread{
// 玩具刀和玩具枪都只有一份
static Knife knife = new Knife();
static Gun gun = new Gun();

@Override
public void run() {

if (Thread.currentThread().getName().equals("小明")) {
// 这一次,小黄和小明都在资源使用完成后再访问别的资源,所以避免了死锁
synchronized (knife) {
System.out.println("小明得到了玩具枪");
}
synchronized (gun) {
System.out.println("小明得到了玩具刀");
}
}else {
synchronized (gun) {
System.out.println("小黄得到了玩具刀");
}
synchronized (knife) {
System.out.println("小明得到了玩具刀");
}
}
}

public static void main(String[] args) {
Test test = new Test();

Thread thread1 = new Thread(test,"小明");
Thread thread2 = new Thread(test,"小黄");

thread1.start();
thread2.start();

}
}

// 刀
class Knife {

}

// 枪
class Gun {

}

线程通信——生成者与消费者关系问题

管程法

利用一个缓冲区来解决问题

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
public class Test {
public static void main(String[] args) {
SynContainer container = new SynContainer();

new Productor(container).start();
new Consumer(container).start();
}

}

// 生产者
class Productor extends Thread {
// 获取容器
SynContainer container;

// 构造方法传入容器
public Productor(SynContainer container) {
this.container = container;
}

// 生产者的线程操作
@Override
public void run() {
try {
for (int i=0; i<20; i++) {
System.out.println("生产了第" + (i+1) + "只鸡");
container.push(new Chicken(i+1));
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

// 消费者
class Consumer extends Thread {
SynContainer container;

public Consumer(SynContainer container) {
this.container = container;
}

@Override
public void run() {
try {
for (int i=0; i<20; i++) {
System.out.println("消费了第" + container.pop().getId() + "只鸡");
}
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}

// 产品
class Chicken {
// 产品编号
private int id;

public Chicken(int id) {
this.id = id;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}
}

// 缓冲区
class SynContainer {
// 容器大小
static Chicken[] chickens = new Chicken[10];
// 计数器
int count = 0;

// 生产者放入商品
public synchronized void push(Chicken chicken) throws InterruptedException {
if (count == chickens.length) {
// 容器满了,生产者停止生产,等待消费者消费
this.wait();
}

// 容器没满,放入产品到容器
chickens[count] = chicken;
count++;

//可以通知消费者消费
this.notify();
}

public synchronized Chicken pop() throws InterruptedException {
// 判断是否能消费
if (count == 0) {
// 容器为空,停止消费,等待生产者生产
this.wait();
}

// 容器不为空,从容器拿出产品
count--;
Chicken chicken = chickens[count];

//可以通知消费者消费
this.notify();

return chicken;
}
}

信号灯法

利用一个标识符来解决问题

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
public class Test02 {
public static void main(String[] args) {
Process process = new Process();
new Productor(process).start();
new Consumer(process).start();
}
}

// 生产者
class Productor extends Thread {
Process process;

// 构造方法传入容器
public Productor(Process process) {
this.process = process;
}

// 生产者的线程操作
@Override
public void run() {
for (int i=0; i<20; i++) {
try {
this.process.push(new Chicken(i+1));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

// 消费者
class Consumer extends Thread {
Process process;

// 构造方法传入容器
public Consumer(Process process) {
this.process = process;
}

// 消费者的线程操作
@Override
public void run() {
for (int i=0; i<20; i++) {
try {
process.pop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

// 产品
class Chicken {
// 产品编号
private int id;

public Chicken() {
}

public Chicken(int id) {
this.id = id;
}

@Override
public String toString() {
return "鸡,id为" + id;
}
}

// 过程
class Process {
Chicken chicken;

// 标识符 true时消费者等待 false时生产者等待
private boolean flag = true;

// 生产过程
public synchronized void push(Chicken chicken) throws InterruptedException {
if (!flag) {
this.wait();
}

System.out.println("生产了" + chicken);
this.notifyAll();
this.chicken = chicken;
this.flag = !this.flag;
}

// 消费过程
public synchronized void pop() throws InterruptedException {
if (flag) {
this.wait();
}

System.out.println("消费了" + chicken);
this.notifyAll();
this.flag = !this.flag;

}
}

线程池

1
2
3
4
5
6
7
8
9
10
// 1.创建服务,创建线程池,参数为线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);

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

// 3.关闭连接
service.shutdown();