# java_concurrent_learning **Repository Path**: cckevincyh/java_concurrent_learning ## Basic Information - **Project Name**: java_concurrent_learning - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2019-12-25 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 目录 * [目录](#目录) * [sychronized new一个对象作为锁](#sychronized-new一个对象作为锁) * [sychronized 锁定自身对象](#sychronized-锁定自身对象) * [sychronized 锁定静态方法](#sychronized-锁定静态方法) * [synchronized 锁住线程的run方法](#synchronized-锁住线程的run方法) * [同步方法与非同步方法是否可以同时调用?](#同步方法与非同步方法是否可以同时调用) * [对业务写方法加锁,对业务读方法不加锁,容易产生脏读问题](#对业务写方法加锁对业务读方法不加锁容易产生脏读问题) * [一个同步方法可以调用另外一个同步方法吗?](#一个同步方法可以调用另外一个同步方法吗) * [在继承中,子类重写的同步方法可以调用父类的同步方法吗?](#在继承中子类重写的同步方法可以调用父类的同步方法吗) * [出现异常,默认情况下锁会被释放](#出现异常默认情况下锁会被释放) * [volatile的可见性](#volatile的可见性) * [volatile不具备原子性](#volatile不具备原子性) * [synchronized既保证可见性又保证了原子性](#synchronized既保证可见性又保证了原子性) * [原子操作类](#原子操作类) * [证明原子操作类比synchronized更高效](#证明原子操作类比synchronized更高效) * [原子操作类可以保证可见性吗?](#原子操作类可以保证可见性吗) * [原子操作类的多个方法调用并不构成原子性](#原子操作类的多个方法调用并不构成原子性) * [synchronized优化 同步代码块中的语句越少越好](#synchronized优化-同步代码块中的语句越少越好) * [锁是锁在堆内存的对象上,而不是锁在栈内存的引用](#锁是锁在堆内存的对象上而不是锁在栈内存的引用) * [不要以字符串常量作为锁定对象](#不要以字符串常量作为锁定对象) * [模拟死锁](#模拟死锁) * [wait和notify](#wait和notify) * [CountDownLatch](#countdownlatch) * [ReentrantLock可以用来代替synchronized](#reentrantlock可以用来代替synchronized) * [ReentrantLock可以进行尝试锁定tryLock()](#reentrantlock可以进行尝试锁定trylock) * [ReentrantLock的lockInterruptibly方法](#reentrantlock的lockinterruptibly方法) * [ReentrantLock可以指定为公平锁](#reentrantlock可以指定为公平锁) * [面试题](#面试题) * [使用wait和notifyAll方法来实现](#使用wait和notifyall方法来实现) * [使用Lock和Condition来实现](#使用lock和condition来实现) * [ThreadLocal线程局部变量](#threadlocal线程局部变量) * [线程安全的单例模式](#线程安全的单例模式) * [多线程安全单例模式(不使用同步锁)](#多线程安全单例模式不使用同步锁) * [多线程安全单例模式(使用同步方法)](#多线程安全单例模式使用同步方法) * [多线程安全单例模式(使用双重同步锁)](#多线程安全单例模式使用双重同步锁) * [多线程安全单例模式(延迟/懒加载 使用静态内部类)](#多线程安全单例模式延迟懒加载--使用静态内部类) * [多线程安全单例模式(枚举实现)](#多线程安全单例模式枚举实现) * [并发容器](#并发容器) * [多线程卖票问题](#多线程卖票问题) * [使用线程不安全的容器List](#使用线程不安全的容器list) * [使用线程安全的容器Vector](#使用线程安全的容器vector) * [在判断和操作放在同步代码块中](#在判断和操作放在同步代码块中) * [使用队列(Queue)来实现](#使用队列queue来实现) * [List、Map](#listmap) * [ConcurrentHashMap和ConcurrentSkipListMap](#concurrenthashmap和concurrentskiplistmap) * [CopyOnWriteArrayList 写时复制容器](#copyonwritearraylist-写时复制容器) * [Collections.synchronizedList](#collectionssynchronizedlist) * [Queue](#queue) * [ConcurrentLinkedQueue 并发队列](#concurrentlinkedqueue-并发队列) * [BlockingQueue 阻塞式队列](#blockingqueue-阻塞式队列) * [LinkedBlockingQueue 无界阻塞式队列](#linkedblockingqueue-无界阻塞式队列) * [ArrayBlockingQueue 有界阻塞式队列](#arrayblockingqueue-有界阻塞式队列) * [DelayQueue 执行定时任务](#delayqueue-执行定时任务) * [TransferQueue](#transferqueue) * [SynchronizedQueue](#synchronizedqueue) * [Executor](#executor) * [ExecutorService](#executorservice) * [Callable](#callable) * [Executors](#executors) * [Future](#future) * [线程池](#线程池) * [FixedThreadPool](#fixedthreadpool) * [CachedThreadPool](#cachedthreadpool) * [SingleThreadPool](#singlethreadpool) * [ScheduledThreadPool](#scheduledthreadpool) * [WorkStealingPool](#workstealingpool) * [ForkJoinPool](#forkjoinpool) * [线程池的底层实现](#线程池的底层实现) * [ThreadPoolExecutor](#threadpoolexecutor) * [FixedThreadPool](#fixedthreadpool-1) * [CachedThreadPool](#cachedthreadpool-1) * [ScheduledThreadPool](#scheduledthreadpool-1) * [SingleThreadExecutor](#singlethreadexecutor) * [参考](#参考) * [源代码](#源代码) ## sychronized new一个对象作为锁 ```java package demo01; /** * sychronized关键字 * 对某个对象加锁 */ public class T { private int count = 10; private Object o = new Object(); public void m() { synchronized (o) {//任何线程要执行下面的代码,必须要先拿到o对象的锁 count--; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("%s count = %d%n", Thread.currentThread().getName(), count); } } public static void main(String[] args) { final T t = new T(); Thread thread1 = new Thread(t::m); Thread thread2 = new Thread(t::m); thread1.start(); thread2.start(); } } ``` 运行结果 ``` Thread-0 count = 9 Thread-1 count = 8 ``` ![](.README_images/4da9f04f.png) ## sychronized 锁定自身对象 每次new出一个毫无其他功能的对象就当锁的对象比较麻烦。所以可以用`synchronized(this)` ```java package demo2; public class T { private int count = 10; public void m() { synchronized (this) {//任何线程要执行下面的代码,必须先拿到this的锁 count--; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("%s count = %d%n", Thread.currentThread().getName(), count); } } //m()等同于下面的m2() public synchronized void m2() { //等同于在方法的代码执行时要synchronized(this) count--; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("%scount =%d%n", Thread.currentThread().getName(), count); } public static void main(String[] args) { final T t = new T(); Thread thread1 = new Thread(t::m); Thread thread2 = new Thread(t::m); thread1.start(); thread2.start(); } } ``` 运行结果 ``` Thread-0 count = 9 Thread-1 count = 8 ``` ## sychronized 锁定静态方法 ```java package demo3; /** * synchronized锁定静态方法 */ public class T { private static int count = 10; public synchronized static void m() { count--; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("%s count = %d%n", Thread.currentThread().getName(), count); } public static void mm() { synchronized (T.class) { //T.class是Class中的一个对象,这里是不能用synchronized(this)的,因为静态方法是不需要new对象去访问的 count--; try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.printf("%s count = %d%n", Thread.currentThread().getName(), count); } } public static void main(String[] args) { Thread thread1 = new Thread(T::m); Thread thread2 = new Thread(T::m); thread1.start(); thread2.start(); } } ``` 运行结果 ``` Thread-0 count = 9 Thread-1 count = 8 ``` ## synchronized 锁住线程的run方法 没有锁住线程的run方法之前 ```java package demo4; public class T implements Runnable { private int count = 10; @Override public /*synchronized*/ void run() { count--; System.out.println(Thread.currentThread().getName() + " count = " + count); } public static void main(String[] args) { T t = new T(); for (int i = 0; i < 5; i++) { new Thread(t, "THREAD-" + i).start(); } } } ``` 运行结果 ``` THREAD-1 count = 9 THREAD-0 count = 8 THREAD-4 count = 5 THREAD-3 count = 6 THREAD-2 count = 7 ```

可以想想问什么这里的打印顺序这么奇怪?

**注意: start() 方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。从程序运行的结果可以发现,多线程程序是乱序执行。** 锁住线程的run方法之后 ```java package demo4; public class T implements Runnable { private int count = 10; @Override public synchronized void run() { //加了synchronized这两条语句相当于是一个原子操作,一个run方法执行完毕释放了锁,下一个线程才能拿到锁执行run方法 count--; System.out.println(Thread.currentThread().getName() + " count = " + count); } public static void main(String[] args) { T t = new T(); for (int i = 0; i < 5; i++) { new Thread(t, "THREAD-" + i).start(); } } } ``` 运行结果 ``` THREAD-0 count = 9 THREAD-4 count = 8 THREAD-1 count = 7 THREAD-3 count = 6 THREAD-2 count = 5 ``` ## 同步方法与非同步方法是否可以同时调用? 答案是可以的。 ```java package demo5; /*同步方法与非同步方法是可以同时调用的。只有synchronized修饰的方法在运行过程中才需要申请锁,普通方法是不需要申请的*/ public class T { public synchronized void m1() { //同步方法 System.out.println(Thread.currentThread().getName() + " m1.start... "); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " m1 end"); } public void m2() { //非同步方法 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " m2 "); } public static void main(String[] args) { T t = new T(); new Thread(t::m1, "t1").start(); new Thread(t::m2, "t2").start(); } } ``` 运行结果: ``` t1 m1.start... t2 m2 t1 m1 end ``` ## 对业务写方法加锁,对业务读方法不加锁,容易产生脏读问题 ```java package demo6; import java.util.concurrent.TimeUnit; /*对业务写方法加锁,对业务读方法不加锁,容易产生脏读问题,读到在写的过程中还没有完成的数据,可以对读方法加锁*/ public class Account { private String name; private double balance; //账户余额为成员变量 默认为0.0 public synchronized void set(String name, double balance) { //写 this.name = name; try { Thread.sleep(2000); //2s } catch (InterruptedException e) { e.printStackTrace(); } this.balance = balance; } public /*synchronized*/ double getBalance(String name) { //读 return this.balance; } public static void main(String[] args) { Account a = new Account(); new Thread(() -> a.set("张三", 100.0)).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(a.getBalance("张三")); //0.0 try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(a.getBalance("张三")); //100.0 } } ``` 运行结果 ``` 0.0 100.0 ``` 因为不加synchronized,count--和打印语句中间,有可能有别的线程来执行count--,导致前后数据不一致。加了synchronized这两条语句相当于是一个原子操作,一个run方法执行完毕释放了锁,下一个线程才能拿到锁执行run方法! ```java package demo6; import java.util.concurrent.TimeUnit; /*对业务写方法加锁,对业务读方法不加锁,容易产生脏读问题,读到在写的过程中还没有完成的数据,可以对读方法加锁*/ public class Account { private String name; private double balance; //账户余额为成员变量 默认为0.0 public synchronized void set(String name, double balance) { //写 this.name = name; try { Thread.sleep(2000); //2s } catch (InterruptedException e) { e.printStackTrace(); } this.balance = balance; } public synchronized double getBalance(String name) { //读 return this.balance; } public static void main(String[] args) { Account a = new Account(); new Thread(() -> a.set("张三", 100.0)).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(a.getBalance("张三")); //0.0 try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(a.getBalance("张三")); //100.0 } } ``` 运行结果 ``` 100.0 100.0 ``` ## 一个同步方法可以调用另外一个同步方法吗? 答案是可以的。 ```java package demo7; import java.util.concurrent.TimeUnit; /*一个同步方法可以调用另外一个同步方法。一个线程已经拥有某个对象的锁,再次申请的时候任然会得到该对象的锁,即synchronized获得的锁是可重入的。*/ public class T { synchronized void m1() { System.out.println("m1 start..."); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } m2(); } synchronized void m2() { try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m2"); } public static void main(String[] args) { T t = new T(); new Thread(t::m1).start(); } } ``` 运行结果 ``` m1 start... m2 ``` ## 在继承中,子类重写的同步方法可以调用父类的同步方法吗? 答案是可以的。 ```java package demo8; import java.util.concurrent.TimeUnit; /*一个同步方法可以调用另外一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到锁的对象,即可重入的。 在继承中,子类同步方法可以调用父类的同步方法*/ public class T { synchronized void m() { System.out.println("m start..."); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("m end"); } public static void main(String[] args) { new Thread(new TT()::m).start(); //锁定的都是同一个对象(子类对象) } } class TT extends T { @Override synchronized void m() { System.out.println("child m start"); super.m(); System.out.println("child m end"); } } ``` 运行结果 ``` child m start m start... m end child m end ``` ## 出现异常,默认情况下锁会被释放 ```java package demo9; import java.util.concurrent.TimeUnit; /** * 程序执行过程中,如果出现异常,默认情况锁会被释放,所以在并发处理的过程中,有异常要多加小心,不然会发生不一致的情况; * 比如,在一个web application处理请求时,多个servlet线程共同访问同一个资源,这时如果异常处理不合适; * 在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生的数据,因此要非常小心地处理同步业务逻辑中的异常。 */ public class T { int count = 0; synchronized void m() { System.out.println(Thread.currentThread().getName() + " start"); while (true) { count++; System.out.println(Thread.currentThread().getName() + " count = " + count); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } if (count == 5) { int i = 1 / 0; //此处抛出异常,锁将会被释放。要想锁不被释放,可以在这里进行catch,然后循环继续 } } } public static void main(String[] args) { T t = new T(); Runnable r = t::m; new Thread(r, "t1").start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(r, "t2").start(); } } ``` 运行结果 ``` t1 start t1 count = 1 t1 count = 2 t1 count = 3 t1 count = 4 t1 count = 5 Exception in thread "t1" t2 start t2 count = 6 java.lang.ArithmeticException: / by zero at demo9.T.m(T.java:24) at java.lang.Thread.run(Thread.java:745) t2 count = 7 t2 count = 8 t2 count = 9 t2 count = 10 ``` 两个线程t1和t2争用同一个对象的锁,正常来说线程t1获取到t对象的锁之后就会一直在while进行循环,t2线程根本拿不到t对象的锁,因为t1线程还没执行完不会释放锁。但是当线程t1跑到count==5的时候会抛出一个异常,导致锁释放了,这个时候t2就获取到t对象的锁了。 解决方式就是进行try..catch ```java package demo9; import java.util.concurrent.TimeUnit; /** * 程序执行过程中,如果出现异常,默认情况锁会被释放,所以在并发处理的过程中,有异常要多加小心,不然会发生不一致的情况; * 比如,在一个web application处理请求时,多个servlet线程共同访问同一个资源,这时如果异常处理不合适; * 在第一个线程中抛出异常,其他线程就会进入同步代码区,有可能会访问到异常产生的数据,因此要非常小心地处理同步业务逻辑中的异常。 */ public class T { int count = 0; synchronized void m() { System.out.println(Thread.currentThread().getName() + " start"); while (true) { count++; System.out.println(Thread.currentThread().getName() + " count = " + count); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } if (count == 5) { try { int i = 1 / 0; //此处抛出异常,锁将会被释放。要想锁不被释放,可以在这里进行catch,然后循环继续 } catch (Exception e) { } } } } public static void main(String[] args) { T t = new T(); Runnable r = t::m; new Thread(r, "t1").start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(r, "t2").start(); } } ``` 运行结果 ``` t1 start t1 count = 1 t1 count = 2 t1 count = 3 t1 count = 4 t1 count = 5 t1 count = 6 t1 count = 7 ``` ## volatile的可见性 ```java package demo10; /** * volatile关键字,使一个变量在多个线程间可见 * AB线程都用到一个变量,java默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道使用volatile关键字,会让所有线程都会读到变量的修改值 * 在下面的代码中,running是存在于堆内存的t对象中 * 当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区,在运行过程中直接使用这个copy,并不会每次都去读取堆内存, * 这样,当主线程修改running的值之后,t1线程感知不到,所以不会停止运行使用volatile, * 将会强制所有线程都去堆内存中读取running的值可以阅读这篇文章进行更深入的理解 * http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html * volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized */ public class T { /*volatile*/ boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别 void m() { System.out.println("m start"); while (running) { //死循环。只有running=false时,才能执行后面的语句 } System.out.println("m end"); } public static void main(String[] args) { T t = new T(); new Thread(t::m, "t1").start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } t.running = false; //若running不被volatile关键字修饰时,线程“看不见”running被修改了 } } ``` 运行结果 ``` m start ``` ```java package demo10; /** * volatile关键字,使一个变量在多个线程间可见 * AB线程都用到一个变量,java默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道使用volatile关键字,会让所有线程都会读到变量的修改值 * 在下面的代码中,running是存在于堆内存的t对象中 * 当线程t1开始运行的时候,会把running值从内存中读到t1线程的工作区,在运行过程中直接使用这个copy,并不会每次都去读取堆内存, * 这样,当主线程修改running的值之后,t1线程感知不到,所以不会停止运行使用volatile, * 将会强制所有线程都去堆内存中读取running的值可以阅读这篇文章进行更深入的理解 * http://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html * volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized */ public class T { volatile boolean running = true; //对比一下有无volatile的情况下,整个程序运行结果的区别 void m() { System.out.println("m start"); while (running) { //死循环。只有running=false时,才能执行后面的语句 } System.out.println("m end"); } public static void main(String[] args) { T t = new T(); new Thread(t::m, "t1").start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } t.running = false; //若running不被volatile关键字修饰时,线程“看不见”running被修改了 } } ``` 运行结果 ``` m start m end ``` > volatile 只保证原子性,而 synchronized 既保证可见性又保证原子性,但是synchronized太“重”了(效率很低)! ![](.README_images/005ca931.png) ## volatile不具备原子性 ```java package demo11; import java.util.ArrayList; import java.util.List; /*10个线程分别执行10000次count++,count是对象vna的成员变量,按理来说最终count=100000, 但是最终每次执行结果都不一样,count一直小于100000,说明volatile不具备原子性*/ public class T { volatile int count = 0; void m() { for (int i = 0; i < 10000; i++) { count++; } } public static void main(String[] args) { T t = new T(); List threads = new ArrayList<>(); for (int i = 0; i < 10; i++) { threads.add(new Thread(t::m, "thread" + i)); } threads.forEach(Thread::start); threads.forEach((o) -> { try { //join()方法阻塞调用此方法的线程,直到线程t完成,此线程再继续。通常用于在main()主线程内,等待其它线程完成再结束main()主线程。 o.join(); //相当于在main线程中同步o线程,o执行完了,main线程才有执行的机会 } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println(t.count); } } ``` 运行结果 ``` 84772 ``` ## synchronized既保证可见性又保证了原子性 ```java package demo12; import java.util.ArrayList; import java.util.List; /*上例中,可以用synchronized解决,synchronized可以保证可见性和原子性,volatile只能保证可见性*/ public class T { int count = 0; synchronized void m() { //m方法加了synchronized修饰,保证了原子性和可见性 for (int i = 0; i < 10000; i++) { count++; } } public static void main(String[] args) { T t = new T(); List threads = new ArrayList<>(); for (int i = 0; i < 10; i++) { threads.add(new Thread(t::m, "thread-" + i)); } threads.forEach(Thread::start); threads.forEach((o) -> { try { o.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println(t.count); //100000 } } ``` 运行结果 ``` 100000 ``` ## 原子操作类 ![](.README_images/a81e3414.png) ```java package demo13; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * 解决同样的问题的更高效的方法,使用AtomXXX类 * AtomXXX类本身方法都是原子性的,但是不能保证多个方法连续调用是原子性的 */ public class T { AtomicInteger count = new AtomicInteger(0); void m() { for (int i = 0; i < 10000; i++) { count.incrementAndGet(); //原子操作 } } public static void main(String[] args) { T t = new T(); List threads = new ArrayList<>(); for (int i = 0; i < 10; i++) { threads.add(new Thread(t::m, "thread-" + i)); } threads.forEach(Thread::start); threads.forEach(o -> { try { o.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println(t.count); } } ``` 运行结果 ``` 100000 ``` ## 证明原子操作类比synchronized更高效 ```java package demo14; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * 写一个程序,证明Atomxxx类比synchronized更高效 */ public class T { AtomicInteger atomicCount = new AtomicInteger(0); int count = 0; void m() { for (int i = 0; i < 1000000; i++) { atomicCount.incrementAndGet(); //原子操作 } } void m2() { for (int i = 0; i < 1000000; i++) { synchronized (this) { count++; } } } public static void main(String[] args) { T t1 = new T(); T t2 = new T(); long time1 = time(t1::m); System.out.println(t1.atomicCount); long time2 = time(t2::m2); System.out.println(t2.count); System.out.println(time1); System.out.println(time2); } private static long time(Runnable runnable) { List threads = new ArrayList<>(); long startTime = System.currentTimeMillis(); for (int i = 0; i < 10; i++) { threads.add(new Thread(runnable, "thread-" + i)); } threads.forEach(Thread::start); threads.forEach(o -> { try { o.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); long endTime = System.currentTimeMillis(); return endTime - startTime; } } ``` 运行结果 ``` 10000000 10000000 177 373 ``` ## 原子操作类可以保证可见性吗? ```java package demo15; import java.util.concurrent.atomic.AtomicBoolean; /** * AtomXXX类可以保证可见性吗?请写一个程序来证明 */ public class T { AtomicBoolean running = new AtomicBoolean(true); void m() { System.out.println("m start"); while (running.get()) { //死循环。只有running=false时,才能执行后面的语句 } System.out.println("m end"); } public static void main(String[] args) { T t = new T(); new Thread(t::m, "t1").start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } t.running.getAndSet(false); } } ``` 运行结果 ``` m start m end ``` ## 原子操作类的多个方法调用并不构成原子性 ```java package demo16; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * 写一个程序证明AtomXXX类的多个方法并不构成原子性 */ public class T { AtomicInteger count = new AtomicInteger(0); void m() { for (int i = 0; i < 10000; i++) { if (count.get() < 100 && count.get() >= 0) { //如果未加锁,之间还会有其他线程插进来 count.incrementAndGet(); } } } public static void main(String[] args) { T t = new T(); List threads = new ArrayList<>(); for (int i = 0; i < 10; i++) { threads.add(new Thread(t::m, "thread" + i)); } threads.forEach(Thread::start); threads.forEach((o) -> { try { //join()方法阻塞调用此方法的线程,直到线程t完成,此线程再继续。通常用于在main()主线程内,等待其它线程完成再结束main()主线程。 o.join(); //相当于在main线程中同步o线程,o执行完了,main线程才有执行的机会 } catch (InterruptedException e) { e.printStackTrace(); } }); System.out.println(t.count); } } ``` 运行结果 期望结果是100,但是实际运行结果可能是101 ``` 101 ``` ## synchronized优化 同步代码块中的语句越少越好 ```java package demo17; import java.util.concurrent.TimeUnit; /** * synchronized优化 * 同步代码块中的语句越少越好 */ public class T { int count = 0; synchronized void ml() { //do sth need not sync try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁 count++; //do sth need not sync try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } void m2() { //do sth need not sync try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁 //采用细粒度的锁,可以使线程争用时间变短,从而提高效率 synchronized (this) { count++; } //do sth need not sync try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } } ``` ## 锁是锁在堆内存的对象上,而不是锁在栈内存的引用 ```java package demo18; import java.util.concurrent.TimeUnit; /** * 锁定某个对象o,如果o的属性发生改变,不影响使用. * 但是如果o变成另外一个对象,则锁定的对象发生改变. * 应该避免将锁定对象的引用变成另外对象 */ public class T { Object o = new Object(); void m() { synchronized (o) { while (true) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); } } } public static void main(String[] args) { T t = new T(); //启动线程 new Thread(t::m, "t1").start(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } //创建第二个线程 Thread t2 = new Thread(t::m, "t2"); //锁定对象发生变化,所以t2线程得以进行,如注释掉这句话,线程2将永远得不到执行机会 //锁是锁在堆内存 不是锁在栈内存 t.o = new Object(); t2.start(); } } ``` 运行结果 ``` t1 t1 t1 t2 t1 t2 t1 t2 t1 ``` ## 不要以字符串常量作为锁定对象 ```java package demo19; /** * 不要以字符串常量作为锁定对象 * 在下面m1 m2 其实锁定的是同一个对象 * 这种情况下还会发生比较诡异的现象,比如你用到了一个类库,在该类库中的代码锁定了"Hello", * 但是你读不到源码,所以你在自己的代码中锁定了"Hello",这时候有可能发生非常诡异的死锁阻塞, * 因为你的程序和你用到的类库不经意间使用了同一把锁. */ public class T { String s1 = "Hello"; String s2 = "Hello"; void m1() { synchronized (s1) { while (true) { System.out.println("m1"); } } } void m2() { synchronized (s2) { while (true) { System.out.println("m2"); } } } public static void main(String[] args) { T t = new T(); new Thread(t::m1).start(); new Thread(t::m2).start(); } } ``` 运行结果 ``` m1 m1 m1 m1 m1 m1 m1 m1 m1 m1 ``` 只有调用m1方法的线程能够执行。 ## 模拟死锁 ```java package demo20; public class T { private Object o1 = new Object(); private Object o2 = new Object(); public void m1() { synchronized (o1) { try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o2) { System.out.println("如果出现这句话表示没有死锁"); } } } public void m2() { synchronized(o2) { synchronized (o1) { System.out.println("如果出现这句话表示没有死锁"); } } } public static void main(String[] args) { T t=new T(); new Thread(t::m1).start(); new Thread(t::m2).start(); } } ``` ## wait和notify - 实现一个容器,提供两个方法,add,size - 写两个线程,线程1 添加10个元素容器中,线程2 实现监控元素的个数,当个数到5时,线程2给出提示并结束 ```java package demo21; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * 曾经的面试题 * 实现一个容器,提供两个方法,add,size * 写两个线程,线程1 添加10个元素容器中,线程2 实现监控元素的个数,当个数到5时,线程2给出提示并结束 *

* 分析下面这个程序,能完成这个功能? */ public class MyContainer1 { List lists = new ArrayList(); public void add(Object o) { lists.add(o); } public int size() { return lists.size(); } public static void main(String[] args) { MyContainer1 myContainer = new MyContainer1(); new Thread(() -> { for (int i = 0; i < 10; i++) { myContainer.add(new Object()); System.out.println("add " + i); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }, "t1").start(); new Thread(() -> { while (true) { if (myContainer.size() == 5) { break; } } System.out.println("t2 结束"); }, "t2").start(); } } ``` 运行结果 ``` add 0 add 1 add 2 add 3 add 4 add 5 add 6 add 7 add 8 add 9 ``` 我们可以发现在size等于5的时候t2并没有结束,原因就因为没有加volatile关键字,我们需要添加volatile关键字,保证t2线程能够得到通知。 ```java package demo21; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * 曾经的面试题 * 实现一个容器,提供两个方法,add,size * 写两个线程,线程1 添加10个元素容器中,线程2 实现监控元素的个数,当个数到5时,线程2给出提示并结束 *

* 分析下面这个程序,能完成这个功能? * * 虽然加了volatile关键字,t2线程能够接收到通知。但是上面这个代码还是存在两个问题。 * 第一个就是同步的问题,在判断条件`if (myContainer.size() == 5)`这里可能存在其他线程争用的情况,判断加上break不是一个原子操作。 * 有可能一个线程判断size已经等于5,准备要break跳出的时候,有其他线程添加了新元素,导致最终break出来之后size为6。 * * t2线程的死循环`while (true)`很浪费CPU,如果不用死循环应该怎么做? */ public class MyContainer2 { volatile List lists = new ArrayList(); //添加volatile关键字,保证t2线程能够得到通知 public void add(Object o) { lists.add(o); } public int size() { return lists.size(); } public static void main(String[] args) { MyContainer2 myContainer = new MyContainer2(); new Thread(() -> { for (int i = 0; i < 10; i++) { myContainer.add(new Object()); System.out.println("add " + i); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }, "t1").start(); new Thread(() -> { while (true) { if (myContainer.size() == 5) { break; } } System.out.println("t2 结束"); }, "t2").start(); } } ``` 运行结果 ``` add 0 add 1 add 2 add 3 add 4 t2 结束 add 5 add 6 add 7 add 8 add 9 ``` 虽然加了volatile关键字,t2线程能够接收到通知。但是上面这个代码还是存在两个问题。 - 第一个就是同步的问题,在判断条件`if (myContainer.size() == 5)`这里可能存在其他线程争用的情况,判断加上break不是一个原子操作。有可能一个线程判断size已经等于5,准备要break跳出的时候,有其他线程添加了新元素,导致最终break出来之后size为6。 - t2线程的死循环`while (true)`很浪费CPU,如果不用死循环应该怎么做? ```java package demo21; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * 曾经的面试题 * 实现一个容器,提供两个方法,add,size * 写两个线程,线程1 添加10个元素容器中,线程2 实现监控元素的个数,当个数到5时,线程2给出提示并结束 *

* 分析下面这个程序,能完成这个功能? *

* 虽然加了volatile关键字,t2线程能够接收到通知。但是上面这个代码还是存在两个问题。 * 第一个就是同步的问题,在判断条件`if (myContainer.size() == 5)`这里可能存在其他线程争用的情况,判断加上break不是一个原子操作。 * 有可能一个线程判断size已经等于5,准备要break跳出的时候,有其他线程添加了新元素,导致最终break出来之后size为6。 *

* t2线程的死循环`while (true)`很浪费CPU,如果不用死循环应该怎么做? *

*

* 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁 * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以。 *

*

* 阅读下面的程序,并分析输出的结果 * 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出 * 想想这是为什么? * * 当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。 * 当 notify/notifyAll() 被执行时,会唤醒一个或多个正处于等待状态的线程,然后继续往下执行, * 直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。 * * * 这里并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出的原因是 * 当t2线程的中判断size不等于5,调用wait方法,等待被t1叫醒,并且释放当前的lock对象的锁。 * 然后t1线程在执行中判断size等于5了,就会调用notify方法,但是notify方法并不会释放锁。线程t2虽然被线程t1叫醒了,但是t2线程无法获取到lock对象的锁,所以无法往下执行。 * 只有等到t1结束之后,也就是执行完synchronized 代码块的代码才释放了锁。这个时候线程t2才能获取到锁并往下执行。 */ public class MyContainer3 { volatile List lists = new ArrayList(); //添加volatile关键字,保证t2线程能够得到通知 public void add(Object o) { lists.add(o); } public int size() { return lists.size(); } public static void main(String[] args) { MyContainer3 myContainer = new MyContainer3(); final Object lock = new Object(); new Thread(() -> { synchronized (lock) { System.out.println("t2 启动"); if (myContainer.size() != 5) { try { lock.wait(); //size不等于5时,就一直在那等着,直到被t1叫醒 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("t2 结束"); } }, "t2").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { System.out.println("t1 启动"); synchronized (lock) { for (int i = 0; i < 10; i++) { myContainer.add(new Object()); System.out.println("add " + i); if (myContainer.size() == 5) { lock.notify(); //唤醒等待的t2线程 } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "t1").start(); } } ``` 运行结果 ``` t2 启动 t1 启动 add 0 add 1 add 2 add 3 add 4 add 5 add 6 add 7 add 8 add 9 t2 结束 ``` 输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出,想想这是为什么? 原因是:当t2线程的中判断size不等于5,调用wait方法,等待被t1线程叫醒,并且释放当前的lock对象的锁。然后t1线程获取到锁,并在执行到判断size等于5了,就会调用notify方法去唤醒等待的线程t2,但是notify方法并不会释放锁。线程t2虽然被线程t1叫醒了,但是t2线程无法获取到lock对象的锁,所以无法往下执行。 只有等到t1结束之后,也就是执行完synchronized代码块的代码才释放了锁。这个时候线程t2才能获取到锁并往下执行。 解决方式如下: ```java package demo21; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * 曾经的面试题 * 实现一个容器,提供两个方法,add,size * 写两个线程,线程1 添加10个元素容器中,线程2 实现监控元素的个数,当个数到5时,线程2给出提示并结束 *

* 分析下面这个程序,能完成这个功能? *

* 虽然加了volatile关键字,t2线程能够接收到通知。但是上面这个代码还是存在两个问题。 * 第一个就是同步的问题,在判断条件`if (myContainer.size() == 5)`这里可能存在其他线程争用的情况,判断加上break不是一个原子操作。 * 有可能一个线程判断size已经等于5,准备要break跳出的时候,有其他线程添加了新元素,导致最终break出来之后size为6。 *

* t2线程的死循环`while (true)`很浪费CPU,如果不用死循环应该怎么做? *

*

* 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁 * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以。 *

*

* 阅读下面的程序,并分析输出的结果 * 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出 * 想想这是为什么? * * 当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。 * 当 notify/notifyAll() 被执行时,会唤醒一个或多个正处于等待状态的线程,然后继续往下执行, * 直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。 * * * 这里并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出的原因是 * 当t2线程的中判断size不等于5,调用wait方法,等待被t1叫醒,并且释放当前的lock对象的锁。 * 然后t1线程在执行中判断size等于5了,就会调用notify方法,但是notify方法并不会释放锁。线程t2虽然被线程t1叫醒了,但是t2线程无法获取到lock对象的锁,所以无法往下执行。 * 只有等到t1结束之后,也就是执行完synchronized 代码块的代码才释放了锁。这个时候线程t2才能获取到锁并往下执行。 * * * 解决方式如下 */ public class MyContainer4 { volatile List lists = new ArrayList(); //添加volatile关键字,保证t2线程能够得到通知 public void add(Object o) { lists.add(o); } public int size() { return lists.size(); } public static void main(String[] args) { MyContainer4 myContainer = new MyContainer4(); final Object lock = new Object(); new Thread(() -> { synchronized (lock) { System.out.println("t2 启动"); if (myContainer.size() != 5) { try { lock.wait(); //size不等于5时,就一直在那等着,直到被t1叫醒 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("t2 结束"); lock.notify(); //通知线程t1继续执行 } }, "t2").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { System.out.println("t1 启动"); synchronized (lock) { for (int i = 0; i < 10; i++) { myContainer.add(new Object()); System.out.println("add " + i); if (myContainer.size() == 5) { lock.notify(); //唤醒等待的t2线程,本线程继续执行,直至synchronized包裹的代码块结束或者调用了wait try { lock.wait(); //释放锁,让t2线程获取锁,让t2得以执行 } catch (InterruptedException e) { e.printStackTrace(); } } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "t1").start(); } } ``` 运行结果 ``` t2 启动 t1 启动 add 0 add 1 add 2 add 3 add 4 t2 结束 add 5 add 6 add 7 add 8 add 9 ``` ## CountDownLatch 上述使用wait和notify也解决了问题,但是有点过于复杂。java提供了门闩。 ```java package demo21; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * 曾经的面试题 * 实现一个容器,提供两个方法,add,size * 写两个线程,线程1 添加10个元素容器中,线程2 实现监控元素的个数,当个数到5时,线程2给出提示并结束 *

* 分析下面这个程序,能完成这个功能? *

* 虽然加了volatile关键字,t2线程能够接收到通知。但是上面这个代码还是存在两个问题。 * 第一个就是同步的问题,在判断条件`if (myContainer.size() == 5)`这里可能存在其他线程争用的情况,判断加上break不是一个原子操作。 * 有可能一个线程判断size已经等于5,准备要break跳出的时候,有其他线程添加了新元素,导致最终break出来之后size为6。 *

* t2线程的死循环`while (true)`很浪费CPU,如果不用死循环应该怎么做? *

*

* 这里使用wait和notify做到,wait会释放锁,而notify不会释放锁 * 需要注意的是,运用这种方法,必须要保证t2先执行,也就是首先让t2监听才可以。 *

*

* 阅读下面的程序,并分析输出的结果 * 可以读到输出结果并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出 * 想想这是为什么? *

* 当线程执行wait()方法时候,会释放当前的锁,然后让出CPU,进入等待状态。 * 当 notify/notifyAll() 被执行时,会唤醒一个或多个正处于等待状态的线程,然后继续往下执行, * 直到执行完synchronized 代码块的代码或是中途遇到wait() ,再次释放锁。 *

*

* 这里并不是size=5时t2退出,而是t1结束时t2才接收到通知而退出的原因是 * 当t2线程的中判断size不等于5,调用wait方法,等待被t1叫醒,并且释放当前的lock对象的锁。 * 然后t1线程在执行中判断size等于5了,就会调用notify方法,但是notify方法并不会释放锁。线程t2虽然被线程t1叫醒了,但是t2线程无法获取到lock对象的锁,所以无法往下执行。 * 只有等到t1结束之后,也就是执行完synchronized 代码块的代码才释放了锁。这个时候线程t2才能获取到锁并往下执行。 *

*

* notify之后,t1必须释放锁,t2退出后,也必须notify,通知t1继续执行整个通信过程比较繁琐 * 使用Latch(门闩)替代wait notify来进行通知 * 好处是通信方式简单,同时也可以指定等待时间 * 使用await和countdown方法替代wait和notify * CountDownLatch不涉及锁定,当count的值为0时当前线程继续运行 * 当不涉及同步,只是涉及线程通信的时候,用synchronized+wait/notify就显得太重了 * 这时应该考虑countdownlatch/cyclicbarrier/semaphore */ public class MyContainer5 { volatile List lists = new ArrayList(); //添加volatile关键字,保证t2线程能够得到通知 public void add(Object o) { lists.add(o); } public int size() { return lists.size(); } public static void main(String[] args) { MyContainer5 myContainer = new MyContainer5(); CountDownLatch latch = new CountDownLatch(1); new Thread(() -> { System.out.println("t2 启动"); if (myContainer.size() != 5) { try { latch.await(); //也可以指定等待时间 //latch.await(5000,TimeUnit.MILLISECONDS); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("t2 结束"); }, "t2").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { System.out.println("t1 启动"); for (int i = 0; i < 10; i++) { myContainer.add(new Object()); System.out.println("add " + i); if (myContainer.size() == 5) { //打开门闩,让t2得以执行 latch.countDown(); } try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.print("t1结束"); }, "t1").start(); } } ``` 运行结果 ``` t2 启动 t1 启动 add 0 add 1 add 2 add 3 add 4 t2 结束 add 5 add 6 add 7 add 8 add 9 t1结束 ``` ## ReentrantLock可以用来代替synchronized ReentrantLock必须要手动释放锁。使用synchronized锁定如果遇到异常,jvm会自动释放锁,但是Lock必须手动释放,因此常常在finally中释放锁 ```java package demo22; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /* ReentrantLock用来替代synchronized * ReentrantLock必须要手动释放锁。使用synchronized锁定如果遇到异常,jvm会自动释放锁,但是Lock必须手动释放,因此常常在finally中释放锁*/ public class T { Lock lock = new ReentrantLock(); void m1() { try { lock.lock(); //加锁 //相当于synchronized(this) for (int i = 0; i < 10; i++) { TimeUnit.SECONDS.sleep(1); System.out.println(" " + i); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); //释放锁 } } void m2() { lock.lock(); //加锁 System.out.println(" m2()... "); lock.unlock(); //释放锁 } public static void main(String[] args) { T t = new T(); new Thread(t::m1).start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(t::m2).start(); } } ``` 运行结果 ``` 0 1 2 3 4 5 6 7 8 9 m2()... ``` ## ReentrantLock可以进行尝试锁定tryLock() - 不指定时间 ```java package demo23; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 使用ReentrantLock可以进行尝试锁定tryLock();若无法锁定或在指定时间内无法锁定,线程可以决定是否等待 */ public class T1 { Lock lock = new ReentrantLock(); void m1() { try { lock.lock(); for (int i = 0; i < 10; i++) { TimeUnit.SECONDS.sleep(1); System.out.print(" " + i); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } /** * 使用tryLock进行尝试锁定,不管锁定与否,方法都将会继续执行,可以根据tryLock的返回值判定是否被锁定了 * 可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unlock的处理,必须放到finally中。 */ void m2() { boolean locked = lock.tryLock(); System.out.print(" m2..." + locked + " "); if (locked) lock.unlock(); //false 不指定尝试时间 } public static void main(String[] args) { T1 t = new T1(); new Thread(t::m1).start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(t::m2).start(); } } ``` 运行结果 ``` 0 m2...false 1 2 3 4 5 6 7 8 9 ``` - 指定时间 ```java package demo23; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 使用ReentrantLock可以进行尝试锁定tryLock();若无法锁定或在指定时间内无法锁定,线程可以决定是否等待 */ public class T2 { Lock lock = new ReentrantLock(); void m1() { try { lock.lock(); for (int i = 0; i < 10; i++) { TimeUnit.SECONDS.sleep(1); System.out.print(" " + i); } } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } /** * 使用tryLock进行尝试锁定,不管锁定与否,方法都将会继续执行,可以根据tryLock的返回值判定是否被锁定了 * 可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unlock的处理,必须放到finally中。 */ void m2() { boolean locked = false; try { locked = lock.tryLock(5, TimeUnit.SECONDS); //指定超时时间为5s System.out.println(" m2..." + locked + " "); } catch (InterruptedException e) { e.printStackTrace(); } finally { if (locked) { lock.unlock(); } } } public static void main(String[] args) { T2 t = new T2(); new Thread(t::m1).start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(t::m2).start(); } } ``` 运行结果 ``` 0 1 2 3 4 5 m2...false 6 7 8 9 ``` ## ReentrantLock的lockInterruptibly方法 ReentrantLock可调用lockInterruptibly方法,对线程的interrupt方法作出响应,在一个线程等待的过程中,可以被打断。 ```java package demo24; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /*ReentrantLock可调用lockInterruptibly()方法,对线程的interrupt()方法作出响应,在一个线程等待的过程中,可以被打断。 * ReentrantLock的lock()方法是不能被打断的,即锁用lock()方法锁定,线程调用interrupt()方法是毫无作用的*/ public class T { public static void main(String[] args) { Lock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { try { lock.lockInterruptibly(); System.out.print(" t1 start... "); TimeUnit.SECONDS.sleep(Integer.MAX_VALUE); //t1不停的运行,睡死了 System.out.print(" t1 end... "); } catch (InterruptedException e) { System.out.print(" t1-interrupted! "); } finally { System.out.println("t1 解锁"); lock.unlock(); } }, "t1"); t1.start(); Thread t2 = new Thread(() -> { try { lock.lock(); //不能对interrupt()方法作出响应 // lock.lockInterruptibly(); //也是上锁,但是可以对interrupt()方法作出响应 System.out.print(" t2 start... "); TimeUnit.SECONDS.sleep(5); System.out.print(" t2 end... "); } catch (InterruptedException e) { System.out.println(" t2-interrupted! "); } finally { try { System.out.println("t2 解锁"); lock.unlock(); } catch (Exception e) { System.out.println("没有得到锁的线程运行结束"); } } }, "t2"); t2.start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } t2.interrupt(); //打断t2的等待,如果使用的是t2使用的是lock()方法进行上锁就无法打断,如果使用lockInterruptibl()方法就可以打断 } } ``` 运行结果 ``` t1 start... ``` 无法打断t2线程。 改用lockInterruptibl() ```java package demo24; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /*ReentrantLock可调用lockInterruptibly()方法,对线程的interrupt()方法作出响应,在一个线程等待的过程中,可以被打断。 * ReentrantLock的lock()方法是不能被打断的,即锁用lock()方法锁定,线程调用interrupt()方法是毫无作用的*/ public class T { public static void main(String[] args) { Lock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { try { lock.lockInterruptibly(); System.out.print(" t1 start... "); TimeUnit.SECONDS.sleep(Integer.MAX_VALUE); //t1不停的运行,睡死了 System.out.print(" t1 end... "); } catch (InterruptedException e) { System.out.print(" t1-interrupted! "); } finally { System.out.println("t1 解锁"); lock.unlock(); } }, "t1"); t1.start(); Thread t2 = new Thread(() -> { try { //lock.lock(); //不能对interrupt()方法作出响应 lock.lockInterruptibly(); //也是上锁,但是可以对interrupt()方法作出响应 System.out.print(" t2 start... "); TimeUnit.SECONDS.sleep(5); System.out.print(" t2 end... "); } catch (InterruptedException e) { System.out.println(" t2-interrupted! "); } finally { try { System.out.println("t2 解锁"); lock.unlock(); } catch (Exception e) { System.out.println("没有得到锁的线程运行结束"); } } }, "t2"); t2.start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } t2.interrupt(); //打断t2的等待,如果使用的是t2使用的是lock()方法进行上锁就无法打断,如果使用lockInterruptibl()方法就可以打断 } } ``` 运行结果 ``` t1 start... t2-interrupted! t2 解锁 没有得到锁的线程运行结束 ``` ## ReentrantLock可以指定为公平锁 ```java package demo25; import java.util.concurrent.locks.ReentrantLock; /*ReentrantLock可以指定为公平锁,构造方法中将fair属性设置为true即为公平锁,fair默认为false*/ public class T extends Thread { private static ReentrantLock lock = new ReentrantLock(true); //参数为true表示为公平锁,可对比输出结果 @Override public void run() { for (int i = 0; i < 20; i++) { lock.lock(); try { System.out.println(Thread.currentThread().getName() + "-获得锁 "); } finally { lock.unlock(); } } } public static void main(String[] args) { T t = new T(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t1.start(); t2.start(); } } ``` 运行结果 ``` Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 Thread-1-获得锁 Thread-2-获得锁 ``` ## 面试题 写一个固定容量的同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用。 ### 使用wait和notifyAll方法来实现 下面使用wait和notifyAll方法来实现 ```java package demo26; import java.util.LinkedList; /** * 写一个固定容量的同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程以及10个消费者线程的阻塞调用。 * wait和notifyAll方法来实现。 */ public class MyContainer1 { final private LinkedList list = new LinkedList(); final private int MAX = 10; //最多十个元素 private int count = 0; public synchronized void put(Object t) { while (list.size() == MAX) { //想想为什么用while而不是if try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } list.add(t); ++count; this.notifyAll(); //通知消费者进程进行消费 } public synchronized Object get() { Object t = null; while (list.size() == 0) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } t = list.removeFirst(); count--; this.notifyAll(); //通知生产者进程进行生产 return t; } public static void main(String[] args) { MyContainer1 pc = new MyContainer1(); for (int i = 0; i < 10; i++) { //10个消费者 new Thread(() -> { for (int j = 0; j < 5; j++) { //每个消费者最多消费5个 System.out.println("消费者线程" + Thread.currentThread().getName() + ",开始消费: " + pc.get()); } }, "c" + i).start(); } //启动生产者线程 for (int i = 0; i < 2; i++) { //2个生产者 new Thread(() -> { for (int j = 0; j < 25; j++) { //每个生产者最多生产25个 pc.put(Thread.currentThread().getName() + " " + j); } }, "p" + i).start(); } } } ``` 运行结果 ``` 消费者线程c9,开始消费: p0 0 消费者线程c9,开始消费: p0 1 消费者线程c8,开始消费: p0 2 消费者线程c7,开始消费: p0 3 消费者线程c0,开始消费: p0 4 消费者线程c6,开始消费: p0 5 消费者线程c7,开始消费: p0 6 消费者线程c5,开始消费: p0 7 消费者线程c0,开始消费: p0 8 消费者线程c6,开始消费: p0 9 消费者线程c4,开始消费: p0 11 消费者线程c9,开始消费: p0 10 消费者线程c1,开始消费: p0 12 消费者线程c8,开始消费: p0 13 消费者线程c5,开始消费: p0 14 消费者线程c6,开始消费: p0 15 消费者线程c3,开始消费: p0 16 消费者线程c2,开始消费: p0 17 消费者线程c4,开始消费: p0 18 消费者线程c6,开始消费: p0 19 消费者线程c8,开始消费: p0 20 消费者线程c7,开始消费: p0 21 消费者线程c7,开始消费: p0 24 消费者线程c4,开始消费: p0 23 消费者线程c9,开始消费: p0 22 消费者线程c9,开始消费: p1 0 消费者线程c0,开始消费: p1 4 消费者线程c0,开始消费: p1 5 消费者线程c0,开始消费: p1 6 消费者线程c1,开始消费: p1 7 消费者线程c1,开始消费: p1 8 消费者线程c1,开始消费: p1 9 消费者线程c1,开始消费: p1 10 消费者线程c5,开始消费: p1 3 消费者线程c5,开始消费: p1 11 消费者线程c5,开始消费: p1 12 消费者线程c7,开始消费: p1 13 消费者线程c8,开始消费: p1 2 消费者线程c6,开始消费: p1 16 消费者线程c8,开始消费: p1 17 消费者线程c4,开始消费: p1 1 消费者线程c3,开始消费: p1 15 消费者线程c2,开始消费: p1 14 消费者线程c3,开始消费: p1 19 消费者线程c3,开始消费: p1 21 消费者线程c4,开始消费: p1 18 消费者线程c3,开始消费: p1 22 消费者线程c2,开始消费: p1 20 消费者线程c2,开始消费: p1 23 消费者线程c2,开始消费: p1 24 ``` 这里我们可以想想两个问题: 1. `public synchronized void put(Object t)`方法中的判断`while (list.size() == MAX) { //想想为什么用while而不是if`这里为什么需要用while?而不用if? 2. `public synchronized void put(Object t)`方法中的`this.notifyAll(); //通知消费者进程进行消费`为啥这里使用的是notifyAll()而不是notify()方法? 答1: 我们假设判断是if的情况如下: ```java public synchronized void put(Object t) { if (list.size() == MAX) { try { this.wait(); //1. 假如有一个线程t1满足了size==MAX的情况,然后一进来就被wait了,并且释放了锁,线程t1等待在这里。 //2. 这个时候其他的线程t2获取了锁,也调用了put的方法,这个时候判断是满的,所以t2线程也是释放了锁并wait等待在这里了。 //3. 这个时候其他线程t3获取了锁,这时候调用的是get方法了。get方法执行完之后会size-1,这个时候size不是满的了,然后notifyAll,叫醒等待的线程。 //4. 这时候刚好叫醒了等待的线程t1,t1线程重新获取锁,并从wait()方法继续往下执行,因为我们已经判断过if了,所以往下执行add的操作了,这个时候size+1了,又变成满的了,最后又调用了notifyAll方法。叫醒等待的线程 //5. 这个时候刚刚好叫醒的是t2线程了,t2线程重新获取锁了,并从wait方法继续往下执行,因为if条件判断过了,所以这个时候不会再判断size是不是满了,但是这个时候size已经是满的了,但还是继续往下执行add的操作,所以size最终就变成了MAX+1。 } catch (InterruptedException e) { e.printStackTrace(); } } list.add(t); ++count; this.notifyAll(); } ``` 所以我们需要用while,让线程重新在判断一下size是不是满了。所以同理get方法中也是需要用while 答2: 这里假设使用notify的情况如下: ```java public synchronized void put(Object t) { while (list.size() == MAX) { //想想为什么用while而不是if try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } list.add(t); ++count; this.notify(); //通知消费者进程进行消费 //在某些情况下可能会出现问题,举个例子比如我现在有50个消费者线程,2个生产者线程 //1. 运气不好一开始运行了20个消费者线程,然后发现size都为0,全部等待,等待了20个消费线程 //2. 生产者线程开始执行了,而且再没加到size为MAX之前,CPU都恰巧调度了生产者线程,调度了10次,所以会叫醒10个消费者线程,还有10个消费线程在等待,然后现在size已经满了。 //3. CPU又不巧调度了生产者线程,判断满了,所以wait了一个生产者线程,还剩下一个生产者线程,但是不巧的是,下次执行的还是一个生产者线程,判断满了,又wait了一个生产者线程,此时生产者线程都在等待了。 //4. 还好我们还有消费者线程,接下来依次调度了10个消费者线程,所以会叫醒10个线程,但是叫醒的这10个线程不巧刚刚好是之前等待的那10个消费者线程。并且这个时候size已经为0了,而且全剩下消费者线程了,所以这些真完蛋了,剩下的消费者都会判断size为0,然后进入等待,最终所有的线程都gg了。所有的线程都在等待,程序卡死了。 //总结:最好还是用notifyAll,别用notify } ``` 值得注意的是notify只是把等待的线程变成可运行的状态,到底开始执行哪个线程还是要看CPU的调度,所以调用了notify不一定下一个执行的就是wait的线程,是有可能是其他的线程。 比如下面这个例子就可以说明了。 ```java package demo26; public class T { /** * 运行结果: * * 主线程结束 * 线程t1 start * 线程t2 start * 线程t2 end * 线程t3 start * 线程t3 end * 线程t4 start * 线程t4 end * 线程t1 end * * * * 1. t1线程开始wait * 2. 运行t2线程 * 3. 运行t3线程,并且调用了notify方法 * 4. 运行的不是wait线程t1,而是运行了t4线程 * 5. 轮到t1线程执行了。 * * 所以说明调用了notify只是把wait的线程叫醒了,变成可执行的状态,并不是下一个一定会执行wait的线程 */ public static void main(String[] args) { Object lock = new Object(); new Thread(() -> { synchronized (lock) { try { System.out.println("线程t1 start"); lock.wait(); System.out.println("线程t1 end"); } catch (InterruptedException e) { e.printStackTrace(); } } }, "t1").start(); new Thread(() -> { synchronized (lock) { System.out.println("线程t2 start"); System.out.println("线程t2 end"); } }, "t2").start(); new Thread(() -> { synchronized (lock) { System.out.println("线程t3 start"); lock.notify(); System.out.println("线程t3 end"); } }, "t3").start(); new Thread(() -> { synchronized (lock) { System.out.println("线程t4 start"); System.out.println("线程t4 end"); } }, "t4").start(); System.out.println("主线程结束"); } } ``` 运行结果 ``` 主线程结束 线程t1 start 线程t2 start 线程t2 end 线程t3 start 线程t3 end 线程t4 start 线程t4 end 线程t1 end ``` 顺便附上一个线程的状态(参考:https://www.cnblogs.com/williamjie/p/9440846.html) ![](.README_images/38e85420.png) >- 当一个线程执行了start方法后,不代表这个线程就会立即被执行,只代表这个线程处于可运行的状态,最终由OS的线程调度来决定哪个可运行状态下的线程被执行。 >- 一个线程一次被选中执行是有时间限制的,这个时间段叫做CPU的时间片,当时间片用完但线程还没有结束时,这个线程又会变为可运行状态,等待OS的再次调度;在运行的线程里执行Thread.yeild()方法同样可以使当前线程变为可运行状态。 >- 在一个运行中的线程等待用户输入、调用Thread.sleep()、调用了其他线程的join()方法,则当前线程变为阻塞状态。 >- 阻塞状态的线程用户输入完毕、sleep时间到、join的线程结束,则当前线程由阻塞状态变为可运行状态。 >- 运行中的线程调用wait方法,此线程进入等待队列。 >- 运行中的线程遇到synchronized同时没有拿到对象的锁标记、等待队列的线程wait时间到、等待队列的线程被notify方法唤醒、有其他线程调用notifyAll方法,则线程变成锁池状态。 > - 锁池状态的线程获得对象锁标记,则线程变成可运行状态。 > - 运行中的线程run方法执行完毕或main线程结束,则线程运行结束。 ### 使用Lock和Condition来实现 ```java package demo26; import java.util.LinkedList; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 使用Lock和Condition来实现生产者和消费者的同步容器, * 相比使用wait/notifyAll,使用Condition的方式能更加精确地指定哪些线程被唤醒。 */ public class MyContainer2 { final private LinkedList list = new LinkedList<>(); final private int MAX = 10; private int count = 0; private Lock lock = new ReentrantLock(); private Condition producer = lock.newCondition(); private Condition consumer = lock.newCondition(); public void put(Object obj) { try { lock.lock(); while (list.size() == MAX) { producer.await(); } list.add(obj); ++count; consumer.signalAll(); //通知消费者进行消费 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public Object get() { Object obj = null; try { lock.lock(); while (count == 0) { consumer.await(); } obj = list.removeFirst(); count--; producer.signalAll(); //通知生产者进行生产 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } return obj; } public static void main(String[] args) { MyContainer2 c = new MyContainer2(); for (int i = 0; i < 10; i++) { //10个消费者 new Thread(() -> { for (int j = 0; j < 5; j++) { //每个消费者最多消费5个 System.out.println("消费者线程" + Thread.currentThread().getName() + ",开始消费: " + c.get()); } }, "c" + i).start(); } //启动生产者线程 for (int i = 0; i < 2; i++) { //2个生产者 new Thread(() -> { for (int j = 0; j < 25; j++) { //每个生产者最多生产25个 c.put(Thread.currentThread().getName() + " " + j); } }, "p" + i).start(); } } } ``` 运行结果 ``` 消费者线程c0,开始消费: p0 0 消费者线程c0,开始消费: p1 4 消费者线程c0,开始消费: p1 7 消费者线程c4,开始消费: p1 3 消费者线程c3,开始消费: p1 2 消费者线程c0,开始消费: p0 3 消费者线程c9,开始消费: p0 5 消费者线程c9,开始消费: p0 8 消费者线程c9,开始消费: p0 9 消费者线程c9,开始消费: p0 10 消费者线程c1,开始消费: p1 0 消费者线程c1,开始消费: p0 11 消费者线程c1,开始消费: p0 12 消费者线程c2,开始消费: p1 1 消费者线程c2,开始消费: p0 14 消费者线程c2,开始消费: p0 15 消费者线程c2,开始消费: p0 16 消费者线程c2,开始消费: p0 17 消费者线程c1,开始消费: p0 13 消费者线程c1,开始消费: p0 18 消费者线程c4,开始消费: p0 7 消费者线程c4,开始消费: p0 19 消费者线程c4,开始消费: p0 20 消费者线程c4,开始消费: p0 21 消费者线程c0,开始消费: p0 6 消费者线程c3,开始消费: p0 4 消费者线程c3,开始消费: p0 22 消费者线程c3,开始消费: p0 23 消费者线程c3,开始消费: p0 24 消费者线程c8,开始消费: p0 2 消费者线程c8,开始消费: p1 8 消费者线程c8,开始消费: p1 9 消费者线程c8,开始消费: p1 10 消费者线程c8,开始消费: p1 11 消费者线程c7,开始消费: p0 1 消费者线程c7,开始消费: p1 12 消费者线程c7,开始消费: p1 13 消费者线程c6,开始消费: p1 6 消费者线程c5,开始消费: p1 5 消费者线程c5,开始消费: p1 16 消费者线程c5,开始消费: p1 17 消费者线程c5,开始消费: p1 18 消费者线程c6,开始消费: p1 15 消费者线程c7,开始消费: p1 14 消费者线程c6,开始消费: p1 20 消费者线程c5,开始消费: p1 19 消费者线程c6,开始消费: p1 22 消费者线程c6,开始消费: p1 23 消费者线程c7,开始消费: p1 21 消费者线程c9,开始消费: p1 24 ``` ## ThreadLocal线程局部变量 ```java package demo27; import java.util.concurrent.TimeUnit; /** * ThreadLocal线程局部变量 * ThreadLocal是使用空间换时间,synchronized是使用时间换空间比如在hibernate中session就存在与ThreadLocal中,避免synchronized的使用 * 线程局部变量属于每个线程都有自己的,线程间不共享,互不影响 * 运行下面的程序,理解ThreadLocal */ public class T { static ThreadLocal tl = new ThreadLocal<>(); public static void main(String[] args) { new Thread(()->{ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(tl.get()); }).start(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } tl.set(new Person()); }).start(); } } class Person{ private String name = "kevin"; public String getName() { return name; } public void setName(String name) { this.name = name; } } ``` 运行结果 ``` null ``` ## 线程安全的单例模式 参考: [设计模式之单例模式(线程安全)](https://ww.cnblogs.com/xudong-bupt/p/3433643.html) [Java单例模式:为什么我强烈推荐你用枚举来实现单例模式](https://www.cnblogs.com/happy4java/p/11206105.html) ### 多线程安全单例模式(不使用同步锁) ```java package demo28; /** * 这种代码中的一个缺点是该类加载的时候就会直接new 一个静态对象出来,当系统中这样的类较多时,会使得启动速度变慢 。现在流行的设计都是讲“延迟加载”,我们可以在第一次使用的时候才初始化第一个该类对象。所以这种适合在小系统。 */ public class Singleton1 { private static Singleton1 sin = new Singleton1(); ///直接初始化一个实例对象 private Singleton1() { ///private类型的构造函数,保证其他类对象不能直接new一个该对象的实例 } public static Singleton1 getSin() { ///该类唯一的一个public方法 return sin; } public static void main(String[] args) { Runnable runnable = () -> System.out.println(Singleton1.getSin()); new Thread(runnable,"t1").start(); new Thread(runnable,"t2").start(); } } ``` 运行结果 ``` demo28.Singleton1@686e97cd demo28.Singleton1@686e97cd ``` ### 多线程安全单例模式(使用同步方法) ```java package demo28; /** * 缺点:锁住了一个方法,锁的力度有点大 */ public class Singleton2 { private static Singleton2 instance; private Singleton2(){ } public static synchronized Singleton2 getInstance(){ //对获取实例的方法进行同步 if (instance == null) instance = new Singleton2(); return instance; } public static void main(String[] args) { Runnable runnable = () -> System.out.println(Singleton2.getInstance()); new Thread(runnable,"t1").start(); new Thread(runnable,"t2").start(); } } ``` 运行结果 ``` demo28.Singleton2@47f6725c demo28.Singleton2@47f6725c ``` ### 多线程安全单例模式(使用双重同步锁) ```java package demo28; /** * 改进:不锁住整个方法,只锁住其中的new语句就OK。就是所谓的“双重锁”机制 */ public class Singleton3 { private static Singleton3 instance; private Singleton3(){ } public static Singleton3 getInstance(){ //对获取实例的方法进行同步 if (instance == null){ synchronized(Singleton3.class){ if (instance == null) instance = new Singleton3(); } } return instance; } public static void main(String[] args) { Runnable runnable = () -> System.out.println(Singleton3.getInstance()); new Thread(runnable,"t1").start(); new Thread(runnable,"t2").start(); } } ``` 运行结果 ``` demo28.Singleton3@76575e50 demo28.Singleton3@76575e50 ``` ### 多线程安全单例模式(延迟/懒加载 使用静态内部类) ```java package demo28; public class Singleton4 { private Singleton4() { System.out.println("初始化Singleton4.."); } private static class Inner { //静态内部类 static{ System.out.println("Inner静态内部类加载了..."); } private static Singleton4 s = new Singleton4(); } private static Singleton4 getSingle() { System.out.println("获取Single实例..."); return Inner.s; } public static void main(String[] args) { Runnable runnable = () -> System.out.println(Singleton4.getSingle()); new Thread(runnable,"t1").start(); new Thread(runnable,"t2").start(); } } ``` 运行结果 ``` 获取Single实例... 获取Single实例... Inner静态内部类加载了... 初始化Singleton4.. demo28.Singleton4@1d8087e8 demo28.Singleton4@1d8087e8 ``` 从运行结果可以看出来,内部类和静态内部类都是延时加载的,也就是说只有在明确用到内部类时才加载。只使用外部类时不加载。 ### 多线程安全单例模式(枚举实现) ```java package demo28; /** * 目前最佳的单例写法——枚举模式—— 《Effective Java》 */ public enum Singleton5 { INSTANCE; public void doSomething() { System.out.println("doSomething"); } public static void main(String[] args) { Singleton5.INSTANCE.doSomething(); } } ``` Effective Java这本书以后有机会拜读一下。这里就不细讲了,以后在慢慢研究一下枚举的用法。感兴趣的可以参考上面的链接。 ## 并发容器 参考:https://blog.csdn.net/zl_StepByStep/article/details/88819859 ### 多线程卖票问题 #### 使用线程不安全的容器List ```java package demo29; import java.util.ArrayList; import java.util.List; /** 下面程序模拟卖票可能会出现两个问题: 1.票卖重了 2.还剩最后一张票时,好几个线程同时抢,出现-1张票 出现上面两个问题主要是因为: 1.remove()方法不是原子性的 2.判断+操作不是原子性的 */ public class TicketSeller1 { static List tickets = new ArrayList<>(); static { for (int i = 0; i < 10000; i++) { //共一万张票 tickets.add("票编号--" + i); } } public static void main(String[] args) { for (int i = 0; i < 10; i++) { //共10个线程卖票 new Thread(() -> { while (tickets.size() > 0) { //判断余票 System.out.println("销售了..." + tickets.remove(0)); //操作减票 } }).start(); } } } ``` 运行结果 ``` Exception in thread "Thread-5" java.lang.ArrayIndexOutOfBoundsException: -1 at java.util.ArrayList.remove(ArrayList.java:501) at demo29.TicketSeller1.lambda$main$0(TicketSeller1.java:26) at java.lang.Thread.run(Thread.java:745) ``` #### 使用线程安全的容器Vector ```java package demo29; import java.util.Vector; /** * 本程序虽然用了Vector作为容器,Vector中的方法都是原子性的,但是在判断size和减票的中间还是可能被打断的,即被减到-1张 */ public class TicketSeller2 { static Vector tickets = new Vector<>(); //Vector是一个同步容器 static { for (int i = 0; i < 100; i++) tickets.add("票编号-" + i); } public static void main(String[] args) { for (int i = 0; i < 10; i++) { new Thread(() -> { while (tickets.size() > 0) { //判断余票 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("销售了--" + tickets.remove(0)); //操作减票 } }).start(); } } } ``` 运行结果 ``` Exception in thread "Thread-9" Exception in thread "Thread-8" Exception in thread "Thread-0" Exception in thread "Thread-1" Exception in thread "Thread-7" Exception in thread "Thread-5" Exception in thread "Thread-6" Exception in thread "Thread-4" Exception in thread "Thread-3" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 0 at java.util.Vector.remove(Vector.java:831) at demo29.TicketSeller2.lambda$main$0(TicketSeller2.java:24) at java.lang.Thread.run(Thread.java:745) java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 0 at java.util.Vector.remove(Vector.java:831) at demo29.TicketSeller2.lambda$main$0(TicketSeller2.java:24) at java.lang.Thread.run(Thread.java:745) ``` #### 在判断和操作放在同步代码块中 ```java package demo29; import java.util.LinkedList; import java.util.List; /*将判断和操作外面加锁,程序完全没有功能上的问题,但是效率很低*/ public class TicketSeller3 { static List tickets = new LinkedList<>(); static { for (int i = 0; i < 100; i++) { //共100张票 tickets.add("票编号:" + i); } } public static void main(String[] args) { for (int i = 0; i < 10; i++) { //共10个线程卖票 new Thread(() -> { while (true) { synchronized (tickets) { if (tickets.size() <= 0) break; //判断 余票 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("销售了--" + tickets.remove(0)); //操作减票 } } }).start(); } } } ``` #### 使用队列(Queue)来实现 ```java package demo29; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; /** * ConcurrentLinkedQueue底层不是加锁的实现,而是使用 CAS 原子指令来处理对数据的并发访问,非阻塞式,效率高很多 */ public class TicketSeller4 { static Queue tickets = new ConcurrentLinkedQueue<>(); static { for (int i=0; i<1000; i++) { System.out.println("票编号:" + i ); } } public static void main(String[] args) { for (int i=0; i<10; i++) { new Thread( ()-> { while(true) { String str = tickets.poll(); //poll方法是原子性的,拿出一张票 //这里的判断和操作不是原子性的,但是也不会有线程安全问题,因为没有对queue做任何修改操作 if(str == null) break; //先poll,再判断tickets是不是空的,最后没有任何操作,所以不用加锁也不会出现任何问题 else System.out.println("销售了.." + str); } }).start(); } } } ``` ### List、Map #### ConcurrentHashMap和ConcurrentSkipListMap 这里就简单讲一下使用,以后有机会在研究一下底层的实现。 ```java package demo30; import java.util.Arrays; import java.util.Map; import java.util.Random; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; /** * 阅读ConcurrentSkipListMap * http://blog.csdn.net/sunxianghuang/article/details/52221913 *

* 比较一下ConcurrentHashMap和Hashtable的效率 *

* ConcurrentHashMap效率应该是比较高的,因为ConcurrentHashMap和Hashtable加锁的方式不一样。 * Hashtable是把自己整个都加锁了,而ConcurrentHashMap是采取锁分段。 */ public class T { public static void main(String[] args) { Map map = new ConcurrentHashMap<>(); // Map map = new ConcurrentSkipListMap<>(); //高并发,排序。插入时效率比较低。查快 // Map map = new Hashtable<>(); // 所有操作加锁的,效率低 // Map map = new HashMap<>(); //没有锁,但是可以通过Collections.synchronizedXXXX去加锁 // Map map = new TreeMap<>(); //插入时要排序,所以插入可能会比较慢 Random r = new Random(); Thread[] threads = new Thread[100]; CountDownLatch latch = new CountDownLatch(threads.length); //门闩计数器 100 long start = System.currentTimeMillis(); //开始时间 for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(() -> { for (int j = 0; j < 10000; j++) { //向map中加入1万个随机字符串 map.put("a" + r.nextInt(100000), "a" + r.nextInt(100000)); } latch.countDown(); //每执行一个线程,就countdown一次 }); } Arrays.asList(threads).forEach(Thread::start); //所有线程启动 try { latch.await(); //主线程在这等着,直到countdown到0 } catch (InterruptedException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); //结束时间 System.out.println(end - start); //程序执行时间 } } ``` > 简单总结:Map和Set本质上是一样的,只是Set只有key,没有value,所以下面谈到的Map可以替换成Set。 > - 在不加锁的情况下,可以用:HashMap、TreeMap、LinkedHashMap。想加锁可以用Hashtable(用的非常少)。 >- 在并发量不是很高的情况下,可以用Collections.synchronizedXxx()方法,在该方法中传一个不加锁的容器(如Map),它返回一个加了锁的容器(容器中的所有方法加锁)! > - 在并发性比较高的情况下,用ConcurrentHashMap ,如果并发性高且要排序的情况下,用ConcurrentSkipListMap。 #### CopyOnWriteArrayList 写时复制容器 这里就简单讲一下使用,以后有机会在研究一下底层的实现。 > CopyOnWriteArrayList在多线程环境下,写时效率低,读时效率高,适合写少读多的环境,比如事件监听器。 ```java package demo31; import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.concurrent.CopyOnWriteArrayList; /** * 写时复制:添加元素的时候,会把这个容器复制一份,在复制的那份后面加一个新的,将引用指向复制的那份。 * 读的时候不用加锁,适合写的很少,读的特别多的时候。 * */ public class T { public static void main(String[] args) { List list = // new ArrayList<>(); //这个会出并发问题,最后size<100000,,运行时间:0.1秒多 // new Vector<>(); //size=100000,,运行时间:0.1秒多 new CopyOnWriteArrayList<>(); //size=100000,写效率很低,因为一直在"复制、写",运行时间:5秒多 Random r = new Random(); Thread[] threads = new Thread[100]; for (int i=0; i { for (int j=0; j<1000; j++) list.add("a" + r.nextInt()); }; threads[i] = new Thread(task); } runAndComputeTime(threads); System.out.println(list.size()); } static void runAndComputeTime(Thread[] threads) { long start = System.currentTimeMillis(); Arrays.asList(threads).forEach(Thread::start); Arrays.asList(threads).forEach(t->{ try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } }); long end = System.currentTimeMillis(); System.out.println(end-start); } } ``` #### Collections.synchronizedList 返回一个加了锁的容器List ```java package demo32; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class T { public static void main(String[] args) { List strs = new ArrayList<>(); List strsSync = Collections.synchronizedList(strs); } } ``` ### Queue #### ConcurrentLinkedQueue 并发队列 ```java package demo33; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; public class ConcurrentQueue { public static void main(String[] args) { Queue strs = new ConcurrentLinkedQueue<>(); //还有双端队列...Deque for (int i = 0; i < 10; i++) { //类似于add方法,如果是ArrayQueue,add方法可能会抛异常,但是offer方法不会抛异常,返回boolean类型即是否添加成功 //strs.add("a" + i); strs.offer("a" + i); } System.out.println(strs); //[a0, a1, a2, a3, a4, a5, a6, a7, a8, a9] System.out.println("队列原始大小:" + strs.size()); //队列原始大小:10 //poll方法表示从头上拿出一个删掉;peek方法表示从头上拿出一个用一下不删。 System.out.println("poll " + strs.poll() + "后的大小为:" + strs.size()); //poll a0后的大小为:9 System.out.println("peek " + strs.peek() + "后的大小为:" + strs.size()); //peek a1后的大小为:9 } } ``` 运行结果 ``` [a0, a1, a2, a3, a4, a5, a6, a7, a8, a9] 队列原始大小:10 poll a0后的大小为:9 peek a1后的大小为:9 ``` #### BlockingQueue 阻塞式队列 ##### LinkedBlockingQueue 无界阻塞式队列 ```java package demo33; import java.util.Random; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; /*无界阻塞式队列*/ public class LinkedBlockingQueueTest { static BlockingQueue strs = new LinkedBlockingQueue<>(); static Random r = new Random(); public static void main(String[] args) { new Thread(() -> { //1个生产者线程 for (int i = 0; i < 100; i++) { try { strs.put("a" + i); //如果满了,就会等待 TimeUnit.MILLISECONDS.sleep(r.nextInt(100)); } catch (InterruptedException e) { e.printStackTrace(); } } }, "producer").start(); for (int i = 0; i < 5; i++) { //5个消费者进程 new Thread(() -> { for (; ; ) { try { System.out.println(Thread.currentThread().getName() + " take-" + strs.take()); //如果空了,就等待 } catch (InterruptedException e) { e.printStackTrace(); } } }, "customer" + i).start(); } } } ``` ##### ArrayBlockingQueue 有界阻塞式队列 ```java package demo33; import java.util.Random; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; /*有界阻塞式队列*/ public class ArrayBlockingQueueTest { static BlockingQueue strs = new ArrayBlockingQueue<>(10); //最多装10个 static Random r = new Random(); public static void main(String[] args) { for (int i = 0; i < 10; i++) { try { strs.put("a" + i); //向容器中添加10个,就满了 } catch (InterruptedException e) { e.printStackTrace(); } } try { //strs已经满了,以下方法都加不进去,但是处理方式不同 strs.put("aaa");//发现满了,就会等待,程序阻塞 // strs.add("aaa"); //已经满了,再往里面装就会报异常 // strs.offer("aaa");//不会报异常,但是加不进去,返回是否添加成功 // strs.offer("aaa",1, TimeUnit.SECONDS); //1秒钟后加不进去,就不往里面加了 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(strs); } } ``` ##### DelayQueue 执行定时任务 > 往DelayQueue里加的元素是按时间排好序的,该队列是无界的。另外元素要实现Delayed接口,而Delayed接口又继承了Comparable接口,所以该类元素需要实现compareTo()方法;并且每个元素记载着自己还有多长时间才能被拿走,还要实现getDelay()方法。 ```java package demo33; import java.util.concurrent.DelayQueue; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit; public class DelayQueueTest { static DelayQueue tasks = new DelayQueue<>(); static class MyTask implements Delayed { //实现Delayed接口 long runningTime; String name; MyTask(long rt, String name) { this.runningTime = rt; this.name = name; } @Override public long getDelay(TimeUnit unit) { return unit.convert(runningTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } @Override public int compareTo(Delayed o) { if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS)) return -1; else if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) return 1; else // == return 0; } @Override public String toString() { return name + "--" + runningTime; } } public static void main(String[] args) { long now = System.currentTimeMillis(); MyTask t1 = new MyTask(now + 1000, "task1"); //1 s 后执行 //② MyTask t2 = new MyTask(now + 2000, "task2"); //2 s后执行 //④ MyTask t3 = new MyTask(now + 1500, "task3"); //1.5s后执行 //③ MyTask t4 = new MyTask(now + 500, "task4"); //0.5s后执行 //① MyTask t5 = new MyTask(now + 2500, "task5"); //2.5s后执行 //⑤ tasks.put(t1); tasks.put(t2); tasks.put(t3); tasks.put(t4); tasks.put(t5); System.out.println(tasks); for (int i = 0; i < 5; i++) { try { System.out.println(tasks.take()); //按放进去的顺序拿出 } catch (InterruptedException e) { e.printStackTrace(); } } } } ``` 运行结果 ``` [task4--1578152889744, task1--1578152890244, task3--1578152890744, task2--1578152891244, task5--1578152891744] task4--1578152889744 task1--1578152890244 task3--1578152890744 task2--1578152891244 task5--1578152891744 ``` ##### TransferQueue > 适用场景:消费者先启动,生产者生产一个东西的时候,不扔在队列里,而是直接去找有没有消费者,有的话直接扔给消费者,若没有消费者线程,调用transfer()方法就会阻塞,调用add()、offer()、put()方法不会阻塞。TransferQueue适用于更高的并发情况 消费者先启动,然后调用transfer方法,这个时候不会阻塞 ```java package demo33; import java.util.concurrent.LinkedTransferQueue; /** * 消费者先启动,生产者生产一个东西的时候,不扔在队列里,而是直接去找有没有消费者,有的话直接扔给消费者, * 若没有消费者线程,调用transfer()方法就会阻塞,调用add()、offer()、put()方法不会阻塞。 */ public class TransferQueueTest { public static void main(String[] args) throws InterruptedException { LinkedTransferQueue strs = new LinkedTransferQueue<>(); new Thread(() -> { //消费者先启动,可以拿走aaa try { System.out.println(strs.take()); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); strs.transfer("aaa"); } } ``` 运行结果 ``` aaa ``` 如果是先调用transfer方法,然后在启动消费者线程,这个时候就会阻塞了。 ```java package demo33; import java.util.concurrent.LinkedTransferQueue; /** * 消费者先启动,生产者生产一个东西的时候,不扔在队列里,而是直接去找有没有消费者,有的话直接扔给消费者, * 若没有消费者线程,调用transfer()方法就会阻塞,调用add()、offer()、put()方法不会阻塞。 */ public class TransferQueueTest2 { public static void main(String[] args) throws InterruptedException { LinkedTransferQueue strs = new LinkedTransferQueue<>(); strs.transfer("aaa"); new Thread(() -> { //消费者在生产者后启动,拿不到aaa,程序阻塞 try { System.out.println(strs.take()); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } } ``` 如果是调用put方法的话就不会阻塞 ```java package demo33; import java.util.concurrent.LinkedTransferQueue; /** * 消费者先启动,生产者生产一个东西的时候,不扔在队列里,而是直接去找有没有消费者,有的话直接扔给消费者, * 若没有消费者线程,调用transfer()方法就会阻塞,调用add()、offer()、put()方法不会阻塞。 */ public class TransferQueueTest3 { public static void main(String[] args) throws InterruptedException { LinkedTransferQueue strs = new LinkedTransferQueue<>(); strs.put("aaa"); //如果用put的话就不会阻塞了 new Thread(() -> { //后启动消费者 try { System.out.println(strs.take()); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); } } ``` 运行结果 ``` aaa ``` ##### SynchronizedQueue > 特殊的TransferQueue,容量为0。扔在队列的东西必须被消费者马上消费掉,否则就会出问题。 ```java package demo33; import java.util.concurrent.BlockingQueue; import java.util.concurrent.SynchronousQueue; /*一种特殊的TransferQueue,生产的任何一个东西必须直接交给消费者消费,不能搁在容器里,容器的容量为0*/ public class SynchronizeQueueTest { public static void main(String[] args) throws InterruptedException { BlockingQueue strs = new SynchronousQueue<>(); new Thread(() -> { //消费者线程 try { System.out.println(strs.take()); } catch (InterruptedException e) { e.printStackTrace(); } }).start(); strs.put("aaaa"); //不能调用add(报错),add不进去,put阻塞,等待消费者消费,内部调用的transfer. System.out.println(strs.size()); //0 } } ``` 运行结果 ``` 0 aaaa ``` ## Executor 参考:https://blog.csdn.net/qq_34993631/article/details/82713550 > 执行器,这是一个接口,内部维护了一个方法execute它负责执行一项任务。参数为Runnable,方法的具体实现由我们自己来执行。如下面的代码,我们既可以使用单纯的方法调用也可以新启一个新的线程去执行Runnable的run方法。 ```java package demo34; import java.util.concurrent.Executor; public class MyExecutor implements Executor { public static void main(String[] args) { new MyExecutor().execute(() -> System.out.println(Thread.currentThread().getName() + "hello executor")); } @Override public void execute(Runnable command) { new Thread(command).start(); // command.run(); } } ``` 运行结果 ``` Thread-0hello executor ``` ## ExecutorService 代表着启动一系列的线程为用户提供服务(本质上也是一个执行器,继承自Executor) ![](.README_images/b4d76440.png) ![](.README_images/ba425fec.png) > 同时它可以提交Callable与Runnable的对象返回一个未来的执行结果对象Future。这里顺便说一下,Callable是一个增强版的Runnable,它的call方法可以抛出异常可以有返回值。其中它的返回值放在了Future对象中,我们可以使用Future对象的get方法来获得返回值。同时它也是一系列线程池的接口比如说ForkJoinPool、ScheduledThreadPoolExecutor,、ThreadPoolExecutor等。 ![](.README_images/5e7b103f.png) ![](.README_images/7d240581.png) ![](.README_images/e049c389.png) ## Callable Callable是一个增强版的Runnable,它的call方法可以抛出异常可以有返回值。其中它的返回值放在了Future对象中,我们可以使用Future对象的get方法来获得返回值。 ## Executors 是一个操作Executor的工具方法。 ![](.README_images/1ca554b8.png) ## Future Future常与Callable联合使用,Future可以获得Callable执行后的返回值。如果想新建一个线程执行一个这个Callable中的call方法而且获得返回值的话我们可以使用以下的思路。 方案一:new Thread(new FutureTask(一个实现了Callable的类的对象)).start();使用FutureTask来接收任务的返回值。 方案二:new一个线程池然后然后提交Callable的实现的对象。使用Future来获得Callable的返回值。 ```java package demo34; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; import java.util.concurrent.TimeUnit; /** * new Thread(new FutureTask(一个实现了Callable的类的对象)).start();使用FutureTask来接收任务的返回值 */ public class MyFuture01 { public static void main(String[] args) throws InterruptedException, ExecutionException { FutureTask task = new FutureTask<>(() -> { TimeUnit.MILLISECONDS.sleep(500); return 1000; }); //new Callable () { Integer call();} new Thread(task).start(); System.out.println(task.get()); //阻塞 } } ``` 运行结果 ``` 1000 ``` ```java package demo34; import java.util.concurrent.*; /** * new一个线程池然后然后提交Callable的实现的对象。使用Future来获得Callable的返回值。 */ public class MyFuture02 { public static void main(String[] args) throws InterruptedException, ExecutionException { ExecutorService service = Executors.newFixedThreadPool(5); Future f = service.submit(() -> { TimeUnit.MILLISECONDS.sleep(500); return 1; }); System.out.println(f.get()); System.out.println(f.isDone()); } } ``` 运行结果 ``` 1 true ``` ## 线程池 ### FixedThreadPool ```java package demo35; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class MyFixedThreadPool { public static void main(String[] args) throws InterruptedException { ExecutorService service = Executors.newFixedThreadPool(5); //execute submit for (int i = 0; i < 6; i++) { service.execute(() -> { try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); }); } System.out.println(service); service.shutdown(); System.out.println(service.isTerminated()); System.out.println(service.isShutdown()); System.out.println(service); TimeUnit.SECONDS.sleep(5); System.out.println(service.isTerminated()); System.out.println(service.isShutdown()); System.out.println(service); } } ``` 运行结果 ``` java.util.concurrent.ThreadPoolExecutor@7cca494b[Running, pool size = 5, active threads = 5, queued tasks = 1, completed tasks = 0] false true java.util.concurrent.ThreadPoolExecutor@7cca494b[Shutting down, pool size = 5, active threads = 5, queued tasks = 1, completed tasks = 0] pool-1-thread-1 pool-1-thread-2 pool-1-thread-4 pool-1-thread-5 pool-1-thread-3 pool-1-thread-1 true true java.util.concurrent.ThreadPoolExecutor@7cca494b[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 6] ``` > - 整个程序new了一个5个线程的线程池,使用for循环向这个线程池抛了5个任务。它的执行原则是哪一个线程空闲就由哪个线程来执行这个任务。所以我们看到的线程池的线程序号是不固定的乱序的,但是它有个规则就是先执行完任务的线程会在新线程到来时优先分配到任务。 > - 线程池shutdown之后程序不会立刻停止而是要等待的所有线程都执行完毕之后再停止服务,所以我们看到的就是Running Shutting down Terminated > - 线程池的任务大体上分为两类,等待就绪队列与已完成任务的队列。通过输出结果我们可以看出在开始有5个正在执行的任务1个任务驻留在就绪队列等待执行,在执行结束后我们的已执行队列中就会有6个元素。 利用线程池做并行计算。 ```java /** * 线程池的概念 * 并行计算 */ package demo35; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class ParallelComputing { public static void main(String[] args) throws InterruptedException, ExecutionException { long start = System.currentTimeMillis(); List results = getPrime(1, 200000); long end = System.currentTimeMillis(); System.out.println(end - start); final int cpuCoreNum = 4; ExecutorService service = Executors.newFixedThreadPool(cpuCoreNum); MyTask t1 = new MyTask(1, 80000); MyTask t2 = new MyTask(80001, 130000); MyTask t3 = new MyTask(130001, 170000); MyTask t4 = new MyTask(170001, 200000); Future> f1 = service.submit(t1); Future> f2 = service.submit(t2); Future> f3 = service.submit(t3); Future> f4 = service.submit(t4); start = System.currentTimeMillis(); f1.get(); f2.get(); f3.get(); f4.get(); end = System.currentTimeMillis(); System.out.println(end - start); } static class MyTask implements Callable> { int startPos, endPos; MyTask(int s, int e) { this.startPos = s; this.endPos = e; } @Override public List call() throws Exception { List r = getPrime(startPos, endPos); return r; } } static boolean isPrime(int num) { for(int i=2; i<=num/2; i++) { if(num % i == 0) return false; } return true; } static List getPrime(int start, int end) { List results = new ArrayList<>(); for(int i=start; i<=end; i++) { if(isPrime(i)) results.add(i); } return results; } } ``` > 这是一个质数计算的问题,我们把质数计算划分为不同的数据段是因为越大的质数越难计算,所以直观上计算大量的小数字的质数的时间相当于计算少量的大数字花的时间。这样一来我们就将这个大的任务相对均匀的拆分开来避免了任务分配不均匀造成的等待(也就是时间浪费)。 运行结果 ``` 3204 954 ``` ### CachedThreadPool > CachedPool的主要特点就是如果新来的一个任务需要这个线程池来执行的话,如果当前线程池没有闲置的线程那么就新启动一个线程,如果有空闲线程那么就使用其中的一个空闲线程。就是这样的一个有弹性的线程池。默认情况下当一个线程空闲超过60s那么就会销毁,而且线程数量最大不能超过int类型的最大值或者是计算机内存的大小。 ```java package demo35; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class CachedPool { public static void main(String[] args) throws InterruptedException { ExecutorService service = Executors.newCachedThreadPool(); System.out.println(service); for (int i = 0; i < 2; i++) { service.execute(() -> { try { TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); }); } System.out.println(service); TimeUnit.SECONDS.sleep(80); System.out.println(service); } } ``` 运行结果 ``` java.util.concurrent.ThreadPoolExecutor@677327b6[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0] java.util.concurrent.ThreadPoolExecutor@677327b6[Running, pool size = 2, active threads = 2, queued tasks = 0, completed tasks = 0] pool-1-thread-1 pool-1-thread-2 java.util.concurrent.ThreadPoolExecutor@677327b6[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 2] ``` ### SingleThreadPool > 这个线程池中只有一个线程,它的使用场景就是当我们需要保证任务执行的先后顺序的时候就可以使用它。 ```java package demo35; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class MySingleThreadPool { public static void main(String[] args) { ExecutorService service = Executors.newSingleThreadExecutor(); for (int i = 0; i < 5; i++) { final int j = i; service.execute(() -> { System.out.println(j + "" + Thread.currentThread().getName()); }); } } } ``` 运行结果 ``` 0pool-1-thread-1 1pool-1-thread-1 2pool-1-thread-1 3pool-1-thread-1 4pool-1-thread-1 ``` ### ScheduledThreadPool 一个定时执行任务的一个线程池它所执行的任务的参数如下: ```java public ScheduledFuture scheduleAtFixedRate(Runnable command,long initialDelay,long period, TimeUnit unit) ``` > - initialDelay:在开始多少单位时间的时候执行第一个任务。 > - Period:每隔多长时间执行下一个任务。 > - Unit:时间的单位。 > - 它的底层基于DelayedWorkQueue。 ```java package demo35; import java.util.Random; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; /** * 以下代码展示了已启动就开始执行的而且步幅为0.5s的线程执行方式 */ public class MyScheduledPool { public static void main(String[] args) { ScheduledExecutorService service = Executors.newScheduledThreadPool(4); service.scheduleAtFixedRate(() -> { try { TimeUnit.MILLISECONDS.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()); }, 0, 500, TimeUnit.MILLISECONDS); } } ``` 运行结果 ``` pool-1-thread-1 pool-1-thread-1 pool-1-thread-2 pool-1-thread-1 pool-1-thread-3 pool-1-thread-3 pool-1-thread-3 pool-1-thread-3 .... ``` ### WorkStealingPool > 工作窃取线程池,一般情况下CPU是几核的就会启动几个线程,每一个线程都维护者自己的一个执行队列的,当某些线程将自己队列中的任务都执行完毕的时候就会去其他线程的队列中窃取任务来执行以此提高效率。它的底层是基于ForkJoinPool的,常常用于任务分配不均匀的场景中。 ![](.README_images/77fbb0e9.png) ```java package demo35; import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class MyWorkStealingPool { public static void main(String[] args) throws IOException { ExecutorService service = Executors.newWorkStealingPool(); System.out.println(Runtime.getRuntime().availableProcessors()); service.execute(new R(1000)); service.execute(new R(2000)); service.execute(new R(2000)); service.execute(new R(2000)); //daemon service.execute(new R(2000)); //由于产生的是精灵线程(守护线程、后台线程),主线程不阻塞的话,看不到输出 System.in.read(); } static class R implements Runnable { int time; R(int t) { this.time = t; } @Override public void run() { try { TimeUnit.MILLISECONDS.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(time + " " + Thread.currentThread().getName()); } } } ``` 运行结果 ``` 4 1000 ForkJoinPool-1-worker-1 2000 ForkJoinPool-1-worker-0 2000 ForkJoinPool-1-worker-2 2000 ForkJoinPool-1-worker-3 2000 ForkJoinPool-1-worker-1 ``` ### ForkJoinPool 这个线程池设计的思想就与MapReduce极其相似,将一个大的任务分解成一个个小的任务当多个线程来执行。然后将计算的结果汇总得到最终结果。这也是用到了递归的思想。其中它的任务分为两种一种没有返回值是RecursiveAction,一种有返回值RecursiveTask。常常用于大量数据的运算以下为示例代码: ```java package demo35; import java.io.IOException; import java.util.Arrays; import java.util.Random; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveTask; public class MyForkJoinPool { static int[] nums = new int[1000000]; static final int MAX_NUM = 50000; static Random r = new Random(); static { for (int i = 0; i < nums.length; i++) { nums[i] = r.nextInt(100); } System.out.println(Arrays.stream(nums).sum()); //stream api } /* static class AddTask extends RecursiveAction { int start, end; AddTask(int s, int e) { start = s; end = e; } @Override protected void compute() { if(end-start <= MAX_NUM) { long sum = 0L; for(int i=start; i