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
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
拒绝策略:线程池关闭时/队列和最大线程数已满时
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 生命周期不能后退