线程通信实现方式

ゞ 浴缸里的玫瑰 2022-04-08 12:39 314阅读 0赞

第一种方式是wait和(notify)notifyAll的方式,一个线程负责变量的新增,一个线程负责变量的相减,一个线程操作完,另一个线程等待,具体操作请看下面的代码

这里需要注意的是一定尽量要用while判断,不要用if判断

第一消除notifyAll() 引起的并发问题,第二在while循环里而不是if语句下使用wait。这样,循环会在线程睡眠前后都检查wait的条件,并在条件实际上并未改变的情况下处理唤醒通知。

  1. /**
  2. *
  3. */
  4. package demo2;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. /**
  8. * @author liuchaojun
  9. * @date 2018-12-13 下午06:50:25
  10. */
  11. public class AwaitAndNotifyTest {
  12. private int i;
  13. public void end() {
  14. System.out.println(Thread.currentThread().getName() + ":所有线程执行完毕!");
  15. }
  16. public synchronized void add() {
  17. try {
  18. while(i==1){
  19. wait();// 做完本线程等待
  20. Thread.sleep(10);
  21. }
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. System.out.println(Thread.currentThread().getName() + ":" + (i++));
  26. notifyAll();// 释放其他线程
  27. }
  28. public synchronized void remove() {
  29. try {
  30. while(i==0){
  31. wait();// 做完本线程等待
  32. Thread.sleep(10);
  33. }
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. }
  37. System.out.println(Thread.currentThread().getName() + ":" + (i--));
  38. notifyAll();// 释放其他线程
  39. }
  40. public static void main(String[] args) {
  41. AwaitAndNotifyTest c = new AwaitAndNotifyTest();
  42. ExecutorService t = Executors.newFixedThreadPool(4);
  43. for (int i = 0; i < 25; i++) {
  44. t.execute(new Task1(c));
  45. t.execute(new Task2(c));
  46. }
  47. }
  48. }
  49. class Task1 implements Runnable {
  50. private AwaitAndNotifyTest awaitAndNotifyTest;
  51. public Task1(AwaitAndNotifyTest awaitAndNotifyTest) {
  52. super();
  53. this.awaitAndNotifyTest = awaitAndNotifyTest;
  54. }
  55. /*
  56. * (non-Javadoc)
  57. *
  58. * @see java.lang.Runnable#run()
  59. */
  60. @Override
  61. public void run() {
  62. awaitAndNotifyTest.add();
  63. }
  64. }
  65. class Task2 implements Runnable {
  66. private AwaitAndNotifyTest awaitAndNotifyTest;
  67. public Task2(AwaitAndNotifyTest awaitAndNotifyTest) {
  68. super();
  69. this.awaitAndNotifyTest = awaitAndNotifyTest;
  70. }
  71. /*
  72. * (non-Javadoc)
  73. *
  74. * @see java.lang.Runnable#run()
  75. */
  76. @Override
  77. public void run() {
  78. awaitAndNotifyTest.remove();
  79. }
  80. }

20181214092801138.png

第二种方式使用ReentrantLock的锁,Condition等待和释放,具体操作看下面代码

这里excute和submit的方法在于

  1. 接收的参数不一样;

2.submit有返回值,而execute没有(返回一个future。可以用这个future来判断任务是否成功完成)

  1. 例如,有个validationtask,希望该task执行完后告诉我它的执行结果,是成功还是失败,然后继续下面的操作。

3.submit方便Exception处理

   例如,如果task里会抛出checked或者unchecked exception,而你又希望外面的调用者能够感知这些exception并做出及时的处理,那么就需要用到submit,通过对Future.get()进行抛出异常的捕获,然后对其进行处理。

  1. /**
  2. *
  3. */
  4. package demo2;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.locks.Condition;
  8. import java.util.concurrent.locks.ReentrantLock;
  9. /**
  10. * @author liuchaojun
  11. * @date 2018-12-14 上午08:34:09
  12. */
  13. public class ReentrantLockAndConditionTest {
  14. private int i;
  15. private ReentrantLock reentrantLock = new ReentrantLock();
  16. private Condition condition = reentrantLock.newCondition();
  17. public void end() {
  18. System.out.println(Thread.currentThread().getName() + ":所有线程执行完毕!");
  19. }
  20. public void add() {
  21. try {
  22. reentrantLock.lock();
  23. while (i != 0) {
  24. condition.await();
  25. }
  26. System.out.println(Thread.currentThread().getName() + ":" + (i++));
  27. condition.signalAll();
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. } finally {
  31. reentrantLock.unlock();
  32. }
  33. }
  34. public void remove() {
  35. try {
  36. reentrantLock.lock();
  37. while (i != 1) {
  38. condition.await();
  39. }
  40. System.out.println(Thread.currentThread().getName() + ":" + (i--));
  41. condition.signalAll();
  42. } catch (InterruptedException e) {
  43. e.printStackTrace();
  44. } finally {
  45. reentrantLock.unlock();
  46. }
  47. }
  48. public static void main(String[] args) {
  49. ReentrantLockAndConditionTest c = new ReentrantLockAndConditionTest();
  50. ExecutorService t = Executors.newFixedThreadPool(4);
  51. for (int i = 0; i < 5; i++) {
  52. t.execute(new Task3(c));
  53. t.execute(new Task4(c));
  54. }
  55. t.shutdown();
  56. }
  57. }
  58. class Task3 implements Runnable {
  59. private ReentrantLockAndConditionTest reentrantLockAndConditionTest;
  60. public Task3(ReentrantLockAndConditionTest reentrantLockAndConditionTest) {
  61. super();
  62. this.reentrantLockAndConditionTest = reentrantLockAndConditionTest;
  63. }
  64. /*
  65. * (non-Javadoc)
  66. *
  67. * @see java.lang.Runnable#run()
  68. */
  69. @Override
  70. public void run() {
  71. reentrantLockAndConditionTest.add();
  72. }
  73. }
  74. class Task4 implements Runnable {
  75. private ReentrantLockAndConditionTest reentrantLockAndConditionTest;
  76. public Task4(ReentrantLockAndConditionTest reentrantLockAndConditionTest) {
  77. super();
  78. this.reentrantLockAndConditionTest = reentrantLockAndConditionTest;
  79. }
  80. /*
  81. * (non-Javadoc)
  82. *
  83. * @see java.lang.Runnable#run()
  84. */
  85. @Override
  86. public void run() {
  87. reentrantLockAndConditionTest.remove();
  88. }
  89. }

" class="reference-link">20181214092745465.png

第三种方式使用队列LinkedBlockingQueue

java.util.concurrent包下的新类。LinkedBlockingQueue就是其中之一,是一个阻塞的线程安全的队列,底层采用链表实现。

  1. LinkedBlockingQueue构造的时候若没有指定大小,则默认大小为Integer.MAX\_VALUE,当然也可以在构造函数的参数中指定大小。LinkedBlockingQueue不接受null
  2. 添加元素的方法有三个:add,put,offer,且这三个元素都是向队列尾部添加元素的意思。
  3. 区别:
  4. add方法在添加元素的时候,若超出了度列的长度会直接抛出异常:
  5. put方法,若向队尾添加元素的时候发现队列已经满了会发生阻塞一直等待空间,以加入元素。
  6. offer方法在添加元素时,如果发现队列已满无法添加的话,会直接返回false
  7. 从队列中取出并移除头元素的方法有:pollremovetake
  8. poll: 若队列为空,返回null
  9. remove:若队列为空,抛出NoSuchElementException异常。
  10. take:若队列为空,发生阻塞,等待有元素。

我们这里使用的是put和take方法,下面就是实现的代码。

  1. /**
  2. *
  3. */
  4. package demo2;
  5. import java.util.concurrent.ExecutorService;
  6. import java.util.concurrent.Executors;
  7. import java.util.concurrent.LinkedBlockingQueue;
  8. import java.util.concurrent.TimeUnit;
  9. /**
  10. * @author liuchaojun
  11. * @date 2018-12-14 上午09:01:48
  12. */
  13. public class LinkedBlockingQueueTest {
  14. public static void main(String[] args) {
  15. LinkedBlockingQueue<String> blockingQueue = new LinkedBlockingQueue<String>();
  16. ExecutorService t = Executors.newFixedThreadPool(4);
  17. for (int i = 0; i < 5; i++) {
  18. t.execute(new Task5(blockingQueue));
  19. t.execute(new Task6(blockingQueue));
  20. }
  21. if (!t.isShutdown()) {
  22. t.shutdown();
  23. System.out.println("线程池停止接收外部任务!");
  24. try {
  25. boolean flag = t.awaitTermination(300, TimeUnit.SECONDS);
  26. if (flag) {
  27. System.out.println("线程池已经停止!");
  28. }
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. }
  33. }
  34. }
  35. class Task5 implements Runnable {
  36. private LinkedBlockingQueue blockingQueue;
  37. public Task5(LinkedBlockingQueue blockingQueue) {
  38. super();
  39. this.blockingQueue = blockingQueue;
  40. }
  41. /*
  42. * (non-Javadoc)
  43. *
  44. * @see java.lang.Runnable#run()
  45. */
  46. @Override
  47. public void run() {
  48. try {
  49. while (blockingQueue.size() < 1) {
  50. System.out.println(Thread.currentThread().getName()
  51. + ":生产者生产元素");
  52. blockingQueue.put("元素");
  53. }
  54. } catch (InterruptedException e) {
  55. e.printStackTrace();
  56. }
  57. }
  58. }
  59. class Task6 implements Runnable {
  60. private LinkedBlockingQueue blockingQueue;
  61. public Task6(LinkedBlockingQueue blockingQueue) {
  62. super();
  63. this.blockingQueue = blockingQueue;
  64. }
  65. /*
  66. * (non-Javadoc)
  67. *
  68. * @see java.lang.Runnable#run()
  69. */
  70. @Override
  71. public void run() {
  72. try {
  73. while (true) {
  74. System.out.println(Thread.currentThread().getName() + "消费者"
  75. + "消费:" + blockingQueue.take());
  76. }
  77. } catch (InterruptedException e) {
  78. e.printStackTrace();
  79. }
  80. }
  81. }

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzI3MDI2NjAz_size_16_color_FFFFFF_t_70

其实线程通信有很多种,还有管道PipedOutputStream,计数countdownlatch等都可以实现,我这里写了三种作为示范。

发表评论

表情:
评论列表 (有 0 条评论,314人围观)

还没有评论,来说两句吧...

相关阅读