多线程并发2

JMM

屏蔽硬件和操作系统的内存访问差异,JMM定义JVM在计算机内存中的工作方式;

JMM决定一个线程对共享变量的写入何时对另一个线程可见

线程间通信

  • 共享内存

  • 消息传递 wait(),notify()

线程间同步

共享内存并发模型中,同步是显示的

主内存和工作内存

所有的变量都储存在主内存中,每个线程有自己的工作内存;工作内存存储在高速缓存或寄存器中,保存该线程使用变量的主内存副本拷贝

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oYZm9hvX-1630569171437)(pic\image-20210831160001077.png)]

内存间交互操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D40QkUfh-1630569171441)(pic\image-20210831160047897.png)]

  • read:把一个变量的值从主内存传输到工作内存中
  • load:在 read 之后执行,把 read 得到的值放入工作内存的变量副本中
  • use:把工作内存中一个变量的值传递给执行引擎
  • assign:把一个从执行引擎接收到的值赋给工作内存的变量
  • store:把工作内存的一个变量的值传送到主内存中
  • write:在 store 之后执行,把 store 得到的值放入主内存的变量中
  • lock:作用于主内存的变量
  • unlock

八种指令规则

  • 只能配对出现

当指令不成对出现从就会造成程序异常 通过volati解决

JMM特性

原子性

JMM保证8个操作有原子性;但是JMM允许虚拟机将没有被volatile修饰的64位数据(long,double)读写划分两次,所以load,store,read,write可以不具备;

只是单个操作具有原子性可以使用Atomic;或者使用synchronized,代表lock和unlock

可见性

线程修改了共享变量的值,其他线程可以马上得知修改;JMM通过在变量修改后将新值同步回主内存,在读取前从主内存刷新变量

实现可见三种方式
  • volatile
  • synchronized,变量执行unlock前,把变量值同步回主内存
  • fianal,被final修饰,在构造器构造完成且没有this逃逸,其他线程通过this引用访问到一半的对象

有序性

指令重排,JMM中允许编译器和处理器对指令重排序

volatile通过添加内存屏障,重排序时不能把后面的指令放到内存屏障前

synchronized,保证每个时刻只有一个线程执行同步代码,让线程顺序执行

内存屏障

LoadLoad,StoreStore,LoadStore,StoreLoad

  • LoadLoad屏障:对于这样的语句Load1; LoadLoad; Load2,在Load2及后续读取操作要读取的数据被访问前,保证Load1要读取的数据被读取完毕。
  • StoreStore屏障:对于这样的语句Store1; StoreStore; Store2,在Store2及后续写入操作执行前,保证Store1的写入操作对其它处理器可见。
  • LoadStore屏障:对于这样的语句Load1; LoadStore; Store2,在Store2及后续写入操作被刷出前,保证Load1要读取的数据被读取完毕。
  • StoreLoad屏障:对于这样的语句Store1; StoreLoad; Load2,在Load2及后续所有读取操作执行前,保证Store1的写入对所有处理器可见。*它的开销是四种屏障中最大的。在大多数处理器的实现中,这个屏障是个万能屏障,兼具其它三种内存屏障的功能*

volatile

写操作前插入StoreStore,写操作后StoreLoad

读前LoadLoad,读后LoadStore

final

  • 一个对象所有的final域被写入完毕才能引用和读取
  • 写final域完毕,构造结束前,插入SS,保证前面的final写入对其他线程cpu可见,阻止重排序,读final域前插入LL

先行发生原则

上面提到了可以用 volatile 和 synchronized 来保证有序性。除此之外,JVM 还规定了先行发生原则,让一个操作无需控制就能先于另一个操作完成。

1. 单一线程原则

Single Thread rule

在一个线程内,在程序前面的操作先行发生于后面的操作。


2. 管程锁定规则

Monitor Lock Rule

一个 unlock 操作先行发生于后面对同一个锁的 lock 操作。


3. volatile 变量规则

Volatile Variable Rule

对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作。


4. 线程启动规则

Thread Start Rule

Thread 对象的 start() 方法调用先行发生于此线程的每一个动作。


5. 线程加入规则

Thread Join Rule

Thread 对象的结束先行发生于 join() 方法返回。


6. 线程中断规则

Thread Interruption Rule

对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过 interrupted() 方法检测到是否有中断发生。

7. 对象终结规则

Finalizer Rule

一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始。

8. 传递性

Transitivity

如果操作 A 先行发生于操作 B,操作 B 先行发生于操作 C,那么操作 A 先行发生于操作 C。

线程安全

不可变

  • final
  • 枚举类

Collections.unmodifiableXXX() 获得不可变的集合

互斥同步

排他锁

非阻塞同步

互斥会带来阻塞同步;乐观并发策略,先操作,失败则补偿(自旋)

CAS

CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。

原子类

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JM0qdm5y-1630569171443)(pic\image-20210831163831956.png)]

ABA

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NKdC6zoz-1630569171446)(pic\image-20210831163848012.png)]

带版本号的原子类AtomicStampedReference

无同步方案

栈封闭

多个线程访问同一个方法的局部变量

public static void add(){
    int a = 0;
    a++;
    System.out.println(a);
}

public static void main(String[] args) throws ExecutionException, InterruptedException, BrokenBarrierException {
      ExecutorService executorService = Executors.newCachedThreadPool();
    for (int i = 0; i < 10; i++) {
        executorService.execute(Demo::add);
    }
      executorService.shutdown();
}

ThreadLocal

线程本地存储;

为了理解 ThreadLocal,先看以下代码:

public class ThreadLocalExample1 {
    public static void main(String[] args) {
        ThreadLocal threadLocal1 = new ThreadLocal();
        ThreadLocal threadLocal2 = new ThreadLocal();
        Thread thread1 = new Thread(() -> {
            threadLocal1.set(1);
            threadLocal2.set(1);
        });
        Thread thread2 = new Thread(() -> {
            threadLocal1.set(2);
            threadLocal2.set(2);
        });
        thread1.start();
        thread2.start();
    }
}

它所对应的底层结构图为:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-biXOQAsA-1630569171448)(pic\image-20210831174952164.png)]

当一个线程有多个 ThreadLocal 时,需要一个容器来管 理多个 ThreadLocal,ThreadLocalMap 的作用就是这个,管理线程中多个 ThreadLocal

当调用一个 ThreadLocal 的 set(T value) 方法时,先得到当前线程的 ThreadLocalMap 对象,然后将 ThreadLocal->value 键值对插入到该 Map 中。

内存泄露

已经动态分配的堆内存由于某种原因没有释放或者无法释放,造成系统内部的浪费;内存泄露的堆积将导致内存溢出

key=threadLocal强引用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mmUF9f6I-1630569171451)(pic\image-20210831191013753.png)]

弱引用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j54szvGB-1630569171452)(pic\image-20210831191448567.png)]

原因
  1. 没有手动清楚entry
  2. currentThread依然运行
解决
  1. 使用完ThreadLocal调用remove方法删除对应entry
  2. 使用完ThreadLocal当前Thread结束运行

线程池

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oRGF5llX-1630569171454)(pic\image-20210831164731476.png)]

类结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hhC7u1TE-1630569171456)(pic\image-20210831165235851.png)]

ExecutorService丰富了对任务的执行和管理

// 关闭,不会接受新的任务,也不会等待未完成的任务
// 如果需要等待未完成的任务,可以使用 awaitTermination 方法
void shutdown();
// executor 是否已经关闭了,返回值 true 表示已关闭
boolean isShutdown();
// 所有的任务是否都已经终止,是的话,返回 true
boolean isTerminated();
// 在超时时间内,等待剩余的任务终止
boolean awaitTermination(long timeout, TimeUnit unit)
 throws InterruptedException;
// 提交有返回值的任务,使用 get 方法可以阻塞等待任务的执行结果返回
<T> Future<T> submit(Callable<T> task);
// 提交没有返回值的任务,如果使用 get 方法的话,任务执行完之后得到的是 null 值
Future<?> submit(Runnable task);
// 给定任务集合,返回已经执行完成的 Future 集合,每个返回的 Future 都是 isDone = true 的
状态
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
 throws InterruptedException;
// 给定任务中有一个执行成功就返回,如果抛异常,其余未完成的任务将被取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks)
 throws InterruptedException, ExecutionException;
1. AbstractExecutorService 是一个抽象类,封装了 Executor 的很多通用功能,比如:
// 把 Runnable 转化成 RunnableFuture
// RunnableFuture 是一个接口,实现了 Runnable 和 Future
// FutureTask 是 RunnableFuture 的实现类,主要是对任务进行各种管理
// Runnable + Future => RunnableFuture => FutureTask
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
 return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
 return new FutureTask<T>(callable);
}
// 提交无返回值的任务
public Future<?> submit(Runnable task) {
 if (task == null) throw new NullPointerException();
 // ftask 其实是 FutureTask
 RunnableFuture<Void> ftask = newTaskFor(task, null);
 execute(ftask);
 return ftask;
}
// 提交有返回值的任务
public <T> Future<T> submit(Callable<T> task) {
 if (task == null) throw new NullPointerException();
 // ftask 其实是 FutureTask
 RunnableFuture<T> ftask = newTaskFor(task);
 execute(ftask);
 return ftask;
}

属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OAkdcQnm-1630569171457)(pic\image-20210831170343344.png)]

    private static final int RUNNING    = -1 << COUNT_BITS; //接受新任务或者处理队列里的任务。
    private static final int SHUTDOWN   =  0 << COUNT_BITS; //不接受新任务,但是处理队列中的任务
    private static final int STOP       =  1 << COUNT_BITS; //不接受,不处理队列中的任务,中断正在执行的任务
    private static final int TIDYING    =  2 << COUNT_BITS; //TIDYING所有任务被中断,整理状态
    private static final int TERMINATED =  3 << COUNT_BITS; //已经完成的时候

常见线程池

  1. CachedThreadPool:可缓存的线程池,没有核心线程,非核心线程数量无限大,有需要时创建线程,没有需要时回收线程
  2. SecudleThreadPool:周期性执行任务,按计划执行线程任务,有核心线程和非核心线程(无限大)
  3. SingleThreadPool:只有一条线程执行任务,适用于顺序任务的应用场景
  4. FixedThreadPool:定长的线程池,核心线程即最大线程数量,没有非核心线程

经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响大;提前创建多个线程,放入线程池。

public ThreadPoolExecutor(int corePoolSize//核心线程大小,
                          int maximumPoolSize,//最大线程数
                          long keepAliveTime,//超时自动释放
                          TimeUnit unit,//超时单位
                          BlockingQueue<Runnable> workQueue//阻塞队列) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory()//线程工厂
         , defaultHandler//拒绝策略);
}

img

handler拒绝策略

  1. AbortPolicy,不执行新任务,直接抛出异常,提示线程已满
  2. DisCardPolicy,不执行新任务,丢掉任务,不抛出异常
  3. DisCardOldSetPolicy,将最早的线程竞争(尝试),成功则替换为新进来的任务执行
  4. CallerRunsPolicy,直接调用execute来执行当前任务,哪个线程调用哪个,调用main线程执行

线程池定义方法

coreSize==maxSize

很好的应对波峰低谷;需要先设置前提

  • allowCoreThreadTimeOut,为false线程空闲时不会回收核心线程
  • keepAliveTime 和 TimeUnit 我们都会设置很大,这样线程空闲的时间就很长,线程就不会 轻易的被回收。

maxSize无界+SynchronousQueue

任务被消费时,才会返回,请求知道当前请求已经在被消费;

消耗资源,大量请求到来需要新建大量的线程处理

maxSize 有界 + Queue 无界

把请求放到队列中等待,减少线程间cpu竞争

请求实时性不能保证

licy,直接调用execute来执行当前任务,哪个线程调用哪个,调用main线程执行

线程池定义方法

coreSize==maxSize

很好的应对波峰低谷;需要先设置前提

  • allowCoreThreadTimeOut,为false线程空闲时不会回收核心线程
  • keepAliveTime 和 TimeUnit 我们都会设置很大,这样线程空闲的时间就很长,线程就不会 轻易的被回收。

maxSize无界+SynchronousQueue

任务被消费时,才会返回,请求知道当前请求已经在被消费;

消耗资源,大量请求到来需要新建大量的线程处理

maxSize 有界 + Queue 无界

把请求放到队列中等待,减少线程间cpu竞争

请求实时性不能保证

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值