javajavase

6 实现 Callable 接口

Java 5.0 在 java.util.concurrent 提供了一个新的创建执行线程的方式:Callable 接口
Callable 需要依赖FutureTask ,FutureTask 也可以用作闭锁

6.1 创建线程的四种方式

无返回

  1. 实现Runnable接口,重写run()
  2. 继承Thread类,重写run()

有返回

  1. 实现Callable接口,重写call(),利用FutureTask包装Callable,并作为task传入Thread构造函数
  2. 利用线程池

    6.2 Callable的使用

  3. 创建执行线程的方式三:实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常

  4. 执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。 FutureTask 是 Future 接口的实现类 ```java class ThreadDemo implements Callable { @Override public Integer call() throws Exception {
    1. int sum = 0;
    2. for (int i = 0; i <= 100000; i++) {
    3. sum += i;
    4. }
    5. return sum;
    } }

public class TestCallable {

  1. public static void main(String[] args) {
  2. ThreadDemo td = new ThreadDemo();
  3. // 1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
  4. FutureTask<Integer> result = new FutureTask<>(td);
  5. new Thread(result).start();
  6. // 2.接收线程运算后的结果
  7. try {
  8. Integer sum = result.get(); // FutureTask 可用于 闭锁
  9. System.out.println(sum);
  10. System.out.println("------------------------------------");
  11. } catch (InterruptedException | ExecutionException e) {
  12. e.printStackTrace();
  13. }
  14. }

}

  1. ---
  2. <a name="3d06b53a"></a>
  3. ## 7 -Lock 同步锁
  4. Java 5.0 之前,协调共享对象的访问时可以使用的机制只有 synchronized volatile Java 5.0 后增加了一些新的机制,但并不是一种替代内置锁的方法,而是当内 置锁不适用时,作为一种可选择的高级功能。<br />ReentrantLock 实现了 Lock 接口,并提供了与 synchronized 相同的互斥性和内存可见性。但相较于synchronized 提供了更高的处理锁的灵活性。<br />解决多线程安全问题的三种方式
  5. - jdk 1.5
  6. - synchronized:隐式锁<br />1.同步代码块<br />2.同步方法
  7. - jdk 1.5
  8. - 3.同步锁 Lock:显式锁<br />注意:是一个显示锁,需要通过 lock() 方法上锁,必须通过 unlock() 方法进行释放锁
  9. ```java
  10. public class TestLock {
  11. public static void main(String[] args) {
  12. Ticket ticket = new Ticket();
  13. new Thread(ticket, "1号窗口").start();
  14. new Thread(ticket, "2号窗口").start();
  15. new Thread(ticket, "3号窗口").start();
  16. }
  17. }
  18. class Ticket implements Runnable {
  19. private int tick = 100;
  20. private Lock lock = new ReentrantLock();
  21. @Override
  22. public void run() {
  23. while(true) {
  24. lock.lock(); // 上锁
  25. try {
  26. if(tick > 0) {
  27. try {
  28. Thread.sleep(200);
  29. } catch (InterruptedException e) {
  30. }
  31. System.out.println(Thread.currentThread().getName()
  32. + " 完成售票,余票为:" + --tick);
  33. }
  34. }finally{
  35. lock.unlock(); // 必须执行 因此放在finally中 释放锁
  36. }
  37. }
  38. }
  39. }

8 Condition 控制线程通信

Condition 接口描述了可能会与锁有关联的条件变量。这些变量在用法上与使用 Object.wait 访问的隐式监视器类似,但提供了更强大的功能。需要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了避免兼容性问题,Condition 方法的名称与对应的 Object 版本中的不同。
在 Condition 对象中,与 wait、notify 和 notifyAll 方法对应的分别是 await、signal 和 signalAll
Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。

8.1 使用Condition

使用Condition控制线程通信

  1. 如果不使用 synchronized 关键字保证同步,而是直接使用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也就不能使用 wait() notify() notifyAll() 来进行线程通信了
  2. 当使用 lock 对象来保证同步时,Java提供了一个 Condition 类来保持协调,使用Condition可以让那些已经得到 lock 对象却无法继续执行的线程释放lock对象,Condition对象也可以唤醒其他处于等待状态的进程。
  3. Condition 实例被绑定在一个 Lock 对象上。要获得 Lock 实例的 Condition 实例,调用 Lock 对象的newCondition() 方法即可

    8.2 生产者和消费者案例

    使用synchronized方式 ```java public class TestProductorAndConsumer { public static void main(String[] args) {

    1. Clerk clerk = new Clerk();
    2. Productor productor = new Productor(clerk);
    3. Consumer consumer = new Consumer(clerk);
    4. new Thread(productor, "生产者A").start();
    5. new Thread(consumer, "消费者B").start();
    6. new Thread(productor, "生产者C").start();
    7. new Thread(consumer, "消费者D").start();

    } }

class Clerk { // 店员 private int product = 0;

  1. public synchronized void get() { // 进货
  2. while (product >= 1) { // 为了避免虚假唤醒问题,应该总是使用在循环中而不用if
  3. System.out.println("产品已满!");
  4. try {
  5. this.wait();
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. }
  10. System.out.println(Thread.currentThread().getName() + " : " + ++product);
  11. this.notifyAll();
  12. }
  13. public synchronized void sale() { // 销售
  14. while (product <= 0) { // 为了避免虚假唤醒问题,应该总是使用在循环中
  15. System.out.println("缺货!");
  16. try {
  17. this.wait();
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. }
  22. System.out.println(Thread.currentThread().getName() + " : " + --product);
  23. this.notifyAll();
  24. }

}

class Productor implements Runnable { // 生产者 private Clerk clerk;

  1. public Productor(Clerk clerk) {
  2. this.clerk = clerk;
  3. }
  4. @Override
  5. public void run() {
  6. for (int i = 0; i < 20; i++) {
  7. try {
  8. Thread.sleep(200);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. clerk.get();
  13. }
  14. }

}

class Consumer implements Runnable { // 消费者 private Clerk clerk;

  1. public Consumer(Clerk clerk) {
  2. this.clerk = clerk;
  3. }
  4. @Override
  5. public void run() {
  6. for (int i = 0; i < 20; i++) {
  7. clerk.sale();
  8. }
  9. }

}

  1. 使用Lock方式
  2. ```java
  3. public class TestProductorAndConsumer {
  4. public static void main(String[] args) {
  5. Clerk clerk = new Clerk();
  6. Productor productor = new Productor(clerk);
  7. Consumer consumer = new Consumer(clerk);
  8. new Thread(productor, "生产者A").start();
  9. new Thread(consumer, "消费者B").start();
  10. new Thread(productor, "生产者C").start();
  11. new Thread(consumer, "消费者D").start();
  12. }
  13. }
  14. class Clerk {//店员
  15. private int product = 0;
  16. private Lock lock = new ReentrantLock();
  17. private Condition condition = lock.newCondition();
  18. private Condition condition2 = lock.newCondition();
  19. public void get() { // 进货
  20. lock.lock();
  21. try {
  22. while (product >= 1) {
  23. System.out.println("产品已满!");
  24. try {
  25. condition.await();
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. System.out.println(Thread.currentThread().getName() + " : " + ++product);
  31. condition2.signalAll();
  32. } finally {
  33. lock.unlock();
  34. }
  35. }
  36. public void sale() { // 销售
  37. lock.lock();
  38. try {
  39. while (product <= 0) {
  40. System.out.println("缺货!");
  41. try {
  42. condition2.await();
  43. } catch (InterruptedException e) {
  44. e.printStackTrace();
  45. }
  46. }
  47. System.out.println(Thread.currentThread().getName() + " : " + --product);
  48. condition.signalAll();
  49. } finally {
  50. lock.unlock();
  51. }
  52. }
  53. }
  54. class Productor implements Runnable { // 生产者
  55. private Clerk clerk;
  56. public Productor(Clerk clerk) {
  57. this.clerk = clerk;
  58. }
  59. @Override
  60. public void run() {
  61. for (int i = 0; i < 20; i++) {
  62. try {
  63. Thread.sleep(200);
  64. } catch (InterruptedException e) {
  65. e.printStackTrace();
  66. }
  67. clerk.get();
  68. }
  69. }
  70. }
  71. class Consumer implements Runnable { // 消费者
  72. private Clerk clerk;
  73. public Consumer(Clerk clerk) {
  74. this.clerk = clerk;
  75. }
  76. @Override
  77. public void run() {
  78. for (int i = 0; i < 20; i++) {
  79. clerk.sale();
  80. }
  81. }
  82. }

image.png


8.3 线程按序交替

要求:编写一个程序,开启 3 个线程,这三个线程的 ID 分别为 A、B、C,每个线程将自己的 ID 在屏幕上打印 10 遍,要 求输出的结果必须按顺序显示。 如:ABCABCABC…… 依次递归

  1. public class TestABCAlternate {
  2. public static void main(String[] args) {
  3. AlternateDemo ad = new AlternateDemo();
  4. new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. for (int i = 1; i <= 10; i++) {
  8. ad.loopA(i);
  9. }
  10. }
  11. }, "A").start();
  12. new Thread(new Runnable() {
  13. @Override
  14. public void run() {
  15. for (int i = 1; i <= 10; i++) {
  16. ad.loopB(i);
  17. }
  18. }
  19. }, "B").start();
  20. new Thread(new Runnable() {
  21. @Override
  22. public void run() {
  23. for (int i = 1; i <= 10; i++) {
  24. ad.loopC(i);
  25. }
  26. }
  27. }, "C").start();
  28. }
  29. }
  30. class AlternateDemo {
  31. private int number = 1;
  32. Lock lock = new ReentrantLock();
  33. Condition condition1 = lock.newCondition();
  34. Condition condition2 = lock.newCondition();
  35. Condition condition3 = lock.newCondition();
  36. /**
  37. * @param totalLoop 循环第几轮
  38. */
  39. public void loopA(int totalLoop) {
  40. lock.lock();
  41. try {
  42. if (number != 1) {
  43. condition1.await();
  44. }
  45. for (int i = 1; i <= 1; i++) {
  46. System.out.println(Thread.currentThread().getName()
  47. + "\t" + i + "\t" + totalLoop);
  48. }
  49. number = 2;
  50. condition2.signal();
  51. } catch (Exception e) {
  52. e.printStackTrace();
  53. } finally {
  54. lock.unlock();
  55. }
  56. }
  57. public void loopB(int totalLoop) {
  58. lock.lock();
  59. try {
  60. if (number != 2) {
  61. condition2.await();
  62. }
  63. for (int i = 1; i <= 1; i++) {
  64. System.out.println(Thread.currentThread().getName()
  65. + "\t" + i + "\t" + totalLoop);
  66. }
  67. number = 3;
  68. condition3.signal();
  69. } catch (Exception e) {
  70. e.printStackTrace();
  71. } finally {
  72. lock.unlock();
  73. }
  74. }
  75. public void loopC(int totalLoop) {
  76. lock.lock();
  77. try {
  78. if (number != 3) {
  79. condition3.await();
  80. }
  81. for (int i = 1; i <= 1; i++) {
  82. System.out.println(Thread.currentThread().getName()
  83. + "\t" + i + "\t" + totalLoop);
  84. }
  85. number = 1;
  86. condition1.signal();
  87. } catch (Exception e) {
  88. e.printStackTrace();
  89. } finally {
  90. lock.unlock();
  91. }
  92. }
  93. }

image.png


9 ReadWriteLock 读写锁

ReadWriteLock 维护了一对相关的锁,一个用于只读操作, 另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的
ReadWriteLock 读取操作通常不会改变共享资源,但执行写入操作时,必须独占方式来获取锁。对于读取操作占多数的数据结构。 ReadWriteLock 能提供比独占锁更高的并发性。而对于只读的数据结构,其中包含的不变性 可以完全不需要考虑加锁操作

  • 写写/读写 需要“互斥”
  • 读读 不需要互斥 ```java public class TestReadWriteLock { public static void main(String[] args) {

    1. ReadWriteLockDemo rw = new ReadWriteLockDemo();
    2. new Thread(new Runnable() {
    3. @Override
    4. public void run() {
    5. rw.set((int)(Math.random() * 101));
    6. }
    7. }, "write").start();
    8. for (int i = 0; i < 100; i++) {
    9. new Thread(new Runnable() {
    10. @Override
    11. public void run() {
    12. rw.get();
    13. }
    14. }).start();
    15. }

    } }

class ReadWriteLockDemo { private int number = 0; private ReadWriteLock lock = new ReentrantReadWriteLock();

  1. public void get() { // 读
  2. lock.readLock().lock(); // 上锁
  3. try {
  4. System.out.println(Thread.currentThread().getName() + " : " + number);
  5. } catch (Exception e) {
  6. e.printStackTrace();
  7. } finally {
  8. lock.readLock().unlock(); // 释放锁
  9. }
  10. }
  11. public void set(int number) {
  12. lock.writeLock().lock();
  13. try {
  14. System.out.println(Thread.currentThread().getName());
  15. this.number = number;
  16. } catch (Exception e) {
  17. e.printStackTrace();
  18. } finally {
  19. lock.writeLock().unlock();
  20. }
  21. }

}

  1. ---
  2. <a name="e1202f16"></a>
  3. ## 10 线程8锁
  4. 判断打印的 "one" or "two"
  5. 1. 两个普通同步方法,两个线程,标准打印, 打印结果?
  6. 1. 新增 Thread.sleep() getOne(),打印结果?
  7. 1. 新增普通方法 getThree() , 打印结果?
  8. 1. 两个普通同步方法,两个 Number 对象,打印结果?
  9. 1. 修改 getOne() 为静态同步方法,打印结果?
  10. 1. 修改两个方法均为静态同步方法,一个 Number 对象,打印结果?
  11. 1. 一个静态同步方法,一个非静态同步方法,两个 Number 对象,打印结果?
  12. 1. 两个静态同步方法,两个 Number 对象,打印结果?
  13. 要想知道上面线程8锁的答案,需要知晓关键所在
  14. - 非静态方法的锁默认为 this(实例对象), 静态方法的锁为对应的 Class 对象(类对象)
  15. - 某一个时刻,同一个对象,只能有一个线程持有锁,无论几个方法
  16. - 锁静态方法,某一个时刻,不同实例对象也只能有一个对象持有锁
  17. ```java
  18. public class TestThread8Monitor {
  19. public static void main(String[] args) {
  20. Number number = new Number();
  21. Number number2 = new Number();
  22. new Thread(new Runnable() {
  23. @Override
  24. public void run() {
  25. number.getOne();
  26. }
  27. }).start();
  28. new Thread(new Runnable() {
  29. @Override
  30. public void run() {
  31. // number.getTwo();
  32. number2.getTwo();
  33. }
  34. }).start();
  35. // new Thread(new Runnable() {
  36. // @Override
  37. // public void run() {
  38. // number.getThree();
  39. // }
  40. // }).start();
  41. }
  42. }
  43. class Number {
  44. public static synchronized void getOne() { // Number.class
  45. try {
  46. Thread.sleep(3000);
  47. } catch (InterruptedException e) {
  48. }
  49. System.out.println("one");
  50. }
  51. public synchronized void getTwo() { // this
  52. System.out.println("two");
  53. }
  54. public void getThree() {
  55. System.out.println("three");
  56. }
  57. }

答案

  1. 两个普通同步方法,两个线程,一个 Number 对象,标准打印, 打印结果? //one two
  2. 新增 Thread.sleep() 给 getOne() ,打印结果? // —过了3秒— one two
  3. 新增普通方法 getThree() , 打印结果? //three —过了3秒— one two
  4. 两个普通同步方法,两个 Number 对象,打印结果? //two —过了3秒— one
  5. 修改 getOne() 为静态同步方法,打印结果? //two —过了3秒— one
  6. 修改两个方法均为静态同步方法,一个 Number 对象,打印结果? //—过了3秒— one two
  7. 一个静态同步方法,一个非静态同步方法,两个 Number 对象,打印结果? //two —过了3秒— one
  8. 两个静态同步方法,两个 Number 对象,打印结果? //—过了3秒— one two