悲观锁: 一定会出现多线程场景,先加锁,Synchronized 和 Lock 都是悲观锁 (适合 write多)
乐观锁: 不一定出现多线程场景,先不加锁,如果数据未更新,单线程write;如果数据更新;多线程write (适合read多),CAS算法就是乐观锁, Atomic Number 中 CAS自旋是一种典型的乐观锁
/* 乐观锁与悲观锁的调用方法 */
// ------------------------- 悲观锁的调用方式 -------------------------
// synchronized
public synchronized void testMethod() {
// 操作同步资源
}
// ReentrantLock
private ReentrantLock lock = new ReentrantLock(); // 需要保证多个线程使用的是同一个锁
public void modifyPublicResources() {
lock.lock();
// 操作同步资源
lock.unlock();
}
// ------------------------- 乐观锁的调用方式 -------------------------
private AtomicInteger atomicInteger = new AtomicInteger(); // 需要保证多个线程使用的是同一个AtomicInteger
atomicInteger.incrementAndGet(); //执行自增1
自旋锁: 让当前thread 不放弃CPU执行, 等待直到前面的thread已经释放了Lock,自旋锁的实现原理也是CAS
自旋锁常见3种Lock的形式: TicketLock,CLHlock,MCSlock
这四种锁都是描述 Synchronized 关键字的状态
Synchronized 实现Thread同步的原理: 使用 Java Object Header 和 Monitor实现同步 (Monitor是使用的 OS中 Mutex Lock)
Lock状态在下方表格中,从上到下升级
偏向锁针对的是一个Thread, 轻量锁和重量锁针对的是多个Thread
公平锁: 按照申请Lock的顺序获取Lock,Thread进入Queue 且 Queue的 Header 获取 Lock 优点: 没有Thread会被饿死,缺点: 效率低, CPU开销大
非公平锁: 直接 加Lock, 如果成功就直接执行, 如果失败就进入Queue等待
可重入锁:ReentrantLock(可重入锁),synchronized(同步锁),递归锁
不可重入锁:NonReentrantLock
/* 可重入锁的例子 doOthers()被调用时可以获得Lock */
/* 如果下面代码是 不可重入锁, 需要 doSomething()释放Lock, 会造成 DeadLock */
/* 可重入锁可以一定程度上避免死锁*/
public class Widget {
public synchronized void doSomething() {
System.out.println("方法1执行...");
doOthers();
}
public synchronized void doOthers() {
System.out.println("方法2执行...");
}
}
独占锁(排他锁):Lock只能被一个Thread占用 (比如 ReentrantLock)
共享锁: Lock可以被多个Thread占用 (比如 ReadWriteLock)
/* Write Lock 写锁的源码 */
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState(); // 取到当前锁的个数
int w = exclusiveCount(c); // 取写锁的个数w
if (c != 0) { // 如果已经有线程持有了锁(c!=0)
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread()) // 如果写线程数(w)为0(换言之存在读锁) 或者持有锁的线程不是当前线程就返回失败
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT) // 如果写入锁的数量大于最大数(65535,2的16次方-1)就抛出一个Error。
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) // 如果当且写线程数为0,并且当前线程需要阻塞那么就返回失败;或者如果通过CAS增加写线程数失败也返回失败。
return false;
setExclusiveOwnerThread(current); // 如果c=0,w=0或者c>0,w>0(重入),则设置当前线程或锁的拥有者
return true;
}
/*Read Lock 读锁的源码*/
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1; // 如果其他线程已经获取了写锁,则当前线程获取读锁失败,进入等待状态
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
共享锁和独占锁通过更改State的值实现ReadRead的时候是共享,其他时候是独占
非公平锁和公平锁都是独占锁
AQS中的Node类定义了 SHARED 和 EXCLUSIVE,代表了不同的锁获取模式
为了提高性能,Java提出ReadWriteLock,在读的地方用 Read Lock, 在写的地方用 Write Lock
ReadWriteLock 的 关系为: 多个Read Lock 不 Mutex, Read Lock 和 Write Lock 是 Mutex 的
Read Lock --- 如果想要 read-only 并且可以多人同时读,但不能多人同时写,使用 Read Lock
Write Lock --- 如果在修改数据,并且一个人在写,同时不能读取数据,使用 Write Lock
源码可以参照 java.util.concurrent.locks.ReadWriteLock 或者 ReentrantReadWriteLock
这种 counter=1的互斥锁,又称 二元信号量
// 创建一个计数阈值为 5 的信号量对象
// 只能 5 个线程同时访问
Semaphore semp = new Semaphore(5);
try { // 申请许可
semp.acquire();
try {
// 业务逻辑
} catch (Exception e) {
} finally {
// 释放许可
semp.release();
}
} catch (InterruptedException e) {
// code
}
同步锁Synchronized 是 独占式的悲观锁,也是可重入锁
ReentrantLock 继承了 Lock接口,除了可以完成Synchronized可以完成的工作,还可以支持可响应中断锁,可轮询锁请求,定时锁等避免deadlock的方法
public class MyService {
private Lock lock = new ReentrantLock();
//Lock lock=new ReentrantLock(true);//公平锁
//Lock lock=new ReentrantLock(false);//非公平锁
private Condition condition=lock.newCondition();//创建 Condition
public void testMethod() {
try {
lock.lock();//lock 加锁
//1:wait 方法等待:
//System.out.println("开始 wait");
condition.await();
//通过创建 Condition 对象来使线程 wait,必须先执行 lock.lock 方法获得锁
//:2:signal 方法唤醒
condition.signal();//condition 对象的 signal 方法可以唤醒 wait 线程
for (int i = 0; i < 5; i++) {
System.out.println("ThreadName=" + Thread.currentThread().getName()+ (" " + (i + 1)));
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
1. Semaphore 可以完成ReentrantLock所有工作,通过 acquire() 和 release() 进行获取和释放;
2. Semaphore.acquire() 和 ReentrantLock.lockInterruptibly() 作用一样,都可以被 Thread.interrupt() 中断
3. Semaphore 有 可轮询的锁请求 和 定时锁功能,用法和ReentrantLock一样,名字有变化,是tryAcquire()
4. Semaphore 提供公平锁与非公平锁机制
5. Semaphore 和 ReentrantLock一样也可以手动释放锁
Atomic Integer 是提供原子操作的Integer,此外还有 Atomic Boolean, Atomic Long,Atomic Reference 等
当我们处理不具有原子性,不安全的线程操作的时候,我们需要将 Integer的加减乘除变成具有原子性,此时使用 Atomic Integer.
Segment Lock 是一种锁的思想,具体实现见 ConcurrentHashMap
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。