引言

如何理解多线程呢,目前我们写的程序都单线程,单线程指的是整个程序只有一条唯一执行路径 所有代码被串联到这条路径中。多线程指的就是一个程序中,存在类似多条执行路径。

一.多线程基础

1.1 线程与进程

1.1.1 进程概念

进程是对应操作系统而言的,一个应用程序或者服务,操作系统主要的任务就是进程调度。目前的操作系统都支持多进程的任务调度。
image.png

1.1.2 线程概念

线程是进程的细分,一个进程可以包含多条线程,线程也叫轻量级进程。CPU调度的最小单位是线程
QQ图片20210416085105.png

1.2 线程创建[高频面试]

java中,线程类Thread ,就是用于描述线程这类事物的,每个线程对象都是可运行的,所以Thread类中有一个run() 方法, 但是每个线程做的事情不一样,所以通常就要自定义类来继承Thread根据自己的要求重写run()。

1.2.1 继承Thread类

自动定义线程类

  1. //线程类
  2. class WashFeet extends Thread{
  3. }

重写run()

  1. //线程类
  2. class WashFeet extends Thread{
  3. //重写run , 这个线程要做的事情
  4. @Override
  5. public void run() {
  6. while (true){
  7. System.out.println("洗脚........");
  8. }
  9. }
  10. }

创建并启动线程

  1. public class HeavenandEarth {
  2. public static void main(String[] args) {
  3. //实例化一个洗脚线程对象
  4. WashFeet son1 = new WashFeet();
  5. //启动线程
  6. son1.start();
  7. }
  8. }

启动线程 必须使用 start() 不可直接调用run() , 区别在于 调用start() 是触发JVM调用run() 为当前对象对象分配自己的栈空间,形成一条线程。 直接调用run() 则不会,就跟平时一样相当于掉一个普通方法。

1.2.2 实现Runnable接口

Runnable 可以跑,可以运行的,描述的是这样一类事物,线程就是一种可以运行事物。Thread 类本身就是Runnable的实现类,它还定义了一些管理线程的方法。

使用Runnable方式创建线程,依然要依赖Thread。Runnable 仅仅表达的是线程要执行的任务。使用这种方式的思想,希望把线程对象 与线程要执行的任务分离开来。

创建Runnable实现类( Task任务 )

  1. //线程任务
  2. class WashFeet implements Runnable{
  3. @Override
  4. public void run() {
  5. while (true)
  6. System.out.println("洗脚.....");
  7. }
  8. }
  9. class WashHand implements Runnable{
  10. @Override
  11. public void run() {
  12. while (true)
  13. System.out.println("洗手.....");
  14. }
  15. }

创建线程并启动

  1. public class SkyAndEarth {
  2. public static void main(String[] args) {
  3. Thread son = new Thread( new WashFeet() );
  4. son.start();
  5. while (true){
  6. System.out.println("正在营业....");
  7. }
  8. }
  9. }

1.2.3 两种方式对比

方式一,线程对象和线程任务是耦合的,方式二,线程对象和线程任务是分离的
方法一,扩展性不好,类单继承的。 方式二,扩展性更好,接口是多实现。
方法一,调用线程方法直接 ,方式二,不可直接调用线程方法,需要先调用 Thread.currentThread(),这个方法的作用是拿到与当前线程任务绑定的线程对象。

1.3 线程常见方法

1.3.1 start()

启动线程。

1.3.2 ~~stop() ~~

停止线程,过期不建议使用。

1.3.3 Thread.sleep( long ms )

睡眠 暂停执行,直到睡眠时间到。

1.3.4 setName()/getName()

取名字/获得名字,如果没有设置名字,默认名字为Thread-(0-n)

1.3.5 Thread.yield()

线程礼让. 让出cpu执行时间片,自己进入就绪状态,再次等待调度。

1.3.6 join()

同步,加入线程线程中

1.3.7 setDeamon(boolean xx)/isDeamon()
设置守护线程/判断守护线程

1.3.8 setPriority(int xx)/getPriority()

设置/获取优先级 ,默认三个 MIN_PRIORITY = 1;NORM_PRIORITY = 5;MAX_PRIORITY = 10;

1.4 理解同步与异步

同步:一个线程全部做完以后,另一个线程才开始执行,也就是一个线程的开始总是接着另一个线程的结尾。
异步:一个线程不需要等待,另一个线程执行完毕后,才执行,从宏观上看线程是同时执行,但是微观上是一个线程执行一点点后,另外一个线程又执行一点点,这样交替执行**。

思考, 多线程好处是什么 ? 可以充分利用CPU,尤其是在处理阻塞问题的时候,使用多线程可以提高效率。

二.线程同步[高频面试]

2.1 为什么要线程同步

就是为了数据一致,数据的安全性。
image.png

2.2线程同步方法

2.2.1 同步代码块

使用 synchronized( obj ){ 被同步代码 } 同步代码块方式,它原理当线程执行到有synchronized的代码块时,先尝试去获取obj 对象的锁(每个对象有且只有一把),如果取到了则进入代码块中执行,期间不被打扰(如果这时有其他线程准备进入代码块但是取不到锁),直到执行完毕后,自动归还锁。其他线程方可进入。

  1. //同步案例
  2. public class TicketsDemo {
  3. public static void main(String[] args) {
  4. //模拟3个窗口
  5. Thread win1 = new Thread( new Window() );
  6. win1.setName("张阿姨");
  7. win1.start();
  8. Thread win2 = new Thread( new Window() );
  9. win2.setName("王阿姨");
  10. win2.start();
  11. Thread win3 = new Thread( new Window() );
  12. win3.setName("李阿姨");
  13. win3.start();
  14. }
  15. }
  16. //窗口线程
  17. class Window implements Runnable{
  18. //模拟100张票
  19. static int total = 100;
  20. @Override
  21. public void run() {
  22. while (true){
  23. 20个语句
  24. 1000线程
  25. synchronized( "" ){ // 小括号()里需要一个对象,这个对对象必须满足,不同线程进入时是同一个对象。这样才有互斥性。
  26. if(total>0) {
  27. total--;
  28. //没时间了
  29. System.out.println(Thread.currentThread().getName() + "卖出1张,还剩" + total);
  30. }else{
  31. break;
  32. }
  33. }
  34. }
  35. }
  36. }

2.2.2 同步方法

和同步代码块原理基本一致也是使用synchronized 只是把同步范围扩大至整个方法,也就是锁的粒度更粗。

  1. public class TicketsDemo {
  2. public static void main(String[] args) {
  3. Thread w1 = new Thread(new Window( ) );
  4. Thread w2 = new Thread(new Window( ) );
  5. Thread w3 = new Thread(new Window( ) );
  6. w1.setName("张");
  7. w2.setName("王");
  8. w3.setName("刘");
  9. w1.start();
  10. w2.start();
  11. w3.start();
  12. }
  13. }
  14. //窗口线程
  15. class Window implements Runnable{
  16. @Override
  17. public void run() {
  18. while ( SoftWare.size()>0 ){
  19. //1000个
  20. SoftWare.sales();
  21. }
  22. }
  23. }
  24. //卖票软件
  25. class SoftWare{
  26. private static int total = 100 ;
  27. public static synchronized void sales(){
  28. 30 句话
  29. if( total>0 ){
  30. total--;
  31. System.out.println( Thread.currentThread().getName() +"销售1张,还剩余"+total);
  32. }
  33. }
  34. public static int size(){
  35. return total;
  36. }
  37. }

2.2.3 同步不安全的集合

Collections 是集合的工具类,它提供了除了存取元素以外的其他功能,这里比如就有去把一个线程不安全的集合转换成线程安全的集合。

static ``<T> [List](../../java/util/List.html)<T> [**synchronizedList**](../../java/util/Collections.html#synchronizedList(java.util.List))([List](../../java/util/List.html)<T> list)
返回指定列表支持的同步(线程安全的)列表。
static ``<K,V> [Map](../../java/util/Map.html)<K,V> [**synchronizedMap**](../../java/util/Collections.html#synchronizedMap(java.util.Map))([Map](../../java/util/Map.html)<K,V> m)
返回由指定映射支持的同步(线程安全的)映射。
static ``<T> [Set](../../java/util/Set.html)<T> [**synchronizedSet**](../../java/util/Collections.html#synchronizedSet(java.util.Set))([Set](../../java/util/Set.html)<T> s)
返回指定 set 支持的同步(线程安全的)set。

[**synchronizedList**](https://www.yuque.com/java/util/Collections.html#synchronizedList(java.util.List))([List](https://www.yuque.com/java/util/List.html)<T> list) 为例, 实现原理就是使用 同步代码块,这方法返回值实际上是 SynchronizedList 的实例,这个实例套娃使用了 出入参数不安全的集合 ,在不安全外围包裹了一个 同步代码块。
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}

Vector Hashtable 它们安全是因为使用同步方法 而 Collections 工具类让集合安全使用的是 同步代码块。

2.2.4 死锁问题[高频面试]

多个个线程因为竞争资源而陷入相互等待的情况,无法恢复的场景。
产生死锁的必要条件:
互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
环路等待条件:在发生死锁时,必然存在一个进程—资源的环形链。

  1. package deadlock;
  2. /**
  3. * 死锁案例
  4. */
  5. public class DeathLock {
  6. public static void main(String[] args) {
  7. Object o1 = new Object();
  8. Object o2 = new Object();
  9. Thread son1 = new Thread( new Foo(o1,o2) );
  10. son1.start();
  11. Thread son2 = new Thread( new Boo(o1,o2) );
  12. son2.start();
  13. }
  14. }
  15. class Foo implements Runnable {
  16. Object o1 = null;
  17. Object o2 =null;
  18. public Foo(Object o1, Object o2) {
  19. this.o1 = o1;
  20. this.o2 = o2;
  21. }
  22. @Override
  23. public void run() {
  24. synchronized (o1){
  25. System.out.println( "Foo线程获得o1,想去锁o2");
  26. //可选 主要是为其他线程争取时间
  27. try {
  28. Thread.sleep(1);
  29. } catch (InterruptedException e) {
  30. e.printStackTrace();
  31. }
  32. synchronized (o2){
  33. System.out.println("Foo线程获得o2");
  34. }
  35. System.out.println("Foo释放o2");
  36. }
  37. System.out.println("Foo 释放o1");
  38. }
  39. }
  40. class Boo implements Runnable{
  41. Object o1 = null;
  42. Object o2 =null;
  43. public Boo(Object o1, Object o2) {
  44. this.o1 = o1;
  45. this.o2 = o2;
  46. }
  47. @Override
  48. public void run() {
  49. synchronized (o2){
  50. System.out.println( "Boo线程获得o2,想去锁o1");
  51. synchronized (o1){
  52. System.out.println("Boo线程获得o1");
  53. }
  54. System.out.println("Boo释放o1");
  55. }
  56. System.out.println("Boo 释放o2");
  57. }
  58. }

预防死锁:
资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
实际操作:按照统一顺序分配锁, 使用jdk.15 Lock的 tryLock( 5000 )

三.线程生命周期[高频面试]

线程的生命周期,指的是线程对象从创建到死亡的一系列的状态。
image.png

四.线程通信

4.1 什么是线程通信

协调多个线程线程有序访问某些资源。

4.2 线程通信方法

wait():void , 它是Object的方法,不是线程提供的方法,让执行这个wait()调用的线程等待。 无限期等待,直到有被唤醒。
wait( long ms ) : 等待指定的毫米数,如果没有被唤醒,则自动唤醒。
wait( long ms, int naos) : 更精确的等待时间。
notify(): 唤醒等待在该对象上的线程(只会唤醒一个)。
notifyAll(): 唤醒全部等待在该对象上的全部线程。

这些方法必须使用在存在 synchronized 代码块内 或者 synchronized 方法中。

  1. package waitandnofity;
  2. /**
  3. * 等待唤醒案例
  4. */
  5. public class WaitAndNotifyCase {
  6. public static void main(String[] args) throws InterruptedException {
  7. Object obj = new Object();
  8. Thread son1 = new Thread( new Foo(obj) );
  9. son1.setName("张三");
  10. Thread son2 = new Thread( new Foo(obj) );
  11. son2.setName("李四");
  12. son1.start();
  13. son2.start();
  14. //让主线程等5s后去唤醒其他线程
  15. Thread.sleep(5000);
  16. synchronized (obj){
  17. //obj.notify();//唤醒一个
  18. obj.notifyAll();//唤醒全部
  19. }
  20. }
  21. }
  22. class Foo implements Runnable{
  23. Object obj = null;
  24. public Foo(Object obj) {
  25. this.obj = obj;
  26. }
  27. @Override
  28. public void run() {
  29. synchronized (obj){
  30. System.out.println(Thread.currentThread().getName()+ "执行开始...");
  31. try {
  32. obj.wait();
  33. } catch (InterruptedException e) {
  34. e.printStackTrace();
  35. }
  36. System.out.println(Thread.currentThread().getName()+ "执行完毕...");
  37. }
  38. }
  39. }

4.3 生产者消费者模型

  1. package producerconsumer;
  2. /**
  3. * 生产者和消费者案例
  4. */
  5. public class ProducerAndConsumer {
  6. public static void main(String[] args) {
  7. //容器
  8. Container container = new Container();
  9. Thread xfz1 = new Thread( new Consumer(container) );
  10. xfz1.setName("张三");
  11. xfz1.start();
  12. Thread xfz2 = new Thread( new Consumer(container) );
  13. xfz2.setName("李四");
  14. xfz2.start();
  15. Thread scz1 = new Thread( new Producer( container ) );
  16. scz1.setName("王师傅");
  17. scz1.start();
  18. Thread scz2 = new Thread( new Producer( container ) );
  19. scz2.setName("李师傅");
  20. scz2.start();
  21. }
  22. }
  23. //抽象出一个容器事物
  24. class Container{
  25. Object[] data = new Object[10]; //底层使用数组模拟栈
  26. int size; //元素计数器
  27. //存
  28. public synchronized void add( Object obj ){
  29. while( size==data.length ){ //存满了,不能存了,生产者线程,等待
  30. try {
  31. this.wait();
  32. } catch (InterruptedException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. data[size++] = obj;
  37. System.out.println(Thread.currentThread().getName() + "生产"+obj);
  38. //唤醒刚刚因为没有商品而等待的消费者线程
  39. this.notifyAll();
  40. }
  41. //取
  42. public synchronized Object get(){
  43. while (size==0){
  44. try {
  45. this.wait(); //商品不足 消费者线程等待。
  46. } catch (InterruptedException e) {
  47. e.printStackTrace();
  48. }
  49. }
  50. Object result = data[--size];
  51. System.out.println(Thread.currentThread().getName() +"消费"+result);
  52. //唤醒刚刚因为没有空间 而等待的生产者线程
  53. this.notifyAll();
  54. return result;
  55. }
  56. }
  57. //生产者
  58. class Producer implements Runnable{
  59. //共享容器
  60. Container container ;
  61. public Producer(Container container) {
  62. this.container = container;
  63. }
  64. @Override
  65. public void run() {
  66. for ( int i =1;i<=20; i++ ){
  67. container.add( "汉堡"+i);
  68. }
  69. }
  70. }
  71. //消费者
  72. class Consumer implements Runnable{
  73. //共享容器
  74. Container container ;
  75. public Consumer(Container container) {
  76. this.container = container;
  77. }
  78. @Override
  79. public void run() {
  80. for ( int i =1;i<=20; i++ ){
  81. container.get();
  82. }
  83. }
  84. }

五.线程池

5.1 什么是线程池

是一种容器的概念,池化技术,都是容器技术。把多个线程对象存入一个容器中,因为线程也是宝贵的资源,频繁的创建销毁线程会存在内存开销。

5.2 如何创建线程池[面试题]

Executor : 线程池的顶级接口
ExecutorService: Executor 的子接口,扩展了一些管理线程任务的方法。
ScheduledExecutorService: ExecutorService 的子接口 提供了延时执行任务的方法。

Executors 线程池的工具类 ,可以创建出线程池实例
//1 单线程池
_ExecutorService es = Executors._newSingleThreadExecutor
();

//2 固定线程池
_ExecutorService es2 = Executors._newFixedThreadPool
(10);

//3 可变数量的线程池
_ExecutorService es3 = Executors._newCachedThreadPool
();

//4 创建定时任务线程池
_ScheduledExecutorService se4 = Executors._newScheduledThreadPool
(10);

5.3 提交任务到线程池

线程池创建以后,就可以接收任务了,在java中线程任务有两种,第一种就是Runnable 前面已经学习了。

5.3.1 Runnable 类型的任务

  1. //关注运行没有结果的任务
  2. class WashFeet implements Runnable{
  3. @Override
  4. public void run() {
  5. System.out.println(Thread.currentThread().getName()+"洗脚...");
  6. try {
  7. Thread.sleep(3000);//模拟一个延时
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. }
  12. }

方法名为 run() 没有返回值 不可抛出异常

5.3.2 Callable 类型的任务

  1. //关注结果有返回值的任务
  2. class NumberCount implements Callable<Integer> {
  3. @Override
  4. public Integer call() throws Exception {
  5. int sum = 0;
  6. for(int i=0;i<100;i++){
  7. sum += i;
  8. }
  9. System.out.println(Thread.currentThread().getName() +"sum:"+sum);
  10. return sum;
  11. }
  12. }

特点: 方法名为 call() 有返回值 可抛出异常

5.3.3 Future 异步结果

Future 保存异步运算的结果,它提供了一个get()用于返回真正的异步计算结果,但是调用get()方法会造成调用线程阻塞,直到获得结果为止。

  1. Future<Integer> result = es2.submit( new NumberCount() );
  2. Integer sum = result.get(); //阻塞调用语句的线程
  3. System.out.println(sum);

5.4 关闭线程池

shutdown() 方法,调用后线程池可拒绝接收新的任务,待全部现有任务执行完毕后关闭线程池。

5.5 统计多个任务运行的总时间

  1. 使用 shutdown()+isTerminated() 循环判断
  2. CountDownLatch 同步辅助工具类 , 构造一个任务总数,每个任务结束就减一,直到为0,把调用 await() 的线程唤醒。 ```java package case12;

import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;

public class TestCount {

  1. public static void main(String[] args) throws InterruptedException {
  2. CountDownLatch cdl = new CountDownLatch(3);
  3. //实例化线程池
  4. ExecutorService es = Executors.newFixedThreadPool(3);
  5. //提交50个任务
  6. for ( int i=0 ; i< 50; i++ ){
  7. es.submit( new Foo( cdl ) );
  8. es.submit( new Boo( cdl ) );
  9. }
  10. //关闭
  11. es.shutdown();
  12. Foo.cdl.await(); //让执行这语句的线程等等,等计数器减为0,调用线程才会被唤醒。
  13. System.out.println("所以人都执行完毕了");
  14. }

}

class Foo implements Runnable{

  1. //所以Foo实例共享。
  2. CountDownLatch cdl = null;
  3. Foo( CountDownLatch cdl ){
  4. this.cdl=cdl;
  5. }
  6. @Override
  7. public void run() {
  8. try {
  9. Thread.sleep(1000);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }
  13. System.out.println(Thread.currentThread().getName()+"Over");
  14. //线程执行结束 ,让计数器 -1
  15. cdl.countDown();
  16. }

} class Boo implements Runnable{

  1. //所以Foo实例共享。
  2. //所以Foo实例共享。
  3. CountDownLatch cdl = null;
  4. Boo( CountDownLatch cdl ){
  5. this.cdl=cdl;
  6. }
  7. @Override
  8. public void run() {
  9. try {
  10. Thread.sleep(1000);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. System.out.println(Thread.currentThread().getName()+"Over");
  15. //线程执行结束 ,让计数器 -1
  16. cdl.countDown();
  17. }

}

  1. <a name="dxwXu"></a>
  2. ## 六.JUC锁[高频面试]
  3. <a name="cvoxg"></a>
  4. ### 6.1 锁分类
  5. 锁的作用就是保证数据一致性,但是往往就会牺牲效率,所以使用锁需要自己做好平衡和取舍。同等情况下,当然使用性能更好的锁,是最好的方案。
  6. <a name="IIDUI"></a>
  7. #### 6.1.1 悲观锁
  8. 怀疑任何情况都会出现并发问题,所以在设计的时候 就默认锁定,jdk1.5前就是 synchronized ,早期说这个性能有问题(经过优化现在一般不去比较了)。在JDK1.5新的解决方法是提供了Lock接口和他的实现类。例如 ReentrantLock ReentrantReadWriteLock等。
  9. <a name="KBzeP"></a>
  10. #### 6.1.2 乐观锁
  11. 乐观锁就是和悲观锁相反的思想,就是先不怀疑存在并发问题,如果确有存在再解决,通常需要为并发修改的数据设定一个版本号,修改前对一下先前获取的版本号,修改时在比较版本号如果一致才修改,不一致说明有版本变化不可修改**。CAS算法**就是一种乐观锁算法,它是硬件层面实现的,把多语句指令和为一个指令,确保了原子操作。<br />**Compare And Swap**( 比较 和 交换 ) V (内存值) E(期望值) N(新值) 当且仅当 V==E时 才将 V=N 。
  12. <a name="rQbnl"></a>
  13. #### 6.1.3 ReentrantLock
  14. **JDK1.5 引入全新的锁体系,JUC包大部分都是这个版本引入的。ReentrantLock是Lock接口的实现类,意为重入锁(所谓重入锁就是允许同一个线程多次对对象取锁),具有和**synchronized ,还提供了一些方法。功能更强大
  15. ```java
  16. package lock.synchronize.block;
  17. import java.util.concurrent.locks.Lock;
  18. import java.util.concurrent.locks.ReentrantLock;
  19. //同步案例
  20. public class TicketsDemo {
  21. public static void main(String[] args) {
  22. //模拟3个窗口
  23. Thread win1 = new Thread( new Window() );
  24. win1.setName("张阿姨");
  25. win1.start();
  26. Thread win2 = new Thread( new Window() );
  27. win2.setName("王阿姨");
  28. win2.start();
  29. Thread win3 = new Thread( new Window() );
  30. win3.setName("李阿姨");
  31. win3.start();
  32. }
  33. }
  34. //窗口线程
  35. class Window implements Runnable{
  36. //声明锁
  37. static Lock lock = new ReentrantLock(true);
  38. //模拟100张票
  39. static int total = 100;
  40. @Override
  41. public void run() {
  42. while (true){
  43. try{
  44. lock.lock(); //获得锁
  45. if(total>0) {
  46. total--;
  47. System.out.println(Thread.currentThread().getName() + "卖出1张,还剩" + total);
  48. }else{
  49. break;
  50. }
  51. }finally {
  52. lock.unlock(); //确保一定可以解锁
  53. }
  54. }
  55. }
  56. }

和对比 synchronized

  1. synchronized 关键字 ReentrantLock 类(jdk1.5新引入)
  2. 都是重入锁(ReentrantLock 功能强大 提供了更多的方法,以及实现公平锁策略)
  3. synchronized 上锁与解锁自动完成 , ReentrantLock 需要自己上锁和解锁。

6.1.3 ReadWriteLock

凡是用锁都会影响效率,如果把这种影响做到最小了呢? 读写锁就是一种经过细化考量的一种优化的锁。这种锁的特点是 读-读不阻塞线程, 读-写要阻塞线程,写-写要阻塞。一个把读写锁可以分离出 读锁和写锁。

  1. package lock.readwriteLock;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.locks.ReadWriteLock;
  5. import java.util.concurrent.locks.ReentrantReadWriteLock;
  6. /**
  7. * 读写锁案例
  8. */
  9. public class TestReadWriteLockCase {
  10. public static void main(String[] args) {
  11. long begin = System.currentTimeMillis();
  12. //并发访问源
  13. Foo obj = new Foo();
  14. //线程池
  15. ExecutorService executorService = Executors.newFixedThreadPool(20);
  16. //提交任务 18 次读
  17. for(int i= 0; i<18; i++){
  18. executorService.submit(new Runnable() {
  19. @Override
  20. public void run() {
  21. obj.getAge();
  22. }
  23. });
  24. }
  25. //提交2次写任务
  26. for(int i=0;i<2;i++){
  27. executorService.submit(new Runnable() {
  28. @Override
  29. public void run() {
  30. obj.setAge( 100 );
  31. }
  32. });
  33. }
  34. //关闭
  35. executorService.shutdown();
  36. while ( !executorService.isTerminated() ){
  37. }
  38. long end = System.currentTimeMillis();
  39. //计算时间
  40. System.out.println( (end-begin)/1000 );
  41. }
  42. }
  43. class Foo {
  44. private Integer age ;
  45. private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  46. public Integer getAge() {
  47. try {
  48. readWriteLock.readLock().lock();
  49. Thread.sleep(1000);
  50. } catch (InterruptedException e) {
  51. e.printStackTrace();
  52. }finally {
  53. readWriteLock.readLock().unlock();
  54. }
  55. return age;
  56. }
  57. public void setAge(Integer age) {
  58. try {
  59. readWriteLock.writeLock().lock();
  60. Thread.sleep(1000);
  61. } catch (InterruptedException e) {
  62. e.printStackTrace();
  63. }finally {
  64. readWriteLock.writeLock().unlock();
  65. }
  66. this.age = age;
  67. }
  68. }

6.2 Condition 通信

在jdk1.5以后,如果不使用 synchronized 同步方法, 那么线程通信可以使用Condition 实现,它的核心做法是,

  1. 获得一个 阻塞条件对象 Conditon cd = lock.newCondition();
  2. 阻塞方法 cd.awiat() 作用同 wait()
  3. 唤醒方法 cd.signal() / cd.signalAll() 作用同 notify() 和 notifyAll()
    ```java package lock.producerconsumer; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /**

    • 生产者和消费者案例 */ public class ProducerAndConsumer { public static void main(String[] args) { //容器 Container container = new Container(); Thread xfz1 = new Thread( new Consumer(container) ); xfz1.setName(“张三”); xfz1.start();

      Thread scz1 = new Thread( new Producer( container ) ); scz1.setName(“王师傅”); scz1.start(); } }

//抽象出一个容器事物 class Container{ private Object[] data = new Object[10]; //底层使用数组模拟栈 private int size; //元素计数器 private ReentrantLock lock = new ReentrantLock(); //从锁上获取阻塞条件 private Condition cd = lock.newCondition(); //存 public void add( Object obj ){

  1. try{
  2. lock.lock();
  3. while( size==data.length ){ //存满了,不能存了,生产者线程,等待
  4. try {
  5. cd.await();
  6. } catch (InterruptedException e) {
  7. e.printStackTrace();
  8. }
  9. }
  10. data[size++] = obj;
  11. System.out.println(Thread.currentThread().getName() + "生产"+obj);
  12. //唤醒刚刚因为没有商品而等待的消费者线程
  13. //TODO
  14. cd.signalAll();
  15. }finally {
  16. lock.unlock();
  17. }
  18. }
  19. //取
  20. public Object get(){
  21. try{
  22. lock.lock();
  23. while (size==0){
  24. try {
  25. cd.await(); ; //商品不足 消费者线程等待。
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. Object result = data[--size];
  31. System.out.println(Thread.currentThread().getName() +"消费"+result);
  32. //唤醒刚刚因为没有空间 而等待的生产者线程
  33. cd.signalAll();
  34. return result;
  35. }finally {
  36. lock.unlock();
  37. }
  38. }

} //生产者 class Producer implements Runnable{ //共享容器 Container container ;

  1. public Producer(Container container) {
  2. this.container = container;
  3. }
  4. @Override
  5. public void run() {
  6. for ( int i =1;i<=20; i++ ){
  7. container.add( "汉堡"+i);
  8. }
  9. }

} //消费者 class Consumer implements Runnable{ //共享容器 Container container ; public Consumer(Container container) { this.container = container; } @Override public void run() { for ( int i =1;i<=20; i++ ){ container.get(); } } } ```

七.并发集合 (JUC 集合)

image.png

CopyOnWriteArrayList : 底层使用 ReentrantLock实现锁,并且修改数据,拷贝一个新的数字操作。
CopyOnWriteArraySet : 底层套娃使用 CopyOnWriteArrayList ,添加前通过addIfAbsent(e) 判断元素是否已经存在,如果存在着不添加。
ConcurrentHashMap : 底层使用(1.8早期分段锁 后期使用CAS 无锁算法)

ConcurrentLinkedQueue : 并发安全的 队列,链表实现
ArrayBlockingQueue : 阻塞队列,基于数组实现 ,也是有有界队列
LinkedBlockingQueue: 阻塞队列,基于链表实现, 是无界队列

补充:
volatile 【面试题】
关键字 用于修饰变量 , 它作用是,可以将线程工作内存中的数据,立即同步到主存,以保证其他线程池立即可见。简单的说就是解决内存可见性问题。但是不能保证原子性问题。
JAVA (JMM)java内存模型:
image.png

JUC 中除有线程池 锁 同步工具, 还提供了一大堆 原子操作类。
AtomicInger AtomicLong……. 类它们的底层使用cas算法保证线程安全问题。

附图

JVM内存模型:

多线程 - 图7