目录 start
目录 end
|2018-04-27| 码云 | CSDN | OSChina
个人相关代码
主要知识来源 Java程序员修炼之道 | 并发编程网
该模块最早在1.5引入,由Doug Lea开发 | doug lea博客中文版
线程模型
concurrent包的设计理念
(this){...}
代码块 但是两者的二进制码的表示是不同的线程的状态模型:
完全同步对象 策略 一个满足下面所有条件的类就是完全同步类:
- 如果锁定的是类的成员属性,或者this, 就是对该对象进行了加锁变成了'单线程', 就影响了整体性能
- 使用局部变量就会多线程且保证了数据的一致性
- 切记不能锁常量(或者显式声明的String)从而引起死锁
查看JDK源码 ForkJoinTask 的 externalAwaitDone 方法
// 这个模块的标准使用方式
synchronized(this){
while(condition){
Object.wait();
}
}
当多个线程共享一个变量的时候,每个读写都必须加锁进行同步, 如果没有正确的同步,就容易造成程序的活性失败和安全性失败,这样的失败是很难复现的.所以务必要保证锁的正确使用
// 这个就是个错误使用的案例
int size = 0;
public synchronized void increase(){
size++;
}
public int current(){
return size;
}
这个案例保证了多线程下并发时,对size变量的正确修改,但是不能保证实时读取到的变量值是正确的
正确的做法是 current 方法也要加上synchronized关键字
打开Netty中NioEventLoop的源码 有一个属性
private volatile int ioRatio = 50;
该变量是用于控制IO操作和其他任务运行比例的
public class ResortJavaDemo {
private static boolean stop;
public static void main(String[]s) throws InterruptedException {
Thread workThread = new Thread(() -> {
int i = 0;
while(!stop){
i++;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("i"+i);
}
});
workThread.start();
TimeUnit.SECONDS.sleep(3);
stop = true;
}
}
我们预期程序会在3s后停止, 但是实际上它会一直执行下去, 原因就是虚拟机对代码进行了指令重排序和优化, 优化后的指令如下:
if (!stop)
while(true)
所以需要在stop前加上volatile修饰符, 解决了如下两个问题
- main线程对stop的修改在workThread中可见
- 禁止指令重排序, 防止因为重排序导致的并发访问逻辑混乱
一些人认为volatile可以替代传统锁,提升并发性能, 这个认识是错误的. volatile仅仅解决了可见性的问题, 并不能保证互斥性
volatile最适合使用的是一个线程写, 其他线程读的场景. 如果有多个线程并发
写
操作,仍然需要使用锁
或者线程安全的容器
或者原子变量
来代替
简称为J.U.C (java.util.concurrent) | The j.u.c Synchronizer Framework中文翻译版
线程池
,Task(Runnable/Callable)
,读写锁
,原子类
和线程安全容器
来代替传统的同步锁,wait和notify
线程安全容器底层使用了CAS,volatile,和ReadWriteLock实现
互斥同步最主要的问题就是进行线程阻塞和唤醒所带来的性能额外损耗, 因此这种同步也被称为阻塞同步,悲观锁
与之对应的乐观锁是, 先进行操作, 操作完成之后再判断操作是否成功, 是否有并发问题, 如果有则进行失败补偿, 如果没有就算操作成功.
Java中的非阻塞同步就是CAS 1.5就有了
java.util.concurrent.atomic
提供适当的原子方法 避免在共享数据上出现竞争危害的方法
使用Java自带的原子类, 可以避免同步锁带来的并发访问性能降低的问题, 减少犯错的机会. 对于 int, long, boolean 等成员变量大量使用原子类但是使用者必须通过类似 compareAndSet或者set或者与这些操作等价的
原子操作
来保证更新的原子性.
AtomicInteger
或AtomicLong
上用原子
getAndIncrement()
方法, 并且提供了nextId 方法得到唯一的完全增长的数值在读多写少的场景下, 使用同步锁比同步块性能要好
java.util.concurrent.locks
块结构同步方式基于锁这样的的概念,具有缺点
如果要重构对线程锁的支持, 事实上该包下Lock接口也都实现了:
lock()
: 官方API1.8 locktrylock()
: 官方API1.8 trylock是一种简单的同步模式,这种模式允许线程在通过同步屏障之前做少量的准备工作
构建实例时,需要提供一个数值(计数器),通过两个方法来实现这个机制
countDown()
作用:计数器减一
await()
作用:让线程在计数器到0之前一直等待,
a.await()
如果锁存器a的Count不为0 ,就把当前线程休眠掉能做到: 当一堆线程之间的同步,为了确保有指定数量正常初始化的线程 创建成功,才能开始同步
ConcurrentHashMap
是 HashMap的并发版本ConcurrentHashMap
类 还实现了ConcurrentMap接口,有些提供了还提供了原子操作的新方法
putIfAbsent()
如果还没有对应键,就把键/值添加进去remove()
如果键存在而且值与当前状态相等,则用原子方式移除键值对replace()
API 为HashMap中原子替换的操作方法提供了两种不同的形式标准的ArrayList的替代,通过写时复制语义来实现线程安全性,也就是说修改列表的任何操作都会创建一个列表底层数组的新副本
这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法更 有效。在不能或不想进行同步遍历
但又需要从并发线程中排除冲突时,它也很有用。“快照”风格的迭代器方法在创建迭代器时使用了对数组状态的引用。此数组在迭代器的生存期内不会更改,
因此不可能发生冲突,并且迭代器保证不会抛出 ConcurrentModificationException。创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。
在迭代器上进行的元素更改操作(remove、set 和 add)不受支持。这些方法将抛出 UnsupportedOperationException。
BlockingQueue<Pro<Author>>
public class Pro<T>{
private T pro;
public T getPro(){
return pro;
}
public Pro(T pro){
this.pro = pro;
}
}
并发扩展类,
要把目标代码做成可调用(执行者调用)的结构,而不是单独开线程运行 展示代码
Callable接口
Future接口
FutureTask类
ScheduleThreadPoolExecutor 简称 STPE 线程池类中很重要的类
引入一种新的执行者服务,称为 ForkJoinPool
ForkJoinPool 服务处理一种比线程更小的并发单元 ForkJoinTask
通常使用两种任务
提供了支持大型任务分解的基本方法,还有自动调度和重新调度的能力
这个框架的关键特性之一就是:这些轻量的任务都能够生成新的ForkJoinTask实例,而这些实例仍然由执行他们父任务的线程池来安排调度,这就是分而治之
工作窃取:
由 RecursiveAction 或者 RecursiveTask 派生出来的才能作为任务单元 这俩也是派生ForkJoinTask而来
protected void compute()
protected Object compute()
ForkJoinTask里的 invoke 和 invokeAll
public final V invoke()
public static <T extends ForkJoinTask<?>> Collection<T> invokeAll(Collection<T> tasks)
ForkJoinTask和工作窃取
工作窃取
并行问题
Java Memory Model JMM
同步动作和被称为偏序的数据结构描述JMM,
JMM 的主要规则:
敏感行为:
重要概念: 如果对象不可改变,确保改变对所有线程可见的相关问题就不会出现
代码块之间的 之前发生(Happens-Before)
和 同步约束(Synchronizes-With)
关系