synchronized
在实际开发中,多线程是很常见的,既然有多线程必然会出现并发,并发操作同一资源时,比如多个线程写入同一文件,就需要进行同步,也就是同一时间只能一个线程写入同一文件。在java中synchronized关键字就是同步用的。
由于没有系统学习过多线程编程,只能按照自己的理解和看到源码的记录一下synchronized的使用场景。
(1)一种使用情况就是,一个线程执行完另一个线程才能执行,只要synchronized关键字就可以实现,两个线程是不需要通信的。
(2)还有一种使用情况是,一个线程执行时,发现条件不满足时线程进入等待阻塞状态,释放锁,另一个线程获取锁可以执行,根据情况唤醒等待的线程进行执行。需要synchronized和Object的wait/notify配合实现,两个线程进行了通信。下面的HandlerThread就是这种场景。
synchronized同步是jvm实现的,底层实现是采用了锁机制。
在学习HandlerThread源码的时候,看到了线程同步的用法,借此机会粗浅的学习一下,为以后有机会深入学习的时候做个铺垫,要不然每次这块知识都是0基础,一想起就心虚。
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
上面就是同步的标准用法,而且用到了wait和notifyAll方法。synchronized关键字,能让调用方法的当前线程拥有this对象(HandlerThread)的监控锁(monitor lock)。wait/notify/notifyAll这些方法必须要和锁机制一块使用,标准解释是:The current thread must own this object's monitor. 解释一下上面的代码
在实际开发中,HandlerThread的getLooper()方法是在主线程中调用的,也就是说,在主线程中调用getLooper方法执行到sychronized(this)时,主线程申请拥有HandlerThread对象监控锁,如果上锁成功,循环判断线程存活并且mLooper为null,执行wait方法,此时主线程释放了cpu资源和锁资源,进入WAITING状态。run方法是线程的执行方法,执行到sychronized(this)时,HandlerThread线程申请拥有HandlerThread对象的监控锁(俗称上锁),如果没有其它线程对HandlerThread对象的监控,那么就可以上锁成功,执行了notifyAll,退出了同步块。主线程会被唤醒,继续执行。
我们看看wait和notifyAll方法,这两个方法是在Object中定义的,我们看看注释
/**
* Causes the current thread to wait until another thread invokes the
* {@link java.lang.Object#notify()} method or the
* {@link java.lang.Object#notifyAll()} method for this object.
* In other words, this method behaves exactly as if it simply
* performs the call {@code wait(0)}.
* <p>
* The current thread must own this object's monitor. The thread
* releases ownership of this monitor and waits until another thread
* notifies threads waiting on this object's monitor to wake up
* either through a call to the {@code notify} method or the
* {@code notifyAll} method. The thread then waits until it can
* re-obtain ownership of the monitor and resumes execution.
* <p>
* As in the one argument version, interrupts(中断) and spurious(欺骗的、伪造的) wakeups are
* possible, and this method should always be used in a loop:
* <pre>
* synchronized (obj) {
* while (<condition does not hold>)
* obj.wait();
* ... // Perform action appropriate to condition
* }
* </pre>
* This method should only be called by a thread that is the owner
* of this object's monitor.(这个方法仅仅被拥有对象监控的的线程调用) See the {@code notify} method for a
* description of the ways in which a thread can become the owner of
* a monitor.
*
* @throws IllegalMonitorStateException if the current thread is not
* the owner of the object's monitor.
* @throws InterruptedException if any thread interrupted the
* current thread before or while the current thread
* was waiting for a notification. The <i>interrupted
* status</i> of the current thread is cleared when
* this exception is thrown.
* @see java.lang.Object#notify()
* @see java.lang.Object#notifyAll()
*/
@FastNative
public final native void wait() throws InterruptedException;
根据wait的注释,我们知道,要调用某个对象A的wait方法,首先当前线程必须拥有A的监控,也就是上锁A。调用wait之后,当前线程处于了等待状态,释放了锁,也就是释放了对A的监控,其它线程可以对A上锁了。线程间对A上锁是竞争关系。
wait还有其它两个重载方法,用于设置等待时间,0的话表示永远等待,和无参的wait方法一个意思,这点得注意一下。
interrupts and spurious wakeups are possible, and this method should always be used in a loop. 中断和伪唤醒是可能的,所以wait方法应该在循环中使用。
线程调用了对象方法interrupt,会给线程设置一个中断标记,如果此线程之前因为wait处于WAITING状态时,会catch到InterruptedException,中断标记会被清除,catch后可以进行一些处理。
/**
* Wakes up a single thread that is waiting on this object's
* monitor. If any threads are waiting on this object, one of them
* is chosen to be awakened. The choice is arbitrary (随意的)and occurs at
* the discretion (考虑周到、自由裁量)of the implementation. A thread waits on an object's
* monitor by calling one of the {@code wait} methods.
* <p>
* The awakened thread will not be able to proceed until the current
* thread relinquishes(放弃) the lock on this object. The awakened thread will
* compete in the usual manner with any other threads that might be
* actively(积极地) competing(竞争) to synchronize on this object; for example, the
* awakened thread enjoys no reliable privilege(特权) or disadvantage in being
* the next thread to lock this object.
* <p>
* This method should only be called by a thread that is the owner
* of this object's monitor. A thread becomes the owner of the
* object's monitor in one of three ways:
* <ul>
* <li>By executing a synchronized instance method of that object.
* <li>By executing the body of a {@code synchronized} statement
* that synchronizes on the object.
* <li>For objects of type {@code Class,} by executing a
* synchronized static method of that class.
* </ul>
* <p>
* Only one thread at a time can own an object's monitor.
*
* @throws IllegalMonitorStateException if the current thread is not
* the owner of this object's monitor.
* @see java.lang.Object#notifyAll()
* @see java.lang.Object#wait()
*/
@FastNative
public final native void notify();
从注释,我们知道,notify的调用前提和wait是一样的。如果有多个线程处于等待状态是,notify只会唤醒一个线程,这个被唤醒的线程是随意的,由实现来决定的。被唤醒的线程也是需要重新和其它线程竞争对A的监控,它们直接是平等的。
notifyAll会唤醒,对A对象监控后进入等待状态的线程,这些线程会对A监控进行竞争。
synchronized提供的锁很简单,使用比较方便,上锁和释放锁是jvm帮助完成的,这种锁属于可重入锁,而且阻塞是不能被中断的。所以,使用synchronized不能使用非重入锁,而且也不具备查询是否有锁的能力。synchronized使用不够灵活。java引入了AQS锁。
AbstractQueuedSynchronizer(AQS)
具体原理细节没有看源码,不过使用很简单,只要子类重写几个方法就可以了
AQS其实上锁的时候,使用CAS更新state表示有没有上锁,上锁的话记录一下获取锁的线程。jvm使用sychronized估计也是这个思想。
AQS tryAcquire方法尝试去获取锁,获取到返回true,获取不到返回false。这个是需要子类重写决定的。acquire方法里面调用了tryAcquire方法,获取不到锁会阻塞。
可以学习一下ThreadPoolExecutor中Worker AQS的使用
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker. */
public void run() {
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}