├── README.md ├── .idea ├── vcs.xml ├── modules.xml ├── misc.xml └── workspace.xml ├── src ├── chapter4 │ ├── ThreadPool.java │ ├── SleepUtils.java │ ├── MultiThread.java │ ├── Profiler.java │ ├── ConnectionDriver.java │ ├── Join.java │ ├── Interrupted.java │ ├── ConnectionPool.java │ ├── WaitNotify.java │ ├── PoolTest.java │ ├── chapter4.md │ └── DefaultThreadPool.java ├── chapter7 │ └── chapter7.md ├── chapter3 │ ├── ReentrantLockExample.java │ └── chapter3.md ├── chapter1 │ ├── chapter1.md │ └── DeadLockDemo.java ├── chapter9 │ └── chapter9.md ├── chapter5 │ ├── Cache.java │ ├── TwinsLockTest.java │ ├── Mutex.java │ ├── TwinsLock.java │ └── chapter5.md ├── chapter6 │ ├── CountTask.java │ └── chapter6.md └── chapter2 │ ├── Counter.java │ └── chapter2.md └── Java_concurrent.iml /README.md: -------------------------------------------------------------------------------- 1 | # java_concurrent_art 2 | java并发编程的艺术 笔记 3 | 4 | 整理了一下初读java并发编程艺术一书的个人认为的重点,方便回顾。 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/chapter4/ThreadPool.java: -------------------------------------------------------------------------------- 1 | package chapter4; 2 | 3 | /** 4 | * Created by xdcao on 2017/5/4. 5 | */ 6 | public interface ThreadPool { 7 | 8 | void execute(Job job); 9 | 10 | void shutDown(); 11 | 12 | void addWorkers(int num); 13 | 14 | void removeWorker(int num); 15 | 16 | int getJobSize(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/chapter4/SleepUtils.java: -------------------------------------------------------------------------------- 1 | package chapter4; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | /** 6 | * Created by xdcao on 2017/5/3. 7 | */ 8 | public class SleepUtils { 9 | 10 | public static final void second(long seconds){ 11 | try { 12 | TimeUnit.SECONDS.sleep(seconds); 13 | }catch (InterruptedException e){ 14 | 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Java_concurrent.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/chapter7/chapter7.md: -------------------------------------------------------------------------------- 1 | #第七章 Java中的13个原子操作类 2 | 3 | 包含4种类型的原子更新方式:原子更新基本类型、原子更新数组、原子更新引用、原子更新属性(字段) 4 | 5 | ##一、原子更新基本类型 6 | 7 | AtomicBoolean,AtomicInteger,AtomicLong 8 | 9 | 其实就是用CAS实现,也可以采取源码中的思路自己写char、float、double的原子类 10 | 11 | ##二、原子更新数组 12 | 13 | AtomicLongArray,AtomicReferenceArray,AtomicIntegerArray 14 | 15 | 注意:上述三种会将传入的数组复制一份,当其对内部的数组进行修改时,不会影响到传入的数组 16 | 17 | ##三、原子更新引用类型 18 | 19 | AtomicReference,AtomicReferenceFieldUpdater,AtomicMarkableReference 20 | 21 | ##四、原子更新字段类 22 | 23 | 创建更新器、 24 | 25 | 更新类的字段必须使用volatile 26 | 27 | AtomicIntegerFieldUpdater,AtomicLongfieldUpdater,AtomicStampedReference -------------------------------------------------------------------------------- /src/chapter4/MultiThread.java: -------------------------------------------------------------------------------- 1 | package chapter4; 2 | 3 | import java.lang.management.ManagementFactory; 4 | import java.lang.management.ThreadInfo; 5 | import java.lang.management.ThreadMXBean; 6 | 7 | /** 8 | * Created by xdcao on 2017/5/3. 9 | */ 10 | public class MultiThread { 11 | 12 | public static void main(String[] args){ 13 | 14 | ThreadMXBean threadMXBean= ManagementFactory.getThreadMXBean(); 15 | ThreadInfo[] threadInfos=threadMXBean.dumpAllThreads(false,false); 16 | for (ThreadInfo threadInfo:threadInfos){ 17 | System.out.println("["+threadInfo.getThreadId()+"]"+threadInfo.getThreadName()); 18 | } 19 | 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/chapter3/ReentrantLockExample.java: -------------------------------------------------------------------------------- 1 | package chapter3; 2 | 3 | import java.util.SplittableRandom; 4 | import java.util.concurrent.locks.ReentrantLock; 5 | 6 | /** 7 | * Created by xdcao on 2017/5/2. 8 | */ 9 | public class ReentrantLockExample { 10 | 11 | int a=0; 12 | 13 | ReentrantLock lock=new ReentrantLock(); 14 | 15 | public void writer(){ 16 | lock.lock(); 17 | try { 18 | a++; 19 | }finally { 20 | lock.unlock(); 21 | } 22 | } 23 | 24 | 25 | public static void main(String[] args){ 26 | ReentrantLockExample reentrantLockExample=new ReentrantLockExample(); 27 | reentrantLockExample.writer(); 28 | } 29 | 30 | 31 | } 32 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 1.8 11 | 12 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/chapter1/chapter1.md: -------------------------------------------------------------------------------- 1 | #第一章 并发编程的挑战 2 | 3 | ##一、上下文切换 4 | 5 | 即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片(一般为几十毫秒)实现这个机制。 6 | 7 | 当前任务执行一个时间片后会切换到下一个任务。在切换前会保存上一个任务的状态,以便下次 8 | 切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。 9 | 10 | >如何减少上下文切换? 11 | >1. 无锁并发编程。多线程竞争锁时,会引起上下文切换,可以用一些办法避免使用锁,如将数据 12 | 的id取模分段,不同的线程处理不同段的数据 13 | >2. CAS算法:Java的Atomic包使用CAS算法来更新数据,不需要加锁 14 | >3. 使用最少线程,避免创建不需要的线程 15 | >4. 在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换 16 | 17 | ##二、死锁 18 | 19 | 避免死锁的几个常见方法: 20 | 21 | 1. 避免一个线程获取多个锁 22 | 2. 避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源 23 | 3. 尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制 24 | 4. 对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况 25 | 26 | ##三、资源限制的挑战 27 | 28 | 问题:如果将某段串行的代码并发执行,因为受限于资源,仍然在串行执行,这时候程序不仅不会加快执行, 29 | 反而会更慢,因为增加了上下文切换和资源调度的时间。 30 | 31 | 如何解决: 32 | 33 | >对于硬件资源的限制,使用机群并行执行程序,通过搭建服务器集群,不同的机器处理不同的数据 34 | 35 | >对于软件资源的限制,可以考虑使用资源池将资源复用 36 | 37 | 根据不同的资源限制调整程序的并发度 38 | -------------------------------------------------------------------------------- /src/chapter4/Profiler.java: -------------------------------------------------------------------------------- 1 | package chapter4; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | /** 6 | * Created by xdcao on 2017/5/4. 7 | */ 8 | public class Profiler { 9 | 10 | private static final ThreadLocal TIME_THREADLOCAL=new ThreadLocal(){ 11 | @Override 12 | protected Long initialValue() { 13 | return System.currentTimeMillis(); 14 | } 15 | }; 16 | 17 | public static final void begin(){ 18 | TIME_THREADLOCAL.set(System.currentTimeMillis()); 19 | } 20 | 21 | public static final Long end(){ 22 | return System.currentTimeMillis()-TIME_THREADLOCAL.get(); 23 | } 24 | 25 | 26 | public static void main(String[] args) throws InterruptedException { 27 | Profiler.begin(); 28 | TimeUnit.SECONDS.sleep(1); 29 | System.out.println("Cost "+Profiler.end()); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/chapter4/ConnectionDriver.java: -------------------------------------------------------------------------------- 1 | package chapter4; 2 | 3 | import java.lang.reflect.InvocationHandler; 4 | import java.lang.reflect.Method; 5 | import java.lang.reflect.Proxy; 6 | import java.sql.Connection; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * Created by xdcao on 2017/5/4. 11 | */ 12 | public class ConnectionDriver { 13 | 14 | static class ConnectionHandler implements InvocationHandler{ 15 | @Override 16 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 17 | if(method.getName().equals("commit")){ 18 | TimeUnit.MILLISECONDS.sleep(100); 19 | } 20 | return null; 21 | } 22 | } 23 | 24 | public static Connection createConnection() { 25 | 26 | return (Connection) Proxy.newProxyInstance(ConnectionDriver.class.getClassLoader(),new Class[] {Connection.class},new ConnectionHandler()); 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/chapter9/chapter9.md: -------------------------------------------------------------------------------- 1 | #第9章 Java中的线程池 2 | 3 | 带来的好处: 4 | 5 | 1.降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗 6 | 2.提高响应速度:任务到达时,任务可以不需要等到线程创建就能立即执行 7 | 3.提高线程的可管理性 8 | 9 | ##一、实现原理 10 | 11 | 提交一个新任务到线程池,处理流程: 12 | 13 | 1.判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程执行任务。如果是,则进入2 14 | 2.判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满,进入3 15 | 3.判断线程池的线程是否都处于工作状态。如果没有,则创建新的工作线程来执行任务。如果满了,则交给饱和 16 | 策略来处理这个任务。 17 | 18 | 以ThreadPoolExecutor执行execute方法举例,分为4种情况: 19 | 20 | 1.如果当前运行线程数少于corePoolSize,则创建新线程来执行任务 21 | 2.如果运行的线程等于或多余corePoolSize,则将任务加入BlockingQueue 22 | 3.如果BlockingQueue已满,则创建新的线程来处理任务 23 | 4.如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用对应的策略 24 | 25 | 工作线程:线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会循环获取工作队列里 26 | 的任务来执行。 27 | 28 | ##二、线程池的使用 29 | 30 | 用ThreadPoolExecutor来创建 31 | 32 | corePoolSize:线程池的基本大小:当提交一个任务到线程池时,线程池会创建一个线程执行任务, 33 | 即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小 34 | 时就不再继续创建。 35 | 36 | 提交任务:execute提交不需要返回值得任务,submit提交需要返回值的 37 | 38 | -------------------------------------------------------------------------------- /src/chapter4/Join.java: -------------------------------------------------------------------------------- 1 | package chapter4; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | /** 6 | * Created by xdcao on 2017/5/4. 7 | */ 8 | public class Join { 9 | 10 | static class Domino implements Runnable{ 11 | 12 | private Thread thread; 13 | 14 | private Domino(Thread thread){ 15 | this.thread=thread; 16 | } 17 | 18 | @Override 19 | public void run() { 20 | try { 21 | thread.join(); 22 | }catch (InterruptedException e){ 23 | 24 | } 25 | System.out.println(Thread.currentThread().getName()+" terminate."); 26 | } 27 | } 28 | 29 | public static void main(String[] args) throws Exception{ 30 | 31 | Thread previous=Thread.currentThread(); 32 | for(int i=0;i<10;i++){ 33 | Thread thread=new Thread(new Domino(previous),String.valueOf(i)); 34 | thread.start(); 35 | previous=thread; 36 | } 37 | TimeUnit.SECONDS.sleep(5); 38 | System.out.println(Thread.currentThread().getName()+" terminate."); 39 | 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/chapter1/DeadLockDemo.java: -------------------------------------------------------------------------------- 1 | package chapter1; 2 | 3 | import jdk.nashorn.internal.runtime.ECMAErrors; 4 | 5 | /** 6 | * Created by xdcao on 2017/4/28. 7 | */ 8 | public class DeadLockDemo { 9 | 10 | private static String A="A"; 11 | private static String B="B"; 12 | 13 | public static void main(String[] args){ 14 | new DeadLockDemo().deadLocak(); 15 | } 16 | 17 | private void deadLocak(){ 18 | 19 | Thread th1=new Thread(() -> { 20 | synchronized (A){ 21 | try { 22 | Thread.currentThread().sleep(2000); 23 | }catch (Exception e){ 24 | e.printStackTrace(); 25 | } 26 | synchronized (B){ 27 | System.out.println(1); 28 | } 29 | } 30 | }); 31 | 32 | Thread th2=new Thread(() -> { 33 | synchronized (B){ 34 | synchronized (A){ 35 | System.out.println(2); 36 | } 37 | } 38 | }); 39 | 40 | th1.start(); 41 | th2.start(); 42 | 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/chapter5/Cache.java: -------------------------------------------------------------------------------- 1 | package chapter5; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.concurrent.locks.Lock; 6 | import java.util.concurrent.locks.ReentrantReadWriteLock; 7 | 8 | /** 9 | * Created by xdcao on 2017/5/18. 10 | */ 11 | public class Cache { 12 | 13 | static Map map=new HashMap(); 14 | static ReentrantReadWriteLock rwl=new ReentrantReadWriteLock(); 15 | static Lock r=rwl.readLock(); 16 | static Lock w=rwl.writeLock(); 17 | 18 | public static final Object get(String key){ 19 | r.lock(); 20 | try { 21 | return map.get(key); 22 | }finally { 23 | r.unlock(); 24 | } 25 | } 26 | 27 | public static final Object put(String key,Object value){ 28 | w.lock(); 29 | try { 30 | return map.put(key,value); 31 | }finally { 32 | w.unlock(); 33 | } 34 | } 35 | 36 | public static final void clear(){ 37 | w.lock(); 38 | try { 39 | map.clear(); 40 | }finally { 41 | w.unlock(); 42 | } 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/chapter5/TwinsLockTest.java: -------------------------------------------------------------------------------- 1 | package chapter5; 2 | 3 | import chapter4.SleepUtils; 4 | 5 | import java.util.concurrent.locks.Lock; 6 | 7 | /** 8 | * Created by xdcao on 2017/5/8. 9 | */ 10 | public class TwinsLockTest { 11 | 12 | public static void main(String[] args){ 13 | 14 | final Lock lock=new TwinsLock(); 15 | 16 | class Worker extends Thread{ 17 | @Override 18 | public void run() { 19 | while (true){ 20 | lock.lock(); 21 | try { 22 | SleepUtils.second(1); 23 | System.out.println(Thread.currentThread().getName()); 24 | SleepUtils.second(1); 25 | }finally { 26 | lock.unlock(); 27 | } 28 | } 29 | } 30 | } 31 | 32 | for(int i=0;i<10;i++){ 33 | Worker worker=new Worker(); 34 | worker.setDaemon(true); 35 | worker.start(); 36 | } 37 | 38 | for (int i=0;i<10;i++){ 39 | SleepUtils.second(1); 40 | System.out.println(); 41 | } 42 | 43 | 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /src/chapter4/Interrupted.java: -------------------------------------------------------------------------------- 1 | package chapter4; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | /** 6 | * Created by xdcao on 2017/5/3. 7 | */ 8 | public class Interrupted { 9 | 10 | static class SleepRunner implements Runnable{ 11 | 12 | @Override 13 | public void run() { 14 | while (true){ 15 | SleepUtils.second(10); 16 | } 17 | } 18 | } 19 | 20 | static class BusyRunner implements Runnable{ 21 | 22 | @Override 23 | public void run() { 24 | while (true){ 25 | 26 | } 27 | } 28 | } 29 | 30 | 31 | public static void main(String[] args) throws Exception{ 32 | 33 | Thread sleepThread=new Thread(new SleepRunner(),"SleepThread"); 34 | sleepThread.setDaemon(true); 35 | 36 | Thread busyThread=new Thread(new BusyRunner(),"BusyRunner"); 37 | busyThread.setDaemon(true); 38 | 39 | sleepThread.start(); 40 | busyThread.start(); 41 | 42 | TimeUnit.SECONDS.sleep(5); 43 | sleepThread.interrupt(); 44 | busyThread.interrupt(); 45 | 46 | System.out.println("SleepThread interrupted is "+sleepThread.isInterrupted()); 47 | System.out.println("BusyThread interrupted is "+busyThread.isInterrupted()); 48 | 49 | SleepUtils.second(2); 50 | 51 | } 52 | 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/chapter6/CountTask.java: -------------------------------------------------------------------------------- 1 | package chapter6; 2 | 3 | import java.util.concurrent.ForkJoinPool; 4 | import java.util.concurrent.Future; 5 | import java.util.concurrent.RecursiveTask; 6 | 7 | /** 8 | * Created by xdcao on 2017/5/21. 9 | */ 10 | public class CountTask extends RecursiveTask { 11 | 12 | private static final int THRESHOLD=2; 13 | 14 | private int start; 15 | private int end; 16 | 17 | public CountTask(int start, int end) { 18 | this.start = start; 19 | this.end = end; 20 | } 21 | 22 | @Override 23 | protected Integer compute() { 24 | 25 | int sum=0; 26 | boolean canCompute=(end-start)<=THRESHOLD; 27 | if (canCompute) { 28 | for (int i=start;i<=end;i++){ 29 | sum+=i; 30 | } 31 | }else { 32 | int middle=(start+end)/2; 33 | CountTask leftTask=new CountTask(start,middle); 34 | CountTask rightTask=new CountTask(middle+1,end); 35 | leftTask.fork(); 36 | rightTask.fork(); 37 | int leftResult=leftTask.join(); 38 | int rightResult=rightTask.join(); 39 | sum=leftResult+rightResult; 40 | } 41 | return sum; 42 | 43 | } 44 | 45 | public static void main(String[] args){ 46 | ForkJoinPool forkJoinPool=new ForkJoinPool(); 47 | CountTask countTask=new CountTask(1,4); 48 | Future result=forkJoinPool.submit(countTask); 49 | try { 50 | System.out.println(result.get()); 51 | }catch (Exception e){ 52 | 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/chapter4/ConnectionPool.java: -------------------------------------------------------------------------------- 1 | package chapter4; 2 | 3 | import java.sql.Connection; 4 | import java.util.LinkedList; 5 | 6 | /** 7 | * Created by xdcao on 2017/5/4. 8 | */ 9 | public class ConnectionPool { 10 | 11 | private LinkedList pool=new LinkedList<>(); 12 | 13 | public ConnectionPool(int initialSize){ 14 | if(initialSize>0){ 15 | for (int i=0;i0){ 42 | pool.wait(remaining); 43 | remaining=future=System.currentTimeMillis(); 44 | } 45 | Connection result=null; 46 | if(!pool.isEmpty()){ 47 | result=pool.removeFirst(); 48 | } 49 | return result; 50 | } 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/chapter2/Counter.java: -------------------------------------------------------------------------------- 1 | package chapter2; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Created by xdcao on 2017/5/1. 9 | */ 10 | public class Counter { 11 | 12 | private AtomicInteger atomicInteger=new AtomicInteger(0); 13 | private int i=0; 14 | 15 | private void safeCount(){ 16 | for (;;){ 17 | int i=atomicInteger.get(); 18 | boolean suc=atomicInteger.compareAndSet(i,++i); 19 | if (suc){ 20 | break; 21 | } 22 | } 23 | } 24 | 25 | private void count(){ 26 | i++; 27 | } 28 | 29 | public static void main(String[] args){ 30 | 31 | final Counter cas=new Counter(); 32 | List ts=new ArrayList<>(600); 33 | long start=System.currentTimeMillis(); 34 | for (int j=0;j<100;j++){ 35 | Thread t=new Thread(new Runnable() { 36 | @Override 37 | public void run() { 38 | for (int i=0;i<10000;i++){ 39 | cas.count(); 40 | cas.safeCount(); 41 | } 42 | } 43 | }); 44 | ts.add(t); 45 | } 46 | for (Thread t:ts){ 47 | t.start(); 48 | } 49 | for (Thread t:ts){ 50 | try { 51 | t.join(); 52 | }catch (Exception e){ 53 | e.printStackTrace(); 54 | } 55 | } 56 | 57 | System.out.println(cas.i); 58 | System.out.println(cas.atomicInteger.get()); 59 | System.out.println(System.currentTimeMillis()-start); 60 | 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/chapter4/WaitNotify.java: -------------------------------------------------------------------------------- 1 | package chapter4; 2 | 3 | import com.sun.xml.internal.bind.v2.model.annotation.RuntimeAnnotationReader; 4 | 5 | import java.text.SimpleDateFormat; 6 | import java.util.Date; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | /** 10 | * Created by xdcao on 2017/5/4. 11 | */ 12 | public class WaitNotify { 13 | 14 | static boolean flag=false; 15 | static Object lock=new Object(); 16 | 17 | static class Wait implements Runnable{ 18 | @Override 19 | public void run() { 20 | synchronized (lock){ 21 | while (flag){ 22 | try { 23 | System.out.println(Thread.currentThread()+" flag is true, wait @ "+new SimpleDateFormat("HH:mm:ss").format(new Date())); 24 | lock.wait(); 25 | }catch (InterruptedException e){ 26 | 27 | } 28 | } 29 | System.out.println(Thread.currentThread()+" flag is false, running @ "+new SimpleDateFormat("HH:mm:ss").format(new Date())); 30 | } 31 | } 32 | } 33 | 34 | static class Notify implements Runnable{ 35 | 36 | @Override 37 | public void run() { 38 | synchronized (lock){ 39 | System.out.println(Thread.currentThread()+" hold lock, notify @ "+ 40 | new SimpleDateFormat("HH:mm:ss").format(new Date())); 41 | lock.notifyAll(); 42 | flag=false; 43 | SleepUtils.second(5); 44 | } 45 | 46 | synchronized (lock){ 47 | System.out.println(Thread.currentThread()+" hold lock again, sleep @ "+ 48 | new SimpleDateFormat("HH:mm:ss").format(new Date())); 49 | SleepUtils.second(5); 50 | } 51 | 52 | } 53 | } 54 | 55 | public static void main(String[] args) throws InterruptedException { 56 | Thread waitThread=new Thread(new Wait(),"WaitThread"); 57 | waitThread.start(); 58 | TimeUnit.SECONDS.sleep(1); 59 | Thread notifyThread=new Thread(new Notify(),"NotifyThread"); 60 | notifyThread.start(); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/chapter5/Mutex.java: -------------------------------------------------------------------------------- 1 | package chapter5; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.concurrent.locks.AbstractQueuedSynchronizer; 5 | import java.util.concurrent.locks.Condition; 6 | import java.util.concurrent.locks.Lock; 7 | 8 | /** 9 | * Created by xdcao on 2017/5/5. 10 | */ 11 | public class Mutex implements Lock { 12 | 13 | private static class Sync extends AbstractQueuedSynchronizer{ 14 | 15 | @Override 16 | protected boolean isHeldExclusively() { 17 | return getState()==1; 18 | } 19 | 20 | @Override 21 | protected boolean tryAcquire(int acqiures) { 22 | if(compareAndSetState(0,1)){ 23 | setExclusiveOwnerThread(Thread.currentThread()); 24 | return true; 25 | } 26 | return false; 27 | } 28 | 29 | @Override 30 | protected boolean tryRelease(int releases) { 31 | if (getState()==0) 32 | throw new IllegalMonitorStateException(); 33 | setExclusiveOwnerThread(null); 34 | setState(0); 35 | return true; 36 | } 37 | 38 | Condition newCondition(){ 39 | return new ConditionObject(); 40 | } 41 | 42 | } 43 | 44 | private final Sync sync=new Sync(); 45 | 46 | @Override 47 | public void lock() { 48 | sync.acquire(1); 49 | } 50 | 51 | @Override 52 | public void lockInterruptibly() throws InterruptedException { 53 | sync.acquireInterruptibly(1); 54 | } 55 | 56 | @Override 57 | public boolean tryLock() { 58 | return sync.tryAcquire(1); 59 | } 60 | 61 | @Override 62 | public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { 63 | return sync.tryAcquireNanos(1,unit.toNanos(time)); 64 | } 65 | 66 | @Override 67 | public void unlock() { 68 | sync.release(1); 69 | } 70 | 71 | @Override 72 | public Condition newCondition() { 73 | return sync.newCondition(); 74 | } 75 | 76 | public boolean isLocked(){ 77 | return sync.isHeldExclusively(); 78 | } 79 | 80 | public boolean hasQueuedThreads(){ 81 | return sync.hasQueuedThreads(); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/chapter5/TwinsLock.java: -------------------------------------------------------------------------------- 1 | package chapter5; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.concurrent.locks.AbstractQueuedSynchronizer; 5 | import java.util.concurrent.locks.Condition; 6 | import java.util.concurrent.locks.Lock; 7 | 8 | /** 9 | * Created by xdcao on 2017/5/8. 10 | */ 11 | public class TwinsLock implements Lock { 12 | 13 | private final Sync sync=new Sync(2); 14 | 15 | private static final class Sync extends AbstractQueuedSynchronizer{ 16 | 17 | Sync(int count){ 18 | if (count<0){ 19 | throw new IllegalArgumentException("count must larger than 0"); 20 | } 21 | setState(count); 22 | } 23 | 24 | @Override 25 | protected int tryAcquireShared(int reduceCount) { 26 | for (;;){ 27 | int current=getState(); 28 | int newCount=current-reduceCount; 29 | if(newCount<0||compareAndSetState(current,newCount)){ 30 | return newCount; 31 | } 32 | } 33 | } 34 | 35 | @Override 36 | protected boolean tryReleaseShared(int returnCount) { 37 | for(;;){ 38 | int current=getState(); 39 | int newCount=current+returnCount; 40 | if (compareAndSetState(current,newCount)){ 41 | return true; 42 | } 43 | } 44 | } 45 | } 46 | 47 | 48 | @Override 49 | public void lock() { 50 | sync.acquireShared(1); 51 | } 52 | 53 | @Override 54 | public void lockInterruptibly() throws InterruptedException { 55 | 56 | } 57 | 58 | @Override 59 | public boolean tryLock() { 60 | int result=sync.tryAcquireShared(1); 61 | if (result>0&&result<3){ 62 | return true; 63 | }else { 64 | return false; 65 | } 66 | } 67 | 68 | @Override 69 | public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { 70 | return false; 71 | } 72 | 73 | @Override 74 | public void unlock() { 75 | sync.releaseShared(1); 76 | } 77 | 78 | @Override 79 | public Condition newCondition() { 80 | return null; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/chapter4/PoolTest.java: -------------------------------------------------------------------------------- 1 | package chapter4; 2 | 3 | import java.sql.Connection; 4 | import java.util.concurrent.CountDownLatch; 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * Created by xdcao on 2017/5/4. 9 | */ 10 | public class PoolTest { 11 | 12 | static ConnectionPool pool=new ConnectionPool(10); 13 | static CountDownLatch start=new CountDownLatch(1); 14 | static CountDownLatch end; 15 | 16 | static class ConnectionRunner implements Runnable{ 17 | 18 | int count; 19 | AtomicInteger got; 20 | AtomicInteger notGot; 21 | 22 | public ConnectionRunner(int count, AtomicInteger got, AtomicInteger notGot) { 23 | this.count = count; 24 | this.got = got; 25 | this.notGot = notGot; 26 | } 27 | 28 | @Override 29 | public void run() { 30 | 31 | try { 32 | start.await();; 33 | }catch (Exception e){ 34 | 35 | } 36 | while (count>0){ 37 | try { 38 | Connection connection=pool.fetchConnection(1000); 39 | if (connection!=null){ 40 | try { 41 | connection.createStatement(); 42 | connection.commit(); 43 | }finally { 44 | pool.releaseConnection(connection); 45 | got.incrementAndGet(); 46 | } 47 | }else { 48 | notGot.incrementAndGet(); 49 | } 50 | }catch (Exception e){ 51 | 52 | }finally { 53 | count--; 54 | } 55 | } 56 | end.countDown(); 57 | } 58 | } 59 | 60 | 61 | public static void main(String[] args) throws InterruptedException { 62 | int threadCount=10; 63 | end=new CountDownLatch(threadCount); 64 | int count=20; 65 | AtomicInteger got=new AtomicInteger(); 66 | AtomicInteger notGot=new AtomicInteger(); 67 | for (int i=0;i这边得上网查 10 | 11 | HashMap:并发执行put操作时会引起死循环,因为多线程会导致HashMap的Entry链表形成环形数据结构,Entry 12 | 的next节点永远不为空,就会产生死循环获取Entry 13 | 14 | HashTable:HashTable使用synchronized来保证线程安全,在线程竞争激烈的情况下其效率十分低下 15 | 16 | ConcurrentHashMap:使用锁分段技术:首先将数据分成一段一段地存储,然后给每一段数据配一把锁, 17 | 当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。 18 | 19 | ###2.ConcurrentHashMap的结构 20 | 21 | Segment数组+HashEntry数组 22 | 23 | segment是一种可重入锁,HashEntry用于存储键值对数据 24 | 25 | 一个segment里包含一个HashEntry数组,每个segment守护着一个HashEntry数组里的元素, 26 | 当对HashEntry数组的数据进行修改时,必须首先获得与它对应的segment锁 27 | 28 | ###3、4初始化和定位segment 29 | 30 | 这里主要讲了hash的散列算法之类的,目前不用去研究 31 | 32 | ###5.ConCurrentHashMap的操作 33 | 34 | (1)get 35 | 36 | 整个get过程不需要加锁,除非读到的值是空的才会加锁重读。 37 | 38 | 做法:get方法里将要使用的共享变量都定义成volatile类型。定义成volatile的变量,能够在线程之间保持可见性, 39 | 能够被多线程同时读,并且保证不会读到过期的值,但是只能被单线程写。 40 | 41 | 之所以不会读到过期的值,是因为根据java内存模型的happens before原则, 42 | 对volatile变量的写入操作先于读操作。 43 | 44 | >上面这是用volatile替换锁的经典应用场景。 45 | 46 | (2)put 47 | 48 | 加锁、扩容、定位添加 49 | 50 | (3)size 51 | 52 | 先尝试两次不加锁的统计(直接累加各segment的count),比对modcount变量看是不是发生变化,如果发生变化再通过加锁的方式统计。 53 | 54 | ##二、ConcurrentLinkedQueue 55 | 56 | 实现线程安全的队列两种方式: 57 | 58 | 1.阻塞方式:使用一个锁(入队出队同一个)或两个锁(入队出队不同锁) 59 | 60 | 2.非阻塞方式:使用循环CAS 61 | 62 | ###1.入队 63 | 64 | 入队过程: 65 | 66 | (1)将入队节点设置成当前队列尾节点的下一个节点 67 | 68 | (2)更新tail节点,如果tail节点的next节点不为空,则将入队节点设置成tail节点, 69 | 如果tail节点的next节点为空,则将入队节点设置成tail的next节点,所以tail节点并不总是尾节点 70 | 71 | 为什么不设计成tail永远是尾节点? 72 | 73 | 这样做每次都需要使用循环cas更新tail节点,而大师的做法可以明显减少循环cas的次数,虽然会 74 | 增加定位真实尾节点的时间,但是实际开销还是减少了 75 | 76 | ###2.出队 77 | 78 | 并不是每次出队时都更新head节点,当head节点里有元素时,直接弹出head里的元素,而不会更新head节点。 79 | 只有head节点里没有元素时,出队操作才会更新head节点。这种做法也是通过hops变量来减少使用CAS更新head节点的 80 | 消耗,从而提高出队效率。 81 | 82 | ##三、Java中的阻塞队列 83 | 84 | 阻塞队列:支持阻塞的插入和移除方法 85 | 86 | (1)支持阻塞的插入方法:当队列满时,队列会阻塞插入元素的线程,直到队列不满 87 | 88 | (2)支持阻塞的移除方法:队列为空时,获取元素的线程会等待队列变为非空 89 | 90 | 抛出异常:add、remove 91 | 返回特殊值:offer、poll 92 | 一直阻塞:put、take 93 | 94 | java提供了7种阻塞队列: 95 | 96 | 1. ArrayBlockingQueue 97 | 98 | 数组实现的有界队列,FIFO,默认情况下不保证线程公平的访问队列 99 | 100 | 2. LinkedBlockingQueue 101 | 102 | 用链表实现的有界阻塞队列,FIFO 103 | 104 | 3. PriorityBlockingQueue 105 | 106 | 支持优先级的无界阻塞队列,默认情况下元素采取自然顺序升序排列,也可以自定义比较方法来指定元素排列规则 107 | 108 | 4. DelayQueue 109 | 110 | 支持延时获取元素的无界阻塞队列。队列使用PriorityQueue来实现。队列中的元素必须实现Delay接口,在创建 111 | 元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。 112 | 113 | DelayQueue的应用场景: 114 | 115 | 缓存系统的设计:用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelqyQueue,一旦能从DelayQueue 116 | 中获取元素,表示缓存有效期到了。 117 | 定时任务调度:使用DelayQueue保存当天将会执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行, 118 | 比如TimerQueue就是使用DelayQueue实现的。 119 | 120 | 5. SynchronousQueue、 121 | 122 | 不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素。 123 | 124 | SynchronousQueue可以看做一个传球手,负责把生产者线程处理的数据直接传递给消费者线程,队列本身并不 125 | 存储任何元素,非常适合传递性场景 126 | 127 | 6. LinkedTransferQueue 128 | 129 | 由链表结构组成的无界阻塞TransferQueue队列,注意其transfer方法 130 | 131 | 7. LinkedBlockingDeque 132 | 133 | 链表结构组成的双向阻塞队列 134 | 135 | ###阻塞队列的实现 136 | 137 | 使用通知模式实现(condition) 138 | 139 | ##四、Fork/Join框架 140 | 141 | 一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。 142 | 143 | 工作窃取算法 -------------------------------------------------------------------------------- /src/chapter4/chapter4.md: -------------------------------------------------------------------------------- 1 | #第四章 Java并发编程基础 2 | 3 | ##一、线程简介 4 | 5 | 使用多线程的原因: 6 | 7 | 1.更多的处理器核心:一个线程在一个时刻只能运行在一个处理器核心上 8 | 2.更快的响应时间 9 | 3.更好的编程模型 10 | 11 | 线程优先级: 12 | 13 | 操作系统基本采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片, 14 | 当线程的时间片用完了就会发生线程调度,并等待着下次分配,线程分配到的时间片多少就决定了线程使用 15 | 处理器资源的多少。 16 | 17 | 线程的状态:6种,在给定的时刻只能处于一种状态 18 | 19 | NEW:初始状态,线程被构建,但还没有调用start方法 20 | RUNNABLE:运行状态,java线程将就绪和运行两种状态统称为运行状态 21 | BLOCKED:阻塞状态,表明线程阻塞于锁 22 | WAITING:等待状态,等待其他线程的通知或中断 23 | TIME_WAITING:超时等待状态,可以在指定的时间自行返回 24 | TERMINATED:终止状态,表示当前线程已经执行完毕 25 | 26 | Daemon线程(守护线程) 27 | 28 | 当一个java虚拟机中不存在非守护线程时,虚拟机将会退出。 29 | 30 | ps. 在构建Daemon线程时,不能依靠finally块中的内容来确保执行关闭或清理资源的逻辑 31 | 32 | ##二、启动和终止线程 33 | 34 | 一个新构造的线程对象是由其父线程来进行空间分配的,而子线程的各种属性继承自父线程,同时还会分配一个唯一的ID。 35 | 36 | 启动线程:start方法:当前线程同步告知虚拟机:只要线程规划器空闲,应立即启动调用start方法的线程 37 | 38 | 中断: 39 | 40 | 可以理解为线程的一个标识符属性,它标识一个运行中的线程是否被其他线程进行了中断操作。 41 | 线程通过isInterrupted()方法判断是否被中断,也可以调用静态方法Thread.interrupted() 42 | 对当前的线程中断标识位进行复位。 43 | 44 | 从java的API可以看出,许多声明抛出InterruptedException的方法在抛出这个异常前,java虚拟机会 45 | 先将该线程的中断标志位清除,然后再抛异常。此时调用isInterrupted返回false 46 | 47 | ##三、线程间通信(重点) 48 | 49 | ###1、volatile和synchronized关键字 50 | 51 | volatile:告知程序任何对该变量的访问均需要从共享内存中获取,而对他的改变必须同步刷新回主内存, 52 | 他能保证所有线程对变量访问的可见性。 53 | synchronized:确保多个线程在同一个时刻,只能有一个线程处于方法或同步块中, 54 | 保证了线程对变量访问的可见性和排他性。 55 | 56 | 关于synchronized:本质是对一个对象的监视器(monitor)的获取,而这个获取过程是排他的,也就是同一时刻 57 | 只能有一个线程获取到有synchronized所保护对象的监视器。任意一个对象都拥有自己的监视器。 58 | 59 | 任意线程对Object的访问,首先要获得Object的监视器。如果获取失败,线程进入同步队列, 60 | 线程状态变为BLOCKED。当访问Object的前驱(获得了锁的线程)释放了锁,则该释放操作 61 | 唤醒阻塞在同步队列中的线程,使其重新尝试对监视器的获取。 62 | 63 | ###2、等待/通知机制(生产者-消费者模型) 64 | 65 | 等待/通知机制,是指一个线程A调用了对象O的wait方法进入等待状态,而另一个线程B调用了对象O的notify 66 | 或notifyall方法,线程A收到通知后从对象O的wait方法返回,进而执行后续操作。 67 | 68 | 注意: 69 | 70 | (1)使用wait、notify、notifyAll时需要先对调用对象加锁 71 | 72 | (2)调用wait方法后,线程状态由RUNNING变为WAITING,并将当前线程防止到对象的等待队列 73 | 74 | (3)notify或notifyAll方法调用后,等待线程依旧不会从wait返回,需要调用notify或 75 | notifyAll的线程释放锁之后,等待线程才有机会从wait返回 76 | 77 | (4)notify方法将等待队列中的等待线程从等待队列中移到同步队列中,被移动的线程从WAITING变为BLOCKED 78 | 79 | (5)从wait方法返回的前提是获得了调用对象的锁 80 | 81 | P101页的图 82 | 83 | ###3、等待/通知经典范式 84 | 85 | 等待方:1.获取对象的锁 86 | 2.如果条件不满足,那么调用对象的wait方法,被通知后仍要检查条件 87 | 3.条件满足则执行对应的逻辑 88 | 89 | 伪代码: 90 | synchronized(对象){ 91 | while(条件不满足){ 92 | 对象.wait(); 93 | } 94 | 对应的处理逻辑 95 | } 96 | 97 | 通知方:1.获得对象的锁 98 | 2.改变条件 99 | 3.通知所有等待在对象上的线程 100 | 101 | 伪代码: 102 | synchronized(对象){ 103 | 改变条件 104 | 对象.notify(); 105 | } 106 | 107 | ###4、管道输入输出流 108 | 109 | PipedOutputStream、PipedInputStream(字节数据) 110 | 111 | PipedReader、PipedWriter(字符) 112 | 113 | 对于piped类型的流,使用时必须先调用connect方法进行绑定,否则会抛出异常 114 | 115 | ###5、Thread.join() 116 | 117 | 含义:当前线程等待thread线程终止之后才从thread.join返回。 118 | 另外还有两个join(long millis)具备超时特性的方法(如果线程在给定的时间内没有终止,那么将会从该超时方法返回)。 119 | 120 | ###6、ThreadLocal 121 | 122 | ##四、应用实例 123 | 124 | 125 | -------------------------------------------------------------------------------- /src/chapter5/chapter5.md: -------------------------------------------------------------------------------- 1 | #第五章 java中的锁(使用与实现) 2 | 3 | ##一、Lock接口 4 | 5 | 提供了synchronized不具有的特性: 6 | 7 | 1.尝试非阻塞地获取锁:tryLock(),调用方法后立刻返回 8 | 2.能被中断地获取锁:lockInterruptibly():在锁的获取中可以中断当前线程 9 | 3.超时获取锁:tryLock(time,unit),超时返回 10 | 11 | Lock接口的实现基本都是通过聚合了一个同步器的子类来完成线程访问控制的。 12 | 13 | ##二、队列同步器 14 | 15 | 队列同步器AbstractQueuedSynchronizer是用来构建锁或其他同步组件的基础框架。 16 | 它使用一个int成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作。 17 | 18 | 同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语义。 19 | 理解两者的关系: 20 | 21 | 锁是面向使用者的,它定义了使用者与锁交互的接口,隐藏了实现细节; 22 | 同步器是面向锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待 23 | 和唤醒等底层操作。 24 | 25 | ###队列同步器的实现分析 26 | 27 | ####1.同步队列 28 | 29 | 通过一个FIFO双向队列来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息 30 | 构造成一个Node并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试 31 | 获取同步状态。 32 | 33 | 首节点是获取同步状态成功的节点,首节点在释放同步状态时,会唤醒后继节点,而后继节点在获取同步状态成功时将自己设置为首节点。 34 | 35 | ####2.独占式同步状态获取和释放 36 | 37 | public final void acquire(int arg) { 38 | if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 39 | selfInterrupt(); 40 | } 41 | 42 | 43 | 44 | 45 | 代码分析: 46 | 首先尝试获取同步状态,如果获取失败,构造独占式同步节点并将其加入到节点的尾部, 47 | 然后调用acquireQueued,使节点一死循环的方式去获取同步状态,如果获取不到就阻塞节点中的线程。 48 | 49 | 两个死循环:入队、入队后 50 | 51 | 只有前驱节点是头结点才能尝试获取同步状态,原因: 52 | 53 | 1. 头结点是成功获取到同步状态的节点,而头结点的线程释放了同步状态后,将会唤醒其后继节点,后继节点的线程 54 | 被唤醒后需要检查自己的前驱节点是否为头节点。 55 | 56 | 2. 维护同步队列的FIFO原则 57 | 58 | 59 | p128页的图非常重要 60 | 61 | 总结:在获取同步状态时,同步器维护一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋, 62 | 移出队列(停止自旋)的条件是前驱节点是头结点且成功获取了同步状态。在释放同步状态时,同步器调用tryRelease 63 | 方法释放同步状态,然后唤醒头结点的后继节点 64 | 65 | 66 | ####3.共享式同步状态获取和释放 67 | 68 | 主要区别:同一时刻是否有多个线程同时获取到同步状态 69 | 70 | 共享式访问资源时,其他共享式的访问均被允许,而独占式访问被阻塞。独占式访问资源时,同一时刻其他访问均被阻塞。 71 | 72 | 73 | ##三、重入锁(ReentrantLock) 74 | 75 | synchronized关键字隐式地支持重入 76 | 77 | ReentrantLock不像synchronized隐式支持,在调用lock方法时,已经获取到锁的线程,能够再次调用lock方法获取锁而不被阻塞。 78 | 79 | 事实上,公平的锁机制往往没有非公平的效率高,但是公平锁的好处在于:公平锁能够减少“饥饿”发生的概率,等待越久的请求越是能够得到 80 | 优先满足。 81 | 82 | ###1.重入的实现 83 | 84 | 两个问题:再次获取锁、最终释放 85 | 86 | ###2.公平锁与非公平锁的区别: 87 | 88 | 如果一个锁是公平的,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是FIFO 89 | 90 | 公平锁:CAS成功,且是队列的首节点 91 | 非公平锁:CAS成功即可 92 | 93 | 重入锁的默认实现是非公平锁,原因:虽然会导致饥饿,但是非公平锁的的开销少(线程切换次数少),从而可以有更高的吞吐量。 94 | 95 | ##四、读写锁(ReentrantReadWriteLock) 96 | 97 | 前文中的锁基本都是排他锁,在同一时刻只允许一个线程访问。 98 | 99 | 读写所在同一时刻可以允许多个读线程访问,但在写线程访问时,所有读线程和其他写线程均被阻塞。(保证了写操作的可见性) 100 | 101 | 读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。 102 | 103 | ###读写锁的实现分析 104 | 105 | ####1.读写状态的设计 106 | 107 | 依赖自定义同步器,读写锁的自定义同步器需要在同步状态(一个int值)上维护多个读线程和一个写线程的状态,高16位表示读,低16位 108 | 表示写。 109 | 110 | 位运算 111 | 112 | ####2.写锁的获取与释放 113 | 114 | 写锁是一个支持重入的排他锁,如果当前线程已经获取了写锁,则增加写状态。 115 | 如果当前线程在获取写锁时,读锁已经被获取或者该线程不是已经获取写锁的线程,则当前线程进入等待状态。 116 | 117 | ####3.读锁的获取与释放 118 | 119 | 在没有其他写线程访问时,读锁总会被成功地获取。如果写锁已经被其他线程获取,则进入等待状态。 120 | 121 | 读状态的线程安全由CAS保证 122 | 123 | ####4.锁降级(写锁降级成为读锁) 124 | 125 | 定义:把持住写锁,再获取到读锁,随后释放写锁的过程 126 | 127 | writeLock.lock(); 128 | readLock.lock(); 129 | writeLock.unlock(); 130 | 131 | >这边不是很理解。。。。 132 | 133 | 锁降级中读锁获取的必要性: 134 | 135 | 为了保证数据的可见性,如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程获取了写锁 136 | 并修改了数据,那么当前线程无法感知到数据的更新.如果当前线程获取读锁,则另一个线程会被阻塞, 137 | 直到当前线程使用数据并释放锁之后,另一个线程才能获取写锁进行数据更新。 138 | 139 | ##五、LockSupport工具 140 | 141 | 略,感觉不是很重要 142 | 143 | ##六、Condition接口 144 | 145 | 略,等待通知模式,有空回头再看 146 | 147 | -------------------------------------------------------------------------------- /src/chapter4/DefaultThreadPool.java: -------------------------------------------------------------------------------- 1 | package chapter4; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | import java.util.concurrent.atomic.AtomicLong; 9 | 10 | /** 11 | * Created by xdcao on 2017/5/4. 12 | */ 13 | public class DefaultThreadPool implements ThreadPool { 14 | 15 | private static final int MAX_WORKER_NUMBERS=10; 16 | 17 | private static final int DEFAULT_WORKER_NUMBERS=5; 18 | 19 | private static final int MIN_WORKER_NUMBERS=1; 20 | 21 | private final LinkedList jobs=new LinkedList(); 22 | 23 | private final List workers= Collections.synchronizedList(new ArrayList()); 24 | 25 | private int workerNum=DEFAULT_WORKER_NUMBERS; 26 | 27 | private AtomicLong threadNum=new AtomicLong(); 28 | 29 | 30 | public DefaultThreadPool() { 31 | initializeWorkers(DEFAULT_WORKER_NUMBERS); 32 | } 33 | 34 | public DefaultThreadPool(int workerNum){ 35 | this.workerNum=workerNum>MAX_WORKER_NUMBERS?MAX_WORKER_NUMBERS:workerNumMAX_WORKER_NUMBERS){ 69 | num=MAX_WORKER_NUMBERS-this.workerNum; 70 | } 71 | initializeWorkers(num); 72 | this.workerNum+=num; 73 | } 74 | } 75 | 76 | @Override 77 | public void removeWorker(int num) { 78 | synchronized (jobs){ 79 | if(num>=this.workerNum){ 80 | throw new IllegalArgumentException("beyond workNum"); 81 | } 82 | int count=0; 83 | while (count本地内存是JMM的一个抽象概念,并不真实存在,涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化 22 | 23 | 线程A与线程B之间要通信必须经过以下两个步骤: 24 | 25 | 1、线程A把本地内存A中更新过的共享变量刷新到主内存中去 26 | 2、线程B到主内存中去读取A之前已更新过的共享变量 27 | 28 | JMM通过控制主内存与每个线程的本地内存之间的交互来为java程序员提供内存可见性保证 29 | 30 | ###3、指令重排序 31 | 32 | 源码--------》编译器优化重排序-------》指令级并行重排序----------》内存系统重排序---------》最终的指令序列 33 | 34 | 后面两个属于处理器的重排序 35 | 36 | 对于编译器,JMM的编译器会禁止特定类型的编译器重排序。对于处理器重排序,JMM的处理器重排序规则 37 | 会要求Java编译器在生成指令序列时,插入特定的内存屏障指令,从而禁止特定类型的处理器重排序。 38 | 39 | ###4、并发模型分类 40 | 41 | 四种内存屏障: 42 | 43 | LoadLoad:确保load1的数据先于laod2及后续所有load指令进行装载 44 | StoreStore:确保store1的数据对其他处理器的可见性先于store2及后续所有存储指令 45 | LoadStore:确保load装载先于store的存储刷新到内存 46 | StoreLoad:该屏障前的指令全部完成之后才会执行后面的指令(开销大) 47 | 48 | ###5、先行发生(happens-before) 49 | 50 | JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。 51 | 52 | ##二、重排序 53 | 54 | 数据依赖性 55 | 56 | 在单线程程序中,对存在控制依赖的操作重排序不会改变执行结果;但在多线程程序中,对存在控制 57 | 依赖的操作重排序,可能会改变程序的执行结果。 58 | 59 | 详见30页的例子 60 | 61 | ##三、顺序一致性 62 | 63 | ##四、volatile内存语义 64 | 65 | volatile变量特性: 66 | 67 | 可见性:对一个volatile变量的读,总是能看到(任意线程)对这个变量最后的写入 68 | 原子性:对任意单个volatile变量的读、写具有原子性(包括long、double),但类似volatile++ 69 | 这种复合操作不具有原子性。 70 | 71 | volatile写-读的内存语义: 72 | 73 | 写:当写一个volatile变量时,JMM会把线程对应的本地内存中的共享变量值刷新到主内存 74 | 75 | 读:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来从主内存中读取共享变量 76 | 77 | ###内存语义的实现: 78 | 79 | 为了实现volatile的内存语义,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型 80 | 的处理器重排序。 81 | 82 | JMM内存屏障插入策略: 83 | 84 | 在每个volatile写操作前面插入StoreStore屏障 85 | 在每个volatile写操作后面插入StoreLoad屏障 86 | 在每个volatile读操作后面插入一个LoadLoad、一个LoadStore 87 | 88 | ##五、锁的内存语义 89 | 90 | 锁的释放和获取的内存语义(和volatile一样) 91 | 92 | 线程释放锁时,会把本地内存中的共享变量刷新到主内存中(对应volatile写) 93 | 94 | 线程获取锁时,会将线程对应的本地内存置为无效,从而临界区代码必须从主内存读取共享变量(对应volatile读) 95 | 96 | 锁内存语义的实现:分析ReentrantLock源码 97 | 98 | 公平锁和非公平锁语义总结: 99 | 100 | 公平锁和非公平锁释放时,最后都要写一个volatile变量state 101 | 公平锁获取时,首先会去读volatile变量 102 | 非公平锁获取时,首先会用CAS更新volatile变量,这个操作同时具有volatile读和volatile写的内存语义 103 | 104 | 可以看出:锁释放-获取的内存语义的实现至少有下面两种方式 105 | 106 | 1、利用volatile变量的写-读所具有的内存语义 107 | 2、利用CAS所附带的volatile读和volatile写的内存语义 108 | 109 | CAS是如何同时具有volatile读和volatile写的内存语义的? 110 | 111 | >多处理器环境,会为cmpxchg指令加上lock前缀,单处理器不用加(单处理器会维护自身的顺序一致性) 112 | 113 | Lock前缀: 114 | 115 | 1、确保对内存的读-改-写操作原子执行,使用缓存锁定来保证 116 | 2、禁止该指令与之前和之后的读和写指令重排序 117 | 3、把写缓冲区的所有数据刷新到内存中 118 | 119 | 上面2、3两点具有的内存屏障效果,足以同时实现volatile读和volatile写的内存语义 120 | 121 | ###concurrent包的通用实现模式 122 | 123 | 首先,声明共享变量为volatile 124 | 然后,使用CAS的原子条件更新来实现线程之间的同步 125 | 同时,配合以volatile的读、写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信 126 | 127 | ##六、final域的内存语义 128 | 129 | ###1、final域的重排序规则 130 | 131 | (1)写:在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作不能重排序 132 | 133 | (2)读:初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作不能重排序。 134 | 135 | ###2、写final域的重排序规则 136 | 137 | 禁止把final域的写重排序到构造函数之外包含两方面: 138 | 139 | 1、编译器: JMM禁止编译器把final域的写重排序到构造函数之外 140 | 2、处理器: 编译器会在final域的写之后,构造函数return之前,插入一个StoreStore屏障。这个屏障禁止处理器把final域 141 | 的写重排序到构造函数之外 142 | 143 | 上述规则可以确保: 144 | 145 | 在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了,而普通域不具有这个保障。 146 | 147 | ###3、读final域的重排序规则 148 | 149 | 处理器:在一个线程中,初次读对象引用与初次读该对象所包含的final域,JMM禁止处理器重排序这两个操作 150 | 151 | 编译器:编译器会在读final域操作的前面插入一个LoadLoad屏障 152 | 153 | 上述重排序规则可以确保:在读一个对象的final域之前,一定会先读包含这个final域的对象的引用 154 | 155 | ###4、当final域为引用类型 156 | 157 | 对于引用类型,写final域的重排序规则增加下面的约束: 158 | 159 | 在构造函数内对一个final引用对象的成员域的写入,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量, 160 | 这两个操作不能重排序。 161 | 162 | ###5、为什么final域不能从构造函数内溢出 163 | 164 | 在构造函数返回前,被构造对象的引用不能为其他线程所见,因为此时的final域可能还没有初始化。 165 | 166 | ##七、happens-before 167 | 168 | as-if-serial语义给编写单线程程序的程序员创造了一个幻境:单线程程序是按程序的顺序执行的。happens-before关系给编写正确同步的 169 | 多线程程序员创造了一个幻境:正确同步的多线程程序是按happens-before指定的顺序执行的。 170 | 171 | 这么做的目的:为了在不改变程序的执行结果的前提下,尽可能地提高程序执行的并行度。 172 | 173 | ##八、双重检查锁定与延迟初始化 174 | 175 | 在单例的懒汉模式中,必须给实例添加volatile修饰符 176 | 177 | 原因:在构造实例时,对象引用指针的操作和初始化操作可能会被重排序, 178 | 这就导致在if(instance==null)的时候认为对象已经创建,但这个时候还没有进行初始化 179 | 180 | 1.分配对象的内存空间 181 | 2.初始化对象 182 | 3.设置instance指向内存空间 183 | 4.初次访问对象 184 | 185 | 3和2可能会被重排序,导致1342这样的问题 186 | 187 | 解决方式: 188 | 189 | 1. volatile 190 | 191 | 2. 基于类初始化的解决方案(还没好好看,记得回头补上)P72 192 | 193 | 194 | -------------------------------------------------------------------------------- /src/chapter2/chapter2.md: -------------------------------------------------------------------------------- 1 | #第二章 Java并发机制的底层实现原理 2 | 3 | ##一、volatile的应用 4 | 5 | 如果volatile变量修饰符使用得当,它比synchronized的使用和执行成本更低, 6 | 因为它不会引起线程上下文的切换和调度。 7 | 8 | ###1. volatile的定义和实现原理 9 | 10 | 有volatile变量修饰符的共享变量进行写操作的时候会多出一个lock前缀的指令 11 | 12 | lock前缀的指令在多核处理器中引发两件事情 13 | 14 | (1)将当前处理器缓存行的数据写回内存 15 | 16 | (2)这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效 17 | 18 | >为了提高处理速度,处理器不直接和内存通信,而是先将内存中的数据读到cache中 19 | 再进行操作,但操作完全不知道何时会写到内存。如果对声明了volatile变量的进行写操作 20 | ,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。 21 | 但是,就算写回到内存,如果其他处理器缓存的值还是旧的,再进行计算操作就会有问题。所以, 22 | 多处理器下,要实行缓存一致性协议,每个处理器通过嗅探在总线上传播的数据来检查自己缓存的 23 | 值是不是过期,如果过期,就将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行 24 | 修改操作的时候,会重新从系统内存中把数据读到处理器缓存中。 25 | 26 | ###2. volatile使用优化 27 | 28 | 将共享变量追加到64字节(貌似不生效了,在源码中没看到) 29 | 30 | LinkedTransferQueue 31 | 32 | 处理器的Cache的高速缓存行是64字节,不支持部分填充缓存行。通过追加到64字节的方式 33 | 来填满高速缓冲区的缓存行,避免头结点和尾节点加载到同一个缓存行,使头、尾节点在修改时不会互相锁定。 34 | 35 | 并不是所有使用volatile变量的时候都要追加到64字节 36 | 37 | 1. 缓存行非64字节宽 38 | 2. 共享变量不会被频繁读写 39 | 40 | ##二、synchronized的实现原理与应用(重量级锁) 41 | 42 | synchronize基础:java中的每个对象都可以作为锁。具体表现为3种形式: 43 | 1. 对于普通同步方法,锁是当前实例对象 44 | 2. 对于静态同步方法,锁是当前类的class对象 45 | 3. 对于同步方法块,锁是synchronize括号里的对象 46 | 47 | >锁到底存在哪里?锁里面会存储什么信息? 48 | 49 | Synchronized在JVM中的实现原理: 50 | 51 | JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但两者的表现细节不同。 52 | 53 | 代码块同步使用monitorenter和monitorexit指令实现,而方法同步是使用另外一种实现方式实现的,细节 54 | 并没有在JVM中说明。但是,方法同步同样可以使用上述两个指令实现。 55 | >monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处 56 | ,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联, 57 | 且当一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的 58 | monitor的所有权,即尝试获得对象的锁。 59 | 60 | 在博客上找一下 61 | 62 | ###1、java对象头 63 | 64 | 对象头三种数据:Mark Word(对象hashcode、锁信息等)、Class元数据地址、数组长度(数组类型的对象才有), 65 | 这三种数据分别占据一个Word(4字节)。 66 | 67 | mark word里存储的数据会随着锁标志位的变化而变化 68 | 69 | ###2、锁的升级与对比 70 | 71 | from JDK1.6 72 | 73 | 锁一共有4中状态,由低到高为:无锁、偏向锁、轻量级锁、重量级锁,这几个状态会随着竞争情况逐渐升级。 74 | 锁可以升级但不能降级(提高获得锁和释放锁的效率) 75 | 76 | ####偏向锁: 77 | 78 | 大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价 79 | 更低,引入了偏向锁。 80 | 81 | 当一个线程访问同步块并获取锁时,会在对象头和栈帧的锁记录里存储锁偏向的线程ID,以后该线程在进入 82 | 和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单测试对象头里的mark word里是否存储着指向 83 | 当前线程的偏向锁。 84 | 85 | 如果测试成功,表示线程已经获得了锁。如果测试失败,则需要再测试一下mark word中偏向锁的标识是否 86 | 设置成1:如果没有设置,则使用CAS竞争锁;如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。 87 | 88 | (1)偏向锁的撤销 89 | 90 | 偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的 91 | 线程才会释放锁。偏向锁的撤销,需要等待全局安全点(在这个时间上没有正在执行的字节码)。 92 | 93 | 首先暂停拥有偏向锁的线程,,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则 94 | 将对象头设置成无锁状态;如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的 95 | 锁记录和对象头的mark word要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁。 96 | 97 | 这一块还是不清楚,得上网再看看 98 | 99 | (2)关闭偏向锁 100 | 101 | java6、7默认启用偏向锁,但是在程序启动后会有几秒延迟,如有必要可以关闭延迟 102 | -XX:BiasedLockingStartupDelay=0,如果确定程序里所有的锁通常处于竞争状态,可以通过JVM参数关闭偏向锁: 103 | -XX:UseBaisedLocking=false,那么程序会默认进入轻量级锁状态 104 | 105 | ####轻量级锁 106 | 107 | (1)加锁 108 | 109 | 线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头中的 110 | Mark word复制到锁记录中(Displaced Mark Word)。然后线程尝试使用CAS将对象头中的 111 | Mark word替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁, 112 | 当前线程便尝试使用自旋来获取锁。 113 | 114 | (2)解锁 115 | 116 | 解锁时,会使用原子的CAS操作将Displaced Mark Word替换回到对象头,如果成功,则表示没有竞争发生。 117 | 如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。 118 | 119 | 因为自旋会消耗CPU,为了避免无用的自旋,一旦升级成重量级锁,就不会再恢复到轻量级锁状态。当 120 | 锁处于这个状态下,其他线程试图获取锁时,都会被阻塞住,当持有锁的线程释放锁之后会唤醒这些线程, 121 | 被唤醒的线程就会进行新一轮的夺锁之争。 122 | 123 | ###3、锁的优缺点对比 124 | 125 | 偏向锁: 126 | 优点:加锁解锁不需要额外的消耗 127 | 缺点:如果线程间存在锁竞争,会带来额外的锁撤销的消耗 128 | 适用于只有一个线程访问同步块的场景 129 | 130 | 轻量级锁: 131 | 优点:竞争的线程不会阻塞,提高了程序的响应速度 132 | 缺点:如果始终得不到锁竞争的线程,使用自旋会消耗CPU 133 | 使用场景:追求响应时间,同步块执行速度非常快 134 | 135 | 重量级锁: 136 | 优点:线程竞争不使用自旋,不会消耗CPU 137 | 缺点:线程阻塞,响应时间慢 138 | 适用场景:追求吞吐量,同步块执行时间较长 139 | 140 | ##三、原子操作的实现原理 141 | 142 | ###1、术语 143 | 144 | CAS:比较并交换 145 | 缓存行:缓存的最小操作单位 146 | 内存顺序冲突:一般由假共享引起,出现内存顺序冲突时,CPU必须清空流水线 147 | 假共享:多个CPU同时修改同一个缓存行的不同部分而引起其中一个CPU的操作无效 148 | 149 | ###2、处理器实现原子操作 150 | 151 | (1)通过总线锁保证原子性 152 | 153 | 如果多个处理器同时对共享变量进行读改写操作(i++),那么共享变量就会被多个处理器同时进行操作, 154 | 这样读改写操作就不是原子的,操作完之后共享变量的值会和期望的不一致。 155 | 156 | 总线锁:当一个处理器在总线上输出LOCK #信号时,其他处理器的请求将被阻塞住,那么该处理器 157 | 可以独占共享内存。 158 | 159 | (2)使用缓存锁保证原子性 160 | 161 | 总线锁定把CPU和内存之间的通信锁住了,锁定期间,其他处理器不能操作其他内存地址的数据, 162 | 因此总线锁定的开销比较大。 163 | 164 | 缓存锁定:内存区域如果被缓存在处理器的缓存行中,并且在LOCK期间被锁定,那么当他执行锁操作 165 | 会写到内存时,处理器不在总线上声言LOCK #信号,而是修改内部的内存地址,并允许它的缓存一致性机制来保证 166 | 操作的原子性,因为缓存一致性机制会阻止同时修改由两个以上处理器缓存的内存区域数据,当 167 | 其他处理器回写已被锁定的缓存行的数据时,会使缓存行无效。 168 | 169 | 两种情况不会使用缓存锁定: 170 | 1、数据不能被缓存在处理器内部,或操作的数据跨多个缓存行,此时用总线锁定 171 | 2、有些处理器不支持缓存锁定 172 | 173 | ###3、Java实现原子操作(锁和循环CAS) 174 | 175 | (1)循环CAS机制 176 | 177 | 处理器的CMPXCHG指令 178 | 179 | 自旋CAS:循环进行CAS操作直至成功为止 180 | 181 | CAS实现原子操作的三大问题: 182 | 183 | 1、ABA问题:A到B再到A,CAS检查值时会以为没有发生变化,实际却发生了变化,解决方式是 184 | 在变量前面追加版本号:1A到2B到3C 185 | 2、循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销 186 | 3、只能保证一个共享变量的原子操作:此时用锁或者将几个共享变量合并 187 | 188 | (2)锁机制 189 | 190 | 除了偏向锁,另外两种锁都使用了循环CAS机制,即当一个线程进入同步块的时候使用循环CAS的方式 191 | 获取锁,当他退出同步块的时候使用循环CAS释放锁。 -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | acquire 71 | 72 | 73 | 74 | 76 | 77 | 80 | 81 | 82 | 115 | 116 | 117 | 118 | 119 | true 120 | DEFINITION_ORDER 121 | 122 | 123 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 153 | 154 | 157 | 158 | 159 | 160 | 163 | 164 | 167 | 168 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 198 | 199 | 200 | 201 | 202 | 203 | 206 | 207 | 220 | 221 | 222 | 223 | 226 | 227 | 240 | 241 | 242 | 243 | 246 | 247 | 260 | 261 | 262 | 263 | 266 | 267 | 280 | 281 | 282 | 283 | 286 | 287 | 300 | 301 | 302 | 307 | 308 | 309 | 335 | 336 | 337 | 362 | 363 | 370 | 371 | 372 | 385 | 386 | 387 | 388 | 393 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 419 | 437 | 444 | 445 | 446 | 447 | 448 | 449 | 466 | 467 | 488 | 501 | 502 | 511 | 515 | 516 | 517 | 524 | 527 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 611 | 612 | 613 | 623 | 624 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 658 | 665 | 666 | project 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 1493305898572 714 | 727 | 728 | 1493306114538 729 | 734 | 735 | 1493392674136 736 | 741 | 742 | 1493607922099 743 | 748 | 749 | 1493608678194 750 | 755 | 756 | 1493696439287 757 | 762 | 763 | 1493791913475 764 | 769 | 770 | 1493818454397 771 | 776 | 777 | 1493905227024 778 | 783 | 784 | 1494007819442 785 | 790 | 791 | 1494259226228 792 | 797 | 798 | 1495034700490 799 | 804 | 805 | 1495034707647 806 | 811 | 812 | 1495114711465 813 | 818 | 819 | 1495207639128 820 | 825 | 826 | 1495374805901 827 | 832 | 833 | 1495376355939 834 | 839 | 842 | 843 | 845 | 846 | 847 | 848 | 849 | 850 | 851 | 852 | 853 | 854 | 855 | 856 | 857 | 858 | 859 | 860 | 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 883 | 884 | 886 | 887 | 888 | 889 | 890 | 891 | 892 | 893 | 894 | 895 | 896 | 897 | 898 | 899 | 900 | 901 | 903 | 904 | 905 | 907 | 908 | 909 | 910 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | 952 | 953 | 954 | 955 | 956 | 957 | 958 | 959 | 960 | 961 | 962 | 963 | 964 | 965 | 966 | 967 | 968 | 969 | 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 | 989 | 990 | 991 | 992 | 993 | 994 | 995 | 996 | 997 | 998 | 999 | 1000 | 1001 | 1002 | 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | 1011 | 1012 | 1013 | 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1022 | 1023 | 1024 | 1025 | 1026 | 1027 | 1028 | 1029 | 1030 | 1031 | 1032 | 1033 | 1034 | 1035 | 1036 | 1037 | 1038 | 1039 | 1040 | 1041 | 1042 | 1043 | 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | 1064 | 1065 | 1066 | 1067 | 1068 | 1069 | 1070 | 1071 | 1072 | 1073 | 1074 | 1075 | 1076 | 1077 | 1078 | 1079 | 1080 | 1081 | 1082 | 1083 | 1084 | 1085 | 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | 1106 | 1107 | 1108 | 1109 | 1110 | 1111 | 1112 | 1113 | 1114 | 1115 | 1116 | 1117 | 1118 | 1119 | 1120 | 1121 | 1122 | 1123 | 1124 | 1125 | 1126 | 1127 | 1128 | 1129 | 1130 | 1131 | 1132 | 1133 | 1134 | 1135 | 1136 | 1137 | 1138 | 1139 | 1140 | 1141 | 1142 | 1143 | 1144 | 1145 | 1146 | 1147 | 1148 | 1149 | 1150 | 1151 | 1152 | 1153 | 1154 | 1155 | 1156 | 1157 | 1158 | 1159 | 1160 | 1161 | 1162 | 1163 | 1164 | 1165 | 1166 | 1167 | 1168 | 1169 | 1170 | 1171 | 1172 | 1173 | 1174 | 1175 | 1176 | 1177 | 1178 | 1179 | 1180 | 1181 | 1182 | 1183 | 1184 | 1185 | 1186 | 1187 | 1188 | 1189 | 1190 | 1191 | 1192 | 1193 | 1194 | 1195 | 1196 | 1197 | 1198 | 1199 | 1200 | 1201 | 1202 | 1203 | 1204 | 1205 | 1206 | 1207 | 1208 | 1209 | 1210 | 1211 | 1212 | 1213 | 1214 | 1215 | 1216 | 1217 | 1218 | 1219 | 1220 | 1221 | 1222 | 1223 | 1224 | 1225 | 1226 | 1227 | 1228 | 1229 | 1230 | 1231 | 1232 | 1233 | 1234 | 1235 | 1236 | 1237 | 1238 | 1239 | 1240 | 1241 | 1242 | 1243 | 1244 | 1245 | 1246 | 1247 | 1248 | 1249 | 1250 | 1251 | 1252 | 1253 | 1254 | 1255 | 1256 | 1257 | 1258 | 1259 | 1260 | 1261 | 1262 | 1263 | 1264 | 1265 | 1266 | 1267 | 1268 | 1269 | 1270 | 1271 | 1272 | 1273 | 1274 | 1275 | 1276 | 1277 | 1278 | 1279 | 1280 | 1281 | 1282 | 1283 | 1284 | 1285 | 1286 | 1287 | 1288 | 1289 | 1290 | 1291 | 1292 | 1293 | 1294 | 1295 | 1296 | 1297 | 1298 | 1299 | 1300 | 1301 | 1302 | 1303 | 1304 | 1305 | 1306 | 1307 | 1308 | 1309 | 1310 | 1311 | 1312 | 1313 | 1314 | 1315 | 1316 | 1317 | 1318 | 1319 | 1320 | 1321 | 1322 | 1323 | 1324 | 1325 | 1326 | 1327 | 1328 | 1329 | 1330 | 1331 | 1332 | 1333 | 1334 | 1335 | 1336 | 1337 | 1338 | 1339 | 1340 | 1341 | 1342 | 1343 | 1344 | 1345 | 1346 | 1347 | 1348 | 1349 | 1350 | 1351 | 1352 | 1353 | 1354 | 1355 | 1356 | 1357 | 1358 | 1359 | 1360 | 1361 | 1362 | 1363 | 1364 | 1365 | 1366 | 1367 | 1368 | 1369 | 1370 | 1371 | 1372 | 1373 | 1374 | 1375 | 1376 | 1377 | 1378 | 1379 | 1380 | 1381 | 1382 | 1383 | 1384 | 1385 | 1386 | 1387 | 1388 | 1389 | 1390 | 1391 | 1392 | 1393 | 1394 | 1395 | 1396 | 1397 | 1398 | 1399 | 1400 | 1401 | 1402 | 1403 | 1404 | 1405 | 1406 | 1407 | 1408 | 1409 | 1410 | 1411 | 1412 | 1413 | 1414 | 1415 | 1416 | 1417 | 1418 | 1419 | 1420 | 1421 | 1422 | 1423 | 1424 | 1425 | 1426 | 1427 | 1428 | 1429 | 1430 | 1431 | 1432 | --------------------------------------------------------------------------------