1、基础概要

Java线程的创建方法有三种,第一种是通过实现Runnable接口,第二种是通过继承Thread类本身,第三种是通过 Callable 和 Future 来创建线程。 第一种和第二种,都是通过重写run方法,来实现线程的执行实例。并通过start方法启动线程。 第三种方法后文再做详细描述。

2、线程死锁

Java是通过synchronized关键字实现线程同步的,调用是synchronized(this){代码块}。在这里,我们需要明白,synchronized申请的锁this是一个class对象,this是为了大多数情况下方便调用,你可以将任意类型的Object对象替代这个this,来实现线程同步。 Attention:同步代码块中的语句越少越好,所以有些无任何安全性问题的代码,可以放在同步代码块之外。 下面我们通过一段代码来理解线程死锁, 线程A申请a的锁,在过程中,还会申请b的锁,线程B申请b的锁,在过程中,还会申请a的锁,这样就形成了一个等待死锁,A线程在等待B线程结束释放b锁,B线程在等待A线程结束释放A锁。注意样例中创建线程处的两处,是使用的Lambda表达式(函数式编程),具体使用可以百度查阅。

  1. package com.company.test02;
  2. /**
  3. * @ClassName : TestLock //类名
  4. * @Description : 线程A申请a的锁,在过程中,还会申请b的锁,线程B申请b的锁,在过程中
  5. * ,还会申请a的锁,这样就形成了一个等待死锁,A线程在等待B线程结束释放b
  6. * 锁,B线程在等待A线程结束释放A锁。 //描述
  7. * @Author : Mr.Duan //作者
  8. * @Date: 2020-08-13 11:21 //时间
  9. */
  10. public class ThreadDeadLock{
  11. Object a=new Object();
  12. Object b=new Object();
  13. void a(){
  14. synchronized (a){
  15. System.out.println(Thread.currentThread().getName()+":这个线程开始了...");
  16. try {
  17. Thread.sleep(200);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. synchronized (b){
  22. }
  23. System.out.println(Thread.currentThread().getName()+":这个线程结束了...");
  24. }
  25. }
  26. void b(){
  27. synchronized (b){
  28. System.out.println(Thread.currentThread().getName()+":这个线程开始了...");
  29. try {
  30. Thread.sleep(200);
  31. } catch (InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. synchronized (a){
  35. }
  36. System.out.println(Thread.currentThread().getName()+":这个线程结束了...");
  37. }
  38. }
  39. public static void main(String[] args) {
  40. ThreadDeadLock threadDeadLock=new ThreadDeadLock();
  41. new Thread(threadDeadLock::a,"线程A").start();
  42. new Thread(()->threadDeadLock.b(),"线程B").start();
  43. }
  44. }

输出结果:(会一直处于等待状态)

  1. "D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=51634:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\IdeaProjects\20200813\ClassDome\out\production\ClassDome" com.company.test02.ThreadDeadLock
  2. 线程B:这个线程开始了...
  3. 线程A:这个线程开始了...

3、读写的脏读问题

在比较常见的读写问题中,如果只对写方法加锁,对读方法不加锁,就可能产生脏读问题,以下代码为例:

  1. package com.company.test03;
  2. /**
  3. * @ClassName : DirtyReader //类名
  4. * @Description : 脏读问题 //描述
  5. * @Author : Mr.Duan //作者
  6. * @Date: 2020-08-13 15:01 //时间
  7. */
  8. public class DirtyReader {
  9. Integer data;
  10. Integer get(){
  11. return data;
  12. }
  13. public synchronized void set(){
  14. try {
  15. Thread.sleep(2);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. data=1;
  20. }
  21. public static void main(String[] args) {
  22. DirtyReader dirtyReader=new DirtyReader();
  23. new Thread(dirtyReader::set).start();
  24. try {
  25. Thread.sleep(1);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. System.out.println("Data="+dirtyReader.get());
  30. try {
  31. Thread.sleep(2);
  32. } catch (InterruptedException e) {
  33. e.printStackTrace();
  34. }
  35. System.out.println("Data="+dirtyReader.get());
  36. }
  37. }

运行结果:可以看出,当写操作中存在一些耗时操作时,如果对读操作不进行加锁,就可能存在脏读问题。

  1. "D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=52054:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\IdeaProjects\20200813\ClassDome\out\production\ClassDome" com.company.test03.DirtyReader
  2. Data=null
  3. Data=1
  4. Process finished with exit code 0

4、锁的重入性

一个同步方法中调用另一个同步方法,如果该线程已经拥有了某个对象的锁,进行调用时,仍然可以。同样,子类调用父类也是可以的。描述的有点抽象,还是通过代码来感受下吧。可以看到,子类对象线程在已经拥有了锁时,调用父类方法,并不会形成阻塞,调用类中另一个同步方法同样如此。

  1. package com.company.test04;
  2. /**
  3. * @ClassName : ReentrancyDome //类名
  4. * @Description : 线程的重入性 //描述
  5. * @Author : Mr.Duan //作者
  6. * @Date: 2020-08-13 15:09 //时间
  7. */
  8. public class ReentrancyDome {
  9. public synchronized void reentrancyTest1(){
  10. System.out.println("父类调用"+Thread.currentThread().getName()+"开始了");
  11. try {
  12. Thread.sleep(2);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. reentrancyTest2();
  17. System.out.println("父类调用"+Thread.currentThread().getName()+"结束了");
  18. }
  19. private synchronized void reentrancyTest2() {
  20. System.out.println("父类调用"+Thread.currentThread().getName());
  21. }
  22. public static void main(String[] args) {
  23. ReentrancyDome reentrancyDome=new ReentrancyTest();
  24. new Thread(()->{
  25. reentrancyDome.reentrancyTest1();
  26. }).start();
  27. }
  28. }
  29. class ReentrancyTest extends ReentrancyDome{
  30. public synchronized void reentrancyTest1(){
  31. System.out.println("子类调用"+Thread.currentThread().getName()+"开始了");
  32. super.reentrancyTest1();
  33. System.out.println("子类调用"+Thread.currentThread().getName()+"结束了");
  34. }
  35. }

输出结果:

  1. "D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=52441:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\IdeaProjects\20200813\ClassDome\out\production\ClassDome" com.company.test04.ReentrancyDome
  2. 子类调用Thread-0开始了
  3. 父类调用Thread-0开始了
  4. 父类调用Thread-0
  5. 父类调用Thread-0结束了
  6. 子类调用Thread-0结束了
  7. Process finished with exit code 0

5、线程中的异常处理与锁的关系

在线程方法中,如果遇到异常,没有进行异常捕捉处理,锁将会主动释放,如果进行了异常处理,则不会因为异常而让出锁。对比以下两种情况下的结果,图1未做异常处理,在执行到异常时,主动释放锁给其他线程,线程B得以执行;图2做了异常处理,所以不会将锁释放给其他线程,形成阻塞。

  1. package com.company.test02;
  2. /**
  3. * @ClassName : TestLock //类名
  4. * @Description : 线程中的异常处理与锁的关系 //描述
  5. * @Author : Mr.Duan //作者
  6. * @Date: 2020-08-13 11:21 //时间
  7. */
  8. public class TestLock {
  9. public synchronized void method(){
  10. System.out.println(Thread.currentThread().getName()+"开始执行了.....");
  11. while (true){
  12. try {
  13. Thread.sleep(2);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. int exception=10/0;
  18. }
  19. }
  20. public static void main(String[] args) {
  21. TestLock testLock=new TestLock();
  22. new Thread(testLock::method,"线程A").start();
  23. try {
  24. Thread.sleep(1);
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. }
  28. new Thread(testLock::method,"线程b").start();
  29. }
  30. }

输出结果为:

  1. "D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=57087:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\IdeaProjects\20200813\ClassDome\out\production\ClassDome" com.company.test02.TestLock
  2. 线程A开始执行了.....
  3. 线程b开始执行了.....
  4. Exception in thread "线程A" java.lang.ArithmeticException: / by zero
  5. at com.company.test02.TestLock.method(TestLock.java:18)
  6. at java.lang.Thread.run(Thread.java:748)
  7. Exception in thread "线程b" java.lang.ArithmeticException: / by zero
  8. at com.company.test02.TestLock.method(TestLock.java:18)
  9. at java.lang.Thread.run(Thread.java:748)
  10. Process finished with exit code 0

然后对exception=10/0进行异常捕获。。。。

  1. package com.company.test02;
  2. /**
  3. * @ClassName : TestLock //类名
  4. * @Description : 线程中的异常处理与锁的关系 //描述
  5. * @Author : Mr.Duan //作者
  6. * @Date: 2020-08-13 11:21 //时间
  7. */
  8. public class TestLock {
  9. public synchronized void method(){
  10. System.out.println(Thread.currentThread().getName()+"开始执行了.....");
  11. while (true){
  12. try {
  13. Thread.sleep(2);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. try{
  18. int exception=10/0;
  19. }catch (ArithmeticException e){
  20. }
  21. }
  22. }
  23. public static void main(String[] args) {
  24. TestLock testLock=new TestLock();
  25. new Thread(testLock::method,"线程A").start();
  26. try {
  27. Thread.sleep(1);
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. new Thread(testLock::method,"线程b").start();
  32. }
  33. }

输出结果:

  1. "D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=57284:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\IdeaProjects\20200813\ClassDome\out\production\ClassDome" com.company.test02.TestLock
  2. 线程A开始执行了.....
  3. Process finished with exit code -1

6、volatile关键字

volatile变量具有可见性,即使得变量每次在使用时,从主存中获取,而不是从线程的工作内存中取,synchronized关键字具有可见性和原子性,原子性能够保证数据同步,volatile变量仅具有可见性,所以它并不能保证线程并发的正确性。下面从一个案例中尝试理解volatile的作用,运行发现,方法线程并不会因为主线程中,running的值的更改,而结束循环使得线程正常结束,会永远得停留在循环里面。想要让running的值的更改,能够被方法线程获取到正确的修改后的running值,需要加上volatile关键字修饰running变量,即volatile boolean running=true。

  1. package com.company.test06;
  2. /**
  3. * @ClassName : VolatileDome //类名
  4. * @Description : volatile关键字 //描述
  5. * @Author : Mr.Duan //作者
  6. * @Date: 2020-08-13 15:28 //时间
  7. */
  8. public class VolatileDome {
  9. boolean running=true;
  10. void method(){
  11. System.out.println(Thread.currentThread().getName()+"start......");
  12. while (running){
  13. }
  14. System.out.println(Thread.currentThread().getName()+"end......");
  15. }
  16. public static void main(String[] args) {
  17. VolatileDome volatileDome=new VolatileDome();
  18. new Thread(volatileDome::method,"方法线程").start();
  19. try {
  20. Thread.sleep(2);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. volatileDome.running=false;
  25. System.out.println("main线程结束了");
  26. }
  27. }

输出结果:

  1. "D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=52733:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\IdeaProjects\20200813\ClassDome\out\production\ClassDome" com.company.test06.VolatileDome
  2. 方法线程start......
  3. main线程结束了
  4. 方法线程end......
  5. Process finished with exit code 0

volatile不能替代synchronized,保证程序并发正确性的体现案例:线程正确并发的输出结果应该是10*1000000,而实际运行结果并不是这个,可见volatile不能保证线程的并发正确性。

  1. package com.company.test06;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. /**
  5. * @ClassName : VolatileTest //类名
  6. * @Description : volitile关键字 //描述
  7. * @Author : Mr.Duan //作者
  8. * @Date: 2020-08-13 15:37 //时间
  9. */
  10. public class VolatileTest {
  11. public volatile int count=0;
  12. public void method(){
  13. for (int i = 0; i < 100000; i++) {
  14. count++;
  15. }
  16. }
  17. public static void main(String[] args) {
  18. for (int j = 0; j < 10; j++) {
  19. VolatileTest test=new VolatileTest();
  20. List<Thread> threads=new ArrayList<>();
  21. for (int i = 0; i < 10; i++) {
  22. threads.add(new Thread(test::method,"thread--"+i));
  23. }
  24. threads.forEach((o)->o.start());
  25. threads.forEach((o)->{
  26. try {
  27. o.join();
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. }
  31. });
  32. System.out.println(test.count);
  33. }
  34. }
  35. }

输出结果:

  1. "D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=53146:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\IdeaProjects\20200813\ClassDome\out\production\ClassDome" com.company.test06.VolatileTest
  2. 768523
  3. 573549
  4. 764217
  5. 985307
  6. 769438
  7. 701912
  8. 575734
  9. 609212
  10. 659729
  11. 637976
  12. Process finished with exit code 0

上面已经讲了volatitle关键字,它只有可见性,没有原子性,所以不能保证线程并发的正确性。所以以上案例的累加结果出现错误。此种情况下,可以使用AtomicInteger来实现多线程累加,它是通过原子方式更新的 int值。相似的类还有AtomicBoolean,AtomicLong等,详情查询API文档。

  1. package com.company.test06;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.concurrent.atomic.AtomicInteger;
  5. /**
  6. * @ClassName : AtomDome //类名
  7. * @Description : Atom关键字 //描述
  8. * @Author : Mr.Duan //作者
  9. * @Date: 2020-08-13 15:51 //时间
  10. */
  11. public class AtomDome {
  12. public AtomicInteger sum=new AtomicInteger();
  13. public void addMethod(){
  14. for (int i = 0; i < 1000; i++) {
  15. sum.incrementAndGet();
  16. }
  17. }
  18. public static void main(String[] args) {
  19. for (int j = 0; j < 10; j++) {
  20. AtomDome atomDome=new AtomDome();
  21. List<Thread> threads=new ArrayList<>();
  22. for (int i = 0; i < 10; i++) {
  23. threads.add(new Thread(atomDome::addMethod,"thread--"+i));
  24. }
  25. threads.forEach((o)->o.start());
  26. threads.forEach((o)->{
  27. try {
  28. o.join();
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. });
  33. System.out.println(atomDome.sum.get());
  34. }
  35. }
  36. }

输出结果:

  1. "D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=53353:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\IdeaProjects\20200813\ClassDome\out\production\ClassDome" com.company.test06.AtomDome
  2. 10000
  3. 10000
  4. 10000
  5. 10000
  6. 10000
  7. 10000
  8. 10000
  9. 10000
  10. 10000
  11. 10000
  12. Process finished with exit code 0

7、CountDownLatch

一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await方法会一直受阻塞。之后,会释放所有等待的线程,await的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用CyclicBarrier。CountDownLatch是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch用作一个简单的开/关锁存器,或入口:在通过调用countDown()的线程打开入口前,所有调用 await的线程都一直在入口处等待。用 N 初始化的 CountDownLatch可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。CountDownLatch的一个有用特性是,它不要求调用 countDown方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个 await。
下面通过一个题目来对CountDownLatch进行一次简单的理解运用。题目描述:实现一个容器,提供两个方法add和size,写两个线程,线程1添加10个元素到容器中,线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束。为了避免线程2一直死循环监听浪费CPU,可以用CountDownLatch来解决。

  1. public class T{
  2. volatile List<Integer> nums=new ArrayList<Integer>();
  3. CountDownLatch lock=new CountDownLatch(1);
  4. void add() {
  5. System.out.println("线程1开始");
  6. for(int i=0;i<10;i++) {
  7. nums.add(i);
  8. System.out.println(nums.get(i));
  9. if(nums.size()==5) {
  10. lock.countDown();
  11. }
  12. try {
  13. TimeUnit.SECONDS.sleep(1);
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. }
  17. }
  18. System.out.println("线程1结束");
  19. }
  20. void size(){
  21. System.out.println("线程2开始");
  22. try {
  23. lock.await();
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. System.out.println("线程2结束");
  28. }
  29. public static void main(String[] args) {
  30. T t=new T();
  31. new Thread(t::size,"线程2").start();
  32. try {
  33. TimeUnit.SECONDS.sleep(2);
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. }
  37. new Thread(t::add,"线程1").start();
  38. }
  39. }

8、ReentrantLock

Reentrantlock可以用于替代synchronized,而且它使用的时候,必须要手动释放锁,使用synchronized锁定的话如果遇到异常,jvm会自动释放锁,但是lock必须手动释放锁,因此我们经常在finally中进行锁的释放。
基本使用样例:

  1. public class ReentrantLockTest {
  2. Lock lock=new ReentrantLock();
  3. void methodA() {
  4. try {
  5. lock.lock();
  6. System.out.println("Method A start ...");
  7. TimeUnit.SECONDS.sleep(2);
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. }finally {
  11. lock.unlock();
  12. System.out.println("Method A end ...");
  13. }
  14. }
  15. void methodB() {
  16. try {
  17. lock.lock();
  18. System.out.println("Method B start ...");
  19. TimeUnit.SECONDS.sleep(2);
  20. } catch (Exception e) {
  21. e.printStackTrace();
  22. }finally {
  23. lock.unlock();
  24. System.out.println("Method B end ...");
  25. }
  26. }
  27. public static void main(String[] args) throws InterruptedException {
  28. ReentrantLockTest test=new ReentrantLockTest();
  29. new Thread(test::methodA).start();
  30. TimeUnit.SECONDS.sleep(2);
  31. new Thread(test::methodB).start();
  32. }
  33. }

我们还可以使用tryLock()方法来尝试申请锁,无论方法返回值是true还是false,后面的代码都会执行,可以根据返回值来进行判断,做相应的处理。还可以对tryLock进行时间限制,即在指定时间内,是否能申请到锁,如tryLock(3,TimeUnit.SECONDS)表示尝试在3S内申请锁,3S后返回申请的结果。

  1. public class ReentrantLockTest {
  2. Lock lock=new ReentrantLock();
  3. void methodA() {
  4. try {
  5. lock.lock();
  6. System.out.println("Method A start ...");
  7. TimeUnit.SECONDS.sleep(10);
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. }finally {
  11. lock.unlock();
  12. System.out.println("Method A end ...");
  13. }
  14. }
  15. void methodB() {
  16. boolean flag=false;
  17. try {
  18. flag=lock.tryLock();//尝试获取锁,无论申请到与否,都会执行下面的方法
  19. System.out.println("Method B try to get the lock "+(flag==true?"success":"failed"));
  20. if(flag==true) {
  21. System.out.println("Method B start ...");
  22. }else {
  23. System.out.println("Method B end without getting the lock");
  24. }
  25. } catch (Exception e) {
  26. e.printStackTrace();
  27. }finally {
  28. if(flag==true) lock.unlock();
  29. }
  30. }
  31. public static void main(String[] args) throws InterruptedException {
  32. ReentrantLockTest test=new ReentrantLockTest();
  33. new Thread(test::methodA).start();
  34. TimeUnit.SECONDS.sleep(2);
  35. new Thread(test::methodB).start();
  36. }
  37. }

lockInterruptibly方法,如果当前线程未被中断,则获取锁,如果锁可用,则获取锁,如果锁不可用,出于线程调优的目的,将禁用当前线程,下面演示一个当前线程锁不可用的例子。

  1. public class ReentrantLockTest {
  2. Lock lock=new ReentrantLock();
  3. void methodA() {
  4. try {
  5. lock.lock();
  6. System.out.println("Method A start ...");
  7. TimeUnit.SECONDS.sleep(10);
  8. } catch (Exception e) {
  9. e.printStackTrace();
  10. }finally {
  11. lock.unlock();
  12. System.out.println("Method A end ...");
  13. }
  14. }
  15. void methodB() {
  16. try {
  17. lock.lockInterruptibly();
  18. } catch (Exception e) {
  19. System.out.println("Method B was interrupted ...");
  20. }
  21. }
  22. public static void main(String[] args) throws InterruptedException {
  23. ReentrantLockTest test=new ReentrantLockTest();
  24. Thread a=new Thread(test::methodA);
  25. a.start();
  26. TimeUnit.SECONDS.sleep(2);
  27. Thread b=new Thread(test::methodB);
  28. b.start();
  29. TimeUnit.SECONDS.sleep(1);
  30. b.interrupt();
  31. }
  32. }

ReentrantLock还可以指定公平锁,在介绍它的应用之前,我们先简单理解一下什么是公平锁和非公平锁。公平锁即一个新线程发出请求时,这个锁真被其他线程占有,或者还有其他线程也在等待,它会选择进入队列中等候。非公平锁即一个新线程在申请一个被其他线程占用的锁时,当这个锁释放了,它会立即抢夺,而不需排队等候。非公平锁的性能要比公平锁的性能高,因为在恢复一个被挂起的线程与该线程真正运行之间存在着严重的延迟。下面介绍它的使用。

  1. package com.company.test02;
  2. import java.util.concurrent.locks.Lock;
  3. import java.util.concurrent.locks.ReentrantLock;
  4. /**
  5. * @ClassName : TestLock //类名
  6. * @Description : ReentrantLock //描述
  7. * @Author : Mr.Duan //作者
  8. * @Date: 2020-08-13 11:21 //时间
  9. */
  10. public class ReentrantLockTest {
  11. Lock lock=new ReentrantLock(true);
  12. void method() {
  13. for(int i=0;i<50;i++) {
  14. try {
  15. lock.lock();
  16. System.out.println(Thread.currentThread().getName()+"抢夺到了该锁");
  17. Thread.sleep(1);
  18. } catch (Exception e) {
  19. e.printStackTrace();
  20. }finally {
  21. lock.unlock();
  22. }
  23. }
  24. }
  25. public static void main(String[] args) throws InterruptedException {
  26. ReentrantLockTest test=new ReentrantLockTest();
  27. new Thread(test::method,"线程1").start();
  28. new Thread(test::method,"线程2").start();
  29. }
  30. }

输出结果:

  1. "D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=49929:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\IdeaProjects\20200813\ClassDome\out\production\ClassDome" com.company.test02.ReentrantLockTest
  2. 线程1抢夺到了该锁
  3. 线程2抢夺到了该锁
  4. 线程1抢夺到了该锁
  5. 线程2抢夺到了该锁
  6. 线程1抢夺到了该锁
  7. 线程2抢夺到了该锁
  8. 线程1抢夺到了该锁
  9. 线程2抢夺到了该锁
  10. 线程1抢夺到了该锁
  11. 线程2抢夺到了该锁
  12. 线程1抢夺到了该锁
  13. 线程2抢夺到了该锁
  14. 线程1抢夺到了该锁
  15. 线程2抢夺到了该锁
  16. 线程1抢夺到了该锁
  17. 线程2抢夺到了该锁
  18. 线程1抢夺到了该锁
  19. 线程2抢夺到了该锁
  20. 线程1抢夺到了该锁
  21. 线程2抢夺到了该锁
  22. 线程1抢夺到了该锁
  23. 线程2抢夺到了该锁
  24. 线程1抢夺到了该锁
  25. 线程2抢夺到了该锁
  26. 线程1抢夺到了该锁
  27. 线程2抢夺到了该锁
  28. 线程1抢夺到了该锁
  29. 线程2抢夺到了该锁
  30. 线程1抢夺到了该锁
  31. 线程2抢夺到了该锁
  32. 线程1抢夺到了该锁
  33. 线程2抢夺到了该锁
  34. 线程1抢夺到了该锁
  35. 线程2抢夺到了该锁
  36. 线程1抢夺到了该锁
  37. 线程2抢夺到了该锁
  38. 线程1抢夺到了该锁
  39. 线程2抢夺到了该锁
  40. 线程1抢夺到了该锁
  41. 线程2抢夺到了该锁
  42. 线程1抢夺到了该锁
  43. 线程2抢夺到了该锁
  44. 线程1抢夺到了该锁
  45. 线程2抢夺到了该锁
  46. 线程1抢夺到了该锁
  47. 线程2抢夺到了该锁
  48. 线程1抢夺到了该锁
  49. 线程2抢夺到了该锁
  50. 线程1抢夺到了该锁
  51. 线程2抢夺到了该锁
  52. 线程1抢夺到了该锁
  53. 线程2抢夺到了该锁
  54. 线程1抢夺到了该锁
  55. 线程2抢夺到了该锁
  56. 线程1抢夺到了该锁
  57. 线程2抢夺到了该锁
  58. 线程1抢夺到了该锁
  59. 线程2抢夺到了该锁
  60. 线程1抢夺到了该锁
  61. 线程2抢夺到了该锁
  62. 线程1抢夺到了该锁
  63. 线程2抢夺到了该锁
  64. 线程1抢夺到了该锁
  65. 线程2抢夺到了该锁
  66. 线程1抢夺到了该锁
  67. 线程2抢夺到了该锁
  68. 线程1抢夺到了该锁
  69. 线程2抢夺到了该锁
  70. 线程1抢夺到了该锁
  71. 线程2抢夺到了该锁
  72. 线程1抢夺到了该锁
  73. 线程2抢夺到了该锁
  74. 线程1抢夺到了该锁
  75. 线程2抢夺到了该锁
  76. 线程1抢夺到了该锁
  77. 线程2抢夺到了该锁
  78. 线程1抢夺到了该锁
  79. 线程2抢夺到了该锁
  80. 线程1抢夺到了该锁
  81. 线程2抢夺到了该锁
  82. 线程1抢夺到了该锁
  83. 线程2抢夺到了该锁
  84. 线程1抢夺到了该锁
  85. 线程2抢夺到了该锁
  86. 线程1抢夺到了该锁
  87. 线程2抢夺到了该锁
  88. 线程1抢夺到了该锁
  89. 线程2抢夺到了该锁
  90. 线程1抢夺到了该锁
  91. 线程2抢夺到了该锁
  92. 线程1抢夺到了该锁
  93. 线程2抢夺到了该锁
  94. 线程1抢夺到了该锁
  95. 线程2抢夺到了该锁
  96. 线程1抢夺到了该锁
  97. 线程2抢夺到了该锁
  98. 线程1抢夺到了该锁
  99. 线程2抢夺到了该锁
  100. 线程1抢夺到了该锁
  101. 线程2抢夺到了该锁
  102. Process finished with exit code 0

实例化一个ReentrantLock对象时,传入一个参数true即可将之指定为公平锁,从上面的运行结果可以看出,线程1和线程2轮流获得锁的权限。

9、生产者消费者问题

题目描述:假定有10个消费者线程,2个生产者线程,容器的最大容量为10,生产者不停生产,消费者不停消费,试着设计一个程序来解决该问题。
解法一:使用基本的wait,notify,notifyAll来形成线程之间的阻塞和等待。

  1. public class T {
  2. static final int MAX=10;
  3. volatile LinkedList<String> container=new LinkedList<>();
  4. volatile int i=0;
  5. void produce() {
  6. while(true) {
  7. try {
  8. TimeUnit.SECONDS.sleep(1);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. synchronized (container) {
  13. while(getCount()==MAX) {
  14. try {
  15. container.wait();
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. }
  20. String s="产品"+i+"号";
  21. container.add(s);
  22. System.out.println(Thread.currentThread().getName()+"生产了"+s+",当前库存"+getCount());
  23. i++;
  24. container.notifyAll();
  25. }
  26. }
  27. }
  28. void consume() {
  29. while(true) {
  30. try {
  31. TimeUnit.SECONDS.sleep(3);
  32. } catch (InterruptedException e) {
  33. e.printStackTrace();
  34. }
  35. synchronized (container) {
  36. String result=null;
  37. while(getCount()==0) {
  38. try {
  39. container.wait();
  40. } catch (InterruptedException e) {
  41. e.printStackTrace();
  42. }
  43. }
  44. System.out.println(Thread.currentThread().getName()+"消费了"+
  45. container.removeFirst()+",剩余库存"+getCount());
  46. container.notifyAll();
  47. }
  48. }
  49. }
  50. synchronized int getCount() {
  51. return container.size();
  52. }
  53. public static void main(String[] args) {
  54. T t=new T();
  55. List<Thread> consumes=new ArrayList<Thread>();
  56. List<Thread> produces=new ArrayList<Thread>();
  57. for(int j=0;j<10;j++) {
  58. consumes.add(new Thread(t::consume,"消费者"+j+"号"));
  59. }
  60. for(int j=0;j<2;j++) {
  61. consumes.add(new Thread(t::produce,"生产者"+j+"号"));
  62. }
  63. consumes.forEach(o->o.start());
  64. produces.forEach(o->o.start());
  65. }
  66. }

解法二:使用Lock和Condition来解决。

  1. public class T {
  2. static final int MAX=10;
  3. volatile LinkedList<String> container=new LinkedList<>();
  4. volatile int i=0;
  5. ReentrantLock lock=new ReentrantLock();
  6. Condition produce=lock.newCondition();
  7. Condition consume=lock.newCondition();
  8. void produce() {
  9. while(true) {
  10. try {
  11. lock.lock();
  12. if(getCount()==MAX) produce.await();
  13. TimeUnit.SECONDS.sleep(1);
  14. String s="产品"+i+"号";
  15. container.add(s);
  16. System.out.println(Thread.currentThread().getName()+"生产了"+s+",当前库存"+getCount());
  17. i++;
  18. consume.signalAll();
  19. }catch (Exception e) {
  20. e.printStackTrace();
  21. }finally {
  22. lock.unlock();
  23. }
  24. }
  25. }
  26. void consume() {
  27. while(true) {
  28. try {
  29. lock.lock();
  30. if(getCount()==0) consume.await();
  31. TimeUnit.SECONDS.sleep(3);
  32. System.out.println(Thread.currentThread().getName()+"消费了"+
  33. container.removeFirst()+",剩余库存"+getCount());
  34. produce.signalAll();
  35. }catch (Exception e) {
  36. e.printStackTrace();
  37. }finally {
  38. lock.unlock();
  39. }
  40. }
  41. }
  42. synchronized int getCount() {
  43. return container.size();
  44. }
  45. public static void main(String[] args) {
  46. T t=new T();
  47. List<Thread> consumes=new ArrayList<Thread>();
  48. List<Thread> produces=new ArrayList<Thread>();
  49. for(int j=0;j<10;j++) {
  50. consumes.add(new Thread(t::consume,"消费者"+j+"号"));
  51. }
  52. for(int j=0;j<2;j++) {
  53. consumes.add(new Thread(t::produce,"生产者"+j+"号"));
  54. }
  55. consumes.forEach(o->o.start());
  56. produces.forEach(o->o.start());
  57. }
  58. }

10、ThreadLocal

ThreadLocal用于代表线程的局部变量,首先我们先通过一段代码来演示一种两个线程之间对同一个数据的读写的可能情况。

  1. package com.company.test02;
  2. /**
  3. * @ClassName : TestLock //类名
  4. * @Description : ThreadLocal用
  5. 于代表线程的局部变量,首先我们先通过一段代码来演示一
  6. 种两个线程之间对同一个数据的读写的可能情况。 //描述
  7. * @Author : Mr.Duan //作者
  8. * @Date: 2020-08-13 11:21 //时间
  9. */
  10. public class TestLock {
  11. String value=new String("修改前");
  12. void a() {
  13. try {
  14. Thread.sleep(2);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. System.out.println(value);
  19. }
  20. void b() {
  21. try {
  22. Thread.sleep(1);
  23. } catch (InterruptedException e) {
  24. e.printStackTrace();
  25. }
  26. value=new String("修改后");
  27. }
  28. public static void main(String[] args){
  29. TestLock t=new TestLock();
  30. new Thread(t::a).start();
  31. new Thread(t::b).start();
  32. }
  33. }

输出结果:

  1. "D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=49394:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\IdeaProjects\20200813\ClassDome\out\production\ClassDome" com.company.test02.TestLock
  2. 修改后
  3. Process finished with exit code 0

运行输出的结果为“修改后”,因为线程A睡眠时间比线程B长,在线程A输出value的值的时候,value的值已经被线程B给修改了。在某些情况下,我们并不想因为一个线程中对对象作出了修改而影响到其他对象,比如一个游戏,一个玩家的行为有时候并不会影响到其他玩家,这个时候我们可以使用ThreadLocal来实现。

  1. package com.company.test02;
  2. /**
  3. * @ClassName : TestLock //类名
  4. * @Description : //描述
  5. * @Author : Mr.Duan //作者
  6. * @Date: 2020-08-13 11:21 //时间
  7. */
  8. public class TestLock {
  9. ThreadLocal<String> value=new ThreadLocal<String>();
  10. void a() {
  11. try {
  12. Thread.sleep(2);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. System.out.println(value.get());
  17. }
  18. void b() {
  19. try {
  20. Thread.sleep(1);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. System.out.println(value.get());
  25. value.set("修改后");
  26. System.out.println(value.get());
  27. }
  28. public static void main(String[] args){
  29. TestLock t=new TestLock();
  30. new Thread(t::a).start();
  31. new Thread(t::b).start();
  32. }
  33. }

运行结果如下:

  1. "D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=49207:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\IdeaProjects\20200813\ClassDome\out\production\ClassDome" com.company.test02.TestLock
  2. null
  3. 修改后
  4. null
  5. Process finished with exit code 0

11、多窗口售票问题

问题描述:假如一个车站有10个售票窗口,车站共计有100张票,所有窗口一起工作,设计一到程序来模拟窗口售票的过程。
错误解法:我们使用ArrayList来存放车票,因为ArrayList它是不安全的,线程并发过程中可能存在问题,出现ArrayIndexOutOfBoundsException异常。

  1. public class T{
  2. package com.company.test02;
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. /**
  6. * @ClassName : TestLock //类名
  7. * @Description : 多窗口售票问题 //描述
  8. * @Author : Mr.Duan //作者
  9. * @Date: 2020-08-13 11:21 //时间
  10. */
  11. public class TestLock {
  12. static List<String> tickets=new ArrayList<String>();
  13. static {
  14. for(int i=1;i<=100;i++) {
  15. tickets.add(new String("编号"+i+"的车票"));
  16. }
  17. }
  18. void sale() {
  19. while(tickets.size()>0) {
  20. try {
  21. Thread.sleep(100);//睡眠100ms
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. System.out.println(Thread.currentThread().getName()+"销售了"+tickets.remove(0));
  26. }
  27. }
  28. public static void main(String[] args){
  29. TestLock t=new TestLock();
  30. List<Thread> conductors=new ArrayList<Thread>();
  31. for(int i=1;i<=10;i++) {
  32. conductors.add(new Thread(t::sale,i+"号窗口"));
  33. }
  34. conductors.forEach(o->o.start());
  35. }
  36. }

输出结果:

  1. "D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=65292:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\IdeaProjects\20200813\ClassDome\out\production\ClassDome" com.company.test02.TestLock
  2. 4号窗口销售了编号1的车票
  3. 8号窗口销售了编号7的车票
  4. 7号窗口销售了编号8的车票
  5. 6号窗口销售了编号6的车票
  6. 5号窗口销售了编号5的车票
  7. 3号窗口销售了编号3的车票
  8. 1号窗口销售了编号1的车票
  9. 2号窗口销售了编号1的车票
  10. 9号窗口销售了编号9的车票
  11. 10号窗口销售了编号10的车票
  12. 4号窗口销售了编号10的车票
  13. 8号窗口销售了编号12的车票
  14. 7号窗口销售了编号13的车票
  15. 6号窗口销售了编号14的车票
  16. 5号窗口销售了编号15的车票
  17. 3号窗口销售了编号16的车票
  18. 1号窗口销售了编号17的车票
  19. 2号窗口销售了编号18的车票
  20. 10号窗口销售了编号19的车票
  21. 9号窗口销售了编号20的车票
  22. 4号窗口销售了编号21的车票
  23. 8号窗口销售了编号22的车票
  24. 7号窗口销售了编号23的车票
  25. 6号窗口销售了编号24的车票
  26. 5号窗口销售了编号25的车票
  27. 3号窗口销售了编号26的车票
  28. 1号窗口销售了编号27的车票
  29. 2号窗口销售了编号28的车票
  30. 9号窗口销售了编号30的车票
  31. 10号窗口销售了编号29的车票
  32. 8号窗口销售了编号31的车票
  33. 4号窗口销售了编号32的车票
  34. 1号窗口销售了编号33的车票
  35. 3号窗口销售了编号34的车票
  36. 5号窗口销售了编号35的车票
  37. 6号窗口销售了编号36的车票
  38. 7号窗口销售了编号37的车票
  39. 10号窗口销售了编号39的车票
  40. 2号窗口销售了编号40的车票
  41. 9号窗口销售了编号38的车票
  42. 5号窗口销售了编号41的车票
  43. 3号窗口销售了编号42的车票
  44. 1号窗口销售了编号43的车票
  45. 4号窗口销售了编号44的车票
  46. 8号窗口销售了编号45的车票
  47. 6号窗口销售了编号46的车票
  48. 9号窗口销售了编号47的车票
  49. 2号窗口销售了编号48的车票
  50. 10号窗口销售了编号49的车票
  51. 7号窗口销售了编号50的车票
  52. 5号窗口销售了编号51的车票
  53. 3号窗口销售了编号51的车票
  54. 1号窗口销售了编号52的车票
  55. 4号窗口销售了编号53的车票
  56. 8号窗口销售了编号54的车票
  57. 6号窗口销售了编号55的车票
  58. 7号窗口销售了编号56的车票
  59. 10号窗口销售了编号57的车票
  60. 9号窗口销售了编号58的车票
  61. 2号窗口销售了编号59的车票
  62. 1号窗口销售了编号60的车票
  63. 3号窗口销售了编号63的车票
  64. 5号窗口销售了编号62的车票
  65. 8号窗口销售了编号60的车票
  66. 4号窗口销售了编号60的车票
  67. 6号窗口销售了编号64的车票
  68. 7号窗口销售了编号65的车票
  69. 10号窗口销售了编号66的车票
  70. 9号窗口销售了编号67的车票
  71. 2号窗口销售了编号69的车票
  72. 4号窗口销售了编号70的车票
  73. 8号窗口销售了编号71的车票
  74. 5号窗口销售了编号72的车票
  75. 3号窗口销售了编号73的车票
  76. 1号窗口销售了编号74的车票
  77. 6号窗口销售了编号75的车票
  78. 7号窗口销售了编号77的车票
  79. 9号窗口销售了编号78的车票
  80. 10号窗口销售了编号79的车票
  81. 2号窗口销售了编号80的车票
  82. 5号窗口销售了编号81的车票
  83. 4号窗口销售了编号83的车票
  84. 8号窗口销售了编号81的车票
  85. 1号窗口销售了编号84的车票
  86. 3号窗口销售了编号85的车票
  87. 6号窗口销售了编号84的车票
  88. 9号窗口销售了编号86的车票
  89. 10号窗口销售了编号87的车票
  90. 7号窗口销售了编号88的车票
  91. 2号窗口销售了编号89的车票
  92. 4号窗口销售了编号90的车票
  93. 8号窗口销售了编号91的车票
  94. 5号窗口销售了编号90的车票
  95. 1号窗口销售了编号92的车票
  96. 3号窗口销售了编号94的车票
  97. 6号窗口销售了编号95的车票
  98. 9号窗口销售了编号96的车票
  99. 10号窗口销售了编号97的车票
  100. 7号窗口销售了编号98的车票
  101. 2号窗口销售了编号98的车票
  102. 5号窗口销售了编号100的车票
  103. 4号窗口销售了编号100的车票
  104. 8号窗口销售了编号100的车票
  105. 3号窗口销售了编号100的车票
  106. 6号窗口销售了编号100的车票
  107. Exception in thread "1号窗口" Exception in thread "7号窗口" Exception in thread "9号窗口" Exception in thread "10号窗口" Exception in thread "2号窗口" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
  108. at java.util.ArrayList.rangeCheck(ArrayList.java:653)
  109. at java.util.ArrayList.remove(ArrayList.java:492)
  110. at com.company.test02.TestLock.sale(TestLock.java:30)
  111. at java.lang.Thread.run(Thread.java:748)
  112. java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
  113. at java.util.ArrayList.rangeCheck(ArrayList.java:653)
  114. at java.util.ArrayList.remove(ArrayList.java:492)
  115. at com.company.test02.TestLock.sale(TestLock.java:30)
  116. at java.lang.Thread.run(Thread.java:748)
  117. java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
  118. at java.util.ArrayList.rangeCheck(ArrayList.java:653)
  119. at java.util.ArrayList.remove(ArrayList.java:492)
  120. at com.company.test02.TestLock.sale(TestLock.java:30)
  121. at java.lang.Thread.run(Thread.java:748)
  122. java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
  123. at java.util.ArrayList.rangeCheck(ArrayList.java:653)
  124. at java.util.ArrayList.remove(ArrayList.java:492)
  125. at com.company.test02.TestLock.sale(TestLock.java:30)
  126. at java.lang.Thread.run(Thread.java:748)
  127. java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
  128. at java.util.ArrayList.rangeCheck(ArrayList.java:653)
  129. at java.util.ArrayList.remove(ArrayList.java:492)
  130. at com.company.test02.TestLock.sale(TestLock.java:30)
  131. at java.lang.Thread.run(Thread.java:748)
  132. Exception in thread "8号窗口" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
  133. at java.util.ArrayList.rangeCheck(ArrayList.java:653)
  134. at java.util.ArrayList.remove(ArrayList.java:492)
  135. at com.company.test02.TestLock.sale(TestLock.java:30)
  136. at java.lang.Thread.run(Thread.java:748)
  137. Exception in thread "4号窗口" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
  138. at java.util.ArrayList.rangeCheck(ArrayList.java:653)
  139. at java.util.ArrayList.remove(ArrayList.java:492)
  140. at com.company.test02.TestLock.sale(TestLock.java:30)
  141. at java.lang.Thread.run(Thread.java:748)
  142. Exception in thread "5号窗口" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
  143. at java.util.ArrayList.rangeCheck(ArrayList.java:653)
  144. at java.util.ArrayList.remove(ArrayList.java:492)
  145. at com.company.test02.TestLock.sale(TestLock.java:30)
  146. at java.lang.Thread.run(Thread.java:748)
  147. Process finished with exit code 0

解法一:使用Vector类来存放车票,注意Vector是安全对象,他的操作都具有安全性,那么Vector已经具有了安全性,我们只需要将上面错误案例中的ArrayList该问Vector就可以了吗?答案是当然不行,因为我们不能保证案例中的size方法和remove方法调用过程中间也都是安全的,即上例中的try catch包裹的部分,我们如果将其删除,程序也是没问题的,但是很显然该方法是不可取的,因为实际情况下,取票总是要时间的,所以我们需要加上synchronized关键字来保证其他过程的安全性。

  1. package com.company.test02;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.Vector;
  5. /**
  6. * @ClassName : TestLock //类名
  7. * @Description : 使用Vector类来存放车票 //描述
  8. * @Author : Mr.Duan //作者
  9. * @Date: 2020-08-13 11:21 //时间
  10. */
  11. public class TestLock {
  12. static List<String> tickets=new Vector<String>();
  13. static {
  14. for(int i=1;i<=100;i++) {
  15. tickets.add(new String("编号"+i+"的车票"));
  16. }
  17. }
  18. void sale() {
  19. while(true) {
  20. synchronized (tickets) {
  21. if(tickets.size()==0) break;
  22. try {
  23. Thread.sleep(100);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. System.out.println(Thread.currentThread().getName()+"销售了"+tickets.remove(0));
  28. }
  29. }
  30. }
  31. public static void main(String[] args){
  32. TestLock t=new TestLock();
  33. List<Thread> conductors=new ArrayList<Thread>();
  34. for(int i=1;i<=10;i++) {
  35. conductors.add(new Thread(t::sale,i+"号窗口"));
  36. }
  37. conductors.forEach(o->o.start());
  38. }
  39. }

输出结果:

  1. "D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=65056:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\IdeaProjects\20200813\ClassDome\out\production\ClassDome" com.company.test02.TestLock
  2. 1号窗口销售了编号1的车票
  3. 1号窗口销售了编号2的车票
  4. 10号窗口销售了编号3的车票
  5. 9号窗口销售了编号4的车票
  6. 8号窗口销售了编号5的车票
  7. 7号窗口销售了编号6的车票
  8. 6号窗口销售了编号7的车票
  9. 5号窗口销售了编号8的车票
  10. 4号窗口销售了编号9的车票
  11. 4号窗口销售了编号10的车票
  12. 4号窗口销售了编号11的车票
  13. 3号窗口销售了编号12的车票
  14. 2号窗口销售了编号13的车票
  15. 3号窗口销售了编号14的车票
  16. 4号窗口销售了编号15的车票
  17. 5号窗口销售了编号16的车票
  18. 6号窗口销售了编号17的车票
  19. 7号窗口销售了编号18的车票
  20. 8号窗口销售了编号19的车票
  21. 9号窗口销售了编号20的车票
  22. 10号窗口销售了编号21的车票
  23. 1号窗口销售了编号22的车票
  24. 10号窗口销售了编号23的车票
  25. 9号窗口销售了编号24的车票
  26. 9号窗口销售了编号25的车票
  27. 8号窗口销售了编号26的车票
  28. 8号窗口销售了编号27的车票
  29. 7号窗口销售了编号28的车票
  30. 6号窗口销售了编号29的车票
  31. 5号窗口销售了编号30的车票
  32. 5号窗口销售了编号31的车票
  33. 4号窗口销售了编号32的车票
  34. 3号窗口销售了编号33的车票
  35. 3号窗口销售了编号34的车票
  36. 2号窗口销售了编号35的车票
  37. 2号窗口销售了编号36的车票
  38. 2号窗口销售了编号37的车票
  39. 2号窗口销售了编号38的车票
  40. 2号窗口销售了编号39的车票
  41. 3号窗口销售了编号40的车票
  42. 3号窗口销售了编号41的车票
  43. 4号窗口销售了编号42的车票
  44. 5号窗口销售了编号43的车票
  45. 6号窗口销售了编号44的车票
  46. 6号窗口销售了编号45的车票
  47. 7号窗口销售了编号46的车票
  48. 7号窗口销售了编号47的车票
  49. 8号窗口销售了编号48的车票
  50. 9号窗口销售了编号49的车票
  51. 10号窗口销售了编号50的车票
  52. 1号窗口销售了编号51的车票
  53. 1号窗口销售了编号52的车票
  54. 10号窗口销售了编号53的车票
  55. 10号窗口销售了编号54的车票
  56. 9号窗口销售了编号55的车票
  57. 9号窗口销售了编号56的车票
  58. 9号窗口销售了编号57的车票
  59. 9号窗口销售了编号58的车票
  60. 8号窗口销售了编号59的车票
  61. 7号窗口销售了编号60的车票
  62. 7号窗口销售了编号61的车票
  63. 6号窗口销售了编号62的车票
  64. 6号窗口销售了编号63的车票
  65. 5号窗口销售了编号64的车票
  66. 4号窗口销售了编号65的车票
  67. 3号窗口销售了编号66的车票
  68. 2号窗口销售了编号67的车票
  69. 3号窗口销售了编号68的车票
  70. 3号窗口销售了编号69的车票
  71. 4号窗口销售了编号70的车票
  72. 4号窗口销售了编号71的车票
  73. 4号窗口销售了编号72的车票
  74. 5号窗口销售了编号73的车票
  75. 5号窗口销售了编号74的车票
  76. 5号窗口销售了编号75的车票
  77. 6号窗口销售了编号76的车票
  78. 6号窗口销售了编号77的车票
  79. 6号窗口销售了编号78的车票
  80. 6号窗口销售了编号79的车票
  81. 6号窗口销售了编号80的车票
  82. 7号窗口销售了编号81的车票
  83. 7号窗口销售了编号82的车票
  84. 8号窗口销售了编号83的车票
  85. 9号窗口销售了编号84的车票
  86. 9号窗口销售了编号85的车票
  87. 10号窗口销售了编号86的车票
  88. 10号窗口销售了编号87的车票
  89. 1号窗口销售了编号88的车票
  90. 1号窗口销售了编号89的车票
  91. 10号窗口销售了编号90的车票
  92. 10号窗口销售了编号91的车票
  93. 9号窗口销售了编号92的车票
  94. 8号窗口销售了编号93的车票
  95. 7号窗口销售了编号94的车票
  96. 7号窗口销售了编号95的车票
  97. 6号窗口销售了编号96的车票
  98. 6号窗口销售了编号97的车票
  99. 6号窗口销售了编号98的车票
  100. 6号窗口销售了编号99的车票
  101. 5号窗口销售了编号100的车票
  102. Process finished with exit code 0

解法二:显然解法一也不少很友好,因为synchronized的使用,使得程序的效率并不是很高,接下来我们尝试用ConcurrentLinkedQueue来解决。ConcurrentLinkedQueue同样也是安全的,结合上例分析,为什么我们不加synchronized,下面代码的运行结果也是正确的那,因为即使线程1执行到了tickets.poll后,线程2在执行下面的if语句,相互之间也不会形成干扰。

  1. package com.company.test02;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. import java.util.Queue;
  5. import java.util.concurrent.ConcurrentLinkedQueue;
  6. /**
  7. * @ClassName : TestLock //类名
  8. * @Description : 用ConcurrentLinkedQueue来解决 //描述
  9. * @Author : Mr.Duan //作者
  10. * @Date: 2020-08-13 11:21 //时间
  11. */
  12. public class TestLock {
  13. static Queue<String> tickets=new ConcurrentLinkedQueue<String>();
  14. static {
  15. for(int i=1;i<=100;i++) {
  16. tickets.add(new String("编号"+i+"的车票"));
  17. }
  18. }
  19. void sale() {
  20. while(true) {
  21. String ticket=tickets.poll();
  22. if(ticket==null) break; System.out.println(Thread.currentThread().getName()+"销售了"+ticket);
  23. }
  24. }
  25. public static void main(String[] args){
  26. TestLock t=new TestLock();
  27. List<Thread> conductors=new ArrayList<Thread>();
  28. for(int i=1;i<=10;i++) {
  29. conductors.add(new Thread(t::sale,i+"号窗口"));
  30. }
  31. conductors.forEach(o->o.start());
  32. }
  33. }

输出结果:

  1. "D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=64838:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\IdeaProjects\20200813\ClassDome\out\production\ClassDome" com.company.test02.TestLock
  2. 1号窗口销售了编号1的车票
  3. 3号窗口销售了编号3的车票
  4. 2号窗口销售了编号2的车票
  5. 2号窗口销售了编号6的车票
  6. 3号窗口销售了编号5的车票
  7. 3号窗口销售了编号8的车票
  8. 3号窗口销售了编号9的车票
  9. 3号窗口销售了编号10的车票
  10. 3号窗口销售了编号11的车票
  11. 3号窗口销售了编号12的车票
  12. 3号窗口销售了编号13的车票
  13. 3号窗口销售了编号14的车票
  14. 3号窗口销售了编号15的车票
  15. 3号窗口销售了编号16的车票
  16. 3号窗口销售了编号17的车票
  17. 3号窗口销售了编号18的车票
  18. 3号窗口销售了编号19的车票
  19. 3号窗口销售了编号20的车票
  20. 3号窗口销售了编号21的车票
  21. 3号窗口销售了编号22的车票
  22. 3号窗口销售了编号23的车票
  23. 3号窗口销售了编号24的车票
  24. 3号窗口销售了编号25的车票
  25. 3号窗口销售了编号26的车票
  26. 3号窗口销售了编号27的车票
  27. 3号窗口销售了编号28的车票
  28. 3号窗口销售了编号29的车票
  29. 3号窗口销售了编号30的车票
  30. 4号窗口销售了编号4的车票
  31. 1号窗口销售了编号32的车票
  32. 3号窗口销售了编号31的车票
  33. 2号窗口销售了编号7的车票
  34. 3号窗口销售了编号35的车票
  35. 3号窗口销售了编号37的车票
  36. 3号窗口销售了编号39的车票
  37. 3号窗口销售了编号40的车票
  38. 3号窗口销售了编号41的车票
  39. 3号窗口销售了编号42的车票
  40. 3号窗口销售了编号43的车票
  41. 3号窗口销售了编号44的车票
  42. 3号窗口销售了编号45的车票
  43. 3号窗口销售了编号46的车票
  44. 3号窗口销售了编号47的车票
  45. 3号窗口销售了编号48的车票
  46. 3号窗口销售了编号49的车票
  47. 3号窗口销售了编号50的车票
  48. 3号窗口销售了编号51的车票
  49. 3号窗口销售了编号52的车票
  50. 3号窗口销售了编号53的车票
  51. 3号窗口销售了编号54的车票
  52. 3号窗口销售了编号55的车票
  53. 3号窗口销售了编号56的车票
  54. 3号窗口销售了编号57的车票
  55. 3号窗口销售了编号58的车票
  56. 3号窗口销售了编号59的车票
  57. 3号窗口销售了编号60的车票
  58. 3号窗口销售了编号61的车票
  59. 3号窗口销售了编号62的车票
  60. 3号窗口销售了编号63的车票
  61. 3号窗口销售了编号64的车票
  62. 3号窗口销售了编号65的车票
  63. 3号窗口销售了编号66的车票
  64. 3号窗口销售了编号67的车票
  65. 3号窗口销售了编号68的车票
  66. 3号窗口销售了编号69的车票
  67. 3号窗口销售了编号70的车票
  68. 3号窗口销售了编号71的车票
  69. 3号窗口销售了编号72的车票
  70. 3号窗口销售了编号73的车票
  71. 3号窗口销售了编号74的车票
  72. 3号窗口销售了编号75的车票
  73. 3号窗口销售了编号76的车票
  74. 3号窗口销售了编号77的车票
  75. 3号窗口销售了编号78的车票
  76. 3号窗口销售了编号79的车票
  77. 3号窗口销售了编号80的车票
  78. 3号窗口销售了编号81的车票
  79. 3号窗口销售了编号82的车票
  80. 3号窗口销售了编号83的车票
  81. 3号窗口销售了编号84的车票
  82. 3号窗口销售了编号85的车票
  83. 3号窗口销售了编号86的车票
  84. 3号窗口销售了编号87的车票
  85. 3号窗口销售了编号88的车票
  86. 3号窗口销售了编号89的车票
  87. 3号窗口销售了编号90的车票
  88. 3号窗口销售了编号91的车票
  89. 3号窗口销售了编号92的车票
  90. 3号窗口销售了编号93的车票
  91. 3号窗口销售了编号94的车票
  92. 3号窗口销售了编号95的车票
  93. 3号窗口销售了编号96的车票
  94. 3号窗口销售了编号97的车票
  95. 3号窗口销售了编号98的车票
  96. 3号窗口销售了编号99的车票
  97. 3号窗口销售了编号100的车票
  98. 1号窗口销售了编号34的车票
  99. 4号窗口销售了编号33的车票
  100. 5号窗口销售了编号38的车票
  101. 2号窗口销售了编号36的车票
  102. Process finished with exit code 0

12、几种并发Map的使用

12.1、ConcurrentHashMap

高并发安全容器,下面与传统的HashMap作对比。

  1. public class T{
  2. ConcurrentHashMap<Object, Object> concurrentHashMap=new ConcurrentHashMap<>();
  3. HashMap<Object, Object> hashMap=new HashMap<>();
  4. final static int SIZE=10000;
  5. void testConcurrentHashMap(){
  6. List<Thread> threads=new ArrayList<>();
  7. long start=System.currentTimeMillis();
  8. for(int i=0;i<100;i++) {
  9. threads.add(new Thread(()-> {
  10. for(int j=0;j<SIZE;j++) {
  11. concurrentHashMap.put(new Object(), new Object());
  12. }
  13. }));
  14. }
  15. threads.forEach(o->o.start());
  16. threads.forEach(o->{
  17. try {
  18. o.join();
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. });
  23. long end=System.currentTimeMillis();
  24. System.out.println("Map大小:"+concurrentHashMap.size()+
  25. ",使用ConcurrentHashMap所用时间"+(end-start));
  26. }
  27. void testHashMap(){
  28. List<Thread> threads=new ArrayList<>();
  29. long start=System.currentTimeMillis();
  30. for(int i=0;i<100;i++) {
  31. threads.add(new Thread(()-> {
  32. for(int j=0;j<SIZE;j++) {
  33. hashMap.put(new Object(), new Object());
  34. }
  35. }));
  36. }
  37. threads.forEach(o->o.start());
  38. threads.forEach(o->{
  39. try {
  40. o.join();
  41. } catch (InterruptedException e) {
  42. e.printStackTrace();
  43. }
  44. });
  45. long end=System.currentTimeMillis();
  46. System.out.println("Map大小:"+hashMap.size()+
  47. ",使用HashMap所用时间"+(end-start));
  48. }
  49. public static void main(String[] args){
  50. T t=new T();
  51. t.testConcurrentHashMap();
  52. t.testHashMap();
  53. }
  54. }

输出结果:

  1. "D:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=64624:D:\IntelliJ IDEA 2020.1\bin" -Dfile.encoding=UTF-8 -classpath "D:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;E:\IdeaProjects\20200813\ClassDome\out\production\ClassDome" com.company.test02.TestLock
  2. Map大小:1000000,使用ConcurrentHashMap所用时间505
  3. Process finished with exit code -1

从运行结果可以看出,ConcurrentHashMap相比HashMap,多了并发安全性,而且效率也要高一点,从Concurrent(英文翻译:并存的)这个前缀也可以联想到。

12.2、ConcurrentSkipListMap

和ConcurrentHashMa相比,这个类在存放数据时,做了一个排序处理,也就是说这个map里面的元素都是有序的,相应他的效率要比ConcurrentHashMap要低很多。

  1. public class T{
  2. Map<Integer, String> a=new ConcurrentSkipListMap<Integer, String>();
  3. Map<Integer, String> b=new ConcurrentSkipListMap<Integer, String>(new MyComparator());
  4. Random random=new Random();
  5. final static int MAX=1000;
  6. void testA() {
  7. List<Thread> threads=new Vector<Thread>();
  8. for(int i=0;i<10;i++) {
  9. threads.add(new Thread(()->{
  10. for(int j=0;j<10;j++) {
  11. Integer key=random.nextInt(MAX);
  12. String value="data"+key;
  13. a.put(key, value);
  14. }
  15. }));
  16. }
  17. threads.forEach(o->o.start());
  18. threads.forEach(o->{
  19. try {
  20. o.join();
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. });
  25. System.out.println("A的容器大小:"+a.size());
  26. show(a);
  27. }
  28. synchronized void testB() {
  29. List<Thread> threads=new Vector<Thread>();
  30. for(int i=0;i<10;i++) {
  31. threads.add(new Thread(()->{
  32. for(int j=0;j<10;j++) {
  33. Integer key=random.nextInt(MAX);
  34. String value="data"+key;
  35. b.put(key, value);
  36. }
  37. }));
  38. }
  39. threads.forEach(o->o.start());
  40. threads.forEach(o->{
  41. try {
  42. o.join();
  43. } catch (InterruptedException e) {
  44. e.printStackTrace();
  45. }
  46. });
  47. System.out.println("B的容器大小:"+b.size());
  48. show(b);
  49. }
  50. void show(Map map) {
  51. Iterator iterator=map.entrySet().iterator();
  52. while (iterator.hasNext()) {
  53. Map.Entry<Integer, String> entry=(Map.Entry)iterator.next();
  54. Integer key=entry.getKey();
  55. String value=entry.getValue();
  56. System.out.println("Key is "+key+",value is "+value);
  57. }
  58. }
  59. public static void main(String[] args){
  60. T t=new T();
  61. t.testA();
  62. System.out.println("-------------------------");
  63. t.testB();
  64. }
  65. }
  66. class MyComparator implements Comparator<Integer>{
  67. @Override
  68. public int compare(Integer o1, Integer o2) {
  69. return (o2-o1);
  70. }
  71. }

Java多线程详细笔记 - 图1
Java多线程详细笔记 - 图2
ConcurrentSkipListMap类对象a是使用的默认的按照键的自然排序规则,对象b使用的是自定义排序器,从Concurrent可以猜到ConcurrentSkipListMap是一种线程安全的类,可是为什么输出的size不是100呢?因为这里,我们使用的是随机数,可能存在两个相同的key值,故size可能不是目标值100。

13、几种List的使用

13.1、ArrayList

  1. public class T{
  2. static List<Integer> arrays=new ArrayList<Integer>();
  3. public static void main(String[] args){
  4. List<Thread> threads=new ArrayList<Thread>();
  5. Random random=new Random();
  6. for(int i=0;i<100;i++) {
  7. threads.add(new Thread(()->{
  8. for(int j=0;j<100;j++)
  9. arrays.add(random.nextInt(100));
  10. }));
  11. }
  12. threads.forEach(o->o.start());
  13. threads.forEach(o->{
  14. try {
  15. o.join();
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. });
  20. System.out.println("List大小:"+arrays.size());
  21. }
  22. }

Java多线程详细笔记 - 图3
从运行结果可以看出size大小可能并不是100*100,因为ArrayList不是一个线程并发安全的List类型。

13.2、Vector

  1. public class T{
  2. static List<Integer> arrays=new Vector<Integer>();
  3. public static void main(String[] args){
  4. List<Thread> threads=new ArrayList<Thread>();
  5. Random random=new Random();
  6. for(int i=0;i<100;i++) {
  7. threads.add(new Thread(()->{
  8. for(int j=0;j<100;j++)
  9. arrays.add(random.nextInt(100));
  10. }));
  11. }
  12. threads.forEach(o->o.start());
  13. threads.forEach(o->{
  14. try {
  15. o.join();
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. });
  20. System.out.println("List大小:"+arrays.size());
  21. }
  22. }

运行结果为100*100,因为Vector是一个安全的List类型。

13.3、CopyOnWriteArrayList

  1. public class T{
  2. static List<Integer> arrays=new CopyOnWriteArrayList<Integer>();
  3. public static void main(String[] args){
  4. List<Thread> threads=new ArrayList<Thread>();
  5. Random random=new Random();
  6. for(int i=0;i<100;i++) {
  7. threads.add(new Thread(()->{
  8. for(int j=0;j<100;j++)
  9. arrays.add(random.nextInt(100));
  10. }));
  11. }
  12. threads.forEach(o->o.start());
  13. threads.forEach(o->{
  14. try {
  15. o.join();
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. });
  20. System.out.println("List大小:"+arrays.size());
  21. }
  22. }

CopyOnWriteArrayList是一个写时复制容器,它写的效率特别低,读的效率很高,上例将arrays改为CopyOnWriteArrayList类型,运行结果也一样是100*100,因为它是一个安全的类型,但其实上例这样改并不合适,因为CopyOnWriteArrayList比较适用于写少读多的应用场景。

13.4、Collections.synchronizedList

  1. public class T{
  2. static List<Integer> arrays=new ArrayList<Integer>();
  3. static {
  4. arrays=Collections.synchronizedList(arrays);
  5. }
  6. public static void main(String[] args){
  7. List<Thread> threads=new ArrayList<Thread>();
  8. Random random=new Random();
  9. for(int i=0;i<100;i++) {
  10. threads.add(new Thread(()->{
  11. for(int j=0;j<100;j++)
  12. arrays.add(random.nextInt(100));
  13. }));
  14. }
  15. threads.forEach(o->o.start());
  16. threads.forEach(o->{
  17. try {
  18. o.join();
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. });
  23. System.out.println("List大小:"+arrays.size());
  24. }
  25. }

Collections.synchronizedList方法可以将一个不安全的List类型转换为一个安全的List类型,经过该操作,上例运行结果一样是100*100。

14、几种Queue的使用

14.1、ConcurrentLinkedQueue

ConcurrentLinkedQueue是一个安全队列,关于ConcurrentLinkedQueue,上面其实已经有了一些简单的运用,下面我们来介绍一下它的常用方法,也可以说是Queue接口的相关实现类的通用方法。

  1. public class T{
  2. static Queue<Integer> queue=new ConcurrentLinkedQueue<>();
  3. public static void main(String[] args){
  4. Random random=new Random();
  5. for(int i=0;i<10;i++){
  6. //offer是向队列中添加一个值,当队列已满时,add是抛出一个异常,offer是返回一个布尔值false
  7. queue.offer(random.nextInt(20));
  8. }
  9. System.out.println(queue);
  10. System.out.println("------------------------------------");
  11. //poll是从队首移出一个元素,并将该元素返回,当队列为空时,remove是抛出一个异常,poll是返回null
  12. System.out.println(queue.poll());
  13. System.out.println(queue);
  14. System.out.println("------------------------------------");
  15. //peek是从队首返回一个元素,不会移除,当队列为空时,element是抛出一个异常,peek则是返回null
  16. System.out.println(queue.peek());
  17. System.out.println(queue);
  18. }
  19. }

14.2、LinkedBlockingQueue

基本使用如下,我们可以尝试用阻塞式队列解决之前的生产者消费者问题,具体实现自行思考。

  1. public class T{
  2. static BlockingQueue<Integer> queue=new LinkedBlockingQueue<Integer>(10);//创建一个大小为10的阻塞式队列
  3. public static void main(String[] args){
  4. Random random=new Random();
  5. new Thread(()->{
  6. for(int i=0;i<100;i++) {
  7. try {
  8. //put方法是向队列中添加元素,当队列已满时,会等待添加,而不会放弃
  9. queue.put(random.nextInt(1000));
  10. TimeUnit.SECONDS.sleep(1);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }
  15. }).start();
  16. new Thread(()->{
  17. while(true) {
  18. try {
  19. //take方法是从队首移除一个元素,如果队列为空,会等待移除
  20. System.out.println("take from queue "+queue.take());
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. }).start();
  26. }
  27. }

14.3、ArrayBlockingQueue

LinkedBlockQueue和ArrayBlockingQueue有以下几点不同:

  • ArrayBlockingQueue是一个基于数组的有界阻塞队列,而LinkedBlockQueue是一个基于链表的阻塞队列;
  • ArrayBlockingQueue的没有实现分离,即put和take用的是同一把锁,而LinkedBlockQueue的put和take则用的是两把不同的锁;
  • ArrayBlockingQueue在进行对象构造时必须指定队列的大小,而LinkedBlockQueue可以不指定队列的大小,默认是Integer.MAX_VALUE;
  • ArrayBlockingQueue在进行插入删除操作时,都是直接操作的,而LinkedBlockQueue都是先转换为一个Node节点再进行操作的,从第一点不同就能推理出,所以ArrayBlockingQueue的性能是优于LinkedBlockQueue的。

    因为ArrayBlockingQueue的方法和LinkedBlockQueue的方法并无太大区别,在此就不做举例了,可以参考上面的LinkedBlockQueue或官方文档。

    14.4、DelayQueue

    DelayQueue是一个延时队列,即在指定的时间后,才能获取队列中的元素中的值。通过设计一个类实现Delayed接口,并重写getDelay和compareTo方法,其中getDelay是用于返回延时时间的,compareTo是返回一个对象与另一个对象的时间间隔的大小比较的结果的,这个设计的类对象将作为队列中的元素。 ```java public class T{

  1. public static void main(String[] args) throws Exception{
  2. DelayQueue<Item> queue=new DelayQueue<Item>();
  3. Item item1=new Item("A", 4, TimeUnit.SECONDS);
  4. Item item2=new Item("B", 2, TimeUnit.SECONDS);
  5. Item item3=new Item("C", 6, TimeUnit.SECONDS);
  6. Long start=System.currentTimeMillis();
  7. queue.put(item1);
  8. queue.put(item2);
  9. queue.put(item3);
  10. for(int i=0;i<3;i++) {
  11. Item item=queue.take();
  12. int get=(int)(System.currentTimeMillis()-start)/1000;
  13. System.out.println("Time in "+get+"s later,"+item+" was token from queue !");
  14. }
  15. }

} class Item implements Delayed{

  1. String value;
  2. private long time;
  3. public Item(String value,long time,TimeUnit unit) {
  4. this.value=value;
  5. this.time=System.currentTimeMillis()+unit.toMillis(time);
  6. }
  7. @Override
  8. public int compareTo(Delayed o) {
  9. Item another=(Item) o;
  10. long diff=this.time-another.time;
  11. return diff<=0?-1:1;
  12. }
  13. @Override
  14. public long getDelay(TimeUnit unit) {
  15. return time-System.currentTimeMillis();
  16. }
  17. @Override
  18. public String toString() {
  19. return value;
  20. }

}

  1. <a name="05i0b"></a>
  2. ### 14.5、LinkedTransferQueue
  3. LinkedTransferQueue是一个无界阻塞队列,它和其他的Queue实现类相比,多了一个比较经典的transfer方法。下面来进行一下代码演示。
  4. ```java
  5. public class T{
  6. public static void main(String[] args) throws Exception{
  7. TransferQueue<Integer> queue=new LinkedTransferQueue<>();//无界阻塞队列
  8. new Thread(()->{
  9. try {
  10. System.out.println(queue.take());
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. }).start();
  15. queue.transfer(1);//将元素给消费者,等待取出,如果没有消费者,就会一直等待
  16. queue.tryTransfer(2, 3, TimeUnit.SECONDS);//3S钟之内如果元素还没有被消费者取出,就停止等待阻塞
  17. queue.put(3);//向队列中添加元素,不会形成等待
  18. System.out.println(queue);
  19. }
  20. }

Java多线程详细笔记 - 图4

14.6、SynchronousQueue

SynchronousQueue是一个没有容量的无界非缓存同步队列。

  1. public class T{
  2. public static void main(String[] args) throws Exception{
  3. BlockingQueue<Integer> queue=new SynchronousQueue<Integer>();//没有容量的阻塞队列
  4. new Thread(()->{
  5. try {
  6. System.out.println(queue.take());
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. }).start();
  11. queue.put(3);//插入元素,等待其他线程取出
  12. queue.put(4);//因为进行了两次put操作,只有一次take操作,所以这行的put操作会形成等待
  13. System.out.println("---------------------");
  14. }
  15. }

15、线程池

前景引入:Executor是执行器的意思,它是Java提供的一个接口;ExecutorService是一个接口,提供了管理终止的方法,以及可为跟踪一个或多个异步任务执行状况而生成Future 的方法;Executors是可以用于创建各种类型的线程池。下面介绍一下它的简单使用。

  1. public class T{
  2. public static void main(String[] args) throws Exception{
  3. new MyExecutor1().execute(()->{
  4. System.out.println(Thread.currentThread().getName()+" is running");
  5. });
  6. new MyExecutor2().execute(()->{
  7. System.out.println(Thread.currentThread().getName()+" is running");
  8. });
  9. System.out.println("main end");
  10. }
  11. }
  12. class MyExecutor1 implements Executor{//为一个任务创建一个新的线程并启动
  13. @Override
  14. public void execute(Runnable command) {
  15. new Thread(command).start();
  16. }
  17. }
  18. class MyExecutor2 implements Executor{//在调用者的线程中,立即运行已提交的任务
  19. @Override
  20. public void execute(Runnable command) {
  21. command.run();
  22. }
  23. }

Java多线程详细笔记 - 图5

15.1、newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

  1. public class T{
  2. public static void main(String[] args) throws Exception{
  3. ExecutorService service=Executors.newFixedThreadPool(5);//创建一个容量为5的有界线程池
  4. for(int i=0;i<6;i++) {//这里相当于给线程池中添加了6个线程
  5. service.execute(()->{
  6. try {
  7. TimeUnit.SECONDS.sleep(1);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. System.out.println(Thread.currentThread().getName());
  12. });
  13. }
  14. System.out.println(service);
  15. service.shutdown();//启动一次顺序关闭,执行以前提交的任务,但不接受新任务。
  16. //isTerminated方法,如果关闭后所有任务都已完成,则返回 true。
  17. System.out.println("任务都已完成:"+service.isTerminated());
  18. System.out.println(service);
  19. TimeUnit.SECONDS.sleep(5);
  20. System.out.println(service);
  21. }
  22. }

Java多线程详细笔记 - 图6
代码解读:以上对于service的输出,Running是表示线程池中的线程正在运行,Shutting down表示线程池已经关闭,pool size表示线程池的大小,active threads表示活跃的线程数,queue tasks表示排队等候的线程数,completed tasks表示完成的线程数,而对Thread.currentThread().getName()的输出可以看出,第一个线程和第六个线程,其实是同一个线程,并没有重新创建新的线程。

15.2、newCachedThreadPool

创建一个可缓存的线程池,如果线程池长度超过实际需要,空闲线程将会被回收,如果没有可回收的线程,则创建新的线程。回收的线程是60S未被使用的线程,从下面案例可以看出。

  1. public class T{
  2. public static void main(String[] args) throws Exception{
  3. ExecutorService service=Executors.newCachedThreadPool();
  4. System.err.println(service);
  5. for(int i=0;i<4;i++) {//这里相当于给线程池中添加了4个线程
  6. service.execute(()->{
  7. try {
  8. TimeUnit.MILLISECONDS.sleep(10);
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. });
  13. }
  14. System.out.println(service);
  15. TimeUnit.SECONDS.sleep(70);
  16. System.out.println(service);
  17. }
  18. }

Java多线程详细笔记 - 图7

15.3、newSingleThreadExecutor

创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程,它的特点就是保证先进先出,即保证线程的顺序执行。

  1. public class T{
  2. public static void main(String[] args){
  3. ExecutorService service= Executors.newSingleThreadExecutor();
  4. service.execute(()->{
  5. try {
  6. TimeUnit.SECONDS.sleep(2);
  7. System.out.println("一号种子选手");
  8. System.out.println(Thread.currentThread().getName());
  9. System.out.println();
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. });
  14. service.execute(()->{
  15. try {
  16. TimeUnit.SECONDS.sleep(1);
  17. System.out.println("二号种子选手");
  18. System.out.println(Thread.currentThread().getName());
  19. System.out.println();
  20. } catch (InterruptedException e) {
  21. e.printStackTrace();
  22. }
  23. });
  24. }
  25. }

Java多线程详细笔记 - 图8

15.4、newScheduledThreadPool

创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行,ScheduledExecutorService相对于Timer更安全,功能更强大。

  1. public class T{
  2. public static void main(String[] args){
  3. ScheduledExecutorService service= Executors.newScheduledThreadPool(4);
  4. //schedule是启动一个定时任务
  5. service.schedule(()->{
  6. System.out.println("2S后执行");
  7. },2, TimeUnit.SECONDS);
  8. //scheduleAtFixedRate是启动一个周期任务,比如下面的就是启动一个0S开始,每2S执行一次的周期任务
  9. service.scheduleAtFixedRate(()->{
  10. try {
  11. TimeUnit.SECONDS.sleep(1);
  12. System.out.println(Thread.currentThread().getName());
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. },0,2,TimeUnit.SECONDS);
  17. /*scheduleWithFixedDelay也是启动一个周期任务,但不同的是这个时间间隔表示的是一个任务完成另一个任务
  18. * 开始之间的间隔,比如下面的就是启动一个0S开始启动第一个任务,第一个任务完成后,2S后再执行第二个任务,
  19. * 即两个相邻任务之间的间隔为3S左右*/
  20. service.scheduleWithFixedDelay(()->{
  21. try {
  22. TimeUnit.SECONDS.sleep(1);
  23. System.out.println(Thread.currentThread().getName());
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. },0,2,TimeUnit.SECONDS);
  28. }
  29. }

15.5、newWorkStealingPool

newWorkStealingPool是JDK1.8之后才引入的,它适用于一些比较耗时的任务,比如文件下载。

  1. public class T{
  2. public static void main(String[] args) throws Exception{
  3. ExecutorService service=Executors.newWorkStealingPool();
  4. //CountDownLatch latch=new CountDownLatch(10);
  5. System.out.println("start.........");
  6. for(int i=0;i<10;i++) {
  7. service.execute(()-> {
  8. try {
  9. TimeUnit.SECONDS.sleep(1);
  10. System.out.println(Thread.currentThread().getName());
  11. //latch.countDown();
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. });
  16. }
  17. //latch.await();
  18. System.out.println("end.........");
  19. }
  20. }

Java多线程详细笔记 - 图9
从运行结果发现,线程名称并没有被打印,程序却显示已经结束,这是因为newWorkStealingPool产生的是一个精灵线程(后台线程、守护线程),不对其进行阻塞,是看不到输出的,将上例中的注释删除,即可发现输出的线程名称。

15.6、ForkJoinPool

ForkJoinPool是JDK1.8引入的线程池,核心思想就是将一个大的任务进行拆分成若干个小的任务(fork),然后再将这些小的任务结果进行汇总(join)。使用了工作窃取算法(Work stealing)。下面我们利用ForkJoinPool来设计一个求和算法,即将其拆成一个个区间,进行拆分求和,分治算法。

  1. public class T{
  2. static int[] nums=new int[1000000];
  3. static Random r=new Random();
  4. static {
  5. for(int i=0;i<nums.length;i++)
  6. nums[i]=r.nextInt(100);
  7. System.out.println(Arrays.stream(nums).sum());//输出nums数组的和
  8. }
  9. /*使用ForkJoinPool首先得创建一个ForkJoin任务,
  10. * 下面的是内部类继承自RecursiveTask,用于有返回值的任务
  11. * 返回值是Long,当不需要返回值是,可以写一个内部类继承自RecursiveAction*/
  12. static class Task extends RecursiveTask<Long>{
  13. int start,end;
  14. final static int MAX=10000;
  15. public Task(int start,int end) {
  16. this.start=start;
  17. this.end=end;
  18. }
  19. @Override
  20. protected Long compute() {
  21. if(end-start<=MAX) {
  22. long sum=0L;
  23. for(int i=start;i<end;i++)
  24. sum+=nums[i];
  25. return sum;
  26. }
  27. int middle=start+(end-start)/2;//区间中间值的位置
  28. Task task1=new Task(start, middle);
  29. Task task2=new Task(middle, end);
  30. task1.fork();
  31. task2.fork();
  32. return task1.join()+task2.join();//其实这里使用了递归算法
  33. }
  34. }
  35. public static void main(String[] args) throws Exception{
  36. ForkJoinPool forkJoinPool=new ForkJoinPool();
  37. Task task=new Task(0,nums.length);
  38. forkJoinPool.execute(task);
  39. long result=task.join();
  40. System.out.println(result);
  41. }
  42. }

15.7、parallelStream

既然讲到了ForkJoinPool,那我们就不得不认识一下parallelStream了,它是一个并行执行的流,将每一段任务分而治之,大任务切成小任务。

  1. public class T{
  2. static List<Integer> arrays =new ArrayList<Integer>();
  3. static {
  4. for(int i=0;i<10;i++)
  5. arrays.add(i);
  6. }
  7. public static void main(String[] args){
  8. System.out.println(arrays);
  9. arrays.parallelStream().forEach(x->print(x));
  10. System.out.println();
  11. arrays.parallelStream().forEachOrdered(x->print(x));
  12. }
  13. static void print(Integer x) {
  14. System.out.print(x+"\t");
  15. }
  16. }

Java多线程详细笔记 - 图10
因为是并行处理,所以foreach输出的结果可能是无序的,如果想要有序,可以使用foreachOrder。

16、FutureTask

可取消的异步计算。利用开始和取消计算的方法、查询计算是否完成的方法和获取计算结果的方法,此类提供了对 Future 的基本实现。仅在计算完成时才能获取结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。

  1. public class T{
  2. static List<Integer> arrays =new ArrayList<Integer>();
  3. static {
  4. for(int i=0;i<1000000;i++)
  5. arrays.add(i);
  6. }
  7. static class Task implements Callable<Long>{
  8. @Override
  9. public Long call() throws Exception {
  10. Long sum=0L;
  11. System.out.println("FutureTask is running");
  12. for(int i=0;i<arrays.size();i++)
  13. sum+=arrays.get(i);
  14. System.out.println("FutureTask is end");
  15. return sum;
  16. }
  17. }
  18. public static void main(String[] args) throws Exception{
  19. System.out.println("Main start");
  20. FutureTask<Long> task=new FutureTask<>(new Task());
  21. new Thread(task).start();
  22. System.out.println(task.get());
  23. System.out.println("Main is end");
  24. }
  25. }