多线程

  1. Executor是启动任务的首选方案
package cn.limbo.homework;


import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Home0 {

   private Lock lock = new ReentrantLock();
   public static void main(String[] args) {
      ExecutorService service = Executors.newCachedThreadPool();
      for(int i=0;i<5;++i){
         service.execute(new LiftOff());
      }
      service.shutdown();
   }
}


class LiftOff implements Runnable {

   private int countDown = 10;
   private static int taskCount = 0;
   private final int id = taskCount++;

   public LiftOff() {
   }

   public LiftOff(int countDown) {
      this.countDown = countDown;
   }

   public String status() {
      return "#" + id + "(" + (countDown > 0 ? countDown : "LiftOff!") + ").";
   }

   @Override
   public void run() {
      while(countDown-- >0){
         System.out.println(status());
         Thread.yield();
      }
   }
}
  1. 使用TimeUnit.DAYS.sleep(1)来代替Thread.sleep()实现线程的休眠,因为TimeUnit允许指定sleep的延迟时间单元,这样可以更好的提供可阅读性
  2. 尽管JDK有10个优先级,但是它与多数操作系统映射得不是很好,唯一可移植的方法是当调整优先级的时候只使用MAX_PRIORITY,NORM_PRIORITY,MIN_PRIORITY
  3. 当最后一个非后台线程终止的时候,后台线程会“突然”终止。因此一旦main()退出,JVM就会立即关闭所有的后台线程,所以,当main函数结束的时候,后台线程的finally字句中的内容不一定会被执行
  4. Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService
  5. 注意,在使用并发的时候,将域设定成private是非常重要的,否则,synchronized关键字不能防止其他任务直接访问域
  6. Brian同步规则(何时需要同步):如果你正在写一个变量,它可能接下将被另一个线程读取,或者正在读取一个上一次已经被另一个线程写过的变量,那么你必须使用同步,并且读写线程必须使用相同的监视器锁同步
  7. 如果你的类中有超过一个方法在处理临界数据,那么你必须同步所有相关的方法
  8. 什么时候使用Lock对象:如果在使用synchronized时,某些事物失败了,那就会抛出一个异常,但是你没有机会去做任何清理的工作,系统就不能处于一个良好的状态了,有了Lock对象,你就可以使用finally字句奖系统维护在正确的状态。但是通常只有在解决特殊问题的时候才使用显式的Lock对象。
  9. 如果多个任务同时访问某个域,或者这些任务中至少有一个是写入任务,那么这个域就应该是volatile的,否则这个域就应该只能经过同步来访问。如果某个域的值受到其他域的值的限制,那么volatile无法工作
  10. Java递增操作不是原子性的,并且涉及一个读操作和一个写操作,所以即便是实在那么简单的操作中,也会产生线程问题
  11. Java中存在一些特殊的原子性变量,比如AtomicInteger,常规编程很少会派上用场但是在性能调优时,它们就会很有用处
  12. 在JDK5中引入了很多新的组件:
    • CountDownLatch:类似于程序计数器的东西,可以将一个程序分成n个互相独立的可以解决的任务但是这个东西只能呗触发一次,计数值不可被重置,使用场景:例如,主线程在做一项工作之前需要一系列的准备工作,只有这些准备工作都完成,主线程才能继续它的工作。这时候之前的准备工作就需要使用CountDownLatch来同步
    • CyclicBarrier:适用于这种情况:你希望创建一组任务,它们并行执行工作,然后在进行下一个步骤之前等待,直到所有的任务完成,CountDownLatch是指触发一次的事件,但是CyclicBarrier可以重用多次,两者用法上的不同:CountDownLatch 适用于一组线程和另一个主线程之间的工作协作。一个主线程等待一组工作线程的任务完毕才继续它的执行是使用 CountDownLatch 的主要场景;CyclicBarrier 用于一组或几组线程,比如一组线程需要在一个时间点上达成一致,例如同时开始一个工作。另外,CyclicBarrier 的循环特性和构造函数所接受的 Runnable 参数也是 CountDownLatch 所不具备的
    • DelayQueue:内部放置实现了Delay接口的,其中的对象只能在其到期的时候取走
    • PriorityBlockingQueue:这是一个很基础的优先级队列,它具有可阻塞的读取操作
    • Semaphore:信号量允许n个任务同时访问这个资源,你还可以将信号量看作是向外分发的使用资源的“许可证”
    • Exchanger:是在两个任务之间交换对象的栅栏。当任务进入这个栅栏的时候,它们各自拥有一个对象,当他们离开的时候,它们都拥有之前对象持有的对象。应用场景:一个任务正在创建对象,这些对象的生产代价高昂,而另外一个任务正在消费这些对象,比如生产者消费者模型​