思考
两个线程在控制台交替打印 1 到 100
这道题看似简单,不过我第一次写不出来,百度了一下,有答案,但不满意,看了下书,果然很有内涵。接下来将循序渐进,先展示创建新线程的方式、再看上面题目的三种解法、再挖掘潜在价值
新线程创建方式
1. Thread
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void test001() throws InterruptedException {
class ThreadTest extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
ThreadTest thread = new ThreadTest();
thread.start();
Thread.sleep(1000);
}
2. Runnable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Test
public void test002() throws InterruptedException {
class Target implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
Target target = new Target();
Thread thread = new Thread(target);
thread.start();
Thread.sleep(1000);
}
3. Callable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
public void test003() throws InterruptedException {
class Target<T> implements Callable<T> {
@Override
public T call() throws Exception {
System.out.println(Thread.currentThread().getName());
return null;
}
}
Target target = new Target();
FutureTask<Integer> task = new FutureTask<>(target);
Thread thread = new Thread(task);
thread.start();
Thread.sleep(1000);
}
交替打印 1 到 100
具体写法有很多种,不局限于我这几种
1. Thread 写法
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
@Test
public void test004() throws InterruptedException {
class Data {
Integer a;
}
class MyThread extends Thread {
private Data obj;
public MyThread(Data obj) {
this.obj = obj;
}
@Override
public void run() {
synchronized (obj) {
while (obj.a < 101) {
System.out.println(Thread.currentThread().getName() + ": " + obj.a++);
obj.notify();
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
Data obj = new Data();
obj.a = 1;
Thread thread001 = new MyThread(obj);
Thread thread002 = new MyThread(obj);
thread001.start();
thread002.start();
Thread.sleep(1000);
}
2. 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
@Test
public void test005() throws InterruptedException {
class Target implements Runnable {
int a = 1;
@Override
public synchronized void run() {
while (a < 101) {
System.out.println(Thread.currentThread().getName() + ": " + a++);
notify();
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Target target = new Target();
Thread thread001 = new Thread(target);
Thread thread002 = new Thread(target);
thread001.start();
thread002.start();
Thread.sleep(1000);
}
3. 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
@Test
public void test006() throws InterruptedException {
class Task implements Callable<Integer> {
int a = 1;
@Override
public synchronized Integer call() throws Exception {
while (a < 101) {
System.out.println(Thread.currentThread().getName() + ": " + a++);
notify();
wait();
}
return a;
}
}
Task task = new Task();
FutureTask<Integer> task001 = new FutureTask<>(task);
FutureTask<Integer> task002 = new FutureTask<>(task);
Thread thread001 = new Thread(task001);
Thread thread002 = new Thread(task002);
thread001.start();
thread002.start();
Thread.sleep(1000);
}
分析
三种写法中,很明显能发现一个共同点,都用了 synchronized 关键字,书中称为同步锁,也可称之为锁。
对于 synchronized,我的理解:多个线程抢夺一个目标,谁抢到谁就可以执行,其它线程就要重新等待目标的释放,再次抢夺
synchronized 可以指定同步监视器,也就是指定抢夺的目标,例如 synchronized (this){...}
,这个目标只需要有一个特征:所有线程都可以访问到。在 Java 中,堆内存共享,所以同步监视器 = 堆内存中的某一个对象,线程同步就是,你能抢到这个对象,你就可以执行 synchronized 包含的代码,抢不到,就等下一次。
- Thread 写法:在堆中定一个了 obj 对象,然后 thread001 和 thread002 就抢夺 obj,抢到的就有执行的资格
- Runnable 写法:target 对象为目标
- Callable 写法:task 对象为目标
目标在所有线程中是共享的,那么这个目标是不是可以用来线程通信?是的,可以用来通信,wait() 和 notify() 就是通过目标来进行的通信
扩展
实现阻塞队列
https://tech.meituan.com/2018/11/15/java-lock.html