Java 基础查漏补缺【多线程机制篇】

Posted by 石福鹏 on 2017-05-07

概念

线程: 是一个程序内部的顺序控制流。通俗的来说,是一个程序里面不同的执行路径 (记住这句话就可以了)

程序: wechat.exe

进程: wechat.exe启动后,叫做一个进程,是相对于程序来说的,是个动态的概念

线程: 作为一个进程里面最小的执行单元就叫一个线程,或者说,一个程序里不同的执行路径就叫做一个线程

平时所讲的进程开始执行了,是指进程中的主线程(main方法)开始执行了

  • Java的线程是通过Java.lang.Thread来实现的
  • VM启动时会有一个由主方法(public static void main(){})所sing以的线程
  • 可以通过创建Thread类的实例来创建新的线程
  • 每个线程都是通过某个特定的Thread对象所对应的run()来完成其操作的,方法run()称为线程体。
  • 通过调用Thread类的start方法来启动一个线程

线程的创建和启动

  • 定义线程类实现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
    public class Runner1 implements Runnable{
    /**
    * When an object implementing interface <code>Runnable</code> is used
    * to create a thread, starting the thread causes the object's
    * <code>run</code> method to be called in that separately executing
    * thread.
    * <p>
    * The general contract of the method <code>run</code> is that it may
    * take any action whatsoever.
    *
    * @see Thread#run()
    */
    @Override
    public void run() {

    }

    public static void main(String[] args) {
    Runner1 runner1 = new Runner1();
    //runner1.run(); //方法调用,不是线程启动
    Thread t = new Thread(runner1);
    t.start(); // 线程启动
    }
    }
  • 继承Thread

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Thread1 extends Thread {
    @Override
    public void run() {
    super.run();
    }

    public static void main(String[] args) {
    Thread1 thread1 = new Thread1();
    thread1.start();
    }
    }

    线程的状态

    image-20200929163343184

image-20200929163521329

线程同步
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 com.shifpeng.scaffold.test;

public class TestSync implements Runnable {
Timer timer = new Timer();

public static void main(String[] args) {
TestSync testSync = new TestSync();
Thread th1 = new Thread(testSync);
Thread th2 = new Thread(testSync);
th1.setName("t1");
th2.setName("t2");
th1.start();
th2.start();
}

/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
@Override
public void run() {
timer.add(Thread.currentThread().getName());
}
}


class Timer {
private static int num = 0;

public void add(String name) {
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf(name + "你是第" + num + "个使用Timer的线程\n");
}
}

打印结果

1
2
t1你是第2个使用Timer的线程
t2你是第2个使用Timer的线程

这个小程序执行出来的效果明显不对,原因就是线程在执行过程中,被另外一个线程打断了,两个线程应该作为原子性的输出

如何解决?在执行当前过程中,锁住该线程:

锁定当前对象synchronized(this)

在Java语言中,引入了对象互斥锁的概念,保证共享数据操作的完整性,保证在一个时刻,只能有一个线程访问该对象

对上面的小程序做相应修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Timer {
private static int num = 0;

public void add(String name) {
synchronized (this) { //加入锁定机制
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf(name + "你是第" + num + "个使用Timer的线程\n");
}
}
}
//t1你是第1个使用Timer的线程
//t2你是第2个使用Timer的线程

另一种简便的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Timer {
private static int num = 0;

public synchronized void add(String name) { //在执行当前方法的时候锁定当前对象
num++;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf(name + "你是第" + num + "个使用Timer的线程\n");
}
}
//t1你是第1个使用Timer的线程
//t2你是第2个使用Timer的线程
死锁
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.shifpeng.scaffold.test;

public class TestDeadLock implements Runnable {
public int flag = 1;
static Object o1 = new Object(), o2 = new Object();

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

public static void main(String[] args) {
TestDeadLock testDeadLock1 = new TestDeadLock();
TestDeadLock testDeadLock2 = new TestDeadLock();
testDeadLock1.flag = 1;
testDeadLock1.flag = 0;
Thread th1 = new Thread(testDeadLock1);
Thread th2 = new Thread(testDeadLock2);
th1.start();
th2.start();
}
}

程序执行到下面这里,就卡住不动了
//flag=0
//flag=1

面试题 :下面的程序中,m1在执行过程中,m2可以执行吗?

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 com.shifpeng.scaffold.test;

public class TT implements Runnable {
int b = 100;

//synchronized 保证同一时间该方法只能允许一个线程访问
public synchronized void m1() throws Exception {
b = 1000;
System.out.printf("b=" + b + "\n");
Thread.sleep(5000);
System.out.printf("b=" + b + "\n");
}

//这里不加synchronized,非同步方法。其他线程可以自由的访问非同步的方法,并且可能会对同步的方法产生影响
public void m2() throws Exception {
Thread.sleep(2500);
b = 2000;
}

@Override
public void run() {
try {
m1();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void main(String[] args) throws Exception {
TT tt = new TT();
Thread th1 = new Thread(tt);
th1.start();
Thread.sleep(1000);
tt.m2();
System.out.println(tt.b);
}
}

//打印结果

b=1000
2000
b=2000

//必须把访问这个资源(b)的所有的方法都要考虑到,每个方法是否都要设置成同步的,都要考虑到,加了同步,可能会导致效率变低,不加同步,可能会产生数据不一致的现象

上面得出的结论即:

一个对象中的非同步方法,可以被其他线程自由访问,并且可能对同步的方法产生影响

上面的程序加以修改(给m2也加上锁)

1
2
3
4
5
6
7
8
9
10
11
  //m1在锁定的过程中,m2也想锁定这个对象的另外一个方法,这个时候智能等到m1的锁释放后,才能获得锁,所以执行的结果如下
public synchronized void m2() throws Exception {
Thread.sleep(2500);
b = 2000;
}

//执行结果

b=1000
b=1000
2000

生产者消费者问题

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
package com.shifpeng.scaffold.test;

public class ProducerConsumer {
public static void main(String[] args) {
SyncStack syncStack = new SyncStack();
Producer producer = new Producer(syncStack);
Consumer consumer = new Consumer(syncStack);
new Thread(producer).start();
new Thread(consumer).start();
}

}

class ManTou {
int id;

ManTou(int id) {
this.id = id;
}

@Override
public String toString() {
return "馒头:" + this.id;
}
}

/**
* 篮子
*/
class SyncStack {
int index = 0;
ManTou[] manTous = new ManTou[6];

/**
* 存馒头
*
* @param mt
*/
public synchronized void push(ManTou mt) {
if (index == manTous.length) {
try {
this.wait(); //Object的方法 线程wait 注意:wait的时候,锁就不归我所有了
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify(); //叫醒一个正在这个对象是wait的线程
manTous[index] = mt;
index++;
}

/**
* 取馒头
*
* @return
*/
public synchronized ManTou pop() {
if (index == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
index--;
return manTous[index];
}
}

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

SyncStack syncStack = null;

Producer(SyncStack ss) {
this.syncStack = ss;
}

@Override
public void run() {
for (int i = 0; i < 20; i++) {
ManTou mt = new ManTou(i);
syncStack.push(mt);
System.out.println("生产了:" + mt);
try {
Thread.sleep((long)Math.random()*2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

/**
* 消费者
*/
class Consumer implements Runnable {

SyncStack syncStack = null;

Consumer(SyncStack ss) {
this.syncStack = ss;
}

@Override
public void run() {
for (int i = 0; i < 20; i++) {
ManTou mt = syncStack.pop();
System.out.println("消费了:" + mt);
try {
Thread.sleep((long)Math.random()*1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

注意:上面的程序有个潜在性的问题

1
2
3
4
5
6
7
8
9
10
11
12
public synchronized void push(ManTou mt) {
if (index == manTous.length) {
try {
this.wait(); //Object的方法 线程wait 注意:wait的时候,锁就不归我所有了
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify(); //叫醒一个正在这个对象是wait的线程
manTous[index] = mt;
index++;
}

上面的this.wait(); 如果被打断,就会进入到Exception,然后程序会继续执行,继续++,这个时候需要注意,程序需要做一些调整

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
/**
* 存馒头
*
* @param mt
*/
public synchronized void push(ManTou mt) {
while (index == manTous.length) {
try {
this.wait(); //Object的方法 线程wait 注意:wait的时候,锁就不归我所有了
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify(); //叫醒一个正在这个对象是wait的线程
manTous[index] = mt;
index++;
}

/**
* 取馒头
*
* @return
*/
public synchronized ManTou pop() {
while (index == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notify();
index--;
return manTous[index];
}

需要将if 换成while

waitsleep的区别

wait是object的方法,sleep是thread的方法

  • wait时别的线程可以访问锁定对象
    调用wait方法的时候必须锁定该对象
  • sleep时别的线程不可以访问锁定对象