Javabasics | Eloise's Paradise
0%

Javabasics

本博客是java基础教程系列的多线程章节.

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
25
26
27
28
29
30
31
32
33
package multiThreadDemos;

public class Demo01 {
public static void main(String[] args) {
MyThread t1 = new MyThread("子线程");
t1.start();
try{
t1.join(); // t1调用join方法的效果就是等待t1线程执行完, 结束后才会释放,主线程才能执行
}catch (InterruptedException ie){
ie.printStackTrace();
}
System.out.println("主线程获得执行权,继续执行...");
for(int i=1 ; i<=100;i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
class MyThread extends Thread{
MyThread(String s){super(s);}

@Override
public void run() {
for(int i=1 ; i<=100;i++){
System.out.println("I'm thread:"+getName());
try{
System.out.println("第"+i+"次, 睡一会...");
sleep(50); //
}catch (InterruptedException ie){
ie.printStackTrace();
}
}
}
}

功能: 等待调用join方法的线程执行完, 才会释放,其他线程才能执行

效果图:

join

yield

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 multiThreadDemos;

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

MyThread2 t1 = new MyThread2();
MyThread2 t2 = new MyThread2();
t1.start();
t2.start();

}
static class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println(getName()+", round:\t"+i);
if (i % 10 == 0) {
System.out.println("changeing to ....");
yield(); //当前线程释放资源给其他线程执行, 只释放一次
}
}
}
}
}

功能: 当前线程释放资源给其他线程执行, 只释放一次

效果图:

yield

优先级设置

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
package multiThreadDemos;

public class Demo01 {
public static void main(String[] args) {
Thread thread1 = new Thread(new T1());
Thread thread2 = new Thread(new T2());
thread1.setPriority(Thread.MAX_PRIORITY); //设置后,1线程执行的时间分片明显比2长
thread1.start();
thread2.start();


}
static class T1 implements Runnable{

@Override
public void run() {
for (int i=1;i<=1000;i++){
System.out.println("--------T1:\t"+i);
}
}
}
static class T2 implements Runnable{

@Override
public void run() {
for (int i=1;i<=1000;i++){
System.out.println("T2:\t"+i);
}
}
}
}

功能: 为某线程设置优先级系数, 系数越大, 获得执行的时间分片就越大, 持续执行的时间就越长.

效果图: (线程1执行到16才给线程2执行1次, 然后1再获取到执行执行权再执行到27才给2执行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
package multiThreadDemos;

public class Ticket extends Thread{
public int num=100;
@Override
public void run() {
while(true){
if(num>0) {
System.out.println(Thread.currentThread().getName() + "...saled ticket No:\t" + num--);
}
}
}
public static void main(String[] args) {
Ticket t1 = new Ticket(); //多批票
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();


}
}

运行发现:

ticket1

显然, 这种方式售票是不符合需求的, 因为 num 是Ticket的成员变量, 每次创建线程都会重新开辟空间, 分配一个新的地址来存储新的num并且存值100.

那么如何才能保证多个线程卖的是同样的100张票(而不是400张). 直观的想法是加上为num static关键字修饰, 使其为静态, 存在堆内存中,对象共享.

static 保证同一批

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
package multiThreadDemos;

public class Ticket extends Thread{
public static int num=100; //相较上面的代码仅仅多了一个static
@Override
public void run() {
while(true){
if(num>0) {
System.out.println(Thread.currentThread().getName() + "...saled ticket No:\t" + num--);
}
}
}
public static void main(String[] args) {
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();


}
}

此时再次运行检查, 发现能够满足需求.

ticket02

但是现实情况是有可能是某些售票点卖的票是有特殊要求的. 虽然总数还是100, 但是有可能某售票点A(对应线程1)专门负责出售80-100号头等舱票. 那么static的方式显然也是不合适的.

Runnable 封装票

所以最符合需求的实现方式应该是将票单独封装.(实现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
package multiThreadDemos;

public class Ticket implements Runnable {
public int num = 100;

@Override
public void run() {
while (true) {
if (num > 0) { // num=99时, 有可能线程一判断完num>0,但是执行权释放, 线程2进来判断, num=99>0, 所以线程2卖了99, num--变成98了, 然后将执行权释放此时线程1获得执行权, 接着上次判断完的, 直接卖99号票. 所以出现了同一张票被卖了多次的情况.
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "...saled ticket No:\t" + num--);
}
}
}

public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}

此时再运行发现: 同一张票被卖了多次的情况又发生了.

ticket03

同步synchronized方法(run上)

为了解决上面的问题, 我们将买票的操作进行加锁同步化. 很直观的想法就是在run方法上加锁

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
package multiThreadDemos;

public class Ticket implements Runnable {
public int num = 100;

@Override
public synchronized void run() { //run()方法同步synchronized
while (true) {
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "...saled ticket No:\t" + num--);
}
}
}

public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}

然而, 在run()方法上同步synchronized并不能解决问题, 因为while(true)也被同步了, 即使线程0 sleep释放了执行权, 其他线程也得不到执行权, 进不来. 因为synchronized是加在run()方法上的, 所以只有当run()方法执行完之后其他线程才有机会获得执行权. 但是, 因为while(true)的存在, 线程0 永远都不会执行完run()方法.

ticket03

同步synchronized代码块

回头再分析Runnable单独封装的方法, 发现同一张票被卖了多次的情况的原因是, 卖票的动作非一行代码(不具备原子性), 所以, 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
package multiThreadDemos;

public class Ticket implements Runnable {
public int num = 100;

@Override
public void run() {
while (true) {
synchronized (this){ // 将需要原子性的代码, 放在synchronized代码块中
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "...saled ticket No:\t" + num--);
}
}

}
}

public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}

此时, 再次运行, 发现一切正常, 完全满足需求. (既没有卖0,-1这种票的, 也没有同一张票被卖了多次的情况)

同步synchronized方法(单独封装)

其实, 更好的一种方法是将synchronized同步代码块中的动作单独封装成方法同步方法, 然后在 run()中调用该方法

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 multiThreadDemos;

public class Ticket implements Runnable {
public int num = 100;

@Override
public void run() {
while (true) {
sale();
}
}

public synchronized void sale(){
if (num > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "...saled ticket No:\t" + num--);
}
}

public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}

ticket05

总结

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
/**
线程安全问题产生的原因: (同时满足)
1. 多个线程操作同一共享数据
2. 操作共享数据的代码有多条.
也就是说:
当一个线程在执行操作共享数据的多行代码的过程中, 此时有另一个线程参与了运算, 就会导致
线程安全问题的产生.

解决思路:
将多条操作共享数据的代码进行封装, 当前线程没执行完这段代码时, 其他线程都进不来, 无法
参与运算,

Java中同步能解决该问题.

同步好处: 保证线程安全.
同步弊端: 相对而言效率会低一些.
同步前提: 必须有多个线程使用同一个锁. 上面同步synchronized代码块案例中,
synchronized (this){...} 的锁对象时this, 因为只有一个Ticket实例
对象, 所以能保证同一个锁,



同步代码块的锁: 任意对象
同步函数: this
静态同步函数: CurrentObj.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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package multiThreadDemos;

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

}
}

class Single1 { //饿汉式
private static final Single1 s = new Single1();

private Single1() {
}

public static Single1 getInstance() {
return s;
}
}

class Single2 { //懒汉式
private static Single2 s = null;

private Single2() {
}

public static Single2 getInstance() { //如果再多线程的run()方法中进行实例化操作,
if (s == null) { //那么此处可能会出现线程不安全的情况, if判断完后, 返回实例之前, 有其他线程进入那么就会导致两个线程返回不同的实例, 单例模式就会被打破
return new Single2();
} else {
return s;
}
}
}

class Single3 { //懒汉式
private static Single3 s = null;

private Single3() {
}

public static synchronized Single3 getInstance() { //为了解决Single2的安全问题, 为该实例方法加上synchronized同步, 但是这又会导致效率下降
if (s == null) {
return new Single3();
} else {
return s;
}
}
}

class Single4 { //懒汉式
private static Single4 s = null;

private Single4() {
}

public static Single4 getInstance() {
if(s==null){ //提升效率
synchronized (Single4.class){ //解决安全
if (s == null) {
return new Single4();
}
}
}
return s;
}
}

线程间通信的同步问题

问题

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
package multiThreadDemos;

public class InterThread {
public static void main(String[] args) {
Resource r=new Resource();
In in = new In(r);
Out out = new Out(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();

}
}

class Resource{
String name;
String sex;
}
class In implements Runnable{
Resource r;
In(Resource r){
this.r=r;
}

@Override
public void run() {
int flag=0;
while(true){
if(flag%2==0){
r.name="Lee";
r.sex="man";
}else{
r.name="艾洛";
r.sex="女女女女女女女女";
}
++flag;
}
}
}
class Out implements Runnable{
Resource r;
Out(Resource r){
this.r=r;
}

@Override
public void run() {
while(true){
System.out.println(r.sex+"............"+r.name);
}
}
}

执行上述代码发现线程不安全:

ticket6

为了解决上面的问题, 所以把run方法里面的操作工资资源数据的逻辑代码进行同步化, 并且保证同步化的锁是同一把锁🔐.

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
package multiThreadDemos;

public class InterThread {
public static void main(String[] args) {
Resource r = new Resource();
In in = new In(r);
Out out = new Out(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();

}
}

class Resource {
String name;
String sex;
}

class In implements Runnable {
Resource r;

In(Resource r) {
this.r = r;
}

@Override
public void run() {
int flag = 0;
while (true) {
synchronized (r) { // synchronized 需要同时作用于in和out, 并且使用同一把锁
if (flag % 2 == 0) {
r.name = "Lee";
r.sex = "man";
} else {
r.name = "艾洛";
r.sex = "女女女女女女女女";
}
}
++flag;
}
}
}

class Out implements Runnable {
Resource r;

Out(Resource r) {
this.r = r;
}

@Override
public void run() {
while (true) {
synchronized (r) { // synchronized 需要同时作用于in和out, 并且使用同一把锁
System.out.println(r.sex + "............" + r.name);
}
}
}
}

ticket7

解决方案

但是如果我们的需求是男女输出是依次交替进行的, 而不是一批一批的.应该怎么办呢? 这就引出了等待唤醒机制, 即wait-notify

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
 package multiThreadDemos;

public class InterThread {
public static void main(String[] args) {
Resource r = new Resource();
In in = new In(r);
Out out = new Out(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();

}
}

class Resource {
String name;
String sex;
boolean isWait=false;
}

class In implements Runnable {
Resource r;

In(Resource r) {
this.r = r;
}

@Override
public void run() {
int flag = 0;
while (true) {

synchronized (r) {
if (r.isWait) { //虽然wait和notify是Object的方法, 但是因为这些方法是操作线程状态的, 所以必须指明要对那个线程进行操作.
try {
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (flag % 2 == 0) {
r.name = "Lee";
r.sex = "man";
} else {
r.name = "艾洛";
r.sex = "女女女女女女女女";
}
r.isWait = true;
r.notify();
}
++flag;
}
}
}

class Out implements Runnable {
Resource r;

Out(Resource r) {
this.r = r;
}

@Override
public void run() {
while (true) {

synchronized (r) {
if (!r.isWait) {
try {
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(r.sex + "............" + r.name);
r.isWait = false;
r.notify();
}

}
}
}

执行上述代码可以看到能满足我们交替输出男女的需求.

ticket8

总结

1
2


生产者-消费者

单个生产者-消费者

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
package multiThreadDemos;

public class ProducerConsumer {
public static void main(String[] args) {
Resource r = new Resource("烤鸭");
Producer p = new Producer(r);
Consumer c = new Consumer(r);
Thread t0 = new Thread(p);
Thread t1 = new Thread(c);
t0.start();
t1.start();

}
}

class Resource {
private String name;
private int count = 0;
private boolean isWait = false;

public Resource(String name) {
this.name = name;
}

synchronized void produde() {
if (isWait) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
setCount(getCount() + 1);
System.out.println(Thread.currentThread().getName() + ",\t生产:" + getName() + getCount());
setWait(true);
notify();
}


synchronized void consume() {
if (!isWait) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ".................消费:" + getName() + getCount());
setWait(false);
notify();
}


public String getName() {
return name;
}

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

public int getCount() {
return count;
}

public void setCount(int count) {
this.count = count;
}

public boolean isWait() {
return isWait;
}

public void setWait(boolean wait) {
isWait = wait;
}
}

class Producer implements Runnable {
Resource r;

Producer(Resource r) {
this.r = r;
}

;

@Override
public void run() {
while(true){
r.produde();
}
}
}

class Consumer implements Runnable {
Resource r;

Consumer(Resource r) {
this.r = r;
}

;

@Override
public void run() {
while (true){
r.consume();
}
}
}

单个生产者消费者

多个生产者消费者(问题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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package multiThreadDemos;

public class ProducerConsumer {
public static void main(String[] args) {
Resource r = new Resource("烤鸭");
Producer p = new Producer(r);
Consumer c = new Consumer(r);
Thread t0 = new Thread(p);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
Thread t3 = new Thread(c);
t0.start();
t1.start();
t2.start();
t3.start();

}
}

class Resource {
private String name;
private int count = 0;
private boolean isWait = false;

public Resource(String name) {
this.name = name;
}

synchronized void produde() {
if (isWait) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
setCount(getCount() + 1);
System.out.println(Thread.currentThread().getName() + ",\t生产:" + getName() + getCount());
setWait(true);
notify();
}


synchronized void consume() {
if (!isWait) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ".................消费:" + getName() + getCount());
setWait(false);
notify();
}


public String getName() {
return name;
}

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

public int getCount() {
return count;
}

public void setCount(int count) {
this.count = count;
}

public boolean isWait() {
return isWait;
}

public void setWait(boolean wait) {
isWait = wait;
}
}

class Producer implements Runnable {
Resource r;

Producer(Resource r) {
this.r = r;
}

;

@Override
public void run() {
while(true){
r.produde();
}
}
}

class Consumer implements Runnable {
Resource r;

Consumer(Resource r) {
this.r = r;
}

;

@Override
public void run() {
while (true){
r.consume();
}
}
}

多个生产者消费者-重复消费

产生该问题的原因是, notify()方法每次只唤醒的线程池中的一个线程, (t0,t1生产者线程, t2,t3消费者线程). 假如某一时刻, 只有t3拥有执行权, 执行完后notify, 唤醒的是t2, 而此时if(isWait) 不会重复判断标记. 那么就出现了重复消费, 同理, 也可能出现重复生产.

那么该怎么解决呢?

多个生产者消费者(问题2)

可能有人会想到, 不用if, 而是改用while(isWait)来判断标记, 保证每个被唤醒的线程都会重复判断标记.

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
package multiThreadDemos;

public class ProducerConsumer {
public static void main(String[] args) {
Resource r = new Resource("烤鸭");
Producer p = new Producer(r);
Consumer c = new Consumer(r);
Thread t0 = new Thread(p);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
Thread t3 = new Thread(c);
t0.start();
t1.start();
t2.start();
t3.start();

}
}

class Resource {
private String name;
private int count = 0;
private boolean isWait = false;

public Resource(String name) {
this.name = name;
}

synchronized void produde() {
while (isWait) { //相较于上面的问题1中的代码, 仅仅将if改为while
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
setCount(getCount() + 1);
System.out.println(Thread.currentThread().getName() + ",\t生产:" + getName() + getCount());
setWait(true);
notify();
}


synchronized void consume() {
while (!isWait) { //相较于上面的问题1中的代码, 仅仅将if改为while
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ".................消费:" + getName() + getCount());
setWait(false);
notify();
}


public String getName() {
return name;
}

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

public int getCount() {
return count;
}

public void setCount(int count) {
this.count = count;
}

public boolean isWait() {
return isWait;
}

public void setWait(boolean wait) {
isWait = wait;
}
}

class Producer implements Runnable {
Resource r;

Producer(Resource r) {
this.r = r;
}

;

@Override
public void run() {
while(true){
r.produde();
}
}
}

class Consumer implements Runnable {
Resource r;

Consumer(Resource r) {
this.r = r;
}

;

@Override
public void run() {
while (true){
r.consume();
}
}
}

这样的话又会产生另外一个问题:

​ (t0,t1生产者线程, t2,t3消费者线程) 假如某一时刻, 当 t0,t1,t2都wait状态, 此时, t3获取到执行权后, 执行完, 将标记置为false, 然后唤醒线程池中的线程, 如果不幸唤醒的是t2, 那么t2再次判断while(!isWait), 就会导致4个线程都会wait. 形成死锁. 如下图

多个生产者消费者-死锁

多个生产者消费者(解决sync-while-notifyAll)

那么难道没有办法解决多生产者消费者的问题了吗? 当然不是.

总结

​ 上面出现问题的原因, 只要同事满足两点即可解决问题:

1. 等代标记必须每次都判断  (while代替if)
2. 每次唤醒必须有对方(消费-生产互为对方)线程. (notifyAll()确保一定会唤醒对方线程)
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
package multiThreadDemos;

public class ProducerConsumer {
public static void main(String[] args) {
Resource r = new Resource("烤鸭");
Producer p = new Producer(r);
Consumer c = new Consumer(r);
Thread t0 = new Thread(p);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
Thread t3 = new Thread(c);
t0.start();
t1.start();
t2.start();
t3.start();

}
}

class Resource {
private String name;
private int count = 0;
private boolean isWait = false;

public Resource(String name) {
this.name = name;
}

synchronized void produde() {
while (isWait) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
setCount(getCount() + 1);
System.out.println(Thread.currentThread().getName() + ",\t生产:" + getName() + getCount());
setWait(true);
notifyAll();
}


synchronized void consume() {
while (!isWait) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ".................消费:" + getName() + getCount());
setWait(false);
notifyAll();
}


public String getName() {
return name;
}

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

public int getCount() {
return count;
}

public void setCount(int count) {
this.count = count;
}

public boolean isWait() {
return isWait;
}

public void setWait(boolean wait) {
isWait = wait;
}
}

class Producer implements Runnable {
Resource r;

Producer(Resource r) {
this.r = r;
}

;

@Override
public void run() {
while(true){
r.produde();
}
}
}

class Consumer implements Runnable {
Resource r;

Consumer(Resource r) {
this.r = r;
}

;

@Override
public void run() {
while (true){
r.consume();
}
}
}

多个生产者消费者(解决lock-condition–signal)

用上面的synchronized-while-notifyAll的方法虽然能解决问题, 但是每次都是线程所有线程池中的所有线程(比如t0执行完唤醒线程池中的t1,t2,t3), t1,t2,t3重新获取执行资格, 但是只有其中一个会立刻获取执行权, 不妨是t2, 那么因为是while判断, 所以t2又要重新判断等待标记. 显然, 这样做效率会有降低.

JDK5之后, 将锁对象进行了封装, 将原来隐式的synchronized锁和其配套的监视器对象方法(wait-notify-notifyAll)进行了封装, 使其显示化, 并且不再是一个锁对象对应一组监视器方法, 而是可以一个锁对应多组监视器方法.

具体的可以参见Java SE API 下的java.utils.concurrent.locks包下的lock和condition对象方法.

condition类的描述中有一句:

1
**Where a `Lock` replaces the use of `synchronized` methods and statements, a `Condition` replaces the use of the Object monitor methods.**

翻译过来也就是说, (可以用下面的图表示):

lock_condition_signal

synchronized和lock对比

synchronized和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
/*
As an example, suppose we have a bounded buffer which supports put and take methods. If a take is attempted on an empty buffer, then the thread will block until an item becomes available; if a put is attempted on a full buffer, then the thread will block until a space becomes available. We would like to keep waiting put threads and take threads in separate wait-sets so that we can use the optimization of only notifying a single thread at a time when items or spaces become available in the buffer. This can be achieved using two Condition instances.
*/

class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();

final Object[] items = new Object[100];
int putptr, takeptr, count;

public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}

public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}

所以lock-condition-signal方式解决代码如下:

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
120
121
122
123
124
125
package multiThreadDemos;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ProducerConsumer {
public static void main(String[] args) {
Resource r = new Resource("烤鸭");
Producer p = new Producer(r);
Consumer c = new Consumer(r);
Thread t0 = new Thread(p);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
Thread t3 = new Thread(c);
t0.start();
t1.start();
t2.start();
t3.start();

}
}

class Resource {
private String name;
private int count = 0;
private boolean isWait = false;
private Lock lock = new ReentrantLock();
Condition c1 = lock.newCondition(); //生产者锁的监视器
Condition c2 = lock.newCondition(); //消费者锁的监视器
public Resource(String name) {
this.name = name;
}

void produce() {
lock.lock();
try{
while (isWait) {
// try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
try { c1.await(); } catch (InterruptedException e) { e.printStackTrace(); }
}
setCount(getCount() + 1);
System.out.println(Thread.currentThread().getName() + ",\t生产:" + getName() + getCount());
setWait(true);
c2.signal(); //生产者执行完之后, 确保只唤醒线程池中消费者线程, 且只唤醒一个
} finally {
lock.unlock();
}
}


void consume() {
lock.lock();
try{
while (!isWait) {
// try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); }
try { c2.await(); } catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println(Thread.currentThread().getName() + ".................消费:" + getName() + getCount());
setWait(false);
c1.signal(); //消费者执行完之后, 确保只唤醒线程池中生产者线程, 且只唤醒一个
}finally {
lock.unlock();
}
}


public String getName() {
return name;
}

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

public int getCount() {
return count;
}

public void setCount(int count) {
this.count = count;
}

public boolean isWait() {
return isWait;
}

public void setWait(boolean wait) {
isWait = wait;
}
}

class Producer implements Runnable {
Resource r;

Producer(Resource r) {
this.r = r;
}

;

@Override
public void run() {
while(true){
r.produce();
}
}
}

class Consumer implements Runnable {
Resource r;

Consumer(Resource r) {
this.r = r;
}

;

@Override
public void run() {
while (true){
r.consume();
}
}
}

wait和sleep区别

wait
1. 归属 Thread静态方法 Object方法
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
package multiThreadDemos;

public class Demo01 {
public static void main(String[] args) throws InterruptedException {
T1 t1 = new T1();
Thread thread = new Thread(t1);
thread.start();
t1.shutdown();

}
static class T1 implements Runnable{
private boolean continueFlag=true;

@Override
public void run() {
int i=1;
while(continueFlag){
System.out.println("T1:\t"+i++);
}
}

public void shutdown(){
continueFlag=false;
}
}


}

run()执行结束, 线程就结束, 也就停止了, 所以正确的停止线程的方法就是想办法让run()执行完业务逻辑后, 自然停止.

interrupt停止线程

interrupt()是另一种停止线程的方式 (不推荐)

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
package multiThreadDemos;


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

InterruptStop is = new InterruptStop();
Thread t1 = new Thread(is);
Thread t2 = new Thread(is);
t1.start();
t2.start();
int num = 0;
for (; ; ) {
if (++num == 50) {
t1.interrupt();
t2.interrupt();
break;
}
System.out.println("...........main\t" + num);
}
System.out.println("over!");
}
}

class InterruptStop implements Runnable {
private boolean flag = true;

public void setFlag() {
flag = false;
}

@Override
public synchronized void run() {
while (flag) {
try {
wait(); // 有可能多个线程在此处都释放资源等待被唤醒, 但是没有notify()方法将其唤醒, 所以也会死锁, 一直等待下去,
} catch (InterruptedException e) {
flag = false;
System.out.println(Thread.currentThread().getName() + e.getMessage());
}
System.out.println(Thread.currentThread().getName() + "...继续执行");
}
}
}

有可能多个线程在此处都释放资源等待被唤醒, 但是没有notify()方法将其唤醒, 所以也会死锁, 一直等待下去, 因此, 在main线程中做一个标记, 当执行到多少回时, 调用线程的interrupt() 方法将线程唤醒, 然后将标记置为false, 然后while(flag)不成立, 所以run方法结束, 线程也就结束.

守护线程Daemon

守护线程也叫后台线程. 非后台线程(一般线程)也叫前台线程. 启动线程前调用setDaemon()方法可以将线程设置为守护线程. 当正在运行的线程都是守护线程时(也就是前台线程全部运行完结束了), 那么JVM会退出.

如果将 interrupt停止线程 部分的代码的main方法中改为只interrupt t1线程, 那么运行程序将一直进行, 永远不会停止, 因为t2一直处于wait状态, 但是, 如果在此基础上再在t2线程启动前,将其设置为守护线程,那么t1线程和主线程执行完,整个程序就会停止.

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
package multiThreadDemos;


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

InterruptStop is = new InterruptStop();
Thread t1 = new Thread(is);
Thread t2 = new Thread(is);
t1.start();
t2.setDaemon(true); // 将t2线程设为守护线程
t2.start();
int num = 0;
for (; ; ) {
if (++num == 50) {
t1.interrupt();
//t2.interrupt();
break;
}
System.out.println("...........main\t" + num);
}
System.out.println("over!");
}
}

class InterruptStop implements Runnable {
private boolean flag = true;

public void setFlag() {
flag = false;
}

@Override
public synchronized void run() {
while (flag) {
try {
wait();
} catch (InterruptedException e) {
flag = false;
System.out.println(Thread.currentThread().getName() + e.getStackTrace());
}
System.out.println(Thread.currentThread().getName() + "...继续执行");
}
}
}

守护线程2

守护线程

线程同步问题

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
package multiThreadDemos;

public class Demo01 implements Runnable{
Timer timer=new Timer();
public static void main(String[] args) throws InterruptedException {
Demo01 demo= new Demo01();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.setName("Alex");
t2.setName("Brooks");
t1.start();
t2.start();
}

@Override
public void run() {
timer.add(Thread.currentThread().getName());
}

class Timer {
int num;

void add(String name) {
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+", 你是第"+num+"个调用timer的线程");
}
}
}

执行上述代码的现象:

synchronized同步

分析:

上述代码的正常执行流程是: T1 执行完add()方法中的num++后sleep, 释放资源 , 给到T2执行; 同样T2 执行完add()方法中的num++后sleep, 释放资源. 所以当T1 重新获取到执行资格的时候, 此时num已经是2.

出现上述现象的原因就是在一个(T1)线程执行的过程中, 另一个线程(T2)也同样访问了该资源(num), 因为是同一个变量(内存中同一地址区域), 所以造成了混乱.

解决办法

直观的想法就是在一个线程访问执行某一资源的时候, 不能让其他线程使用或改变该资源. 这就引出了同步的概念

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
package multiThreadDemos;

public class Demo01 implements Runnable{
Timer timer=new Timer();
public static void main(String[] args) throws InterruptedException {
Demo01 demo= new Demo01();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.setName("Alex");
t2.setName("Brooks");
t1.start();
t2.start();
}

@Override
public void run() {
timer.add(Thread.currentThread().getName());
}

static class Timer {
private static int num;

public void add(String name) {
synchronized (this){ // 在T1线程访问时, 将其锁定
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+", 你是第"+num+"个调用timer的线程");
} //
}
}
}

然后执行上述代码结果:

synchronized同步

一种更简单的方法就是将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
package multiThreadDemos;

public class Demo01 implements Runnable{
Timer timer=new Timer();
public static void main(String[] args) throws InterruptedException {
Demo01 demo= new Demo01();
Thread t1 = new Thread(demo);
Thread t2 = new Thread(demo);
t1.setName("Alex");
t2.setName("Brooks");
t1.start();
t2.start();
}

@Override
public void run() {
timer.add(Thread.currentThread().getName());
}

static class Timer {
private static int num;

public synchronized void add(String name) { //方法签名上添加synchronized关键字
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+", 你是第"+num+"个调用timer的线程");

}
}
}

死锁

产生原因:

同一个线程, 在执行run()里面的业务逻辑时, 对多个对象进行了’锁定’, 而这多个锁定的对象在多个线程值之间又有依赖关系, 此时就产生了死锁.

代码:

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
package multiThreadDemos;

public class Demo01 implements Runnable{
public boolean flag=true;
static Object o1 = new Object();
static Object o2 = new Object();
public static void main(String[] args) throws InterruptedException {
Demo01 demo01 = new Demo01();
Demo01 demo02 = new Demo01();
demo01.flag=true;
demo02.flag=false;
Thread thread1 = new Thread(demo01);
Thread thread2 = new Thread(demo02);
thread1.setName("Alex");
thread2.setName("Brooks");
thread1.start();
thread2.start();
}

@Override
public void run() {
System.out.println("flag="+flag);
if(flag){
synchronized (o1){
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
synchronized (o2){ System.out.println("1"); }
}

}
if(!flag){
synchronized (o2){
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
synchronized (o1){ System.out.println("0"); }
}

}
}
}

现象:

死锁

哲学家吃饭问题:

哲学家吃饭

解决办法

将锁的粒度变为粗粒度.

-------------本文结束感谢您的阅读-------------