锁
悲观锁:
每次获取数据的时候,都会担心数据被修改,所以每次获取数据的时候都会进行加锁,
确保在自己使用的过程中数据不会被别人修改,使用完成后进行数据解锁。
由于数据进行加锁,期间对该数据进行读写的其他线程都会进行等待。
-->Synchronized
-->写多读少
乐观锁:
每次获取数据的时候,都不会担心数据被修改,所以每次获取数据的时候都不会进行加锁,
但是在更新数据的时候需要判断该数据是否被别人修改过。
如果数据被其他线程修改,则不进行数据更新,如果数据没有被其他线程修改,
则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。
-->CAS(compare and set)
-->读多写少
悲观锁缺点:每一次加锁都会引起线程上下文的切换和线程调度,很影响系统运行性能
CAS:比较并修改
涉及到3个信息,一个存储位置V, 期望修改的值为A,获取原有数据为B(存储位置V获取)
步骤:
1、在存储位置V获取数据B
2、比较数据B和存储位置V的值是否相同:相同则将位置V的值修改为A
如果不相等,循环到步骤1,直至相同修改完值退出
CAS存在问题:
ABA问题:-->解决:通过版本号解决
CPU资源消耗大
Synchronized重量级锁:锁的优化
无锁-》偏向锁-》轻量级锁-》重量级锁 --》锁可以升级但不能降级
----------
| 对象头 |
----------
| 对象本身 |
----------
|填充数据 |
----------
锁优化见资料-"Java多线程-锁的介绍"
ReentrantLock锁
该 java.util.concurrent.locks包路径下的锁
公平性锁、非公平锁
公平性锁:多个线程对资源的请求按照先来后到的顺序来有序获取资源
非公平性锁:多线程对资源的获取不是按照请求的顺序来进行资源获取的,需要进行抢夺是资源获取
无参构造函数可以看出:默认是非公平性锁
public ReentrantLock() {
sync = new NonfairSync();
}
有参构造通过Boolean类型来确定使用是公平性锁还是非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock是java中可重入锁的一个实现,一次只能有一个线程持有锁,也即所谓独占锁的概念
包含三个内部类:Sync、NonfairSync、FairSync,通过构造函数的参数来指定锁是否是公平的
常用方法
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
//默认是不公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
//参数fair决定,true为公平锁实现,false为非公平锁实现
public ReentrantLock(boolean fair) {
sync = (fair)? new FairSync() : new NonfairSync();
}
public void lock() {
sync.lock();
}
//不公平锁
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
//带超时时间的锁
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.release(1);
}
//是否有线程等待当前锁
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
通过常用方法可知:
ReentrantLock都是把具体实现委托给内部类(Sync、NonfairSync、FairSync),
ReentrantLock的重入计数是使用AbstractQueuedSynchronizer的state属性的
state大于0表示锁被占用、等于0表示空闲,小于0则是重入次数太多导致溢出了.
ReentrantLock.Sync
static abstract class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
//非公平获取,公平锁和非公平锁都需要这个方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) { //state == 0表示无锁
//CAS确保即使有多个线程竞争锁也是安全的
if (compareAndSetState(0, acquires)) { //加锁成功
//当前哪一个线程获取到锁,将线程信息记录到AQS里面
setExclusiveOwnerThread(current); //设置当前持有锁的线程
return true; //获取成功
}
} else if (current == getExclusiveOwnerThread()) {//当前线程正是锁持有者
int nextc = c acquires;
if (nextc < 0) // 被锁次数上溢(很少出现)
throw new Error("Maximum lock count exceeded");
//锁被持有的情况下,只有持有者才能更新锁保护的资源
setState(nextc);
return true;
}
return false;
}
//释放
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//只有锁的持有者才能释放锁
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) { //锁被释放
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
//当前线程是否持有锁
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
//锁的持有者
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
//加锁次数
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
//是否上锁,根据state字段可以判断
final boolean isLocked() {
return getState() != 0;
}
}
ReentrantLock.NonfairSync【非公平锁实现】
final static class NonfairSync extends Sync {
// 执行lock,尝试立即闯入,失败就退回常规流程
final void lock() {
if (compareAndSetState(0, 1)) //比较并设置state,成功则表示获取成功
setExclusiveOwnerThread(Thread.currentThread());//锁持有者
else
acquire(1);//获取失败,进入常规流程:acquire会首先调用tryAcquire
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
acquire的实现(AbstractQueuedSynchronizer.java)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
ReentrantLock.FairSync(公平性锁实现)
final static class FairSync extends Sync {
//见AbstractQueuedSynchronizer.java, 4.2节有
final void lock() {
acquire(1);
}
//公平版本的tryAcquire,除非是递归调用或没有等待者或者是第一个,否则不授予访问
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//是等待队列的第一个等待者
if (isFirst(current) &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current); //加锁成功
return true;
}
}
//当前线程正是线程的持有者
else if (current == getExclusiveOwnerThread()) {
int nextc = c acquires;
if (nextc < 0) //溢出
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
isFirst的实现,即等待队列为空或者当前线程为等待队列的第一个元素
final boolean isFirst(Thread current) {
Node h, s;
return ((h = head) == null ||
((s = h.next) != null && s.thread == current) ||
fullIsFirst(current));
}
lock() VS lockInterruptibly()
线程请求锁的几个方法:
lock():拿不到lock就不罢休,不然线程就一直block;
tryLock():马上返回,拿到lock就返回true,不然返回false;
tryLock(time):拿不到lock,就等一段时间,超时返回false;
线程在sleep或wait,join, 此时如果别的进程调用此进程的 interrupt()方法,
此线程会被唤醒并被要求处理InterruptedException;
线程在运行中,则不会收到提醒。但是 此线程的 “中断标志”会被设置, 可以通过isInterrupted()查看并作出处理。
lockInterruptibly()和上面的第一种情况是一样的,线程在请求lock并被阻塞时,如果被interrupt,
则“此线程会被唤醒并被要求处理InterruptedException
线程间通信
Java.lang.Object
wait、notify、notifyAll
wait:让线程进入等待 -》线程状态转换中:running->waitting
notify:随机唤醒一个等待的线程
notifyAll:唤醒全部的线程
wait\notify\notifyAll必须作用与同一个对象,才能达到线程间通信的目的
wait
wait方法是Object的方法;
任意一个对象都可以调用wait方法,调用wait方法会将调用者的线程挂起,
使该线程进入一个叫wait的等待区域,直到其他线程调用同一个对象的notify方法才会重新激活调用者;
当wait方法被调用时,它会释放它所占用的锁标记,从而使线程所在对象中的synchronize数据可以被别的线程所使用,
所以wait()方法必须在同步块中使用,
notify()和notifyAll()方法都会对对象的“锁标记”进行修改,
所以都需要在同步块中进行调用,如果不在同步块中调用,虽然可以编译通过,
但是运行时会报IllegalMonitorStateException(非法的监控状态异常);
notify
notify()会通知一个处在wait()状态的线程;如果有多个线程处在wait状态,
他会随机唤醒其中一个;
notifyAll
notifyAll()会通知过所有处在wait()状态的线程,具体执行哪一个线程,
根据优先级而定;