Java多线程

为了以后更好地学习并发编程,现阶段将回顾学习java的多线程,稳固Java基础。

线程的创建

创建线程非常简单,Java提供了3种方式。第一种直接继承Java的Thread类,然后使用start方法进行线程启动。但是Java是单继承的,直接继承的方式显然不够优雅。Java提供的第二种方式,实现Runnable接口,通过Runnable的方法来创建线程,实际上是等价于继承Thread实现的,只不过实现接口的方式需要使用一个Thread来执行这个Runnable。当我们需要在线程执行完后提供一个返回值,那么我们就需要使用第三种方式,实现Callable接口,这个Callable接口使用了泛型,运行Callable的时候使用的不是Thread,而是FutureTask,当线程执行完毕后,我们便可以通过FutureTask的get方法来获取其返回值。具体代码如下所示:

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

static class ExtendWay extends Thread {
@Override
public void run() {
System.out.println("ExtendWay Thread is running!!");
}
}

static class RunnableWay implements Runnable {
public void run() {
System.out.println("RunnableWay Thread is running!!");
}
}

static class CallableWay implements Callable<String> {

public String call() throws Exception {
return "CallableWay Thread is Running!! This is it's return parameter.";
}
}

public static void main(String[] args) {
ExtendWay extendWay = new ExtendWay();
RunnableWay runnableWay = new RunnableWay();
CallableWay callableWay = new CallableWay();

Thread thread = new Thread(runnableWay);
FutureTask<String> futureTask = new FutureTask<String>(callableWay);

extendWay.start();
thread.start();
futureTask.run();

try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}

}

}

安全地中断线程

JDK废弃了使用stop方法的方式来强制中断线程,取代的是interrupt方法。interrupt方法是以中断标记的方式来进行干涉,使得Java的线程更贴切协作式。当线程抛出InterruptException的时候需要手动抓取调用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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class StopThread {

static class InterruptThread extends Thread {
@Override
public void run() {
while (!isInterrupted()) {
System.out.println("thread is running!");
}
System.out.println(this.isInterrupted());
}
}

static class InterruptRunnable implements Runnable {

public void run() {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("runnable is running!");
}
System.out.println(Thread.currentThread().isInterrupted());
}
}

static class InterruptOccurException implements Runnable {

public void run() {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("runnable is running!");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println("Occur Exception");
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().isInterrupted());
}
}

public static void main(String[] args) throws InterruptedException {
InterruptThread interruptThread = new InterruptThread();
InterruptRunnable interruptRunnable = new InterruptRunnable();
InterruptOccurException interruptOccurException = new InterruptOccurException();

interruptThread.start();
Thread.sleep(20);
interruptThread.interrupt();

Thread runnableThread = new Thread(interruptRunnable);
runnableThread.start();
Thread.sleep(20);
runnableThread.interrupt();

Thread occurException = new Thread(interruptOccurException);
occurException.start();
Thread.sleep(500);
occurException.interrupt();

}
}

线程之间的协作

wait和notify/notifyAll

wait方法使当前线程进入等待,notify方法唤醒一个等待线程,notifyAll方法唤醒所以等待线程。需要注意的是,wait和notify、notifyAll都需要获取锁对象,如果没有加锁,则会抛出异常。使用wait方法时,wait方法将会把持有的锁释放,而notify/notifyAll方法调用时,本身是不会释放锁的,它将会等待该方法完成后再将锁释放,所以通常都会将notify/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
package com.wentao.study.basic.thread;

import com.wentao.study.basic.thread.utils.SleepUtils;

/**
* 实验notify、notifyAll、wait方法
*
* @author Vent
* @date 2020/5/13
*/
public class ThreadLifeCircle {

private static class Pack {
int km;
String target;

public synchronized void moving() {
this.km = 100;
notifyAll();
}

public synchronized void arrived() {
this.target = "Shanghai";
notifyAll();
}

public synchronized void waitTarget() {
while (!"Shanghai".equals(target)) {
try {
wait();
System.out.println("check target..." + Thread.currentThread().getId());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("target arrive");
}

public synchronized void waitKm() {
while (this.km < 100) {
try {
wait();
System.out.println("check km..." + Thread.currentThread().getId());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("km end....");
}
}

public static void main(String[] args) {
final Pack pack = new Pack();
pack.km = 0;
pack.target = "Beijing";
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
public void run() {
pack.waitTarget();
}
}).start();
}
for (int i = 0; i < 3; i++) {
new Thread(new Runnable() {
public void run() {
pack.waitKm();
}
}).start();
}
SleepUtils.second(1);
pack.moving();
}

}

join

面试点:如何保证线程A在线程B执行完后再执行?使用join方法。可以通过join方法将线程串联起来。yield方法执行后,其持有的锁是不释放的,sleep方法也不会释放锁。

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 Join {

private Thread prev;

public Join(Thread prev, String name) {
this.prev = prev;
this.setName(name);
}

@Override
public void run() {
System.out.println("this is " + this.getName());
try {
prev.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName() + " is end");
}

public static void main(String[] args) {
Thread pre = Thread.currentThread();
for (int i = 0; i < 10; i++) {
pre = new Join(pre, String.valueOf(i));
pre.start();
}
SleepUtils.second(2);
System.out.println("main thread shut down");
}
}

synchronized、volatile和ThreadLocal

synchronized是Java提供的实现多线程同步锁。其中可分为对象锁和类锁,即对对象加锁和对类加锁。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
public class Synchronized {

static class First implements Runnable {
private Synchronized sync;
private String name;

public First(Synchronized sync, String name) {
this.sync = sync;
this.name = name;
}

public void run() {
while (!Thread.currentThread().isInterrupted()) {
SleepUtils.second(1);
sync.objectPrint(name);
SleepUtils.second(1);
Synchronized.classPrint(name);
}
}
}

static synchronized void classPrint(String s) {
SleepUtils.second(1);
System.out.println("class print is running --- " + s);
SleepUtils.second(1);
System.out.println("class print is end --- " + s);
}

synchronized void objectPrint(String s) {
SleepUtils.second(1);
System.out.println("object print is running --- " + s);
SleepUtils.second(1);
System.out.println("object print is end --- " + s);
}

public static void main(String[] args) {
Synchronized sync = new Synchronized();
Thread fThread = new Thread(new First(sync, "1"));
Thread sThread = new Thread(new First(sync, "2"));

fThread.start();
sThread.start();
}
}

volatile是java中的最轻量级的同步机制,当一个线程改变volatile变量时,会告诉虚拟机将这个变量值刷到主内存中,其他线程中保存的副本将会失效,线程将从主内存中重新读取变量值。其最常见的用途是一个线程写,多个线程读,能确保变量的原子性。volatile并不是线程安全的,它只能保证变量的可见性,不能保证其原子性。如下代码所示:

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 Volatile {

private static class VolatileThread implements Runnable {

private volatile int number = 0;

@Override
public void run() {
String name = Thread.currentThread().getName();
number = number + 1;
System.out.println(name + ": " + number);
SleepUtils.mile(100);
number = number + 1;
System.out.println(name + ": " + number);
}
}

public static void main(String[] args) {
VolatileThread v = new VolatileThread();
Thread t1 = new Thread(v);
Thread t2 = new Thread(v);
Thread t3 = new Thread(v);
Thread t4 = new Thread(v);
Thread t5 = new Thread(v);

t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}

ThreadLocal是线程级别的变量,每个线程拥有其自己的变量,线程与线程间互不干扰。由于其每个线程都拥有一个副本,其占有资源将会是非常庞大的,所以ThreadLocal尽量存储比较小的数据。演示代码如下:

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

public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 1;
}
};

public static class ThreadLocalTest {
public void start() {
Thread[] arr = new Thread[3];
for (int i = 0; i < 3; i++) {
arr[i] = new Thread(new ThreadLocalRunnable(i));
}
for (int i = 0; i < 3; i++) {
arr[i].start();
}
}
}

public static class ThreadLocalRunnable implements Runnable {

private int id;

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

@Override
public void run() {
Integer integer = threadLocal.get();
integer += id;
threadLocal.set(integer);
SleepUtils.second(1);
System.out.println("Thread-" + id + " : " + threadLocal.get());
}
}

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

}