文章目录
AQS
一、多线程中同步如何实现的
wait、notify、sychronized、ReentrantLock
为什么要有锁?
保证线程同步执行。
wait要和synchronized一起使用
public class Synctest
{
public static void main(String[] args) throws InterruptedException
{
Synctest synctest = new Synctest();
synctest.wait();
}
}
上面的代码wait没和synchronized一起使用,就会报错,而且锁住的对象要和调用wait方法的对象是一致的,不然也会报错。
例子:
public class Synctest
{
public static void main(String[] args) throws InterruptedException {
Synctest synctest = new Synctest();
Synctest synctest2 = new Synctest();
synchronized (synctest)
{
synctest2.wait();
}
}
}
正确代码:
public class Synctest
{
public static void main(String[] args) throws InterruptedException {
Synctest synctest = new Synctest();
synchronized (synctest)
{
synctest.wait();
}
}
}
一、ReentrantLock
1.6之前,synchronized实现同步是一个重量级的锁,因为会去调用操作系统的函数。
Thread.start();方法,这个方法会去调用start0()方法,这个方法会去调用C文件启动一个线程,因为这是一个native方法。
同样的道理,synchronized也会去调用一个native方法。
1、模拟一个同步的思路
while自旋
volatile int status = 0;
void lock()
{
while(!compareAndSet(0,1)) //比较status,如果是0,改成1,可以改成1的话返回true,这个时候,status改成了1,再有一个线程来的话,不等于0,返回false,!false=true,会一直在while循环里面。
{
}
//2s
}
void unlock()
{
status = 0;
}
boolean compareAndSet(int except,int newValue)
{
//cas操作。
}
缺点:耗费cpu
改进思路:让得不到锁的线程让出cpu。
yield+自旋
volatile int status = 0;
void lock()
{
while(!compareAndSet(0,1))
{
yield(); //让出cpu
}
}
void unlock()
{
status = 0;
}
yield也存在问题,yield方法只是当前线程让出cpu,有可能操作系统下次还是运行该线程。
sleep+自旋
volatile int status = 0;
void lock()
{
while(!compareAndSet(0,1))
{
sleep(10);
}
}
void unlock()
{
status = 0;
}
睡眠存在的问题:睡多久如何确定?
比如我t1线程只需要1s操作,但是你睡眠了10s。
park、unpark
park是让线程阻塞(阻塞是让出cpu),unpark是叫醒某个线程。
sleep应该也是让出cpu
LockSupport调用unpark、park底层也是调用Unsafe类的这两个native方法。
//import java.util.Scanner;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;
public class Test
{
public static void main(String[] args)
{
Thread t1 = new Thread()
{
@Override
public void run()
{
testSync();
}
};
t1.setName("t1");
t1.start();
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("main----1");
LockSupport.unpark(t1);
}
public static void testSync()
{
System.out.println(Thread.currentThread().getName());
LockSupport.park();
System.out.println("end-------------");
}
}
2、ReentrantLock、Sync、(非)公平之间关系
ReentrantLock初始化其实就是创建一个公平锁或者非公平锁。它的lock、unlock其实就是调用公平锁或者非公平锁的lock或者unlock。
Sync继承了AQS,公平锁和非公平锁继承了Sync,ReentrantLock初始化创建的就是一个公平锁或者非公平锁,所以 ReentrantLock初始化其实就是得到了一个实现AQS的一个对象,AQS里面有一个Node内部类Node,创建了AQS也就创建了一个队列,里面放着线程、下一个结点、前面一个结点等信息。
ReentrantLock的lock方法会调用Sync类的lock方法,Sync是一个抽象类,继承AQS(abstract static class Sync extends AbstractQueuedSynchronizer。),Sync的lock方法是一个抽象方法,Sync有两个实现类FairSync和N onfairSync。
ReentrantLock其实初始化就是创建一个公平锁或者非公平锁,默认实现是一个非公平锁,如果参数是true,则创建一个公平锁,否则创建一个非公平锁。
3、lock代码解析
公平锁调用lock方法,会调用acquire(1);
acquire 英[əˈkwaɪə®]
static final class FairSync extends Sync
{
private static final long serialVersionUID = -3000897897090466540L;
final void lock()
{
acquire(1);
}
.......
tryAcquire方法在锁被别的线程持有的时候返回false,没被其他线程持有返回true。acquire方法发现没被其他线程持有直接return,否则立马排队。
public final void acquire(int arg)
{
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
1、tryAcquire
下面是AQS的tryAcquire方法:
protected boolean tryAcquire(int arg)
{
throw new UnsupportedOperationException();
}
公平锁的tryAcquire方法:
protected final boolean tryAcquire(int acquires)
{
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0)
{
if (!hasQueuedPredecessors() &&
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;
}
public final boolean hasQueuedPredecessors()
{
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
tryAcquire会先得到调用lock的线程,然后调用getState,返回state属性,state默认是0,if(c=0)其实就是判断锁是不是自由状态,第一次调用等于0的话即没有其他线程被锁住,此时,查看自己是否需要排队即查看队列是否是空。
compareAndSetState会调用下面的方法
2、重点总结
原来的synchronized 1.6之前不管你在交替执行还是非交替指向都会调用OS,而ReentrantLock在非交替执行的时候队列为空,直接返回,并不会调用OS,在JDK级别就能解决同步问题,只有在交替指向的时候才会调用OS中的函数。
aqs主要使用自旋、park、unpark、CAS实现
3、ReentrantLock在tryAcquire中重入锁的体现
else if的判断是判断当前线程和持有锁的线程是不是一个,如果就是重入锁,如果相同把c+1,因为acquires等于1,这就是ReentrantLock的重入锁实现。
4、在tryAcquire返回false时,会调用addWriter方法
如果多个线程并发执行的话,有线程没执行完就被抢占,tryAcquire就会return false;!false=true,后来就会执行入队操作acquireQueued(addWaiter(Node.EXCLUSIVE),arg);
addWaiter方法
第一次调用的时候,会调用enq
方法初始化一个结点,线程甚至为null,作为第一个结点,再把线程入队,以后再有线程阻塞的话,直接入队。
private Node addWaiter(Node mode)
{
Node node = new Node(Thread.currentThread(), mode); //初始化一个结点
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null)
{
node.prev = pred;
if (compareAndSetTail(pred, node))
{
pred.next = node;
return node;
}
}
enq(node);
return node;
}
第一次tail为null,head也为null,所以addWaiter会直接调用enq(node);
enq(node)代码如下
只有初始化才调用enq,第一次调的话,初始化队列入队
private Node enq(final Node node)
{
for (;;)
{
Node t = tail;
if (t == null) //第一次调用的话,tail为null,结果为true。
{ // Must initialize
if (compareAndSetHead(new Node())) //第一次设置队列中第一个节点的时候,并没有设置线程
tail = head; //首尾指针都指向第一个结点
}
else
{
node.prev = t; //第二个节点或者说真正的第一个结点头部指向前面一个节点
if (compareAndSetTail(t, node))
{
t.next = node;
return t;
}
}
}
}
compareAndSetHead代码,通过下面的代码设置head。
从上面代码可以看出,第一次设置队列中第一个节点的时候,并没有设置线程,可以得出,AQS队列中的第一个结点中的线程一定为空,只是初始化了一个节点,它的Thread属性为空,然后再把阻塞的线程入队。
private final boolean compareAndSetHead(Node update)
{
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
acquireQueued代码
当队列中有其他结点的时候,当有其他结点在队列中的时候(除了Thread为null的结点),要看能不能拿到锁,因为可能在它入队的时候,。
会自旋两次,第一次刚调用acquireQueued,第二次在因为shouldParkAfterFailedAcquire第一次调用是false,所以还会再自旋一次(即调用tryAcquire),但是如果进来的线程不是第一个被阻塞的线程,就不会自旋
final boolean acquireQueued(final Node node, int arg)
{
boolean failed = true;
try
{
boolean interrupted = false;
for (;;) //这个循环要注意
{
final Node p = node.predecessor(); //取出这次放入结点的前面一个结点
if (p == head && tryAcquire(arg)) //判断p==head也就是判断自己是不是第一个排队的,tryAcquire就是尝试拿一下锁
{
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //不是第一个真正的节点的话,或者是真正的第一个结点,但是有线程正在使用,这样就会判断这个if。
interrupted = true;
}
}
finally
{
if (failed)
cancelAcquire(node);
}
}
shouldParkAfterFailedAcquire代码
后一个节点会把前面一个节点的ws改成-1,-1表示线程正在睡眠。
为什么这个方法让多自旋一次,是因为不希望调用park
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node)
{
int ws = pred.waitStatus; //ws就是线程状态,初始为0
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0)
{
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do
{
node.prev = pred = pred.prev;
}
while (pred.waitStatus > 0);
pred.next = node;
}
else
{
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL); //Node.SIGNAL是-1,把ws改成-1
}
return false; //
}
parkAndCheckInterrupt代码,
中断线程
private final boolean parkAndCheckInterrupt()
{
LockSupport.park(this);
return Thread.interrupted();
}
unlock代码分析
1、unlock代码
public void unlock()
{
sync.release(1);
}
sync里面有队列,通过队列控制哪个线程的锁的释放。
2、release代码分析
public final boolean release(int arg)
{
if (tryRelease(arg))
{
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease代码分析
protected final boolean tryRelease(int releases) //releases传过来为1。
{
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;
}
unparkSuccessor代码
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
AQS流程图