目录
· 线程安全
· 互斥同步
· 非阻塞同步
· 无同步
· 线程间通信
· 哲学家进餐问题
· 读者-写者问题
· 线程内共享
· 定时器
· JDK5新功能
· 线程池
· 并发集合
· 系统峰值评估
· 峰值参数
· 评估方法
线程安全
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
本文版权归作者和博客园共有,欢迎转载,未经同意须保留此段声明,且在文章页面明显位置给出原文连接。欢迎指正与交流。