Java 中文官方教程 2022 版(九)(3)https://developer.aliyun.com/article/1486344
生产者线程,在Producer
中定义,发送一系列熟悉的消息。字符串"DONE"表示所有消息都已发送。为了模拟真实应用程序的不可预测性,生产者线程在消息之间暂停一段随机时间。
import java.util.Random; public class Producer implements Runnable { private Drop drop; public Producer(Drop drop) { this.drop = drop; } public void run() { String importantInfo[] = { "Mares eat oats", "Does eat oats", "Little lambs eat ivy", "A kid will eat ivy too" }; Random random = new Random(); for (int i = 0; i < importantInfo.length; i++) { drop.put(importantInfo[i]); try { Thread.sleep(random.nextInt(5000)); } catch (InterruptedException e) {} } drop.put("DONE"); } }
消费者线程,在Consumer
中定义,简单地检索消息并打印出来,直到检索到"DONE"字符串为止。该线程还会暂停一段随机时间。
import java.util.Random; public class Consumer implements Runnable { private Drop drop; public Consumer(Drop drop) { this.drop = drop; } public void run() { Random random = new Random(); for (String message = drop.take(); ! message.equals("DONE"); message = drop.take()) { System.out.format("MESSAGE RECEIVED: %s%n", message); try { Thread.sleep(random.nextInt(5000)); } catch (InterruptedException e) {} } } }
最后,这是主线程,在ProducerConsumerExample
中定义,启动生产者和消费者线程。
public class ProducerConsumerExample { public static void main(String[] args) { Drop drop = new Drop(); (new Thread(new Producer(drop))).start(); (new Thread(new Consumer(drop))).start(); } }
注意: Drop
类是为了演示受保护的代码块而编写的。在尝试编写自己的数据共享对象之前,请查看 Java 集合框架中的现有数据结构,以避免重复造轮子。有关更多信息,请参考问题和练习部分。
不可变对象
原文:
docs.oracle.com/javase/tutorial/essential/concurrency/immutable.html
如果一个对象在构造后其状态不能改变,则被认为是不可变的。广泛接受的一种创建简单可靠代码的策略是最大程度地依赖不可变对象。
不可变对象在并发应用程序中特别有用。由于它们不能改变状态,因此它们不会受到线程干扰的破坏,也不会以不一致的状态被观察到。
程序员通常不愿使用不可变对象,因为他们担心创建一个新对象的成本,而不是就地更新对象。对象创建的影响经常被高估,可以通过一些与不可变对象相关的效率来抵消。这些效率包括由于垃圾回收而减少的开销,以及消除了为了保护可变对象免受破坏而需要的代码。
以下小节以一个实例是可变的类为例,并从中派生出一个实例是不可变的类。这样做,它们给出了这种转换的一般规则,并展示了不可变对象的一些优势。
一个同步类的示例
原文:
docs.oracle.com/javase/tutorial/essential/concurrency/syncrgb.html
这个类,SynchronizedRGB
,定义了代表颜色的对象。每个对象将颜色表示为三个代表主要颜色值的整数和一个给出颜色名称的字符串。
public class SynchronizedRGB { // Values must be between 0 and 255. private int red; private int green; private int blue; private String name; private void check(int red, int green, int blue) { if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { throw new IllegalArgumentException(); } } public SynchronizedRGB(int red, int green, int blue, String name) { check(red, green, blue); this.red = red; this.green = green; this.blue = blue; this.name = name; } public void set(int red, int green, int blue, String name) { check(red, green, blue); synchronized (this) { this.red = red; this.green = green; this.blue = blue; this.name = name; } } public synchronized int getRGB() { return ((red << 16) | (green << 8) | blue); } public synchronized String getName() { return name; } public synchronized void invert() { red = 255 - red; green = 255 - green; blue = 255 - blue; name = "Inverse of " + name; } }
必须小心使用SynchronizedRGB
,以避免出现不一致的状态。例如,假设一个线程执行以下代码:
SynchronizedRGB color = new SynchronizedRGB(0, 0, 0, "Pitch Black"); ... int myColorInt = color.getRGB(); //Statement 1 String myColorName = color.getName(); //Statement 2
如果另一个线程在语句 1 之后但在语句 2 之前调用color.set
,myColorInt
的值将不匹配myColorName
的值。为了避免这种结果,这两个语句必须绑定在一起:
synchronized (color) { int myColorInt = color.getRGB(); String myColorName = color.getName(); }
这种不一致性只对可变对象有效 — 对于不可变版本的SynchronizedRGB
不会有问题。
定义不可变对象的策略
原文:
docs.oracle.com/javase/tutorial/essential/concurrency/imstrat.html
以下规则定义了创建不可变对象的简单策略。并非所有被记录为“不可变”的类都遵循这些规则。这并不一定意味着这些类的创建者粗心大意 — 他们可能有充分的理由相信他们的类的实例在构造后永远不会改变。然而,这种策略需要复杂的分析,不适合初学者。
- 不提供“setter”方法 — 修改字段或字段引用的对象的方法。
- 使所有字段都是
final
和private
。 - 不允许子类重写方法。这样做的最简单方法是将类声明为
final
。更复杂的方法是将构造函数设为private
,并在工厂方法中构造实例。 - 如果实例字段包括对可变对象的引用,请不要允许更改这些对象:
- 不要提供修改可变对象的方法。
- 不共享对可变对象的引用。永远不要存储传递给构造函数的外部可变对象的引用;如果必要,创建副本,并存储对副本的引用。类似地,在必要时创建内部可变对象的副本,以避免在方法中返回原始对象。
将这种策略应用于SynchronizedRGB
会产生以下步骤:
- 这个类中有两个 setter 方法。第一个
set
方法任意地转换对象,并且在类的不可变版本中没有位置。第二个invert
方法可以通过创建一个新对象来适应,而不是修改现有对象。 - 所有字段已经是
private
;它们进一步被标记为final
。 - 类本身被声明为
final
。 - 只有一个字段引用一个对象,而该对象本身是不可变的。因此,不需要防止改变“包含”可变对象状态的保护措施。
在这些更改之后,我们有了ImmutableRGB
:
final public class ImmutableRGB { // Values must be between 0 and 255. final private int red; final private int green; final private int blue; final private String name; private void check(int red, int green, int blue) { if (red < 0 || red > 255 || green < 0 || green > 255 || blue < 0 || blue > 255) { throw new IllegalArgumentException(); } } public ImmutableRGB(int red, int green, int blue, String name) { check(red, green, blue); this.red = red; this.green = green; this.blue = blue; this.name = name; } public int getRGB() { return ((red << 16) | (green << 8) | blue); } public String getName() { return name; } public ImmutableRGB invert() { return new ImmutableRGB(255 - red, 255 - green, 255 - blue, "Inverse of " + name); } }
高级并发对象
原文:
docs.oracle.com/javase/tutorial/essential/concurrency/highlevel.html
到目前为止,本课程已经专注于 Java 平台从一开始就存在的低级 API。这些 API 对于非常基本的任务是足够的,但对于更高级的任务需要更高级的构建块。这对于充分利用当今的多处理器和多核系统的大规模并发应用程序尤为重要。
在本节中,我们将介绍 Java 平台 5.0 版本引入的一些高级并发特性。这些特性大多数都是在新的java.util.concurrent
包中实现的。Java 集合框架中还有新的并发数据结构。
- 锁对象支持简化许多并发应用程序的锁定习语。
- 执行器定义了一个用于启动和管理线程的高级 API。
java.util.concurrent
提供的执行器实现提供了适用于大规模应用程序的线程池管理。 - 并发集合使得管理大量数据集变得更加容易,并且可以大大减少同步的需求。
- 原子变量具有最小化同步和避免内存一致性错误的特性。
ThreadLocalRandom
(在 JDK 7 中)提供了多线程有效生成伪随机数的功能。
锁对象
原文:
docs.oracle.com/javase/tutorial/essential/concurrency/newlocks.html
同步代码依赖于一种简单的可重入锁。这种类型的锁易于使用,但有许多限制。更复杂的锁习语由 java.util.concurrent.locks
包支持。我们不会详细讨论此包,而是专注于其最基本的接口 Lock
。
Lock
对象的工作方式与同步代码中使用的隐式锁非常相似。与隐式锁一样,一次只有一个线程可以拥有 Lock
对象。Lock
对象还支持通过其关联的 Condition
对象实现 wait/notify
机制。
Lock
对象相对于隐式锁的最大优势在于其能够在尝试获取锁时撤销操作。如果指定了超时时间,tryLock
方法在锁不可用时或超时之前会撤销操作。lockInterruptibly
方法在获取锁之前如果另一个线程发送中断信号,则会撤销操作。
让我们使用 Lock
对象来解决我们在 Liveness 中看到的死锁问题。阿方索和加斯顿已经训练自己注意到朋友即将鞠躬的时刻。我们通过要求我们的 Friend
对象必须在继续鞠躬之前为两个参与者获取锁来模拟这种改进。这是改进模型的源代码,Safelock
。为了展示这种习语的多功能性,我们假设阿方索和加斯顿如此迷恋他们新发现的安全鞠躬能力,以至于他们无法停止向彼此鞠躬:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.Random; public class Safelock { static class Friend { private final String name; private final Lock lock = new ReentrantLock(); public Friend(String name) { this.name = name; } public String getName() { return this.name; } public boolean impendingBow(Friend bower) { Boolean myLock = false; Boolean yourLock = false; try { myLock = lock.tryLock(); yourLock = bower.lock.tryLock(); } finally { if (! (myLock && yourLock)) { if (myLock) { lock.unlock(); } if (yourLock) { bower.lock.unlock(); } } } return myLock && yourLock; } public void bow(Friend bower) { if (impendingBow(bower)) { try { System.out.format("%s: %s has" + " bowed to me!%n", this.name, bower.getName()); bower.bowBack(this); } finally { lock.unlock(); bower.lock.unlock(); } } else { System.out.format("%s: %s started" + " to bow to me, but saw that" + " I was already bowing to" + " him.%n", this.name, bower.getName()); } } public void bowBack(Friend bower) { System.out.format("%s: %s has" + " bowed back to me!%n", this.name, bower.getName()); } } static class BowLoop implements Runnable { private Friend bower; private Friend bowee; public BowLoop(Friend bower, Friend bowee) { this.bower = bower; this.bowee = bowee; } public void run() { Random random = new Random(); for (;;) { try { Thread.sleep(random.nextInt(10)); } catch (InterruptedException e) {} bowee.bow(bower); } } } public static void main(String[] args) { final Friend alphonse = new Friend("Alphonse"); final Friend gaston = new Friend("Gaston"); new Thread(new BowLoop(alphonse, gaston)).start(); new Thread(new BowLoop(gaston, alphonse)).start(); } }