标签:处理 动作 摘要 字节码 成功 场景 定义 否则 代码
线程同步机制是一套用于协调线程间的数据访问及活动的机制。该机制用于保障线程安全及实现这些线程的共同目标。
java平台提供的线程同步机制:
锁机制 :将多线程并发访问共享数据转换为串行访问,一个共享数据每次只能被一个线程访问(获得锁),该线程访问结束后(释放锁)其他线程才能对其访问。
锁的获得 : 一个线程在访问数据前必须申请相应的锁。
锁的持有线程 : 一个线程获得某个锁。
一个锁一次只能被一个线程持有
临界区 :锁的持有线程在获得锁之后和释放锁之前这段时间内所执行的代码被称为临界区。共享数据只能在临界区内进行访问,临界区一次只能被一个线程执行。
排它锁/互斥锁 :锁具有排他性,一次只能被一个线程持有,这种锁被称为排它或锁互斥锁。
java虚拟机对锁实现方式的分类:
保护共享数据实现线程安全:
可重入性 : 一个线程在持有一个锁的时候还能够继续成功申请该锁,就称该锁具有可重入性,反之则称为非可重入性。
锁的粒度 : 一个锁实例可以保护一个或者多个共享数据,一个实例所保护的共享数据的数量大小就被称为该锁的粒度,锁保护的共享数据越大,我们就称该锁的粒度约粗,反之则称粒度细。
锁的开销包括几个:
内部锁是通过Synchronized关键字实现的,Synchronized关键字可以用开修饰方法及代码块。
同步方法的整个方法体就是一个临界区
Synchronized(锁句柄){
//在此代码块访问共享数据
}
锁句柄是一个对象的引用。锁句柄可以直接填写this关键字表示当前对象。锁句柄对应的监视器被称为相应同步块的引导锁,相应的我们称呼相应的同步块为该锁引导的同步块。
锁句柄通常采用final修饰(private final)。这是因为
锁句柄的值一旦改变,会导致同一个代码块的多个线程实际上使用不同的锁,而导致竞态。
同步静态方法相当于当前类为引导锁的同步块。
public class SynchronizedMethodExample {
public static sysnchronized void staticMethod(){
//在此访问共享数据
}
//...
}
//相当于
public class SynchronizedMethodExample {
public static void staticMethod(){
sysnchronized(SynchronizedMethodExample.class){
//在此访问共享数据
}
}
//...
}
线程在执行临界区代码的时候必须持有该临界区的引导锁。一个线程执行到
同步代码块石必须申请该同步块的引导锁,只有申请成功该锁的线程才能够执行相的应临界区。一旦执行完临界区代码,引导该临界区的锁就会被自动释放。整个内部锁申请和释放的过程都是由java虚拟机负责实施的,所以synchronized实现的锁被称为内部锁。
内部锁不会导致锁泄露,java编译器在将同步代码块编译成字节码的时候,对临界区可能抛出的异常(未被捕获)进行了处理,所以即使临界区代码抛出异常也不会妨碍内部锁的释放。
Java虚拟机会给每个内部锁分配一个入口集(Entry Set),用于记录等待获取锁的线程。申请锁失败的线程会被存入入口集中等待再次申请锁的机会(这些线程处于BLOCKED状态,被称为等待线程)。
当锁被释放时,入口集中的一个线程被唤醒,得到再次申请锁的机会,仅仅是机会!因为内部锁的机制仅支持 非公平调度,所以可能被其他新的活跃线程抢占这个释放锁。
jdk1.5引入的排他锁,其作用于内部锁相同, 但是它提供了一些内部锁所不具备的特性。
显示锁是java.util.concurrent.locks.Lock接口的实例。
void lock() 获取锁
void lockInterruptibly() 如果当前线程未被中断,则获取锁
newCondition() 返回绑定到此Lock实例的新Conditon实例
tryLock() 仅在调试时锁为空闲状态才获取锁
tryLock(long time, TimeUnit uinit) 如果说在给定的时间空闲,并且当前线程未被中断,则获取锁
unlock() 释放锁
//一个Lock接口实例就是一个显示锁的对象
private final Lock lock = ...; //创建Lock接口实例
lock.lock(); //申请锁lock
try{
//在此对共享数据访问
}finally{
//总是在finally块中释放锁,避免锁泄露
lock.unlock(); //释放锁
}
//使用显示锁实现循环递增的序列生成器
public class LockbasedCircularSeqGenerator implements CircularSeqGenerator {
private short sequence = -1;
private final Lock lock = new ReentrantLock();
@Override
public short nextSequence() {
lock.lock();
try {
if (sequence >= 999) {
sequence = 0;
} else {
sequence++;
}
return sequence;
} finally {
lock.unlock();
}
}
}
显示锁默认使用非公平策略调度。因为公平锁的开销比非公平锁的开销要大。
公平锁为保证公平增加了线程暂停和唤醒的可能性,导致了上下文切换的消耗要更大。所以公平锁适合用于锁被持有时间相对较长或线程申请锁时间相对较长的情况。总体来说公平锁的消耗比非公平锁消耗要大。
内部锁简单易用,且不会导致锁泄露;显示锁容易被错用而导致锁泄露(缺少释放锁的动作)。
内部锁是基于代码块的锁,灵活性较差,要么使用,用么不使用;而显示锁是基于对象的锁,灵活性强,比如可以在一个方法内申请锁,在另一个方法释放锁,而内部锁是无法做到的。
调度方面,内部是只支持非公平调度;显示锁两者都支持。
显示锁的其他特性。
如果内部锁的持有线程一直不释放该锁(通常代码错误导致),同步在该锁的所以线程都会被暂停而使任务无法进展。显示锁可以避免此问题,使用显示锁的tryLock() 方法,锁处于空闲状态返回true,否则返回false
Lock lock = ...;
if(lock.tryLock()){ //也可给tryLock()指定时间
try{
//在此访问共享数据
}finally{
lock.unlock();
}
}else{
/执行其他任务
}
是一种改进型的排它锁,也被称为共享/排他锁。读写锁允许多个线程同时读取共享变量,当一次只能允许一个线程对共享变量进行更新。
任何线程读取共享变量的时候其他线程无法对该共享变量进行更新,一个线程更新共享变量的时候其他线程都无法访问该变量。
读写锁是java.util.concurrent.locks.ReadWriteLock 接口的实例,默认实现类是java.util.concurrent.locks.ReentrantReadWriteLock。
ReadWriteLock接口定义了两个方法:readLock() 和 writeLock(),两个方法返回值都是lock类型
读锁:读线程访问共享变量时必须持有相应的读锁。且读锁可以被多个线程持有。
写锁:写锁是排他锁,一个线程持有写锁,其他成线程无法获得相应的写锁或读锁。
多个读线程提高了并发性,而写锁保障了写线程能够独占的方式安全的更新共享变量。
public class ReadWriteLockUsage {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.write();
//读线程执行
public void reader(){
readLock.lock(); //申请读锁
try{
//读取共享变量
}finally{
//释放锁避免泄露
readLock.unlock();
}
}
//写线程执行
public void writer(){
writeLock.lock(); //申请写锁
try{
//访问共享变量
}finally{
//释放锁避免泄露
writeLock.unlock();
}
}
}
ReetrantReadWriteLock 说实现的读写锁是可重入锁;且支持锁的降级,即一个线程持有写锁的情况下可以获得相应的写锁。
标签:处理 动作 摘要 字节码 成功 场景 定义 否则 代码
原文地址:https://www.cnblogs.com/sanzashu/p/11145724.html