快速鸟瞰并发编程,- 呕心沥血整理的架构技术【2】,分层展示的架构图
确保此引用在构造期间不逃逸。
this 引用逃逸("this"escape)是指对象还没有构造完成,它的 this 引用就被发布出去了。这是危及到线程安全的,因为其他线程有可能通过这个逸出的引用访问到“初始化了一半”的对象(partially-constructed object)。这样就会出现某些线程中看到该对象的状态是没初始化完的状态,而在另外一些线程看到的却是已经初始化完的状态,这种不一致性是不确定的,程序也会因此而产生一些无法预知的并发错误。在说明并发编程中如何避免 this 引用逸出之前
class JamesThisEscapes {private final String name;ThisEscapes(String name) {JamesCache.putIntoCache(this);this.name = name;}String getName() {return name;}}class JamesCache {private static final Map<String, ThisEscapes> CACHE = new ConcurrentHashMap<>();static void putIntoCache(JamesThisEscapes thisEscapes) {//“this”引用在对象完全构造之前逃逸 CACHE.putIfAbsent(thisEscapes.getName(), thisEscapes);}}
正确同步成员变量。
class JamesSynchronization {private String state;synchronized String getState() {if (state == null)state = "Initial";return state;}}
第 6 节 不可变的对象
不可变对象具备执行安全的特性。此外,相较于可变对象,不可变对象通常也较合理,易于了解,而且提供较高的安全性。不可变对象的一个重要特性是它们都是线程安全的,因此不需要同步。当然对象不可变的是有如下要求滴:
所有变量都是?
final.
所有变量必须是可变对象或不可变对象。
this
?在构造方法执行期间引用不会逃脱。该类是 final,因此无法在子类中覆盖此行为。
不可变对象的示例:
// 声明为 final 类 public final class JamesArtist {// 不可变对象, 字段为 finalprivate final String name;//用于保存不可变对象, final 类型 private final List<JamesTrack> tracks;public JamesArtist(String name, List<JamesTrack> tracks) {this.name = name;//防止拷贝 List<JamesTrack> copy = new ArrayList<>(tracks);//标记为不可更改 this.tracks = Collections.unmodifiableList(copy);// “this”在构造期间不会传递到任何地方。}}// 同上声明为 final 类 public final class JamesTrack {// 不可变对象, 字段为 finalprivate final String title;public JamesTrack(String title) {this.title = title;}}
第 7 节 线程 Thread 类
java.lang.Thread
类用于表示应用程序或 JVM 线程。代码总是在某些 Thread 类的上下文中执行(用于 Thread#currentThread()
获取自己的 Thread)。
线程状态如下
线程协调方法如下
怎么处理 InterruptedException 异常?
在重新抛出 InterruptedException 之前执行特定于任务的清理工作。
声明当前方法抛出?
InterruptedException.
如果未声明某个方法抛出?
InterruptedException
,则应通过调用将中断的标志恢复为 true,?Thread.currentThread().interrupt()
并且应该抛出一个更合适的异常。将标志设置为 true 非常重要,以便有机会处理更高级别的中断。
不可预知的异常处理
线程可以指定 UncaughtExceptionHandler
将接收任何导致线程突然终止的未捕获异常的通知。
Thread thread = new Thread(runnable);thread.setUncaughtExceptionHandler((failedThread,exception)->{logger.error("Caught unexpected exception in thread‘{}’.", failedThread.getName(), exception);});thread.start();
第 8 节 线程的活跃度
死锁
当存在多个线程时会发生死锁,每个线程等待另一个线程持有的资源,从而形成资源循环和获取线程。
潜在的死锁示例:
class JamesAccount {private long amount;void plus(long amount) {this.amount += amount;}void minus(long amount) {if (this.amount < amount)throw new IllegalArgumentException(); elsethis.amount -= amount;}static void transferWithDeadlock(long amount, JamesAccount first, JamesAccount second) {synchronized (first) {synchronized (second) {first.minus(amount);second.plus(amount);}}}}
如果同时发生死锁:
一个线程正在尝试从第一个帐户转移到第二个帐户,并且已经获得了第一个帐户的锁。
另一个线程正在尝试从第二个帐户转移到第一个帐户,并且已经获得了第二个帐户的锁。
避免死锁的技巧:
锁定顺序 - 始终以相同的顺序获取锁。
class JamesAccount {private long id;private long amount;// 此处省略了一些方法 static void transferWithLockOrdering(long amount, JamesAccount first, JamesAccount second) {Boolean lockOnFirstAccountFirst = first.id < second.id;Account firstLock = lockOnFirstAccountFirst ? first : second;Account secondLock = lockOnFirstAccountFirst ? second : first;synchronized (firstLock) {synchronized (secondLock) {first.minus(amount);second.plus(amount);
}}}}
评论