本文内容摘抄自: Java并发编程的艺术
单核处理器也支持多线程执行代码,CPU时间片分配算法来循环执行任务,一次上下文切换:当时间片切换到下一个任务时,会保存当前任务状态,所以任务从保存到再加载的过程就是一次上下文切换
测量工具:
无锁并发编程、CAS算法、是用最少线程和使用协程
协程:在单线程中实现多任务调度、且维持多个任务间的切换
避免死锁方法:
lock.tryLock(timeout)
来替代内部锁机制并不是并发越多越好,这取决于资源限制
为保证内存可见,Java编译器会在适当的位置插入内存屏障指令来禁止特定类型的处理器指令重排,load store
volatile禁止处理器的指令重排和令CPU缓存失效
不会引起线程的上下文切换和调度,可以理解为轻量级的synchronized , 涉及Java内存模型(主内存、工作内存) ,保证共享变量的可见性当变量改变时,所有线程都会同步状态
原理:经过volatile修饰的代码,会在汇编代码中出现,lock前缀指令
对象在内存中的布局分为:对象头
、实例数据
、填充数据
对象头中包含两部分: MarkWord 和 类型指针.
如果是数组对象的话, 对象头还有一部分是存储数组的长度.
多线程下synchronized的加锁就是对同一个对象的对象头中的MarkWord中的变量进行CAS操作.
Mark Word用于存储对象自身的运行时数据, 如HashCode, GC分代年龄, 锁状态标志, 线程持有的锁, 偏向线程ID等等.
占用内存大小与虚拟机位长一致(32位JVM -> MarkWord是32位, 64位JVM->MarkWord是64位).
重量级锁,JDK1.6得到优化,引入‘偏向锁和轻量级锁’,所以锁共有4种状态:从低到高:无锁状态、偏向锁、轻量级锁和重量级锁,状态随竞争不断升级,不能降级
Java中的每个对象都可以作为锁. 具体变现为以下3中形式.
一个线程试图访问同步代码块时, 必须获取锁. 在退出或者抛出异常时, 必须释放锁.
JVM基于进入和退出Monitor对象来实现方法同步和代码块同步, 但是两者的实现细节不一样.
monitorenter指令是在编译后插入到同步代码块的开始位置, 而monitorexit指令是在编译后插入到同步代码块的结束处或异常处.
线程执行到monitorenter指令时,尝试获取对应的monitor所有权,即尝试获取对象的锁
Java的monitor机制,临界区:使用 synchronized 关键字,PV原语:Object 的 wait / notify 等元素
当前线程必须获取到了obj的Monitor,才能去调用其wait方法,即wait必须放在被synchronized修饰的代码中。
notify有两个方法notify和notifyAll,前者只能唤醒一个正在等待这个对象的monitor的线程,具体由JVM决定,后者则会唤醒所有正在等待这个对象的monitor的线程
对象中的对象头中的markWord中的标识位,当其他线程尝试竞争偏向锁时才释放锁
轻量级锁解锁时, 会使用原子的CAS操作将当前线程的锁记录替换回到对象头, 如果成功, 表示没有竞争发生; 如果失败, 表示当前锁存在竞争, 锁就会膨胀成重量级锁.
JVM中的CAS操作利用的是处理器提供的CMPXCHG指令实现的。,但是有三个问题
版本号
JDK中的Atomic包提供compareAndSet方法使用锁机制保证原子操作
除了偏向锁,JVM实现的锁都是采用循环CAS来实现的。
线程之间:数据交换问题 /
进程之间:相互通信问题
在命令式编程中,线程之间的通信机制是:共享内存和消息传递
class ReentrantLockExample{
int a=0;
ReentrantLock lock=new ReentrantLock();
public void writer(){
lock.lock();
try{
a++;
}finally{
lock.unlock();
}
}
public void reader(){
lock.lock();
try{
a++....
}finally{
lock.unlock();
}
}
}
有时需要采用延迟初始化来降低初始化类和创建对象的开销,双重锁检测机制是常见的延迟初始化方式
//俄汉模式
public class Singleton{
private static Singleton instance;
static{
instance=new Singleton();
}
public static Singleton getInstance(){
return instance;
}
}
//懒汉模式
public class Singleton{
private static Singleton instance;
public static synchronized Singleton getInstance{
if(instance==null){
instance=new Singleton();
}
return instance;
}
}
//双重锁检测机制
public class Singleton{
private static Singleton instance;
public static Singleton getInstance{
if(instance==null){
synchronized(Singleton.class){
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
JDK1.5才出现的枚举,可避免多线程同步问题和防止反序列化重新创建新对象
双重锁检测机制存在问题,
2和3可能会出现指令重排,所以得利用volatile修饰。
Java.lang.Obejct上有 wait()
wait(long timeout)
notify()
notifyAll()
和synchronized配合
调用Lock.newCondition()
获取Condition对象
condition.await() 和condition.signal() 对应等待和释放
ConcurrentHashMap是由Segment数组和HashEntry数组组成的,
ConcurrentHashMap的3种操作,get操作、put操作和size操作
get操作不加锁,因为统计Segment大小的count字段和HashEntry的value被修饰为volatile变量
put操作加锁,首先定位到哪个Segment,然后插入操作经过1、HashEntry是否需要扩容,2、将元素加入HashEntry
size操作 先尝试2次不加锁Segment方式来统计各个Segment的大小,如果统计过程中发现modCount改变再通过枷锁的方式来统计
因为put、remove、clean操作都会让modeCount改变
向线程池提交任务:
java的线程既是工作单元也是执行机制,从JDK1.5开始,将工作单元(Runnable和Callable)、执行机制(Executor框架提供)分开
通常使用工厂类Executors来创建,有3种类型:SingleThreadExecutor、FixedThreadPool和CachedThreadPool
有两种类型,ScheduledThreadPoolExecutor和SingleThreadScheduledExctutor
除了实现了Future接口外还实现了Runnable接口
FutureTask的实现基于AQS