读书笔记:Effective Java, Second Edition-1,10章

enum内部实现继承了Enum,所以它不能继承别的类,另外,enum也不能作为别的类的父类。

一 对象构造销毁

Item 1用静态方法代替构造器

优点:

不像构造器那样,静态工厂方法拥有名字,可以包含某些意义。

不像构造器那样,每次调用都要创建对象,静态方法可返回可不返回。构造器你要不返回那只有抛个异常了。

不像构造器那样,静态工厂方法可以返回类型的某个子类型。

静态工厂方法可以减少冗长的创建参数化类型时的代码【扯淡,现在还不支持类型推断】

缺点:

一个类在仅提供静态工厂方法,同时没有公共或受保护的构造器时,此类是没法被子类化的。不能很好的使用集成的便利

静态工厂方法和其他的静态方法没什么差别,用户很迷惑,到底是用构造器还是工厂?

Item 2当构造一个对象需要很多的参数时,建议使用builder方式

最终达到这样的效果:

NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).sodium(35).carbohydrate(27).build();

链式调用以及最后的构建方法build产生对象。

Item 3强制单例类的构造器为不可见,或者使用enum来做单例。

Item 4像Utils这样的类也将他的构造器设为不可见,你懂的。

Item 5不要创建不必要的对象

注意节约资源,特别注意循环中的对象创建,注意封箱拆箱可能造成的问题

Item 6内存泄露

public Object pop() {
if (size == 0)
throw new EmptyStackException();
return elements[--size]; //这里应该Object result = elements[--size];elements[size] = null;
}

如果elements不消除的话,虽然减少了一个元素,它是他还在内存中

……

Item 7: 避免析构

二 对象通用方法

十 并发

Item 66: 同步访问多线程可修改数据

如果要同步,读写都需要,否则没什么用处

这样的代码因为没有考虑同步,造成

// Broken! - How long would you expect this program to run?
public class StopThread {
private static boolean stopRequested;
public static void main(String[] args)
throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() {
public void run() {
int i = 0;
while (!stopRequested) //编译器会优化为while (true),因为stopRequested没有同步
i++;
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}

Item 67: Avoid excessive synchronization

同步块内尽量少做事情,特别是不要包含外部代码(及你不知道它做什么的代码,也许这个代码正要使用你的锁对象或其他)

为性能以及设计考虑,尽量不要对你的类进行同步设计,而应该非同步设计,文档告知调用方来让调用方维护同步,例如1.5新加的stringbuilder就是基于这种考虑而取代stringbuffer。例外的情况是静态字段的修改,则要保证同步。因为调用方可以想办法保证一个对象同步,却不能保证一个静态字段的同步。

tip:

CopyOnWriteArrayList这个类是在写的时候其实写的是拷贝的,实际的列表项没有改变,当然“很贵”,我应该不会用它。

多核心同步的花费:

In a multicore world, the real cost of excessive synchronization is not the CPU time spent obtaining locks; it is the lost opportunities for parallelism and the delays imposed by the need to ensure that every core has a consistent view of memory

Item 68: Prefer executors and tasks to threads

1.5开始包含的Executor Framework,queue和异步

创建ExecutorService executor = Executors.newSingleThreadExecutor();
执行executor.execute(runnable);
关闭executor.shutdown();

立即关闭shutdownNow();

但是不要被关闭着两个方法迷惑了,shutdownNow它只是intterupt方法而已,

它试图终止线程的方法是通过调用Thread.interrupt()方法来实现的,但是大家知道,这种方法的作用有限,如果线程中没有sleep 、wait、Condition、定时锁等应用, interrupt()方法是无法中断当前的线程的。所以,ShutdownNow()并不代表线程池就一定立即就能退出,它可能必须要等待所有正在执行的任务都执行完成了才能退出。

ScheduledThreadPoolExecutor替代java.util.Timer

详见 http://www.iteye.com/topic/366591

总之原来的thread,timer之流在使用时都要考量一番。

Item 69: Prefer concurrency utilities to wait and notify

java.util.concurrent报提供了三个方面的并发"龙套"模块

Executor Framework,

concurrent collections; 并发性能比普通高

synchronizers:线程间协作,等待等CountDownLatch,Semaphore,CyclicBarrier

从名字可以看出,CountDownLatch是一个倒数计数的锁,

当倒数到0时触发事件,也就是开锁,其他人就可以进入了。
在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发事件,以便进行后面的操作。

CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。

下面的例子简单的说明了CountDownLatch的使用方法,模拟了100米赛跑,10名选手已经准备就绪,只等裁判一声令下。当所有人都到达终点时,比赛结束。

package com.eyesmore.concurrent;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchDemo {
    private static final int PLAY_AMOUNT = 10;
    public static void main(String[] args) {
        /*
         * 比赛开始:只要裁判说开始,那么所有跑步选手就可以开始跑了
         * */
        CountDownLatch begin = new CountDownLatch(1);
        /*
         * 每个队员跑到末尾时,则报告一个到达,所有人员都到达时,则比赛结束
         * */
        CountDownLatch end = new CountDownLatch(PLAY_AMOUNT);
        Player[] plays = new Player[PLAY_AMOUNT];
        for(int i = 0;i<PLAY_AMOUNT;i++) {
            plays[i] = new Player(i+1,begin,end);
        }
        ExecutorService exe = Executors.newFixedThreadPool(PLAY_AMOUNT);
        for(Player p : plays) {//各就各位
            exe.execute(p);
        }
        System.out.println("比赛开始");
        begin.countDown();//宣布开始
        try {
            end.await();//等待结束
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println("比赛结束");
        }
        //注意:此时main线程已经要结束了,但是exe线程如果不关闭是不会结束的
        exe.shutdown();
    }
}
class Player implements Runnable {
    private int id;
    private CountDownLatch begin;
    private CountDownLatch end;
    public Player(int id, CountDownLatch begin, CountDownLatch end) {
        super();
        this.id = id;
        this.begin = begin;
        this.end = end;
    }
    public void run() {
        try {
            begin.await();//必须等到裁判countdown到0的时候才开始
            Thread.sleep((long)(Math.random()*100));//模拟跑步需要的时间
            System.out.println("Play "+id+" has arrived. ");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            end.countDown();//向评委报告跑到终点了
        }
    }
}

在实际应用中,有时候需要多个线程同时工作以完成同一件事情,而且在完成过程中,往往会等待其他线程都完成某一阶段后再执行,等所有线程都到达某一个阶段后再统一执行。
比如有几个旅行团需要途经深圳、广州、韶关、长沙最后到达武汉。旅行团中有自驾游的,有徒步的,有乘坐旅游大巴的;这些旅行团同时出发,并且每到一个目的地,都要等待其他旅行团到达此地后再同时出发,直到都到达终点站武汉。

这时候CyclicBarrier就可以派上用场。CyclicBarrier最重要的属性就是参与者个数,另外最要方法是await()。当所有线程都调用了await()后,就表示这些线程都可以继续执行,否则就会等待。

package examples.ch06.example01;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestCyclicBarrier {
    // 徒步需要的时间: Shenzhen, Guangzhou, Shaoguan, Changsha, Wuhan
    private static int[] timeWalk = { 5, 8, 15, 15, 10 };
    // 自驾游
    private static int[] timeSelf = { 1, 3, 4, 4, 5 };
    // 旅游大巴
    private static int[] timeBus = { 2, 4, 6, 6, 7 };
    static String now() {
        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        return sdf.format(new Date()) + ": ";
    }
    static class Tour implements Runnable {
        private int[] times;
        private CyclicBarrier barrier;
        private String tourName;
        public Tour(CyclicBarrier barrier, String tourName, int[] times) {
            this.times = times;
            this.tourName = tourName;
            this.barrier = barrier;
        }
        public void run() {
            try {
                Thread.sleep(times[0] * 1000);
                System.out.println(now() + tourName + " Reached Shenzhen");
                barrier.await();
                Thread.sleep(times[1] * 1000);
                System.out.println(now() + tourName + " Reached Guangzhou");
                barrier.await();
                Thread.sleep(times[2] * 1000);
                System.out.println(now() + tourName + " Reached Shaoguan");
                barrier.await();
                Thread.sleep(times[3] * 1000);
                System.out.println(now() + tourName + " Reached Changsha");
                barrier.await();
                Thread.sleep(times[4] * 1000);
                System.out.println(now() + tourName + " Reached Wuhan");
                barrier.await();
            } catch (InterruptedException e) {
            } catch (BrokenBarrierException e) {
            }
        }
    }
    public static void main(String[] args) {
        // 三个旅行团
        CyclicBarrier barrier = new CyclicBarrier(3);
        ExecutorService exec = Executors.newFixedThreadPool(3);
        exec.submit(new Tour(barrier, "WalkTour", timeWalk));
        exec.submit(new Tour(barrier, "SelfTour", timeSelf));
        exec.submit(new Tour(barrier, "BusTour", timeBus));
        exec.shutdown();
    }
}

Semaphore 信号量,就是一个允许实现设置好的令牌。也许有1个,也许有10个或更多。
谁拿到令牌(acquire)就可以去执行了,如果没有令牌则需要等待。
执行完毕,一定要归还(release)令牌,否则令牌会被很快用光,别的线程就无法获得令牌而执行下去了。

请仔细体会里面关于仓库的处理,

1 是如何保证入库时,如果仓库满就等待,

2 出库时,如果仓库无货就等待的。

3 以及对仓库只有10个库位的处理。

4 对同步问题的处理。

import java.util.concurrent.Semaphore;
/**
* 老紫竹JAVA提高教程-信号量(Semaphore)的使用。<br>
* 生产者和消费者的例子,库存的管理。
*
* @author 老紫竹(java2000.net,laozizhu.com)
*/
public class TestSemaphore {
  public static void main(String[] args) {
    // 启动线程
    for (int i = 0; i <= 3; i++) {
      // 生产者
      new Thread(new Producer()).start();
      // 消费者
      new Thread(new Consumer()).start();
    }
  }
  // 仓库
  static Warehouse buffer = new Warehouse();
  // 生产者,负责增加
  static class Producer implements Runnable {
    static int num = 1;
    @Override
    public void run() {
      int n = num++;
      while (true) {
        try {
          buffer.put(n);
          System.out.println(">" + n);
          // 速度较快。休息10毫秒
          Thread.sleep(10);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
  }
  // 消费者,负责减少
  static class Consumer implements Runnable {
    @Override
    public void run() {
      while (true) {
        try {
          System.out.println("<" + buffer.take());
          // 速度较慢,休息1000毫秒
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
  }
  /**
   * 仓库
   *
   * @author 老紫竹(laozizhu.com)
   */
  static class Warehouse {
    // 非满锁
    final Semaphore notFull = new Semaphore(10);
    // 非空锁
    final Semaphore notEmpty = new Semaphore(0);
    // 核心锁
    final Semaphore mutex = new Semaphore(1);
    // 库存容量
    final Object[] items = new Object[10];
    int putptr, takeptr, count;
    /**
     * 把商品放入仓库.<br>
     *
     * @param x
     * @throws InterruptedException
     */
    public void put(Object x) throws InterruptedException {
      // 保证非满
      notFull.acquire();
      // 保证不冲突
      mutex.acquire();
      try {
        // 增加库存
        items[putptr] = x;
        if (++putptr == items.length)
          putptr = 0;
        ++count;
      } finally {
        // 退出核心区
        mutex.release();
        // 增加非空信号量,允许获取商品
        notEmpty.release();
      }
    }
    /**
     * 从仓库获取商品
     *
     * @return
     * @throws InterruptedException
     */
    public Object take() throws InterruptedException {
      // 保证非空
      notEmpty.acquire();
      // 核心区
      mutex.acquire();
      try {
        // 减少库存
        Object x = items[takeptr];
        if (++takeptr == items.length)
          takeptr = 0;
        --count;
        return x;
      } finally {
        // 退出核心区
        mutex.release();
        // 增加非满的信号量,允许加入商品
        notFull.release();
      }
    }
  }
}

几乎没必要使用wait和notify,notifyAll,他们像是同步的汇编语言,而synchronizers则是提供了上层架构的高级语言。

如果确实需要使用,则牢记同步块中的循环中调用例如:

synchronized (obj) { while (<condition does not hold>) obj.wait(); ... // Perform action appropriate to condition }//这是java doc里面的的范例。

为什么要这样写:

Always use the wait loop idiom to invoke the wait method; never invoke it outside of a loop. The loop serves to test the condition before and after waiting. Testing the condition before waiting and skipping the wait if the condition already holds are necessary to ensure liveness. If the condition already holds and the notify (or notifyAll) method has already been invoked before a thread waits, there is no guarantee that the thread will ever wake from the wait. Testing the condition after waiting and waiting again if the condition does not
hold are necessary to ensure safety. If the thread proceeds with the action when the condition does not hold, it can destroy the invariant guarded by the lock. There are several reasons a thread might wake up when the condition does not hold:

• Another thread could have obtained the lock and changed the guarded state between
the time a thread invoked notify and the time the waiting thread woke.
• Another thread could have invoked notify accidentally or maliciously when
the condition did not hold. Classes expose themselves to this sort of mischief
by waiting on publicly accessible objects. Any wait contained in a synchronized
method of a publicly accessible object is susceptible to this problem.
• The notifying thread could be overly “generous” in waking waiting threads.
For example, the notifying thread might invoke notifyAll even if only some
of the waiting threads have their condition satisfied.
• The waiting thread could (rarely) wake up in the absence of a notify. This is
known as a spurious wakeup [Posix, 11.4.3.6.1; JavaSE6].

并且notifyAll要比notify要好。

note:

计时用 System.nanoTime()

Item 70: Document thread safety 为你的同步的方法写好文档、注释

Item 71: Use lazy initialization judiciously 主要讨论了同步情况下的懒加载问题,一般还是建议不需要迟初始化,以免造成并发情况下多次初始化的问题。

Item 72: Don’t depend on the thread scheduler

不要使用Thread.yield,可用sleep代替它。不要使用优先级,这个功能在各虚拟机上表现不一样。yield和优先级都只是暗示,并不意味着虚拟机会执行它们的功能。

Item 73: Avoid thread groups 请用前面提到的线程池而不要使用线程组,线程组你可以忘记他们了,他们是不成功的实现。


Total views.

© 2013 - 2024. All rights reserved.

Powered by Hydejack v6.6.1