猫头鹰的深夜翻译:核心JAVA并发(二)

前言

上一篇文章请参考猫头鹰的深夜翻译:核心JAVA并发(一)java

安全发布

发布一个对象是指该对象的引用对当前的域以外也可见(好比,从getter方法中获取一个引用)。要确保一个对象被安全的发布(即在初始化完成以后发布),可能须要使用同步。能够经过如下方法实现安全的发布:面试

  • 静态初始化方法。只有一个线程可以初始化静态变量由于该类的初始化是在一个排它锁之下完成的。
class StaticInitializer {
  // Publishing an immutable object without additional initialization
  public static final Year year = Year.of(2017); 
  public static final Set<String> keywords;
  // Using static initializer to construct a complex object
  static {
    // Creating mutable set
    Set<String> keywordsSet = new HashSet<>(); 
    // Initializing state
    keywordsSet.add("java");
    keywordsSet.add("concurrency");
    // Making set unmodifiable 
    keywords = Collections.unmodifiableSet(keywordsSet); 
  }
}
  • volatile关键字。读者线程老是能获取最近的值,由于写线程老是在后续的读取以前进行
class Volatile {
  private volatile String state;
  void setState(String state) {
    this.state = state;
  }
  String getState() {
    return state; 
  }
}
  • Atomics。好比,AtomicInteger将一个值存储为volatile类型,因此这里和volatile变量的规则相同
class Atomics {
  private final AtomicInteger state = new AtomicInteger();
  void initializeState(int state) {
    this.state.compareAndSet(0, state);
  }
  int getState() {
    return state.get();
  }
}
  • Final类型
class Final {
  private final String state;
  Final(String state) {
    this.state = state;
  }
  String getState() {
    return state;
  }
}
确保 this引用不会再初始化过程当中泄漏
class ThisEscapes {
 private final String name;
 ThisEscapes(String name) {
   Cache.putIntoCache(this);
   this.name = name;
 }
 String getName() { return name; }
}
class Cache {
 private static final Map<String, ThisEscapes> CACHE = new ConcurrentHashMap<>();
 static void putIntoCache(ThisEscapes thisEscapes) {
   // 'this' reference escaped before the object is fully constructed.
   CACHE.putIfAbsent(thisEscapes.getName(), thisEscapes);
 }
}
  • 正确同步的域
class Synchronization {
  private String state;
  synchronized String getState() {
    if (state == null)
      state = "Initial";
    return state;
  }
}

不变的对象

不变对象的一个很是棒的属性时,他们是现成安全的,全部无需在其上进行同步。是一个对象成为不变对象的要求为:segmentfault

  • 全部的字段为final类型
  • 全部字段能够是可变对象或不可变对象,但不能越过对象的范围,从而对象的状态在构建后不能更改。
  • this引用在初始化期间不会泄露
  • 该类为final类型,因此没法在子类中修改其行为

不变对象的例子:安全

// Marked as final - subclassing is forbidden
public final class Artist {
  // Immutable object, field is final
  private final String name; 
  // Collection of immutable objects, field is final
  private final List<Track> tracks; 
  public Artist(String name, List<Track> tracks) {
    this.name = name;
    // Defensive copy
    List<Track> copy = new ArrayList<>(tracks); 
    // Making mutable collection unmodifiable
    this.tracks = Collections.unmodifiableList(copy); 
    // 'this' is not passed to anywhere during construction
  }
  // Getters, equals, hashCode, toString
}
// Marked as final - subclassing is forbidden
public final class Track { 
  // Immutable object, field is final
  private final String title; 
  public Track(String title) {
    this.title = title;
  }
  // Getters, equals, hashCode, toString
}

Threads

java.lang.Thread类用来表示一个应用或是一个JVM现场。其代码一般在某个进程类的上下文中执行。(使用Thread#currentThread来获取当前线程自己)微信

线程的状态和相应的描述:并发

NEW: 还未启动
RUNNABLE: 启动并运行
BLOCKED: 在控制器上等待 - 该线程正视图获取锁并进入关键区域
WAITING: 等待另外一个线程执行特殊操做( notify/notifyAll, LockSupport#unpark)
TIMED_WAITING: 和WAITING相似,可是有超时设置
TERMINATED: 中止

Thread的方法和相应的描述:异步

start: 启动一个 Thread实例而且执行 run()方法
join: 阻塞直到线程完成
interrupt: 中断线程。若是该线程在响应终端的方法中阻塞着,则会在另外一个线程中抛出 InterruptedException,不然将会被设置为中断状态。
stop,suspend,resume,destroy: 这些方法都已经失效了。

如何处理InterruptedException

  • 若是可能的话,清理资源并终止线程的运行
  • 声明当前的方法会抛出InterruptedException
  • 若是一个方法并无被声明抛出InterruptedException,应该使用Thread.currentThread().interrupt()将中断标识回复为true,而后在该层抛出异常。将中断标识设为true很重要,它使得异常在能够在更高的层次上进行处。

意料以外的异常处理

Threads能够设置UncaughtExceptionHandler,它会在程序忽然中断的时候收到通知。函数

Thread thread = new Thread(runnable);
thread.setUncaughtExceptionHandler((failedThread, exception) -> {
  logger.error("Caught unexpected exception in thread '{}'.",
      failedThread.getName(), exception);
});
thread.start();

生命力

死锁

当多个线程在等待彼此释放持有的资源,从而造成了资源占有和等待的循环时,就产生了死锁。可能产生死锁的例子:this

class Account {
  private long amount;
  void plus(long amount) { this.amount += amount; }
  void minus(long amount) {
    if (this.amount < amount)
      throw new IllegalArgumentException();
    else
      this.amount -= amount;
  }
  static void transferWithDeadlock(long amount, Account first, Account second){
    synchronized (first) {
      synchronized (second) {
        first.minus(amount);
        second.plus(amount);
      }
    }
  }
}

死锁可能会这样产生:spa

  • 一个线程正视图从第一个帐户向第二个帐户转帐,而且已经得到了第一个帐户的锁
  • 与此同时,另外一个线程正视图从第二个线程像第一个线程转帐,而且已经得到了第二个帐户的锁

避免死锁的方法有:

  • 顺序加锁 - 老是按相同的顺序得到锁
class Account {
  private long id;
  private long amount;
  // Some methods are omitted
  static void transferWithLockOrdering(long amount, Account first, Account 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);
      }
    }
  }
}
  • 会超时的锁 - 不要无限的占有锁,应当释放全部的锁并从新尝试获取
class Account {
  private long amount;
  // Some methods are omitted
  static void transferWithTimeout(
      long amount, Account first, Account second, int retries, long timeoutMillis
  ) throws InterruptedException {
    for (int attempt = 0; attempt < retries; attempt++) {
      if (first.lock.tryLock(timeoutMillis, TimeUnit.MILLISECONDS))
      {
        try {
          if (second.lock.tryLock(timeoutMillis, TimeUnit.MILLISECONDS))
          {
            try {
              first.minus(amount);
              second.plus(amount);
            }
            finally {
              second.lock.unlock();
            }
          }
        }
        finally {
          first.lock.unlock();
        }
      }
    }
  }
}

活锁和线程饥饿

当全部的线程都在协商对资源的访问,或是预防死锁,从而致使没有一个线程真正在运行时,会产生活锁。当一个线程长时间占据一个锁致使别的线程没法进展时,会产生线程饥饿现象。

java.util.concurrent

线程池

线程池的核心接口是ExecutorServicejava.util.concurrent还提供了一个静态工厂Executors,它包含建立具备最多见配置的线程池的工厂方法。

工厂方法以下:

newSingleThreadExecutor: 返回一个只有一个线程的 ExecutorService
newFixedThreadPool: 返回一个具备固定数目线程的 ExecutorService
newCachedThreadPool: 返回一个可变大小的线程池 ExecutorService
newSingleThreadScheduledExecutor: 返回只有一个线程的 ScheduledExecutorService
newScheduledThreadPool: 返回包含一组线程的 ScheduledExecutorService
newWorkStealingPool: 返回一个带有并行级别的 ExecutorService
当调整线程池大小时,最好基于机器运行该应用时分配的逻辑内核数。能够经过调用 Runtime.getRuntime().availableProcessors()来得到该值。

线程池的实现类

clipboard.png

任务经过ExecutorService#submitExecutorService#invokeAllExecutorService#invokeAny提交,它们对不一样类型的任务有多种重载。

任务的功能性接口:

Runnable: 表示一个没有返回值的任务
Callable: 表示一个包含返回值的计算。它还声明能够抛出原始异常,因此不须要对检查异常进行包装

Future

Future是对全部的异步计算的抽象。它表示这些计算的结果,在某些时候可用。大多数的ExecutorService方法都是用Future做为返回值。它包含检查当前future的状态以及阻塞当前读取操做直至结果能够被读取等方法。

ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<String> future = executorService.submit(() -> "result");
try {
  String result = future.get(1L, TimeUnit.SECONDS);
  System.out.println("Result is '" + result + "'.");
} 
catch (InterruptedException e) {
  Thread.currentThread().interrupt();
  throw new RuntimeException(e);
} 
catch (ExecutionException e) {
  throw new RuntimeException(e.getCause());
} 
catch (TimeoutException e) {
  throw new RuntimeException(e);
}
assert future.isDone();

Locks

Lock
java.util.concurrent.locks包中有一个标准的Lock接口,ReentrantLock实现复制了synchronized关键字的功能,同时提供了一些额外的功能,好比获取当前锁状态的信息,非阻塞的tryBlock()方法,以及可中断的锁。下面是使用具体的ReentrantLock实例的例子:

class Counter {
  private final Lock lock = new ReentrantLock();
  private int value;
  int increment() {
    lock.lock();
    try {
      return ++value;
    } finally {
      lock.unlock();
    }
  }
}

ReadWriteLock
java.util.concurrent.locks包还包含了ReadWriteLock接口(以及ReentrantReadWriteLock实现),它被定义为一组读写锁,支持多个同步读者和单一写者。

class Statistic {
  private final ReadWriteLock lock = new ReentrantReadWriteLock();
  private int value;
  void increment() {
    lock.writeLock().lock();
    try {
      value++;
    } finally {
      lock.writeLock().unlock();
    }
  }
  int current() {
    lock.readLock().lock();
    try {
      return value;
    } finally {
      lock.readLock().unlock();
    }
  }
}

CountDownLatch
CountDownLatch经过一个数值初始化。线程会调用await()方法阻塞本身,等待计数值为0后再继续运行。其它的线程(或是同一个线程)调用countDown()来减小计数。一旦计数为0后,该倒计时器便不能够重复使用。用来在达到某个条件后,启动一组未知数量的线程

CompletableFuture
CompletableFuture是异步计算的一个抽象。不一样于Future,只能经过阻塞获取结果,该类鼓励注册回调函数来建立一组任务,从而在获得返回值或是出现异常时执行该任务。

clipboard.png
想要了解更多开发技术,面试教程以及互联网公司内推,欢迎关注个人微信公众号!将会不按期的发放福利哦~

相关文章
相关标签/搜索