需求: 启动两个线程,交替打印奇偶数。 效果如下所示

1
2
3
4
偶数线程:0
奇数线程:1
偶数线程:2
...

两个线程交替打印

无锁实现

不需要进行任何加锁,利用并发包中的AtomicInteger和volidate修饰符组合进行实现。

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
public class Thread_demo1 {
private static volatile Boolean flag = true;
private static AtomicInteger num = new AtomicInteger();
public static CountDownLatch latch = new CountDownLatch(2);

public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();

Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
while(num.get() <= 10000){
if(!flag){
System.out.println(Thread.currentThread().getName()+": " + num.getAndIncrement());
flag = true;
}
}
latch.countDown();
}
}, "奇数线程");

Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {

while(num.get() <= 10000){
if(flag){
System.out.println(Thread.currentThread().getName()+ ":" + num.getAndIncrement());
flag = false;
}
}
latch.countDown();
}
}, "偶数线程");

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

latch.await();
System.out.println("共耗时:"+(System.currentTimeMillis() - start) + "ms");
}
}

加锁实现A

通过一个boolean类型的变量来限制两个线程,分别只输出奇数和偶数。

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
public class Thread_demo2 {
private int count = 0;
private final Object lock = new Object();
public static CountDownLatch latch = new CountDownLatch(2);

public void go() throws InterruptedException {
long start = System.currentTimeMillis();

Thread thread1 = new Thread(() -> {
while (count < 10000) {
synchronized (lock) {
if ((count & 1) == 0) {
System.out.println(Thread.currentThread().getName() + ": " + count ++);
}
}
}
latch.countDown();
}, "偶数线程");

Thread thread2 = new Thread(() -> {
while (count < 10000) {
synchronized (lock) {
if ((count & 1) == 1) {
System.out.println(Thread.currentThread().getName() + ": " + count ++);
}
}
}
latch.countDown();
}, "奇数线程");
thread1.start();
thread2.start();

latch.await();
System.out.println("共耗时:"+(System.currentTimeMillis() - start) + "ms");
}

public static void main(String[] args) throws InterruptedException {
Thread_demo2 threaddemo2 = new Thread_demo2();
threaddemo2.go();
}
}

加锁实现B

通过同一个对象锁来实现。

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 Thread_demo3 {
private int count = 0;
private final Object lock = new Object();
public static CountDownLatch latch = new CountDownLatch(2);

public void go() throws InterruptedException {
long begin = System.currentTimeMillis();
new Thread(new RunnerTest(), "偶数线程").start();
// 确保偶数线程线先获取到锁
Thread.sleep(1);
new Thread(new RunnerTest(), "奇数线程").start();

latch.await();
System.out.println(System.currentTimeMillis() - begin);
}

class RunnerTest implements Runnable {
@Override
public void run() {
while (count < 10000) {
synchronized (lock) {
// 拿到锁就打印
System.out.println(Thread.currentThread().getName() + ": " + count ++);
// 唤醒其他线程
lock.notifyAll();
try {
if (count < 10000) {
// 如果任务还没有结束,则让出当前的锁并休眠
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
latch.countDown();
}
}

public static void main(String[] args) throws InterruptedException {
Thread_demo3 threaddemo3 = new Thread_demo3();
threaddemo3.go();
}
}

多个线程交替打印

需求: n个线程,交替打印数字。 效果如下所示

1
2
3
4
5
6
7
8
线程10
线程21
线程32
线程13
线程24
线程35
线程16
...
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
public class Demo implements Runnable {
private static final Object LOCK = new Object();
/**
* 当前即将打印的数字
*/
private static int current = 0;
/**
* 当前线程编号,从0开始
*/
private int threadNo;
/**
* 线程数量
*/
private int threadCount;
/**
* 打印的最大数值
*/
private int maxInt;

public Demo(int threadNo, int threadCount, int maxInt) {
this.threadNo = threadNo;
this.threadCount = threadCount;
this.maxInt = maxInt;
}

@Override
public void run() {
while (true) {
synchronized (LOCK) {
// 判断是否轮到当前线程执行
while (current % threadCount != threadNo) {
if (current > maxInt) {
break;
}
try {
// 如果不是,则当前线程进入wait
LOCK.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
// 最大值跳出循环
if (current > maxInt) {
break;
}
System.out.println("thread" + threadNo + " : " + current);
current++;
// 唤醒其他wait线程
LOCK.notifyAll();
}
}
}

public static void main(String[] args) {
int threadCount = 3;
int max = 100;
for (int i = 0; i < threadCount; i++) {
new Thread(new Demo(i, threadCount, max)).start();
}
}
}

可以看到,核心思想都是差不多的。都用的是等待通知机制。

这里我们需要注意一个问题。当threadCount(线程数量)比较大的时候,在执行notifyAll唤醒其他线程的时候,可能会出现线程抢到了锁但并不该自己执行,然后又进入wait的情况。比如现在有100个线程,现在是第一个线程在执行,他执行完之后需要第二个线程执行,但是第100个线程抢到了,发现不是自己然后又进入wait,然后第99个线程抢到了,发现不是自己然后又进入wait,然后第98,97…直到第3个线程都抢到了,最后才到第二个线程抢到同步锁,这里就会白白的多执行很多过程,虽然最后能完成目标。

解决办法多种多样,这里介绍一种基于信号量的实现方式。

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

static int result = 0;

public static void main(String[] args) throws InterruptedException {
int N = 3;
Thread[] threads = new Thread[N];
final Semaphore[] syncObjects = new Semaphore[N];
for (int i = 0; i < N; i++) {
syncObjects[i] = new Semaphore(1);
if (i != N-1){
syncObjects[i].acquire();
}
}
for (int i = 0; i < N; i++) {
final Semaphore lastSemphore = i == 0 ? syncObjects[N - 1] : syncObjects[i - 1];
final Semaphore curSemphore = syncObjects[i];
final int index = i;
threads[i] = new Thread(new Runnable() {

public void run() {
try {
while (true) {
// 第一次执行时,由于最后一个信号量并没有执行acquire,所以这里不会阻塞
lastSemphore.acquire();
System.out.println("thread" + index + ": " + result++);
if (result > 100){
System.exit(0);
}
// 释放下一个要执行的线程的
curSemphore.release();
}
} catch (Exception e) {
e.printStackTrace();
}

}
});
threads[i].start();
}
}
}

评论