Concurrency

Concurrency

Synchronized

有对象锁和类锁

对象锁 1 可以加在普通方法名前默认是this对象,2 或者在在方法体中用sychronized把代码块包裹起来

队于一个java类可能多个对象,但是class对象只会有一个

类锁也有两种形式1 static Synchronized,用来修饰静态方法,2 Synchronized(XXX.class)

对于不同的实例也可以实现同步

方法抛出异常会释放锁,两个相同等待线程1持有的锁,如果线程1异常,线程2会马上进入同步方法,意味着锁释放了,JVM帮助释放了锁,LOCK接口不会需要在finally中释放

Join可以让子线程执行完成,主线程才执行

两个Thread访问同一对象的同步方法 --正常同步

同步方法中调用了非同步方法,这个非同步方法就不是线程安全的了

threadobject同步方法结果

21非static正常同步

22非static不能同步,锁的不同实例

22static正常同步

211同步+ 1非同步(非static)非同步方法不受影响。

21一个类的2个不同的非static同步方法正常同步锁为this

21两个同步方法:一个普通,一个static不能同步,因为锁对象不同、

是1 可重入(递归)锁,2 不可中断

可重入:同一线程的外层函数获得锁,内层函数可以直接再次获取该锁,拿到一把锁可以再次用起来,不需要释放锁,ReentryLock也是可重入锁,好处:避免死锁,提升封装性

假设有个两个同步方法,线程a执行方法1拿到了锁,假设不可重入,再去拿锁,又不释放锁,造成死锁。

可重入的粒度: 线程非调用(pthread是调用)在同一个线程中如果已经拿到锁,就可以一直使用

可重入可以是在同一类中的同一同步方法,可以是同一类的不同同步方法,可以是不同类的同步方法

不可重入:锁用完了,必须先释放再和其他线程竞争这把锁

不可中断:如果别人不释放锁,你又想获得,必须等到别人释放,否则永远的等下去

LOCK可中断:1 如果觉得等的太久了,有权中断已经获得锁的线程的执行。2:也可以不在等,直接退出。

可见性:线程会把主存的变量copy到自己的本地内存,A本地变量->主存中,线程B再去获取

近朱者赤

同步代码块中变量释放锁的时候会把本地内存的变量写回主存的,进入代码块的时候也是从主存中获取的

缺陷:效率低:1 锁的释放情况少(1 执行完,2 异常)2 试图获得锁时不能设定超时3 不能中断一个正在试图获得锁的线程

不够灵活:(读写锁更灵活)加锁和释放的时机单一

不能知道尝试获取锁的结果

注意点:1 锁对象不能为空(因为锁的信息保存到对象头中的),2 作用域不宜太大,避免死锁

Thread

创建线程1 实现runnable 接口2 继承Thread类

启动线程start();

停止线程:使用interrupt通知,而不是强制,1 普通case:需要线程本身配合检测!Thread.getCurrentThread().isInterruptted()

2 如果线程在sleep,别人来打断了,他会响应这个中断,响应的方式是抛出异常。

3 如果线程A再每次循环中都会调用sleep或wait,别人来打断他,也会响应这个中断,响应方式为抛出异常,只是不需要A配合检测是否被打断了的判断逻辑--try catch 再while之外的情况

4 while 内 try catch 即使补充了检测的逻辑,线程也不会停止运行 ,原因是sleep 再响应中断时会清除isInterrupted标记位

处理中断:1 优先选择:传递中断 (在方法签名中抛出异常,最顶层函数去catch)2 如果不想传递或无法传递:恢复中断(自己catch 住异常再主动调用 interrupt 方法),不应屏蔽中断

能够响应中断的方法列表

可中断的阻塞:

Object.wait()/wait(long)/wait(long,int)

Thread.sleep(long)/sleep(long,int)// 响应中断时,会清除中断状态

Thread.join()/join(long)/join(long,int)// 新的线程加入了我们,我们要等他们执行完再执行 有工具类countDownLatch 或 CyclicBarrier

//如果两个线程都先后调用start,先调用的有很大概率先执行,countDownLatch如果想让两个线程某些代码同时执行可以使用它的await()方法,执行latch.countDown()两个线程会重awiat的地方苏醒过来,然后在一起执行。

java.util.concurrent.BlockingQueue.take()/put(E)

java.util.concurrent.locks.Lock.lockInterruptibly()

java.util.concurrent.CountDownLatch.await()

java.util.concurrent.CyclicBarrier.await()

java.util.concurrent.Exchanger.exchange(V)

java.nio.channels.InterruptibleChannel相关方法

java.nio.channels.Selector相关方法

如果通过上述10个途径把线程陷入阻塞状态,想让其中阻塞状态中恢复,就可以使用interrupt方法

vo latile 修饰的 boolean 变量对所有的线程具有可见性

陷入阻塞时,volatile是无法终止线程的,假设生产着的生产速度很快,消费者消费速度很慢,阻塞队列满了之后,生产者会阻塞,等待消费者进一步消费。

Thread实例调用isInterrupted()// 获取中断状态,不会清楚中断状态

Thread静态方法interrupted()// 获取中断状态,并且会清楚中断状态,静态方法的目标对象是当前执行他的线程,而不管本方法是哪个实例调用的

阻塞状态:Blocked+Waiting+Timed_waiting

生命周期:

New:已经new Thread 但是未调用start方法

Runnable: 一旦调用了start 直接进入Runnable(可运行的)状态,不会进入waiting状态,有可能是正在执行,也有可能再等待执行,比如等待CPU资源,对应OS的ready+running

Blocked:线程进入了被Synchronized修饰的代码块,并且锁被其他线程持有,但是对于Lock方式就是不这个状态了。

Waiting:没有设置timeout的Object.wait()

Timed Waiting :Thread.sleep(long)

Terminated

|

Thread和Object常见方法

|

阻塞阶段:使线程进行休息,执行wait的前提,获取monitor 锁

直到以下4种情况之一发生时,才会被唤醒

1 另一个线程调用这个对象的notify()方法且刚好被唤醒的是本线程。

2 另一个线程调用这个对象的notifyAll()方法。

3过了wait(time)规定的超时时间,如果传入0 就是永久等待。

4 线程自身调用了interrupt()

唤醒阶段

1 notify 只能唤醒单个正在等待monitor 的线程。唤醒的时候,如果有多个线程等待,他只会选取一个,具体选择哪一个,是任意的。wait,notify都要在synchronized 保护的代码块或者方法种执行,如果在synchronize外边执行会抛出异常。调用wait的时候会马上释放锁,不需要等待sychronized代码块结束。notify的时候好像还是持有着这把锁????是的,notify要等刚才执行notify的线程退出被sychronizided保护的代码并释放monitor锁

2 notifyAll 所有等待的线程一次性唤醒。

 遇到中断,会抛出异常,并释放获取的monitor

Join实际上是调用了wait方法,但是没有进行notify操作,notifyAll的唤醒操作是jvm 再子线程thread 运行完成时调用的。

synchronized (thread){

    thread.wait();// join等价写法 thread.join() 

}

Yield :释放CPU时间片,线程仍然处于runnable状态,jvm不保证遵循

与sleep区别:是否随时可能再次被调度

线程属性:

  线程Id ID从1开始,创建子线程线程id不为2 ,jvm背后创建了很多子线程,++id

  线程名称

  是否是守护线程。true 守护线程 false 用户线程,守护的是用户线程,为用户线程提供服务,比如GC线程,jvm可以随时停止守护线程,不影响jvm退出,线程类型默认继承自父线程,但不能停止用户线程。

  线程优先级 : 告诉调度器,用户希望哪些线程多运行,哪些线程少运行,10 个级别,默认为5,程序设计不应该依赖优先级,不同操作系统不一样,优先级会被操作系统改变

异常体系:

|

未捕获异常:UncaughtException 使用UncaughtExceptionHandler接口 Thread类的接口,有唯一的方法uncaughtException(Thread t, Throwable e);检测线程由于未捕获异常而终止的情况

1 主线程有异常堆栈轻松发现异常,子线程的异常堆栈不容易发现。

2 子线程异常无法用传统方法(try catch)捕获。 原因:try catch 只会在本线程有效,写在主线程的话,子线程抛出异常 无法捕获到。每个线程里的异常,最好让线程本身捕获。

如何捕获异常:1 手动在每个run方法里进程try catch 2 UncaughtExceptionHandler

设置异常处理器:1 给程序统一设置2 给每个线程单独设置 3 给线程池设置

1 给程序统一设置:Thread.setDefaultUncaughtExceptionHander(new MyUncaughtExceptionHander("捕获器"));//MyUncaughtExceptionHander 实现Thread类的UncaughtExceptionHandler接口

线程安全:当多个线程访问一个对象是,如果1 不用考虑这些线程在运行时环境下的调度和交替执行,2 也不需要考虑进行额外的同步,3 或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那则个对象就是线程安全的。

Memory

jvm内存结构,Java对象模型, Java内存模型:

jvm内存结构,和java虚拟机的运行时区域有关。

java对象模型,和Java对象在jvm的表现形式有关

Java内存模型,和并发编程有关

jvm内存结构:

|

堆:保存对象

VMStack:保存对象引用

方法区:static class 常量 永久引用(static 引用)

PC:当前线程执行字节码的行号数,上下文切换时也会保存下来,下一条指令的地址等

java对象模型:java对象自身的存储模型

jvm会给类创建一个instanceKlass,保存在方法区,用来在jvm层表示该Java类。

我们new Object时候,jvm会创建instanceOopDesc对象,这个对象中包含了对象头和实例数据。

|

JMM(是一组标准,规范,需要jvm遵守, 开发者利用, 也是工具类和关键字的原理)

c中不存在,依赖处理器,不同处理器结果不一样,无法保证并发安全。

.java->编译.class(与平台无关了,可以拿到各个OS上执行)->jvm转化程机器指令(翻译过程)->cpu上执行

1 重排序:如果线程内部的两行代码的实际执行顺序和代码在java文件中的顺序不一致,代码指令并不是严格按照代码语句顺序执行的,顺序被改变了。优点:提高处理速度

2 可见性 :一个core对应一个thread 对应一个本地缓存,使用volatile 可以把线程的本地缓存的数据强制flush到共享内存中,另一个线程load数据的时候就是正确的了。

1 所有的变量都存储在主内存中,每个线程也有兜里的工作内存,工作内存中的变量内容时主内存中的拷贝。2 线程不能直接读写主内存中的变量,而只能操作自己工作内存中的变量,然后再同步到主内存中。3 主内存时多个线程共享的,但线程间不共享工作内存,如果线程间需要通信,必须借助主内存中转来完成。

happens-before:如果一个操作happens-before另一个操作,那么第一个操作对于第二个操作时可见的。如果发生重排序了,应该依然能看到才对。

1 单线程规则,2 锁操作(synchronized&Lock)3 Volatile (所有写操作都能被后续的读操作看到)4 线程启动 5 Join 6 传递性,7 中断 interrupt 8 构造方法:finalize方法的第一行指令能看到构造方法的最后一行指令。9 工具类concurrentmap get一定能看到在此之前的put等存入动作。CountDownLatch,Semaphore,Future(线程后台执行并且可以拿到执行结果,有get方法 get会之前的执行时可见的。),线程池(提交很多任务commit,每一个任务都可以看到之前提交任务的结果。),CyclicBarrier

Volatile:可见性,禁止重排序

可见性:读一个volatile变量之前,需要先使相应的本地缓存失效,这样就必须到主内存读取最新值,写一个volatile属性会立即输入到主内存。

禁止指令重排序:解决单利双重锁乱序问题。

1 是一种同步机制,比Synchronized/Lock更轻量,他不会发生上下文切换等开销很大的行为。

2 如果变量被修饰成volatile,jvm就知道了这个变量可能会被并发修改。

3 开销小,能力也小,虽说是用来同步保证线程安全的,但是做不到synchronized那样的原子保护,仅在很有限的场景下发挥作用。

不适用a++;适用1 boolean flag,如果一个共享变量自始自终只被各个线程赋值,而没有其他操作,那么就可以用volatile来代替Synchronized或者代替原子变量,因为赋值自身是有原子性的,而volatile又保证了可见性,足以保证线程安全。适用2 :刷新之前的触发器。

volatile可以使long和double的赋值是原子的。他们是64 位的,32位的jvm会拆分,每32位写一次。

64 位的jvm上是原子的。商用jvm中不会出现。

3 原子性 :一系列操作,要么全部执行成功,要不全部不执行,不会出现执行一半的情况,是不可分割的。

ATM取钱,i++不是原子的1 目前i,2 ++ 3 写入

原子操作+ 原子操作  != 原子操作

1 Java 中原子操作除long和double之外的基本类型,(int,byte,boolean,short,char,float)的赋值操作。2 引用赋值 3 java.concurrent.Atomic.*包中所有的类

单利适用场景:1 无状态的工具类 2 全局信息类。

饿汉(静态常量) //之所以饿是类加在的时候实例就初始化完毕了

1 new 私有静态对象 // static 的对象,类加载的时候就完成实例化,避免了线程同步问题,类的加载是由jvm虚拟机自身保证线程安全的。 2 私有构造 3 用来给外界调用的静态方法获取实例

饿汉(静态代码块)

1  定义出静态对象先不初始化,2 在静态代码块中初始化对象,// 同样由jvm保证了线程安全。后两步同上。

懒汉(不安全) // 不饿还懒,单利只有在用到的时候才加载进来,如果不用就用不加载,就节省了内存

1 先定义私有静态实例 2 构造私有化 3 对外公开静态方法返回实例,但是这个实例还未初始化,那好就在这里初始化,如果实例为空 new 个返回。但是如果存在多线线程竞争的情况下的化就会创建多个实例,线程不安全了。有没有方法线程安全? 可以把对外公开的静态方法搞成同步方法。但是效率比较低下,获取实例无法并行了,既然这样改进一下,方法不搞成同步的了,把创建实例的地方用同步代码块类锁的方式包裹起来,但是这样是线程不安全的了。

双重检测: 1 初始化私有静态实例并用volatile修饰,2 构造私有化 3 发布开放私有方法,如何写1 先判空,在同步代码块中, 再去判断空,如果为空创建实例,返回出去。

New 实际上有3个步骤 rs = new Resource(); 1 构造空的对象resource()2 调用构造方法 3 把对象地址赋值给rs ,如果不加volatile重排序成1 3 2 ,相当于3 时rs 就不为空了,但是2 rs 赋值等操作时没有执行的。。。会带来NPE问题,防止重排序。

静态内部类:1 私有化构造,2 创建静态内部类,持有私有静态final 的外部类的实例3 静态开放的方法发布出去 2 创建的静态内部类所持有的外部类的实例。// 懒汉,是因为在内部类中实例化的外部类对象,在外部类加载的时候,jvm不会直接把内部类给初始化的。饿汉的缺点不复存在。只有真正调用getInstance方法,返回实例的时候,实例才会被初始化。jvm类的加载的性质保证了,类不会多次加载,不会创建多个实例。

枚举: enum 这个枚举中创建INSTANCE;// 枚举经过反编译就是静态的对象,final的

退出码130//DL,normal 0

 1 互斥 2 请求和保持 3 不剥夺 4 环路等待//必要条件,4 个必须同时满足。

Jps 拿到进程id,jstack pid ThreadMXBean, System.identityHashCode(object)

ThreadPool

构造函数参数

  public ThreadPoolExecutor(int corePoolSize,// 核心线程数

                              int maximumPoolSize, // 最大线程数

                              long keepAliveTime,// 保持存活时间,如果当前线程数多余corepoolsize,那么如果多余的线程空闲时间超过这个变量,它们就会被终止。

                              TimeUnit unit,

                              BlockingQueue workQueue,// 任务存储队列

                              ThreadFactory threadFactory,// 当线程池需要新的线程的时候,会使用threadFactory来生成新的线程

                              RejectedExecutionHandler handler)// 由于线程池无法接受你所提交的任务的拒绝策略

1 线程数小于corepoolsize ,即使线程池中有空闲的线程,也会创建一个新线程来运行新任务

2 如果线程数 等于或大于corepoolsize ,但是小于maximumPoolsize,则先将任务方法任务队列

3 队列满了,线程数小于maxpoolsize,就创建新线程来运行任务。

4 队列满了,线程数大于或等于maxpoolsize,则拒绝该任务。

直接交接:SynchronousQueue // 只是起到中转作用,基本没容量,任务马上交给线程处理

无界队列:LinkedBlockingQueue

有界队列:ArrayBlockingQueue

延迟队列 : DelayedWorkQueue

1 Executors.newFixedThreadPool(4)// 这个参数 被配置为corepoolsize,底层调用为

ThreadPoolExecutor(4,4,0,ms,new 无界队列)

2 Executors.newSingleThreadExecutor();// 线程数为1,底层调用为:// 与上一个类似

ThreadPoolExecutor(1,1,0,ms,new 无界队列)

3 Executors.newCachedThreadPool()

可缓存线程池,无界线程池,具有自动回收多余线程的功能。调用:

ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,s,new 直接交付)

4 Executors.newScheduledThreadPool

支持定时以及周期性任务执行的线程池子

ScheduledExecutorService threadPool  = ThreadPoolExecutor(10);// 核心线程数10

ThreadPoolExecutor(10,Integer.MAX_VALUE,0,s, new DelayedWorkQueue) // 延迟对垒

threadPool.schedule(new Task(), 5 /*delay*/, s);

threadPool.scheduleAtFixedRate(new Task(), 1 delay , 3 周期,s );

workStealingPool // jdk8 1 子任务 2 窃取

cpu密集型最佳线程数为cpu core的1~2倍,io密集型可以大于core 很多倍

线程数 = cpu core * (1 + 平均等待时间/平均工作时间)

停止线程池:1 shutdown 会把存量的任务先执行完成在关闭;包括队列中的任务,新加入任务会抛出异常。

isShutdown// 是否进入停止状态了,开始结束。如果调用过shutdown 就返回true,但是任务可能还没执行完。

isTerminated// 线程是否完全停止了,队列的任务也完成了。

awaitTermination(3L,s)// 等待一段时间,检测线程是否完全停止了,如果是,返回ture

List runnableList = shutdownNow() // 立刻关闭线程池,正在运行的线程,intertupt,队列的任务,直接返回。

拒绝策略:线程池关闭时/队列和最大线程数已满时

1 AbortPolicy ,直接抛出异常 2 DiscardPolicy 默默的丢掉,不会抛出异常3 DiscardOldestPolicy 丢弃最老的任务。4  CallerRunsPolicy // 谁提交的自己干试试,让主线程运行

Executors是一个工具类。

继承关系:Executor 接口只有一个方法execute 执行任务,

派生出ExecutorService  增加了一些新的方法,shutdown()管理线程池

AbstractExecutorService 

ThreadPoolExecutor

状态:1 RUNNIng 接受新任务并处理排队任务

2 SHUTDOWN 不接受新任务,但处理排队任务

3 STOP 不接受新任务,也不处理排队任务,并中断正在执行的任务

4 Tidying 整洁,所有任务都一终止,workercount为0,线程会转换到这个状态,并运行terminate()钩子方法

5 TERMINATED terminate() 运行完成。

ThreadLocal

initialvalue// 初始化,1 没重写的话返回null,返回当前线程对应的初始值,是一个延迟加载的方法,只有在调用get的时候,才会触发get-setInitialValue-initialValue

2 当线程第一次使用get 方法访问变量时,将调用此方法,除非线程先调用了set方法,这中case下,不会为线程调用本initialValue 方法。

3 通常,每个线程最多调用一次此方法,但如果已经调用了remove,再调用get,则可以再次调用此方法

4 如果不重写,会返回null,一般使用匿名内部类的方法重写initialValue,以便在后续使用中可以初始化副本对象。

set // 为这个线程设置一个新值

get// 得到这个线程对应的value,如果是首次调用get,则会调用initialize来得到这个值

先获取当前线程的threadlocalmap,然后调用map.getEntry(this)// 当前对像,ThreadLocal.java,把本threadlocal的引用作为参数传入,取出map中属于本threadlocal的value

remove// 删除对应这个线程的值

Map以及map中的key和value都是保存在线程中,而不是保存在ThreadLocal中

每一个thread对象都有一个成员threadlocalmap 这个map的key 是threadlocalX,value就是对应的值

因为一个线程可能有多个threadlocal对象

ThreadLocalMap 类似hashMap,hashmap处理冲突是用的拉链法,java8 优化为红黑树

但是threadlocalmap处理冲突用的是线性探测法,如果发生冲突,就继续找下一个空位置。而不是用链表拉链。

ThreadLocalMap的key,是通过super.key ,弱引用方式赋值的,// 如果一个对象值被弱引用关联,没有心热强引用关联,那么这个对象就可以被回收。弱引用不会阻止GC

这个map的每个entry都是一个对key的弱引用,同时每个entry都包含了一个对value的强引用

正常情况下,当线程终止,保存在map里的value会被垃圾回收,因为没有任何强引用了。

但是,如果线程不终止,比如线程池中,线程需要保持很久,那么key对应的value就不能被回收

Thread-ThreadLocalMap-Entry(key 为null)-value

jdk已经考虑到这个问题,在set remove rehash 方法中会扫描key为null的entry,并把对应的value设置为null,这样value对象就可以被回收,但是如果threadlocal不被使用,上述方法也不会被调用。线程如果也不终止,调用链就一直存在

正确的方式应该是,使用完Threadlocal之后,应该调用remove方法,就会删除对应的entry,避免leak

static class Entry extends WeakReference> {

            /** The value associated with this ThreadLocal. */

            Object value;


            Entry(ThreadLocal k, Object v) {

                super(k);

                value = v;

            }

        }

Lock

lock()获取锁,如果已经被其他线程获取,进行等待

lock不会像synchronized一样在异常时自动释放锁,finally 中释放锁,保证异常时锁一定被释放

tryLock 尝试获取锁,成功返回true,该方法会立即返回,拿不到锁也不会一直等。

tryLock(long time, TimeUnit unit)等一段时间,超时就放弃。可以相互谦让,避免死锁发生。

lockInterruptibly 相当于tryLock 的超时时间时无限的,永远的在等锁,等锁的过程中,线程可以被中断。Unlock 解锁

线程要不要锁住同步资源,锁住 悲观锁(互斥同步锁)synchronized 和lock接口。不锁住乐观锁(非互斥同步锁)一般都是通过CAS算法实现的。典型例子就是原子类和并发容器,git

多线程能否共享一把锁,可以共享锁(读锁),不可以 独占锁 (写锁)

reentrantReadWriteLock读写锁,其中读锁是共享锁,写锁是独享锁。

要么一个或多个线程同时有读锁,要么是一个线程有写锁,但是两者不会同时出现。读写锁只是一把锁,可以通过两种方式锁定,读锁定和写锁定

多线程竞争时,是否排队,排队 公平锁,先尝试插队,插队失败在排队 非公平锁

公平值得时按照线程请求的顺序,来分配锁,非公平时指不完全按照请求的顺序,在一定的情况下,可以插队。ReentrantLock(true// 公平); 默认是非公平锁,应为线程切换花费时间。tryLock会抢锁

同一个线程是否可以重复获取同一把锁,可以 可重入 ReentrantLock.isHeldByCurrentThread; getQueueLength 正在等待锁的队列有多长, 不可以,不可重入锁

是否可中断, 可以 可中断锁, 不可以,非可中断锁

等锁的过程 自旋 自旋锁(不停的尝试,一直占用了cpu java.util.concurrent,atmoic包下的类基本都是),阻塞 非自旋锁

Atomic

|

public final int get() //取当前的值

public final int getAndSet(int newValue) //获取当前的值,并设置新的值

public final int getAndIncrement() //取当前的值,并自增

public final int getAndDecrement() //获取当前的值,并自减

public final int getAndAdd(int delta) //获取当前的值,并加上预期的值

public final boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)

升级型原子类的原理类似反射,1 可见性,2 不能是static

longAdder 的性能比Atomiclong高,java8进入,多段锁。

Final 具有不变性的对象一定是线程安全的,我们不需要对其采取任何额外的安全措施,也能保证线程安全。类防止被继承,防止方法被重写,防止变量被修改。final修饰变量意味着值不能被修改,如果变量是对象,那么对象的引用不能变,但是对象自身的内容依然可以变化

ConcurrentHashMap

Vecoter Hashtable 同步的arraylist和hashmap,它们的方法都是同步的

ArrayList HashMap 线程不安全 Collections.synchronizedList/Map // 同步代码块

ConcurrenthashMap CopyOnWriteArrayList

Map 接口 有两个实现类 1HashTable 2 HashMap 派生出LinkedHashMap 3 SortedMap继承了Map接口,NavigableMap 继承了SortedMap, TreeMap实现了NavigableMap 接口

putval流程:1判断key value不为空2 计算hash值 3 根据对应位置节点的类型来赋值,或者helpTransfer,或者增长链表,或者给红黑树增加节点。检查满足阀值就红黑树化,返回oldval

7 是segment 每个segment继承自reentrantLock,默认16 个;8 节点,每个节点都是独立的。cas + synchronized保证并发,从16 变成每个节点都可以并发,红黑树

replace/putIfAbsent

copyonwriteArrayList/copyonWriteArraySet

Queue

队列分为阻塞队列和非阻塞队列

BlockingQueue:SynchronousQueue ArrayBlockingQueue PriorityBlockingQueue LinkedBlockingQueue 两把锁

线程安全的,take :获取并移除队列头节点,队列无数据,则阻塞,直到队列里有数据

Put 方法,插入,如果队列满了,则阻塞,知道队列里有了空闲空间。

add,remove element,往里放,满了抛出异常,删除的时候空了抛出异常,取出队列元素空抛出异常

Offer ,poll, peek,offer 放,满了返回false, poll 取出的同时会删除,peek只取出头节点不删除,共同点是,空时取出的是null

ConcurrentLinkedQueue  // 适用cas 保证线程安全

Tools

Semaphore        信号量,可以通过控制“许可证”的数量来保证线程之间的配合,线程只有拿到“许可证”后才能继续运行。相比于其他的同步方法,更加灵活

初始化Semaphore并指定许可证的数量;

在需要被限制的代码前加acquire()或者acquireUninterruptibly();

执行结束之后,调用release方法来释放许可证;

new Semaphore(int permits, boolean fair):这里可以设置是否要使用公平策略,如果传入true,那么Semaphore会把之前等待的线程放到FIFO的队列里,以便于当有了新的许可证,可以分发给之前等了最长时间的线程;

acquire()能响应中断,acquireUninterruptibly()不能响应中断;

tryAcquire():看看现在有没有空闲的许可证,如果有的话就获取,如果没有的话,不阻塞而去做别的事;

tryAcquire(timeout):多了个超时时间;

Release 归还许可证

获取和 释放许可证对线程并无要求,也许是A获取了,然后由B释放。

CyclicBarrier        线程会等待,直到足够多的线程达到了事先规定的数目。一旦达到触发条件,就可以进行下一步的动作。适用于线程之间相互等待处理结果就绪的场景

CyclicBarrier和CountDownLatch的区别:

CountDownLatch每次用countDown方法来触发倒数事件;

CyclicBarrier是用await来触发倒数线程;一个是事件一个是线程

CountDownLatch倒数到0就触发事件,不能重用,而CyclicBarrier可以重复使用,只要达到规定的条件;

Phaser        和CyclicBarrier类似,但是计数可变,Java7加入的

CountDownLatch        和CyclicBarrier类似,数量递减到0时,触发动作,但是不可重复使用// 拼单,人满才发货

流程:线程开始-进入等待-倒数结束-继续工作

CountDownLatch(int count):仅有一个构造方法,参数count为倒数的值;

await():调用await方法的线程会被挂起,它会等待直到count值为0才继续执行;//只有点用了await的线程才会被唤醒到0时

countDown():将count减一,直到0时,等待的线程才会被唤醒;// 调用countDown的线程会继续执行不会休眠

Exchanger        让两个线程在合适时交换对象,适用于当两个线程工作在同一个类的不同实例上时,用于交换数据

Condition        可以控制线程的等待和唤醒,是Object.wait()的升级版

AQS

abstractQueuedSynchronizer

Semaphore 内部有一个sync类,派生自AQS ,CountdownLatch也是一样

|

AQS是一个用于构建锁、同步器、协作工具类的工具类(框架)。有了AQS,更多的协作工具类都可以很方便得写出来。有了AQS,构建线程协作类就容易多了。

AQS最核心的就是三个部分:

一:.state:

1.这里的state的具体含义,会根据具体实现类的不同而不同,比如在Semaphore里,它表示“剩余的许可证数量”,而在CountDownLatch中,它表示“还需要倒数的数量”,在ReentrantLock中,state表示锁的占有情况,包括可重入计数,当state为0的时候,表示Lock不被任何线程所占有

2.state是volatile修饰的,会被并发的修改,所以所有修改state的方法都需要保证线程安全,比如getState、setState以及compareAndSetState操作来读取和更新这个状态。这些方法都依赖于juc.atomic包的支持

二:控制线程抢锁和配合FIFO队列

1.这个队列用来存放“等待的线程”,AQS就是“排队管理器”,当多个线程争用同一把锁时,必须有排队机制将那些没有拿到锁的线程串在一起。当锁释放时,锁管理器就会挑一个合适的线程来占有这个刚刚释放的锁

2.AQS会维护一个等待的线程队列,把线程都放到这个队列里,并且这队列是双向链表的形式

三:期望协助工具类去实现的获取/释放等重要方法

1.这里的获取和释放方法,是利用AQS的协作工具类里最重要的方法,是由协作类自己去实现的吗,并且含义各不相同

1.2.获取方法:获取操作会依赖state变量,经常会阻塞(比如获取不到锁的时候)

1.3.在Semaphore中,获取就是acquire方法,作用是获取一个许可证(state减一是正数就可以获取到,是0就获取不到)

1.4.在CountDownLatch里面,获取就是await方法,作用是等待,直到倒数结束(state不等于0就处理阻塞状态,等于0就获取到了)

1.5.释放不会阻塞

1.6 在Semaphore中,释放就是release方法,作用是释放一个许可证

1.7 在CountDownLathc中,释放就是countDown方法,作用是倒数一个数

Future&Callable

Runnable 缺陷 1 不能返回一个返回值 2 不能run 方法声明中抛出 checked Exception,只能try catch

,Thread 类一般不是本人编写的,往上抛的话也无法处理。

Callable 接口类似于runnable,被其他线程执行的任务,需要实现call 方法。有返回值。

Future :1 Future.get来获取callable 接口返回的执行结果,2 Future.isDone来判断任务是否已经执行完了,以及取消这个任务,限时获取任务的结果。。

在call()未执行完毕之前,调用get的线程,比如主线程会被阻塞,知道call方法返回结果以后,此时一 Future.get() 才可以得到结果,然后主线程会被切换到runnable状态。

Future 是一个存储器,它存储了call这个任务的结果,而这个任务的执行时间是无法提前确定的。

Future get 方法获取结果,取决一callable 任务的状态,一般有5 种情况

1 任务正常结束,get 方法会立即返回结果

2 任务尚未结束(未开始或进行中)get请阻塞直到任务完成。 可以把Future 想象成裁判员在等运动员的名次

3 如果任务执行过程中抛出异常,call 无论抛出何种异常,get方法抛出的异常类型都是ExecutionExeption

4 任务被取消,个体会抛出cancellationException

5 任务超时,有重载的方法,会传入一个延迟时间,如果到了时间还没有获得结果,get会抛出TimeoutException

需要注意超时后处理,调用future.cancel,传入true,false的区别,代表是否中断正在执行的任务。

二 cancel 取消任务执行

1 如果任务还没有开始执行,任务会被正常的取消,未来也不会被执行,返回ture

2 如果任务已经完成,或已经取消,cancel方法会执行失败,方法返回false

3 如果任务已经开始执行了,那么取消不会直接取消该任务,而是根据参数mayInterruptIfRunning做判断。

三 isDone 判断线程是否执行完成,不代表任务成功了。

四 isCancelled 判断任务是否被取消。

线程池的submit方法返回future对象

首先,给线程池提交我们的任务,这个任务不在实现runnable接口,而是线callable接口,提交时线程池会立即返回一个空的Future容器,当线程的任务一旦执行完毕,也就是当我们可以获取结果的时候,线程池会把该结果填入到之前我们的那个Future中去,而不是创建一个新的Future,可以从该Future中获得任务执行的结果。

除了线程池的submit 返回值得到Future 之外,也可以通过FutureTask来获取Future和任务的结果

他是一种包装器,可以把callable转化成Future和Runnable,它同时实现二者的接口。

RunnableFuture 接口同时实现了Runable和Future接口,而FutureTask实现了RunnableFuture,意味着FutureTask即是一种Runnable又是一种future,它既可以作为runnable被线程执行,又乐意作为future得到callable的返回值。

1 Ft = New FutureTask(实现了callable接口的task)

2 new Thread(ft)// ft 实现了runnable接口

2-1// 或致直接放到线程池中执行,并且不接受返回结果。

3 ft.get // 获取结果。

用数组批量获取future执行结果时,可能会一到返回时间不一的情况,比如3 4 任务已经执行完成,但是get的时候只能get 0,可以用CompletableFuture解决此问题,

Future 生命周期不能后退

你可能感兴趣的:(Concurrency)