好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Java 用两个线程交替打印数字和字母

前一段时间听马士兵老师讲课,讲到某公司的一个面试,两个线程,其中一个线程输出ABC,另一个线程输出123,如何控制两个线程交叉输出1A2B3C,由于本人多线程掌握的一直不是很好,所以听完这道题,个人感觉收获良多,这是一个学习笔记。这道题有多种解法,不过有些属于纯炫技,所以只记录常见的三种解法。首先看第一种

1. park 和 unpark

?

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

package cn.bridgeli.demo;

 

import com.google.common.collect.Lists;

 

import java.util.List;

import java.util.concurrent.locks.LockSupport;

 

/**

  * @author BridgeLi

  * @date 2021/2/6 16:14

  */

public class Thread_Communication_Park_Unpark {

 

     static Thread t1 = null ;

     static Thread t2 = null ;

 

     public static void main(String[] args) {

 

         final List<Integer> integers = Lists.newArrayList( 1 , 2 , 3 , 4 , 5 , 6 , 7 );

         final List<String> strings = Lists.newArrayList( "A" , "B" , "C" , "D" , "E" , "F" , "G" );

 

         t1 = new Thread(() -> integers.forEach(item -> {

             System.out.print(item);

             LockSupport.unpark(t2);

             LockSupport.park();

         }), "t1" );

 

         t2 = new Thread(() -> strings.forEach(item -> {

             LockSupport.park();

             System.out.print(item);

             LockSupport.unpark(t1);

         }), "t2" );

 

         t1.start();

         t2.start();

     }

 

}

这个是最简单的实现方法,LockSupport.park() 使当前线程阻塞,而 LockSupport.unpark() 则表示唤醒一个线程,所以他需要一个参数,表示你要唤醒哪个线程,很好理解,也比较简单。

2. synchronized、notify、wait

?

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 cn.bridgeli.demo;

 

import com.google.common.collect.Lists;

 

import java.util.List;

 

/**

  * @author BridgeLi

  * @date 2021/2/6 16:14

  */

public class Thread_Communication_Notify_Wait {

 

     public static void main(String[] args) {

 

         final Object o = new Object();

         final List<Integer> integers = Lists.newArrayList( 1 , 2 , 3 , 4 , 5 , 6 , 7 );

         final List<String> strings = Lists.newArrayList( "A" , "B" , "C" , "D" , "E" , "F" , "G" );

 

         new Thread(() -> {

             synchronized (o) {

                 integers.forEach(item -> {

                     System.out.print(item);

                     o.notify();

                     try {

                         o.wait();

                     } catch (InterruptedException e) {

                         e.printStackTrace();

                     }

                 });

 

                 o.notify();

             }

         }, "t1" ).start();

 

         new Thread(() -> {

             synchronized (o) {

                 strings.forEach(item -> {

                     System.out.print(item);

                     o.notify();

                     try {

                         o.wait();

                     } catch (InterruptedException e) {

                         e.printStackTrace();

                     }

                 });

                 o.notify();

             }

         }, "t2" ).start();

     }

}

这是一个比较传统的写法,也是比较难理解的一个写法,掌握了这种写法之后,对 synchronized、notify、wait 的认识也会有一个新高度,下面就简单解析一下这种写法:

我们都知道 synchronized 是一把锁,而锁是什么?就是一个第三方的互斥的一个资源,所以 synchronized (o),就表示我们对 o 这个对象加锁,是通过修改 o 的对象头实现的,也就是两个线程谁成功修改了 o 的对象头,那么谁就拿到了这把锁,然后就可以执行里面的相关逻辑,而没有成功修改 o 的对象头的线程,就只有进入到对象 o 的一个等待队列,等待被系统调度执行(这是一个比较简单的不是很准确说法,详细过程,等我将来再写一个文章想聊锁升级的过程);然后就是 o.notify(),刚说过 synchronized (o) 一堆线程争抢锁,没有抢到锁的线程进入对象 o 的等待队列,所以 o.notify() 含义就是从对象 o 的等待队列中随机叫醒一个线程,然后执行;最后是 o.wait() 的含义,他的含义也很简单,就是当前线程放到对象 o 的等待队列中,让出 CPU。

通过这段描述,所以大家肯定也可以学习到经常遇到的三个问题是怎么回事:1. wait 是否占用 CPU 资源,因为进入了等待队列,所以是不会占用的;2. 既然 notify、wait 是让唤醒线程和让线程进入等待的,为什么不是 Thread 类的方法,反而是 Object 的方法,因为 notify、wait 是配合 synchronized 一起使用的,不一定用在多线程中,他们控制的是 synchronized 锁定的对象的等待队列,而 synchronized 锁定的对象,肯定是一个 Object,所以 notify、wait 比如是 Object 对象的方法;3. 关于 synchronized (o) 括号里面是一个对象实例、Class 对象、锁定代码块、静态变量等等区别,只要明白 synchronized 修改的是什么,这些区别就一目了然了,不再赘述。

最后要说明的一个问题是:循环外边的 o.notify() 必不可少,有些同学写的时候可能随手就忘记了,或者不知道为什么需要最后再 notify 一下,其实仔细想一想就可以明白了,假设最后执行的是输出字母的线程,那么他之前一定是被执行输出数字的线程唤醒的,而执行输出数字的这个线程唤醒执行输出字母的线程之后,自身就进入等待队列了,所以循环结束之后,如果最后执行输出字母的线程没有唤醒执行输出数字的线程的话,那么执行输出数字的线程会一直 wait 阻塞在那里,将等到天荒地来海枯石烂永远无法结束。

3. Condition

?

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

package cn.bridgeli.demo;

 

import com.google.common.collect.Lists;

 

import java.util.List;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

 

/**

  * @author BridgeLi

  * @date 2021/2/6 16:14

  */

public class Thread_Communication_Condition {

 

     public static void main(String[] args) {

 

         final List<Integer> integers = Lists.newArrayList( 1 , 2 , 3 , 4 , 5 , 6 , 7 );

         final List<String> strings = Lists.newArrayList( "A" , "B" , "C" , "D" , "E" , "F" , "G" );

 

         Lock lock = new ReentrantLock();

         Condition condition1 = lock.newCondition();

         Condition condition2 = lock.newCondition();

 

         new Thread(() -> {

             lock.lock();

             try {

                 integers.forEach(item -> {

                     System.out.print(item);

                     condition2.signal();

                     try {

                         condition1.await();

                     } catch (InterruptedException e) {

                         e.printStackTrace();

                     }

                 });

 

                 condition2.signal();

             } finally {

                 lock.unlock();

             }

         }, "t1" ).start();

 

         new Thread(() -> {

             lock.lock();

             try {

                 strings.forEach(item -> {

                     System.out.print(item);

                     condition1.signal();

                     try {

                         condition2.await();

                     } catch (InterruptedException e) {

                         e.printStackTrace();

                     }

                 });

                 condition1.signal();

             } finally {

                 lock.unlock();

             }

         }, "t2" ).start();

 

     }

 

}

当我们理解了上面两种写法之后,那么最后这个写法其实也比较容易理解了,就不用我多赘言了。

如果有幸有同学看到了这里,那么我再提出一个小问题,可以思考一下怎么解决,后面两种写法,我们保证是执行输出数字的线程还是执行输出字母的线程先执行,也就是先输出数字或者字母吗?如果不能的话,现在业务需求要求必须是先输出字母或者数字怎么做?(提示:CAS 自旋)

以上就是Java 用两个线程交替打印数字和字母的详细内容,更多关于Java 线程交替打印的资料请关注其它相关文章!

原文链接:http://www.bridgeli.cn/archives/692

查看更多关于Java 用两个线程交替打印数字和字母的详细内容...

  阅读:29次