Java并发编程笔记——技术点汇总

目录

· 线程安全

· 线程安全的实现方法

    · 互斥同步

    · 非阻塞同步

    · 无同步

· volatile关键字

· 线程间通信

    · Object.wait()方法

    · Object.notify()方法

    · 编写线程间通信代码的套路

    · 面试题:子线程、主线程交替循环

    · 生产者-消费者问题

    · 哲学家进餐问题

    · 读者-写者问题

· 线程内共享

· 定时器

· JDK5新功能

    · 线程池

    · Callable和Future

    · ReentrantLock

    · ReadWriteLock

    · Condition

    · Semaphore

    · CyclicBarrier

    · CountDownLatch

    · Exchanger

    · BlockingQueue

    · 并发集合

· 系统峰值评估

    · 峰值参数

    · 评估方法


线程安全

1. 线程安全:当多个线程访问某一个类(对象或方法)时,这个类(对象或方法)始终能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。

2. 线程不安全的示例。期望结果是100000,实际却不是,并且每次执行结果可能不同。

 1 import java.util.ArrayList;
 2 import java.util.List;
 3 
 4 public class Test {
 5 
 6     public static void main(String[] args) throws Exception {
 7         MyNumber myNumber = new MyNumber();
 8         List<MyThread> myThreads = new ArrayList<>();
 9         for (int index = 0; index < 100; index++) {
10             MyThread myThread = new MyThread(myNumber);
11             myThreads.add(myThread);
12             myThread.start();
13         }
14         for (MyThread myThread : myThreads) {
15             myThread.join();
16         }
17         System.out.println(myNumber.value());
18     }
19 
20 }
21 
22 class MyNumber {
23     
24     private int n = 0;
25     
26     public int value() {
27         return n;
28     }
29     
30     public void increate() {
31         n = n + 1;
32     }
33     
34 }
35 
36 class MyThread extends Thread {
37     
38     private MyNumber myNumber;
39     
40     public MyThread(MyNumber myNumber) {
41         this.myNumber = myNumber;
42     }
43     
44     @Override
45     public void run() {
46         try {
47             sleep(3000);
48         } catch (InterruptedException e) {
49             e.printStackTrace();
50         }
51         
52         for (int index = 0; index < 1000; index++) {
53             myNumber.increate();
54         }
55     }
56     
57 }

线程安全的实现方法

互斥同步

1. 最基本互斥同步手段是synchronized关键字。

2. 例如,上一个线程不安全示例的解决方法如下,两种方法等价,都是对当前对象加锁。

1 public synchronized void increate() {
2     n = n + 1;
3 }
1 public void increate() {
2     synchronized (this) {
3         n = n + 1;
4     }
5 }

3. synchronized关键字是一个重量级操作,因为Java线程是映射到操作系统原生线程上的,如果要阻塞或唤醒线程,都需要操作系统完成,那么线程需要从用户态转换到核心态,状态转换会消耗很多的CPU时间。

4. 额外注意,“static synchronized”方法与“synchronized (MyClass.class) {…}”等价,都是对类的字节对象加锁。

非阻塞同步

1. 互斥同步属于悲观并发策略,非阻塞同步属于乐观并发策略。

2. 即先行操作,如果没有其他线程争用共享数据,那么算操作成功;如果共享数据有争用,产生冲突,那么再采取其他补偿措施(最常见的就是不断重试,直到成功为止)。由于无须挂起线程,所以是非阻塞的。

3. java.util.concurrent.atomic.*包下的原子类就是这个原理,核心操作是CAS(Compare and set,利用x86 CPU的CMPXCHG原子指令实现),具体由sun.misc.Unsafe实现。

4. java.util.concurrent.atomic.*包(JDK5以后)下的原子类分4组(常用已高亮):

    a) AtomicBoolean、AtomicInteger、AtomicLong,线程安全的基本类型的原子性操作。

    b) AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray,线程安全的数组类型的原子性操作,操作的不是整个数组,而是数组中的单个元素。

    c) AtomicLongFieldUpdater、AtomicIntegerFieldUpdater、AtomicReferenceFieldUpdater,基于反射原理对象中的基本类型(长整型、整型和引用类型)进行线程安全的操作。

    d) AtomicReference、AtomicMarkableReference、AtomicStampedReference,线程安全的引用类型及防止ABA问题的引用类型的原子操作。

5. 线程不安全示例的不阻塞解决方法如下。

 1 class MyNumber {
 2     
 3     private AtomicInteger n = new AtomicInteger(0);
 4     
 5     public int value() {
 6         return n.get();
 7     }
 8     
 9     public void increate() {
10         n.addAndGet(1);
11     }
12     
13 }

5. 参考资料:

    a) http://www.cnblogs.com/nullzx/p/4967931.html

    b) http://zl198751.iteye.com/blog/1848575

    c) http://www.cnblogs.com/Mainz/p/3546347.html

无同步

如果本来就不涉及共享数据,那么就无须任何同步措施,代码天生就是线程安全的。

volatile关键字

1. volatile关键字的主要作用是使变量在多个线程间可见。

2. 每个线程都会有一块工作内存区,其中存放着所有线程共享的主内存中的变量值的拷贝。当线程执行时,它在自己的工作内存区中操作这些变量。为了存取共享变量,线程通常先获取锁定并去清除它的工作内存区,把这些共享变量从所有线程的工作内存区中正确的装入其中,当线程解锁时保证该工作内存区中的值写回到共享内存中。volatile的作用就是强制线程到主内存(共享内存)去读取变量,而不去线程工作内存区读取。

 1 public class Test {
 2 
 3     public static void main(String[] args) {
 4         MyThread myThread = new MyThread();
 5         myThread.start();
 6         try {
 7             Thread.sleep(3000);
 8         } catch (InterruptedException e) {
 9             e.printStackTrace();
10         }
11         myThread.run = false;
12         System.out.println(myThread.run);
13     }
14 
15 }
16 
17 class MyThread extends Thread {
18     
19     public volatile boolean run = true;
20     
21     @Override
22     public void run() {
23         while (run) {
24             // running
25         }
26         System.out.println(Thread.currentThread().getName() + " stoped.");
27     }
28     
29 }

3. volatile虽然保证了多个线程间的可见性,但不具备同步性。只能算轻量级的synchronized,性能要比synchronized强很多,不会造成阻塞。上一个线程不安全示例用如下方式不能保证结果正确。

 1 class MyNumber {
 2     
 3     private volatile int n = 0;// 错误代码
 4     
 5     public int value() {
 6         return n;
 7     }
 8     
 9     public void increate() {
10         n = n + 1;
11     }
12     
13 }

线程间通信

Object.wait()方法

1. 使执行instance.wait()方法的当前线程暂停,直至调用该对象instance.notify()或instance.notifyAll()方法唤醒该线程。

2. 当前线程必须先对该对象instance加锁(即synchronized)才可调用instance.wait()方法,否则抛出异常IllegalMonitorStateException。

3. 可能存在中断和虚唤醒,所以一般在循环中执行instance.wait()方法。

4. 综上三点所述,套路如下。

1 synchronized (instance) {
2     while (判断是否可占用资源) {
3         instance.wait();
4     }
5 }

5. 注意Object.wait()方法与Thread.sleep()方法的区别:wait()方法暂停时会释放synchronized的资源,而sleep()方法不会。

Object.notify()方法

1. 唤醒执行了instance.wait()方法的线程。如果存在多个这样的线程,则任意唤醒一个。

2. 当前线程必须先对该对象instance加锁(即synchronized)才可调用instance.notify()方法,否则抛出异常IllegalMonitorStateException。

3. 综上两点所述,套路如下。

1 synchronized (instance) {
2     instance.notify();
3 }

4. Object.notifyAll()方法与Object.notify()方法类似,区别是如果存在多个等待的线程,则全部唤醒。

编写线程间通信代码的套路

由于执行instance.wait()和instance.notify()方法都必须先synchronized (instance),所以这两个方法应写在一个类中。

1 public class ExclusiveResource {    // 互斥资源
2     public synchronized void holdAndUse() {
3         while (<check resource>) {    // 检查是否可占用资源,否则等待
4             wait();
5         }
6         // 执行占用资源后的代码
7         notify();
8     }
9 }

面试题:子线程、主线程交替循环

子线程循环10次,接着主线程循环100次,接着又回到子线程循环10次,接着再回到主线程有循环100次,如此循环50次。

 1 public class Test {
 2 
 3     public static void main(String[] args) {
 4         System.out.println("--------");
 5         
 6         Looper looper = new Looper();
 7         SubThread subThread = new SubThread(looper);
 8         subThread.start();
 9         looper.main();
10     }
11 
12 }
13 
14 class SubThread extends Thread {
15     
16     private Looper looper;
17     
18     public SubThread(Looper looper) {
19         this.looper = looper;
20     }
21     
22     @Override
23     public void run() {
24         looper.sub();
25     }
26     
27 }
28 
29 class Looper {
30     
31     private boolean runSub = true;
32     
33     public void sub() {
34         for (int i = 0; i < 50; i++) {
35             synchronized (this) {
36                 while (!runSub) {
37                     try {
38                         wait();
39                     } catch (InterruptedException e) {
40                         e.printStackTrace();
41                     }
42                 }
43                 for (int j = 0; j < 10; j++) {
44                     System.out.println("子线程第" + i + "次循环" + j + "。");
45                 }
46                 runSub = false;
47                 notify();
48             }
49         }
50     }
51     
52     public void main() {
53         for (int i = 0; i < 50; i++) {
54             synchronized (this) {
55                 while (runSub) {
56                     try {
57                         wait();
58                     } catch (InterruptedException e) {
59                         e.printStackTrace();
60                     }
61                 }
62                 for (int j = 0; j < 100; j++) {
63                     System.out.println("主线程第" + i + "次循环" + j + "。");
64                 }
65                 runSub = true;
66                 notify();
67             }
68         }
69     }
70     
71 }

生产者-消费者问题

一组生产者进程和一组消费者进程共享一个初始为空、大小为 n 的缓冲区,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中取出消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或者一个消费者从中取出消息。

  1 import java.util.Random;
  2 import java.util.concurrent.atomic.AtomicInteger;
  3 
  4 public class Test {
  5 
  6     public static void main(String[] args) {
  7         Container container = new Container();
  8         new Producer(container).start();
  9         new Producer(container).start();
 10         new Producer(container).start();
 11         new Consumer(container).start();
 12         new Consumer(container).start();
 13     }
 14 
 15 }
 16 
 17 class Product {
 18     
 19     private static final AtomicInteger COUNT = new AtomicInteger(0);
 20     
 21     private int id;
 22     
 23     public Product() {
 24         id = COUNT.getAndIncrement();
 25     }
 26     
 27     @Override
 28     public String toString() {
 29         return "Product-" + id;
 30     }
 31     
 32 }
 33 
 34 class Container {
 35     
 36     private final static int CAPACITY = 10;
 37     
 38     private Product[] products = new Product[CAPACITY];
 39     
 40     private int size;
 41     
 42     public synchronized void push(Product product) {
 43         while (size >= CAPACITY) {
 44             try {
 45                 wait();
 46             } catch (InterruptedException e) {
 47                 e.printStackTrace();
 48             }
 49         }
 50         products[size] = product;
 51         size++;
 52         notify();
 53     }
 54     
 55     public synchronized Product pop() {
 56         while (size <= 0) {
 57             try {
 58                 wait();
 59             } catch (InterruptedException e) {
 60                 e.printStackTrace();
 61             }
 62         }
 63         size--;
 64         Product product = products[size];
 65         notify();
 66         return product;
 67     }
 68     
 69 }
 70 
 71 class Producer extends Thread {
 72     
 73     private static int count;
 74     
 75     private Container container;
 76     
 77     public Producer(Container container) {
 78         super("Producer-" + count++);
 79         this.container = container;
 80     }
 81     
 82     @Override
 83     public void run() {
 84         while (true) {
 85             Product product = new Product();
 86             System.out.println(getName() + " produced " + product);
 87             container.push(product);
 88             
 89             try {
 90                 Thread.sleep(new Random().nextInt(500));
 91             } catch (InterruptedException e) {
 92                 e.printStackTrace();
 93             }
 94         }
 95     }
 96     
 97 }
 98 
 99 class Consumer extends Thread {
100     
101     private static int count;
102     
103     private Container container;
104     
105     public Consumer(Container container) {
106         super("Consumer-" + count++);
107         this.container = container;
108     }
109     
110     @Override
111     public void run() {
112         while (true) {
113             Product product = container.pop();
114             System.out.println(getName() + " consumed " + product);
115             
116             try {
117                 Thread.sleep(new Random().nextInt(500));
118             } catch (InterruptedException e) {
119                 e.printStackTrace();
120             }
121         }
122     }
123     
124 }

哲学家进餐问题

一张圆桌上坐着 5 名哲学家,桌子上每两个哲学家之间摆了一根筷子,桌子的中间是一碗米饭。哲学家们倾注毕生精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿的时候,才试图拿起左、右两根筷子(一根一根拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿到了两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。

  1 import java.util.Random;
  2 
  3 public class Test {
  4 
  5     public static void main(String[] args) {
  6         Table table = new Table();
  7         new Philosopher(table).start();
  8         new Philosopher(table).start();
  9         new Philosopher(table).start();
 10         new Philosopher(table).start();
 11         new Philosopher(table).start();
 12     }
 13 
 14 }
 15 
 16 class Chopstick {
 17     
 18     private static int count;
 19     
 20     private int id;
 21     
 22     private boolean isTakenUp;
 23     
 24     public Chopstick() {
 25         id = count++;
 26     }
 27     
 28     public boolean isTakenUp() {
 29         return isTakenUp;
 30     }
 31 
 32     public void setTakenUp(boolean isTakenUp) {
 33         this.isTakenUp = isTakenUp;
 34     }
 35 
 36     @Override
 37     public String toString() {
 38         return "Chopstick-" + id;
 39     }
 40     
 41 }
 42 
 43 class Table {
 44     
 45     private static final int CAPACITY = 5;
 46     
 47     private Chopstick[] chopsticks = new Chopstick[CAPACITY];
 48     
 49     public Table() {
 50         for (int index = 0; index < chopsticks.length; index++) {
 51             chopsticks[index] = new Chopstick();
 52         }
 53     }
 54     
 55     public synchronized void takeUp(int leftChopstickIndex, int rightChopstickIndex) {
 56         Chopstick leftChopstick = chopsticks[leftChopstickIndex];
 57         Chopstick rightChopstick = chopsticks[rightChopstickIndex];
 58         while (leftChopstick.isTakenUp() || rightChopstick.isTakenUp()) {
 59             try {
 60                 wait();
 61             } catch (InterruptedException e) {
 62                 e.printStackTrace();
 63             }
 64         }
 65         leftChopstick.setTakenUp(true);
 66         System.out.println(leftChopstick + " has been taken up.");
 67         rightChopstick.setTakenUp(true);
 68         System.out.println(rightChopstick + " has been taken up.");
 69         notifyAll();
 70     }
 71     
 72     public synchronized void putDown(int leftChopstickIndex, int rightChopstickIndex) {
 73         Chopstick leftChopstick = chopsticks[leftChopstickIndex];
 74         Chopstick rightChopstick = chopsticks[rightChopstickIndex];
 75         leftChopstick.setTakenUp(false);
 76         System.out.println(leftChopstick + " has been put down.");
 77         rightChopstick.setTakenUp(false);
 78         System.out.println(rightChopstick + " has been put down.");
 79         notifyAll();
 80     }
 81     
 82 }
 83 
 84 class Philosopher extends Thread {
 85     
 86     private static final int MAX_COUNT = 5;
 87     
 88     private static int count;
 89     
 90     private int id;
 91     
 92     private Table table;
 93     
 94     public Philosopher(Table table) {
 95         if (count >= MAX_COUNT) {
 96             throw new RuntimeException("Cannot create Philosopher instance more than " + MAX_COUNT + ".");
 97         }
 98         id = count++;
 99         setName("Philosopher-" + id);
100         this.table = table;
101     }
102 
103     @Override
104     public void run() {
105         final int leftChopstickIndex = id;
106         final int rightChopstickIndex = (id + 1) % MAX_COUNT;
107         while (true) {
108             table.takeUp(leftChopstickIndex, rightChopstickIndex);
109             System.out.println(getName() + " is eating.");
110             
111             try {
112                 Thread.sleep(new Random().nextInt(500));
113             } catch (InterruptedException e) {
114                 e.printStackTrace();
115             }
116             
117             System.out.println(getName() + " has eaten.");
118             table.putDown(leftChopstickIndex, rightChopstickIndex);
119             
120             try {
121                 Thread.sleep(new Random().nextInt(500));
122             } catch (InterruptedException e) {
123                 e.printStackTrace();
124             }
125         }
126     }
127     
128 }

读者-写者问题

有读者和写者两组并发线程,共享一个文件,当两个或以上的读线程同时访问共享数据时不会产生副作用,但若某个写线程和其他线程(读线程或写线程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:

1. 允许多个读者可以同时对文件执行读操作;

2. 只允许一个写者往文件中写信息;

3. 任一写者在完成写操作之前不允许其他读者或写者工作;

4. 写者执行写操作前,应让已有的读者和写者全部退出。

  1 import java.util.HashMap;
  2 import java.util.Map;
  3 import java.util.Random;
  4 
  5 public class Test {
  6 
  7     public static void main(String[] args) {
  8         File file = new File();
  9         new Reader(file).start();
 10         new Reader(file).start();
 11         new Reader(file).start();
 12         new Writer(file).start();
 13         new Writer(file).start();
 14     }
 15 
 16 }
 17 
 18 class File {
 19     
 20     public enum Status {
 21         idle, reading, writing,
 22     }
 23     
 24     private Map<Long, Status> threadStatuses = new HashMap<>();
 25     
 26     public synchronized void beginRead() {
 27         outer: while (true) {
 28             for (Status status : threadStatuses.values()) {
 29                 if (Status.writing.equals(status)) {
 30                     try {
 31                         wait();
 32                     } catch (InterruptedException e) {
 33                         e.printStackTrace();
 34                     }
 35                     continue outer;
 36                 }
 37             }
 38             break;
 39         }
 40         threadStatuses.put(Thread.currentThread().getId(), Status.reading);
 41         notifyAll();
 42     }
 43     
 44     public synchronized void endRead() {
 45         threadStatuses.put(Thread.currentThread().getId(), Status.idle);
 46         notifyAll();
 47     }
 48     
 49     public synchronized void beginWrite() {
 50         outer: while (true) {
 51             for (Status status : threadStatuses.values()) {
 52                 if (!Status.idle.equals(status)) {
 53                     try {
 54                         wait();
 55                     } catch (InterruptedException e) {
 56                         e.printStackTrace();
 57                     }
 58                     continue outer;
 59                 }
 60             }
 61             break;
 62         }
 63         threadStatuses.put(Thread.currentThread().getId(), Status.writing);
 64         notifyAll();
 65     }
 66     
 67     public synchronized void endWrite() {
 68         threadStatuses.put(Thread.currentThread().getId(), Status.idle);
 69         notifyAll();
 70     }
 71     
 72 }
 73 
 74 class Reader extends Thread {
 75     
 76     private static int count;
 77     
 78     private File file;
 79     
 80     public Reader(File file) {
 81         super("Reader-" + count++);
 82         this.file = file;
 83     }
 84     
 85     @Override
 86     public void run() {
 87         while (true) {
 88             file.beginRead();
 89             System.out.println(Thread.currentThread().getName() + " is reading.");
 90             
 91             try {
 92                 Thread.sleep(new Random().nextInt(500));
 93             } catch (InterruptedException e) {
 94                 e.printStackTrace();
 95             }
 96             
 97             System.out.println(Thread.currentThread().getName() + " has read.");
 98             file.endRead();
 99             
100             try {
101                 Thread.sleep(new Random().nextInt(500));
102             } catch (InterruptedException e) {
103                 e.printStackTrace();
104             }
105         }
106     }
107     
108 }
109 
110 class Writer extends Thread {
111     
112     private static int count;
113     
114     private File file;
115     
116     public Writer(File file) {
117         super("Writer-" + count++);
118         this.file = file;
119     }
120     
121     @Override
122     public void run() {
123         while (true) {
124             file.beginWrite();
125             System.out.println(Thread.currentThread().getName() + " is writing.");
126             
127             try {
128                 Thread.sleep(new Random().nextInt(500));
129             } catch (InterruptedException e) {
130                 e.printStackTrace();
131             }
132             
133             System.out.println(Thread.currentThread().getName() + " has wroten.");
134             file.endWrite();
135             
136             try {
137                 Thread.sleep(new Random().nextInt(500));
138             } catch (InterruptedException e) {
139                 e.printStackTrace();
140             }
141         }
142     }
143     
144 }

线程内共享

1. ThreadLocal存放的值是线程内共享的,线程间互斥的。主要用于线程内共享数据,避免通过参数来传递,这样能优雅地解决一些实际问题。

2. ThreadLocal的原理是为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象。

3. 数据库连接工具类的举例。

 1 import java.sql.Connection;
 2 import java.sql.DriverManager;
 3 import java.sql.SQLException;
 4 
 5 public class ConnectionUtils {
 6     
 7     private static ThreadLocal<Connection> threadLocalConnection = new ThreadLocal<Connection>();
 8     
 9     public static Connection getCurrent() throws SQLException {
10         Connection connection = threadLocalConnection.get();
11         if (connection == null || connection.isClosed()) {
12             connection = DriverManager.getConnection("...");
13             threadLocalConnection.set(connection);
14         }
15         return connection;
16     }
17     
18     public static void close() throws SQLException {
19         Connection connection = threadLocalConnection.get();
20         if (connection != null && !connection.isClosed()) {
21             connection.close();
22             threadLocalConnection.remove();
23         }
24     }
25 
26 }

定时器

1. 定时器使用JDK的Timer和TimerTask;

2. 复杂的定时器可考虑使用Quartz框架。

 1 import java.util.Timer;
 2 import java.util.TimerTask;
 3 
 4 public class Test {
 5 
 6     public static void main(String[] args) {
 7         new Timer().schedule(new MyTimerTask(), 3000);;
 8     }
 9 
10 }
11 
12 class MyTimerTask extends TimerTask {
13 
14     @Override
15     public void run() {
16         System.out.println("Hello World.");
17     }
18     
19 }

JDK5新功能

线程池

1. 线程池优点:

    a) 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

    b) 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。

    c) 提高线程的可管理性:线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

2. 四种线程池:

    a) 固定大小线程池:Executors.newFixedThreadPool(int nThreads)。

    b) 缓存线程池:Executors.newCachedThreadPool(),线程池大小随提交任务数增加而增加。

    c) 单一线程池:Executors.newSingleThreadExecutor()。

    d) 调度线程池:Executors.newScheduledThreadPool(int corePoolSize),调度执行线程。

3. 关闭线程池:

    a) 任务执行完毕后关闭:ExecutorService.shutdown()。

    b) 不论任务是否完成立即关闭:ExecutorService.shutdownNow()。

4. 示例。

 1 import java.util.Random;
 2 import java.util.concurrent.ExecutorService;
 3 import java.util.concurrent.Executors;
 4 import java.util.concurrent.atomic.AtomicInteger;
 5 
 6 public class Test {
 7 
 8     public static void main(String[] args) {
 9         ExecutorService threadPool = Executors.newFixedThreadPool(3);
10 //        ExecutorService threadPool = Executors.newCachedThreadPool();
11 //        ExecutorService threadPool = Executors.newSingleThreadExecutor();
12         Task0 task0 = new Task0();
13         Task1 task1 = new Task1();
14         int taskCount = 20;
15         for (int index = 0; index < taskCount; index++) {
16             if (index % 2 == 0) {
17                 threadPool.execute(task0);
18             } else {
19                 threadPool.execute(task1);
20             }
21         }
22         System.out.println(taskCount + " tasks have been committed.");
23         threadPool.shutdown();
24     }
25 
26 }
27 
28 class Task0 implements Runnable {
29     
30     private AtomicInteger count = new AtomicInteger(0);
31 
32     @Override
33     public void run() {
34         System.out.println(Thread.currentThread().getName() + " is executing Task0-" + count.getAndIncrement());
35         try {
36             Thread.sleep(new Random().nextInt(500));
37         } catch (InterruptedException e) {
38             e.printStackTrace();
39         }
40 
41     }
42     
43 }
44 
45 class Task1 implements Runnable {
46     
47     private AtomicInteger count = new AtomicInteger(0);
48 
49     @Override
50     public void run() {
51         System.out.println(Thread.currentThread().getName() + " is executing Task1-" + count.getAndIncrement());
52         try {
53             Thread.sleep(new Random().nextInt(500));
54         } catch (InterruptedException e) {
55             e.printStackTrace();
56         }
57     }
58     
59 }

Callable和Future

1. Callable接口:异步任务。

2. Future接口:异步任务的结果。

 1 import java.util.Random;
 2 import java.util.concurrent.Callable;
 3 import java.util.concurrent.ExecutionException;
 4 import java.util.concurrent.ExecutorService;
 5 import java.util.concurrent.Executors;
 6 import java.util.concurrent.Future;
 7 
 8 public class Test {
 9 
10     public static void main(String[] args) throws InterruptedException, ExecutionException {
11         ExecutorService threadPool = Executors.newFixedThreadPool(3);
12         Future<Integer> future0 = threadPool.submit(new Task());
13         Future<Integer> future1 = threadPool.submit(new Task());
14         System.out.println("Two tasks have been committed.");
15         System.out.println("future0=" + future0.get());
16         System.out.println("future1=" + future1.get());
17         threadPool.shutdown();
18     }
19 
20 }
21 
22 class Task implements Callable<Integer> {
23 
24     @Override
25     public Integer call() throws Exception {
26         try {
27             Thread.sleep(new Random().nextInt(500));
28         } catch (InterruptedException e) {
29             e.printStackTrace();
30         }
31         return new Random().nextInt();
32     }
33 
34 }

3. CompletionService接口:提交一组Callable任务,哪个先执行完则先返回结果。

 1 import java.util.Random;
 2 import java.util.concurrent.Callable;
 3 import java.util.concurrent.CompletionService;
 4 import java.util.concurrent.ExecutionException;
 5 import java.util.concurrent.ExecutorCompletionService;
 6 import java.util.concurrent.ExecutorService;
 7 import java.util.concurrent.Executors;
 8 import java.util.concurrent.Future;
 9 import java.util.concurrent.atomic.AtomicInteger;
10 
11 public class Test {
12 
13     public static void main(String[] args) throws InterruptedException, ExecutionException {
14         ExecutorService threadPool = Executors.newFixedThreadPool(3);
15         CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(threadPool);
16         Task task = new Task();
17         int taskCount = 10;
18         for (int index = 0; index < taskCount; index++) {
19             completionService.submit(task);
20         }
21         for (int index = 0; index < taskCount; index++) {
22             Future<Integer> future = completionService.take();
23             System.out.println("future" + index + "=" + future.get());
24         }
25         threadPool.shutdown();
26     }
27 
28 }
29 
30 class Task implements Callable<Integer> {
31     
32     private AtomicInteger count = new AtomicInteger(0);
33 
34     @Override
35     public Integer call() throws Exception {
36         int ret = count.getAndIncrement();
37         try {
38             Thread.sleep(new Random().nextInt(500));
39         } catch (InterruptedException e) {
40             e.printStackTrace();
41         }
42         return ret;
43     }
44 
45 }

ReentrantLock

1. java.util.concurrent.locks.ReentrantLock与synchronized作用类似,只是更面向对象。

2. 注意:Lock应该与try { … } finally { … }使用,保证锁一定释放。

3. 线程不安全示例的Lock写法。

 1 class MyNumber {
 2     
 3     private Lock lock = new ReentrantLock();
 4     
 5     private int n = 0;
 6     
 7     public int value() {
 8         return n;
 9     }
10     
11     public void increate() {
12         try {
13             lock.lock();
14             n = n + 1;
15         } finally {
16             lock.unlock();
17         }
18     }
19     
20 }

ReadWriteLock

1. java.util.concurrent.locks.ReadWriteLock包含读锁和写锁,类似“读者-写者问题”中的读者和写者,即多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥。

2. “读者-写者问题”的读锁、写锁写法。

  1 import java.util.Random;
  2 import java.util.concurrent.locks.ReadWriteLock;
  3 import java.util.concurrent.locks.ReentrantReadWriteLock;
  4 
  5 public class Test {
  6 
  7     public static void main(String[] args) {
  8         File file = new File();
  9         new Reader(file).start();
 10         new Reader(file).start();
 11         new Reader(file).start();
 12         new Writer(file).start();
 13         new Writer(file).start();
 14     }
 15 
 16 }
 17 
 18 class File {
 19     
 20     private ReadWriteLock lock = new ReentrantReadWriteLock();
 21     
 22     public void beginRead() {
 23         lock.readLock().lock();
 24     }
 25     
 26     public void endRead() {
 27         lock.readLock().unlock();
 28     }
 29     
 30     public void beginWrite() {
 31         lock.writeLock().lock();
 32     }
 33     
 34     public void endWrite() {
 35         lock.writeLock().unlock();
 36     }
 37     
 38 }
 39 
 40 class Reader extends Thread {
 41     
 42     private static int count;
 43     
 44     private File file;
 45     
 46     public Reader(File file) {
 47         super("Reader-" + count++);
 48         this.file = file;
 49     }
 50     
 51     @Override
 52     public void run() {
 53         while (true) {
 54             try {
 55                 file.beginRead();
 56                 System.out.println(Thread.currentThread().getName() + " is reading.");
 57                 
 58                 try {
 59                     Thread.sleep(new Random().nextInt(500));
 60                 } catch (InterruptedException e) {
 61                     e.printStackTrace();
 62                 }
 63                 
 64                 System.out.println(Thread.currentThread().getName() + " has read.");
 65             } finally {
 66                 file.endRead();
 67             }
 68             
 69             try {
 70                 Thread.sleep(new Random().nextInt(500));
 71             } catch (InterruptedException e) {
 72                 e.printStackTrace();
 73             }
 74         }
 75     }
 76     
 77 }
 78 
 79 class Writer extends Thread {
 80     
 81     private static int count;
 82     
 83     private File file;
 84     
 85     public Writer(File file) {
 86         super("Writer-" + count++);
 87         this.file = file;
 88     }
 89     
 90     @Override
 91     public void run() {
 92         while (true) {
 93             try {
 94                 file.beginWrite();
 95                 System.out.println(Thread.currentThread().getName() + " is writing.");
 96                 
 97                 try {
 98                     Thread.sleep(new Random().nextInt(500));
 99                 } catch (InterruptedException e) {
100                     e.printStackTrace();
101                 }
102                 
103                 System.out.println(Thread.currentThread().getName() + " has wroten.");
104             } finally {
105                 file.endWrite();
106             }
107             
108             try {
109                 Thread.sleep(new Random().nextInt(500));
110             } catch (InterruptedException e) {
111                 e.printStackTrace();
112             }
113         }
114     }
115     
116 }

3. 使用ReadWriteLock优化缓存:第1个线程写,后续线程只读(类似ReentrantReadWriteLock JDK文档中的示例)。

 1 import java.util.HashMap;
 2 import java.util.Map;
 3 import java.util.Random;
 4 import java.util.concurrent.locks.ReadWriteLock;
 5 import java.util.concurrent.locks.ReentrantReadWriteLock;
 6 
 7 class Cache {
 8     
 9     private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
10     
11     private Map<Object, Object> data = new HashMap<>();
12     
13     public Object getValue(Object key) {
14         readWriteLock.readLock().lock();
15         Object value = null;
16         try {
17             value = data.get(key);
18             if (value == null) {
19                 readWriteLock.readLock().unlock();
20                 readWriteLock.writeLock().lock();
21                 try {
22                     if (value == null) {
23                         value = new Random().nextInt(1000);    // 模拟去数据库查询数据
24                     }
25                 } finally {
26                     readWriteLock.writeLock().unlock();
27                 }
28                 readWriteLock.readLock().lock();
29             }
30             
31         } finally {
32             readWriteLock.readLock().unlock();
33         }
34         return value;
35     }
36     
37 }

Condition

1. java.util.concurrent.locks.Condition类似Object.wait()和Object.notify()/Object.notifyAll()方法,实现了线程间通信。

2. “子线程、主线程交替循环”面试题的Condition写法。

 1 class Looper {
 2     
 3     private Lock lock = new ReentrantLock();
 4     
 5     private Condition condition = lock.newCondition();
 6     
 7     private boolean runSub = true;
 8     
 9     public void sub() {
10         for (int i = 0; i < 50; i++) {
11             lock.lock();
12             try {
13                 while (!runSub) {
14                     try {
15                         condition.await();
16                     } catch (InterruptedException e) {
17                         e.printStackTrace();
18                     }
19                 }
20                 for (int j = 0; j < 10; j++) {
21                     System.out.println("子线程第" + i + "次循环" + j + "。");
22                 }
23                 runSub = false;
24                 condition.signal();
25             } finally {
26                 lock.unlock();
27             }
28         }
29     }
30     
31     public void main() {
32         for (int i = 0; i < 50; i++) {
33             lock.lock();
34             try {
35                 while (runSub) {
36                     try {
37                         condition.await();
38                     } catch (InterruptedException e) {
39                         e.printStackTrace();
40                     }
41                 }
42                 for (int j = 0; j < 100; j++) {
43                     System.out.println("主线程第" + i + "次循环" + j + "。");
44                 }
45                 runSub = true;
46                 condition.signal();
47             } finally {
48                 lock.unlock();
49             }
50         }
51     }
52     
53 }

3. 与Object.wait()和Object.notify不同的是,Condition能实现多路暂停和唤醒,具体示例参考Condition JDK文档。

Semaphore

1. java.util.concurrent.Semaphore信号量可控制同时访问资源的线程个数。

2. Semaphore可以由一个线程获得锁,而由另一个线程释放锁,这可以应用与死锁恢复的场景。

3. Semaphore适合做网站流量控制,拿到信号量的线程可以进入,否则只能等待。

4. 示例。

 1 import java.util.concurrent.ExecutorService;
 2 import java.util.concurrent.Executors;
 3 import java.util.concurrent.Semaphore;
 4 
 5 public class Test {
 6 
 7     public static void main(String[] args) {
 8         ExecutorService threadPool = Executors.newCachedThreadPool();
 9         Semaphore semaphore = new Semaphore(3);
10         Task task = new Task(semaphore);
11         for (int index = 0; index < 10; index++) {
12             threadPool.execute(task);
13         }
14         threadPool.shutdown();
15     }
16 
17 }
18 
19 class Task implements Runnable {
20     
21     private Semaphore semaphore;
22     
23     public Task(Semaphore semaphore) {
24         this.semaphore = semaphore;
25     }
26 
27     @Override
28     public void run() {
29         try {
30             semaphore.acquire();
31             System.out.println(semaphore.availablePermits() + " permit(s) left.");
32             Thread.sleep(1000);
33             System.out.println(Thread.currentThread().getId() + " is done.");
34             
35         } catch (InterruptedException e) {
36             e.printStackTrace();
37         } finally {
38             semaphore.release();
39         }
40     }
41     
42 }

CyclicBarrier

1. java.util.concurrent.CyclicBarrier阻塞一组线程,直到达到指定的阻塞线程数量后才停止阻塞。类似生活中集合的场景,10小伙伴约定地点集合,先到着等待,等全都到齐才出发。

2. 示例。

 1 import java.util.Random;
 2 import java.util.concurrent.BrokenBarrierException;
 3 import java.util.concurrent.CyclicBarrier;
 4 import java.util.concurrent.ExecutorService;
 5 import java.util.concurrent.Executors;
 6 
 7 public class Test {
 8 
 9     public static void main(String[] args) {
10         ExecutorService threadPool = Executors.newCachedThreadPool();
11         CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
12         Task task = new Task(cyclicBarrier);
13         for (int index = 0; index < 9; index++) {
14             threadPool.execute(task);
15         }
16         threadPool.shutdown();
17     }
18 
19 }
20 
21 class Task implements Runnable {
22     
23     private CyclicBarrier cyclicBarrier;
24     
25     public Task(CyclicBarrier cyclicBarrier) {
26         this.cyclicBarrier = cyclicBarrier;
27     }
28 
29     @Override
30     public void run() {
31         try {
32             Thread.sleep(new Random().nextInt(500));
33         } catch (InterruptedException e) {
34             e.printStackTrace();
35         }
36         System.out.println(Thread.currentThread().getName() + "执行完毕。当前等待的线程个数是" + cyclicBarrier.getNumberWaiting() + "。");
37         try {
38             cyclicBarrier.await();
39         } catch (InterruptedException e) {
40             e.printStackTrace();
41         } catch (BrokenBarrierException e) {
42             e.printStackTrace();
43         }
44     }
45     
46 }

CountDownLatch

1. java.util.concurrent.CountDownLatch倒数器,执行CountDownLatch.countDown()计数器减1,当计数器为0时,则所有等待的线程停止阻塞。

2. 可实现一个线程等待多个线程,也可实现多个线程等待一个线程。以3个运动员、1个裁判发令并统计结果为示例。

 1 import java.util.Random;
 2 import java.util.concurrent.CountDownLatch;
 3 
 4 public class Test {
 5 
 6     public static void main(String[] args) {
 7         CountDownLatch beginCountDownLatch = new CountDownLatch(1);
 8         CountDownLatch endCountDownLatch = new CountDownLatch(3);
 9         new Runner(beginCountDownLatch, endCountDownLatch).start();
10         new Runner(beginCountDownLatch, endCountDownLatch).start();
11         new Runner(beginCountDownLatch, endCountDownLatch).start();
12         new Judge(beginCountDownLatch, endCountDownLatch).start();
13     }
14 
15 }
16 
17 class Runner extends Thread {
18     
19     private CountDownLatch beginCountDownLatch;
20     
21     private CountDownLatch endCountDownLatch;
22     
23     public Runner(CountDownLatch beginCountDownLatch, CountDownLatch endCountDownLatch) {
24         this.beginCountDownLatch = beginCountDownLatch;
25         this.endCountDownLatch = endCountDownLatch;
26     }
27 
28     @Override
29     public void run() {
30         try {
31             System.out.println("Runner " + getId() + " is ready.");
32             beginCountDownLatch.await();
33             Thread.sleep(new Random().nextInt(500));
34             System.out.println("Runner " + getId() + " has run.");
35             endCountDownLatch.countDown();
36         } catch (InterruptedException e) {
37             e.printStackTrace();
38         }
39     }
40     
41 }
42 
43 class Judge extends Thread {
44     
45     private CountDownLatch beginCountDownLatch;
46     
47     private CountDownLatch endCountDownLatch;
48     
49     public Judge(CountDownLatch beginCountDownLatch, CountDownLatch endCountDownLatch) {
50         this.beginCountDownLatch = beginCountDownLatch;
51         this.endCountDownLatch = endCountDownLatch;
52     }
53 
54     @Override
55     public void run() {
56         try {
57             Thread.sleep(3000);
58             System.out.println("Judge says "Go!"");
59             beginCountDownLatch.countDown();
60             System.out.println("Judge is waiting for runners.");
61             endCountDownLatch.await();
62             System.out.println("Judge has waited.");
63             
64         } catch (InterruptedException e) {
65             e.printStackTrace();
66         }
67     }
68     
69 }

Exchanger

1. java.util.concurrent.Exchanger可实现两个线程间的数据交互。先执行Exchanger.exchange()方法的线程被阻塞,直到另一个线程也执行Exchanger.exchange()方法才停止,两条线程交换完数据后继续执行。

2. 示例。

 1 import java.util.Random;
 2 import java.util.UUID;
 3 import java.util.concurrent.Exchanger;
 4 
 5 public class Test {
 6 
 7     public static void main(String[] args) {
 8         Exchanger<String> exchanger = new Exchanger<String>();
 9         new MyThread(exchanger).start();
10         new MyThread(exchanger).start();
11     }
12 
13 }
14 
15 class MyThread extends Thread {
16     
17     private Exchanger<String> exchanger;
18     
19     public MyThread(Exchanger<String> exchanger) {
20         this.exchanger = exchanger;
21     }
22 
23     @Override
24     public void run() {
25         String value = UUID.randomUUID().toString();
26         System.out.println(Thread.currentThread().getName() + "准备用" + value + "交换。");
27         String newValue = null;
28         try {
29             Thread.sleep(new Random().nextInt(5000));
30             newValue = exchanger.exchange(value);
31         } catch (InterruptedException e) {
32             e.printStackTrace();
33         }
34         System.out.println(Thread.currentThread().getName() + "交换得到" + newValue + "。");
35     }
36     
37 }

BlockingQueue

1. java.util.concurrent.BlockingQueue为阻塞队列,类似“生产者-消费者问题”中的容器,“满值欲加”和“无值欲取”都会被阻塞。

2. 查JDK文档可知:

                                 场景+操作

结果

“满值欲加”

“无值欲取”

检查

抛出异常

add(e)

remove()

element()

返回值

offer(e)

poll()

peek()

阻塞

put(e)

take()

不可用

3. 固定长度阻塞队列ArrayBlockingQueue,不固定长度阻塞队列LinkedBlockingDeque。

4. “生产者-消费者问题”的BlockingQueue写法。

 1 class Container {
 2     
 3     private final static int CAPACITY = 10;
 4     
 5     private BlockingQueue<Product> blocingQueue = new ArrayBlockingQueue<Product>(CAPACITY);
 6     
 7     public void push(Product product) {
 8         try {
 9             blocingQueue.put(product);
10         } catch (InterruptedException e) {
11             e.printStackTrace();
12         }
13     }
14     
15     public Product pop() {
16         Product product = null;
17         try {
18             product = blocingQueue.take();
19         } catch (InterruptedException e) {
20             e.printStackTrace();
21         }
22         return product;
23     }
24     
25 }

5. 两个具有1个容量的阻塞队列可实现线程间通信。“子线程、主线程交替循环问题”的BlockingQueue写法。

 1 class Looper {
 2     
 3     private BlockingQueue<Integer> subBlockingQueue = new ArrayBlockingQueue<Integer>(1);
 4     
 5     private BlockingQueue<Integer> mainBlockingQueue = new ArrayBlockingQueue<Integer>(1);
 6     
 7     public Looper() {
 8         subBlockingQueue.add(1);
 9     }
10     
11     public void sub() {
12         for (int i = 0; i < 50; i++) {
13             try {
14                 subBlockingQueue.take();
15             } catch (InterruptedException e) {
16                 e.printStackTrace();
17             }
18             for (int j = 0; j < 10; j++) {
19                 System.out.println("子线程第" + i + "次循环" + j + "。");
20             }
21             mainBlockingQueue.add(1);
22         }
23     }
24     
25     public void main() {
26         for (int i = 0; i < 50; i++) {
27             try {
28                 mainBlockingQueue.take();
29             } catch (InterruptedException e) {
30                 e.printStackTrace();
31             }
32             for (int j = 0; j < 100; j++) {
33                 System.out.println("主线程第" + i + "次循环" + j + "。");
34             }
35             subBlockingQueue.add(1);
36         }
37     }
38     
39 }

并发集合

1. 并发集合与传统集合、同步集合对应关系。

传统集合(非线程安全)

同步集合(线程安全)

并发集合(线程安全)

ArrayList

Vector

Collections.synchronizedCollection(...)

Collections.synchronizedList(...)

CopyOnWriteArrayList

Set

Collections.synchronizedSet(...)

CopyOnWriteArraySet

TreeSet

Collections.synchronizedNavigableSet(...)

Collections.synchronizedSortedSet(...)

ConcurrentSkipListSet

HashMap

Hashtable

Collections.synchronizedMap(...)

ConcurrentHashMap

TreeMap

Collections.synchronizedNavigableMap(...)

Collections.synchronizedSortedMap(...)

ConcurrentSkipListMap

2. 并发集合与同步集合的区别。

    a) 并发集合尽量避免synchronized,提供并发性;

    b) 并发集合定义了一些并发安全的复合操作,并且保证并发环境下的迭代操作不会出错。示例如下。

 1 import java.util.ArrayList;
 2 import java.util.Iterator;
 3 import java.util.List;
 4 import java.util.Vector;
 5 import java.util.concurrent.CopyOnWriteArrayList;
 6 
 7 public class Test {
 8 
 9     public static void main(String[] args) {
10 //        List<String> list = new ArrayList<>();    // 抛出ConcurrentModificationException,或结果不正确
11 //        List<String> list = new Vector<>();    // 抛出ConcurrentModificationException,或结果不正确
12         List<String> list = new CopyOnWriteArrayList<String>();
13         list.add("s1");
14         list.add("s2");
15         list.add("s3");
16         list.add("s4");
17         list.add("s5");
18         for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
19             String current = iterator.next();
20             if ("s1".equals(current)
21                     || "s3".equals(current)
22                     || "s5".equals(current)) {
23                 list.remove(current);
24             } else {
25                 System.out.println(current);
26             }
27         }
28     }
29 
30 }

系统峰值评估

峰值参数

1. PV(Page View):网站的总访问量、页面浏览量或点击量,用户每刷新一次就会被记录一次。

2. UV(Unique Visitor):访问网站的一台电脑客户端为一个访客。一般来讲,时间上以00:00至24:00之间相同IP的客户端只记录一次。

3. QPS(Query Per Second):每秒查询数。QPS很大程度上代表了系统业务上的繁忙程度,每次查询的背后,可能对应着多次磁盘I/O,多次网络请求,多次CPU时间片等。通过QPS可以非常直观的了解当前系统业务情况,一旦当前QPS超过所设定的预警阀值,可以考虑增加机器对集群扩容,以免压力过大导致宕机。可以根据前期的压力测试得到估值,在结合后期综合运维情况,估算出阀值。

4. RT(Response Time):请求的响应时间。这个指标非常关键,直接说明前端用户的体验,任何系统设计师都想降低RT。

5. 还有设计CPU、内存、网络、磁盘等情况的参数。

评估方法

1. 一般通过开发、运维、测试以及业务等相关人员,综合出系统的一系列阀值,然后根据关键阀值(如QPS、RT等)对系统进行有效变更。

2. 进行多轮压力测试以后,可以对系统进行峰值评估,采用80/20原则,即80%的PV将在20%的时间内达到。这样计算出系统峰值QPS:

峰值QPS = ( 总PV * 80% ) / ( 60 * 60 * 24 * 20% )

总峰值QPS除以单台机器所能承受的最高QPS就是须要的机器数量:

机器数 = 总峰值QPS / 压测得出的单机极限QPS

还要考虑大型促销活动和双十一、双十二热点事件、遭受DDoS攻击等情况,系统的开发和维护人急需了解当前系统运行的状态和负载情况。

作者:netoxi
出处:http://www.cnblogs.com/netoxi
本文版权归作者和博客园共有,欢迎转载,未经同意须保留此段声明,且在文章页面明显位置给出原文连接。欢迎指正与交流。

原文地址:https://www.cnblogs.com/netoxi/p/7196891.html