Android 锁机制
在我们的日常开发过程中,线程安全是并发编程的重点,并发编程的原子性、可见性和有序性问题,都可通过锁来进行解决。
锁是为了解决并发操作引起的脏读、数据不一致的问题。
- volatile 关键字
通过volatile修饰的可共享变量,在线程编程操作时,禁止线程的工作内存对被volatile修饰的可共享变量进行缓存。在修改被volatile修饰的可共享变量时,会立即修改主存。 - synchronized 关键字
synchronized是对象锁,可用来实现对临界资源的同步互斥访问,一般主要作用与方法和代码块。
关键字锁的作用用法
可见性
当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。
另外,通过synchronized也能够保证可见性,synchronized能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
有序性
我们从一个最经典的例子来分析重排序问题。大家应该都很熟悉单例模式的实现,而在并发环境下的单例实现方式,我们通常可以采用双重检查加锁(DCL)的方式来实现。其源码如下:
public class Singleton {
private static volatile Singleton mSingleton;
public static Singleton getInstance(){
if (mSingleton == null){
synchronized (Singleton.class){
if (mSingleton == null){
mSingleton = new Singleton();
}
}
}
return mSingleton;
}
}
现在我们分析一下为什么要在变量singleton之间加上volatile关键字。要理解这个问题,先要了解对象的构造过程,实例化一个对象其实可以分为三个步骤:
(1)分配内存空间。
(2)初始化对象。
(3)将内存空间的地址赋值给对应的引用。
但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:
(1)分配内存空间。
(2)将内存空间的地址赋值给对应的引用。
(3)初始化对象
如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。
原子性
volatile关键字用于声明简单类型变量,如int、float、 boolean等数据类型。如果这些简单数据类型声明为volatile,对它们的操作就会变成原子级别的。但这有一定的限制。例如,下面的例子中的n就不是原子级别的:
private volatile int n = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
n++;
Log.d("" + Thread.currentThread().getName(), "run: " + n);
}
}
}, "线程" + i).start();
}
}
如果对n的操作是原子级别的,最后输出的结果应该为n=1000,而在执行上面积代码时,很多时侯输出的n都小于1000,这说明n++不是原子级别的操作。原因是声明为volatile的简单变量如果当前值由该变量以前的值相关,那么volatile关键字不起作用,也就是说如下的表达式都不是原子操作:
D/线程9: run: 940
D/线程9: run: 941
D/线程9: run: 942
D/线程9: run: 943
D/线程9: run: 944
D/线程9: run: 945
D/线程9: run: 946
D/线程9: run: 947
D/线程9: run: 948
D/线程9: run: 949
D/线程9: run: 950
如果要想使这种情况变成原子操作,需要使用synchronized关键字,如上的代码可以改成如下的形式:
private volatile int n = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
for (int i = 0; i < 10; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 100; j++) {
// synchronized (MainActivity.class){
// n++;
// Log.d("" + Thread.currentThread().getName(), "run: " + n);
// }
inc();
}
}
}, "线程" + i).start();
}
}
public synchronized void inc() {
n++;
Log.d("" + Thread.currentThread().getName(), "run: " + n);
}
D/线程8: run: 984
D/线程8: run: 985
D/线程8: run: 986
D/线程6: run: 987
D/线程6: run: 988
D/线程6: run: 989
D/线程6: run: 990
D/线程6: run: 991
D/线程6: run: 992
D/线程6: run: 993
D/线程6: run: 994
D/线程6: run: 995
D/线程6: run: 996
D/线程6: run: 997
D/线程6: run: 998
D/线程6: run: 999
D/线程6: run: 1000
上面的代码将n++改成了inc(),其中inc方法使用了synchronized关键字进行方法同步。因此,在使用volatile关键字时要慎重,并不是只要简单类型变量使用volatile修饰,对这个变量的所有操作都是原来操作,当变量的值由自身的上一个决定时,如n++,volatile关键字将失效,只有当变量的值和自身上一个值无关时对该变量的操作才是原子级别的,如n = m + 1,这个就是原级别的。所以在使用volatile关键时一定要谨慎,如果自己没有把握,可以使用synchronized来代替volatile。
Java中的原子操作包括:
1)除long和double之外的基本类型的赋值操作
2)所有引用reference的赋值操作
3)java.concurrent.Atomic.* 包中所有类的一切操作
count++不是原子操作,是3个原子操作组合
1.读取主存中的count值,赋值给一个局部成员变量tmp
2.tmp+1
3.将tmp赋值给count
可能会出现线程1运行到第2步的时候,tmp值为1;这时CPU调度切换到线程2执行完毕,count值为1;切换到线程1,继续执行第3步,count被赋值为1------------结果就是两个线程执行完毕,count的值只加了1;
还有一点要注意,如果使用AtomicInteger.set(AtomicInteger.get() + 1),会和上述情况一样有并发问题,要使用AtomicInteger.getAndIncrement()才可以避免并发问题
synchronized作用与代码块补充
- wait()
object.wait() 让进入object监视器的线程到waitSet等待 - notify()
object.notify()在object上正在waitSet等待的线程中挑一个唤醒 - notifyAll()
object.notifyAll()让object上正在waitSet等待的线程全部唤醒
经典使用案例HandlerThread,下面是8.1HandlerThread源码
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
*/
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}
@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;
}
/**
* @return a shared {@link Handler} associated with this thread
* @hide
*/
@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}
/**
* Quits the handler thread's looper.
* <p>
* Causes the handler thread's looper to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*
* @see #quitSafely
*/
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
/**
* Quits the handler thread's looper safely.
* <p>
* Causes the handler thread's looper to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* Pending delayed messages with due times in the future will not be delivered.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p>
* If the thread has not been started or has finished (that is if
* {@link #getLooper} returns null), then false is returned.
* Otherwise the looper is asked to quit and true is returned.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*/
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
/**
* Returns the identifier of this thread. See Process.myTid().
*/
public int getThreadId() {
return mTid;
}
}
Lock 介绍
Lock是java.util.concurrent.locks包下的接口,Lock提供了的方法与语句可更广泛的锁定操作。相对于synchronized与volatile两个java关键字而言Lock使用起来更灵活,提供了许多api可以让开发者更好的去控制加锁和释放锁操作等等。
Lock提供的api有:
- void lock()
lock() 获取锁,若是当前锁为不可用状态,则一直等待,线程休眠,直到获取到锁为止。 - void lockInterruptibly()
lockInterruptibly() 获取锁,若是当前锁为不可用状态,当前线程禁用休眠,直到当前线程获取到锁或者其他线程中断当前线程,并且支持中断获取锁。如果当前线程:在进入该方法时设置其中断状态;在获取锁的同时被中断并且支持锁获取的中断,则抛出InterruptedException,并清除当前线程的中断状态。 - boolean tryLock()
tryLock() 获取锁,若是当前锁可用返回true,并获取锁,当前锁不可用直接返回false - boolean tryLock(long time, TimeUnit unit)
tryLock(long time, TimeUnit unit) 获取锁,如果锁在给定的等待时间内是空闲的,并且当前线程没有被中断,则获取锁。 如果锁可用,则此方法会立即返回值true。如果锁不可用,则出于线程调度目的,当前线程将被禁用,并处于休眠状态,直到发生以下三种情况之一: 锁是由当前线程获取或其他线程中断当前线程,支持中断锁获取或指定的等待时间已过。
如果获取了锁,则返回值true。 如果当前线程: 在进入该方法时设置其中断状态或在获取锁的同时被中断并且支持锁获取的中断,则抛出InterruptedException,并清除当前线程的中断状态。如果经过了指定的等待时间,则返回值false。如果时间小于或等于零,则该方法根本不会等待。 - void unlock();
unlock() 释放锁, - Condition newCondition();
newCondition() 获取Condition,Condition是Java提供了来实现等待/通知的类,Condition类还提供比wait/notify更丰富的功能,Condition对象是由lock对象所创建的。但是同一个锁可以创建多个Condition的对象,即创建多个对象监视器。这样的好处就是可以指定唤醒线程。notify唤醒的线程是随机唤醒一个。
和wait类提供了一个最长等待时间,awaitUntil(Date deadline)在到达指定时间之后,线程会自动唤醒。但是无论是await或者awaitUntil,当线程中断时,进行阻塞的线程会产生中断异常。Java提供了一个awaitUninterruptibly的方法,使即使线程中断时,进行阻塞的线程也不会产生中断异常。
ReenTrantLock
ReenTrantLock是Lock锁提供的java扩展.
公平锁与非公平锁
在ReenTrantLock的创建时,可创建公平锁(先到先得)与非公平锁(不论先后,谁抢到了就是谁的)。如下的代码输出结果:
//非公平锁,传不传false效果是一样的
private Lock lock = new ReentrantLock(/*false*/);
//公平锁
//private Lock lock = new ReentrantLock(true);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Runnable runnable = () -> {
Log.d("lock", Thread.currentThread().getName()+"开始获取锁");
getLock();
};
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(runnable);
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
}
private void getLock(){
try {
lock.lock();
Log.d("lock", Thread.currentThread().getName()+"拿到锁");
}finally {
lock.unlock();
}
}
输出结果
非公平锁
16:50:21.590 D/lock: Thread-2开始获取锁
16:50:21.590 D/lock: Thread-3开始获取锁
16:50:21.590 D/lock: Thread-4开始获取锁
16:50:21.590 D/lock: Thread-3拿到锁
16:50:21.591 D/lock: Thread-2拿到锁
16:50:21.591 D/lock: Thread-4拿到锁
16:50:21.591 D/lock: Thread-5开始获取锁
16:50:21.591 D/lock: Thread-5拿到锁
16:50:21.593 D/lock: Thread-6开始获取锁
16:50:21.593 D/lock: Thread-6拿到锁
16:50:21.593 D/lock: Thread-8开始获取锁
16:50:21.593 D/lock: Thread-7开始获取锁
16:50:21.593 D/lock: Thread-8拿到锁
16:50:21.593 D/lock: Thread-7拿到锁
16:50:21.594 D/lock: Thread-9开始获取锁
16:50:21.594 D/lock: Thread-9拿到锁
16:50:21.594 D/lock: Thread-10开始获取锁
16:50:21.594 D/lock: Thread-11开始获取锁
16:50:21.594 D/lock: Thread-10拿到锁
16:50:21.594 D/lock: Thread-11拿到锁
公平锁
16:57:59.609 D/lock: Thread-3开始获取锁
16:57:59.609 D/lock: Thread-2开始获取锁
16:57:59.610 D/lock: Thread-3拿到锁
16:57:59.610 D/lock: Thread-5开始获取锁
16:57:59.611 D/lock: Thread-2拿到锁
16:57:59.611 D/lock: Thread-5拿到锁
16:57:59.611 D/lock: Thread-4开始获取锁
16:57:59.611 D/lock: Thread-4拿到锁
16:57:59.612 D/lock: Thread-7开始获取锁
16:57:59.612 D/lock: Thread-7拿到锁
16:57:59.612 D/lock: Thread-8开始获取锁
16:57:59.612 D/lock: Thread-9开始获取锁
16:57:59.612 D/lock: Thread-8拿到锁
16:57:59.612 D/lock: Thread-9拿到锁
16:57:59.613 D/lock: Thread-10开始获取锁
16:57:59.613 D/lock: Thread-11开始获取锁
16:57:59.613 D/lock: Thread-10拿到锁
16:57:59.613 D/lock: Thread-11拿到锁
16:57:59.614 D/lock: Thread-6开始获取锁
16:57:59.614 D/lock: Thread-6拿到锁
在不同的场景下ReenTrantLock还提供了一些其他的辅助方法如下:
- int getHoldCount()
getHoldCount() 获取当前线程锁的个数。 - boolean isHeldByCurrentThread()
isHeldByCurrentThread() 查询锁是不是被当前线程持有 - boolean isLocked()
isLocked() 查询此锁是否由任何线程持有 - boolean isFair()
isFair() 判断当前锁是否为公平锁,公平返回true - boolean hasQueuedThreads()
hasQueuedThreads() 查询是否有线程正在等待获取此锁。 - boolean hasQueuedThread(Thread thread)
hasQueuedThread(Thread thread) 查询指定线程是否等待获取此锁。 - int getQueueLength()
getQueueLength() 查询等待获取此锁的线程数 - boolean hasWaiters(Condition condition)
hasWaiters(Condition condition) 查询是否有线程正在等待与此锁关联的给定条件 - int getWaitQueueLength(Condition condition)
getWaitQueueLength(Condition condition) 查询在与此锁关联的给定条件下等待的线程数 - String toString()
String toString() 获取一个标识此锁及其锁状态的字符串
Condition
Condition是Java提供了来实现等待/通知的类,Condition类还提供比wait/notify更丰富的功能,Condition对象是由lock对象所创建的。但是同一个锁可以创建多个Condition的对象,即创建多个对象监视器。这样的好处就是可以指定唤醒线程。notify唤醒的线程是随机唤醒一个。
通过lock.newCondition()来创建,用condition.await()来实现让线程等待,是线程进入阻塞。用condition.signal()来实现唤醒线程。await/signal和wait/notify一样也是在同步代码块内执行。调用如下例子:
private Lock lock = new ReentrantLock(true);
Condition condition = lock.newCondition();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lock();
Log.d("lock", Thread.currentThread().getName()+" await ==> 1");
condition.await();
Log.d("lock", Thread.currentThread().getName()+" await ==> 2");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
lock.unlock();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
//防止这个线程先启动,先拿到了锁
Thread.sleep(20);
lock.lock();
Log.d("lock", Thread.currentThread().getName()+" signalAll");
condition.signalAll();
}catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
lock.unlock();
}
}
}).start();
}
17:35:58.215 D/lock: Thread-2 await ==> 1
17:35:58.235 D/lock: Thread-3 signalAll
17:35:58.235 D/lock: Thread-2 await ==> 2
ReentrantReadWriteLock
ReentrantReadWriteLock是Lock扩展出来的读写锁,将读取与写入锁分开,实现读锁与读锁之间是共享的,读锁与写锁之间是互斥的,写锁与写锁之间也是互斥。实现例子如下:
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
//取余更打印结果更容易显示读写互斥
// if (i %2 == 0) {
if (i < 5) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
write();
}
}, "写线程 ==> " + i);
} else {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
read();
}
}, "读线程 ==> " + i);
}
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
}
private void write() {
try {
lock.writeLock().lock();
//让每个现场都占用时间长一点
Log.d("lock", Thread.currentThread().getName() + " write: " + System.currentTimeMillis());
Thread.sleep(10);
Log.d("lock", Thread.currentThread().getName() + " write: " + System.currentTimeMillis());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.writeLock().unlock();
}
}
private void read() {
try {
lock.readLock().lock();
//让每个现场都占用时间长一点
Log.d("lock", Thread.currentThread().getName() + " read: " + System.currentTimeMillis());
Thread.sleep(10);
Log.d("lock", Thread.currentThread().getName() + " read: " + System.currentTimeMillis());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.readLock().unlock();
}
}
09:36:57.817 D/lock: 写线程 ==> 0 write: 1684114617817
09:36:57.828 D/lock: 写线程 ==> 0 write: 1684114617828
09:36:57.829 D/lock: 写线程 ==> 1 write: 1684114617828
09:36:57.839 D/lock: 写线程 ==> 1 write: 1684114617839
09:36:57.839 D/lock: 写线程 ==> 2 write: 1684114617839
09:36:57.849 D/lock: 写线程 ==> 2 write: 1684114617849
09:36:57.849 D/lock: 写线程 ==> 3 write: 1684114617849
09:36:57.860 D/lock: 写线程 ==> 3 write: 1684114617860
09:36:57.860 D/lock: 写线程 ==> 4 write: 1684114617860
09:36:57.870 D/lock: 写线程 ==> 4 write: 1684114617870
09:36:57.870 D/lock: 读线程 ==> 5 read: 1684114617870
09:36:57.871 D/lock: 读线程 ==> 6 read: 1684114617871
09:36:57.871 D/lock: 读线程 ==> 7 read: 1684114617871
09:36:57.871 D/lock: 读线程 ==> 8 read: 1684114617871
09:36:57.871 D/lock: 读线程 ==> 9 read: 1684114617871
09:36:57.881 D/lock: 读线程 ==> 5 read: 1684114617881
09:36:57.881 D/lock: 读线程 ==> 6 read: 1684114617881
09:36:57.881 D/lock: 读线程 ==> 7 read: 1684114617881
09:36:57.882 D/lock: 读线程 ==> 9 read: 1684114617882
09:36:57.882 D/lock: 读线程 ==> 8 read: 1684114617882
ReentrantReadWriteLock内部还有WriteLock内部类与ReadLock内部类其内部还有很多方法与ReenTrantLock类方法绝大部分都是一样的,包括Condition对象获取与使用。
StampedLock
StampedLock是Android 7.0之后针对ReentrantReadWriteLock的一个升级,在一般的应用中都存在的是读多写少,而ReentrantReadWriteLock是读写互斥,导致应用在进行读写操作的时候性能上不去。
StampedLock支持乐观读(不加锁的方式读取)和悲观锁(加锁的方式读取)。同时支持多个线程申请乐观读和单线程悲观锁。实现例子如下:
@RequiresApi(api = Build.VERSION_CODES.N)
public class MainActivity extends AppCompatActivity {
StampedLock stampedLock;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (stampedLock == null) {
stampedLock = new StampedLock();
}
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
if (i %2 == 0) {
// if (i < 5) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
write();
// tryWrite();
}
}, "写线程 ==> " + i);
} else {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
// read();
tryRead();
}
}, "读线程 ==> " + i);
}
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
}
//悲观写
private void write() {
if (stampedLock != null) {
long writeLock = 0;
try {
writeLock = stampedLock.writeLock();
Log.d("lock", Thread.currentThread().getName() + " write: " + System.currentTimeMillis());
Thread.sleep(10);
Log.d("lock", Thread.currentThread().getName() + " write: " + System.currentTimeMillis());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
stampedLock.unlockWrite(writeLock);
}
}
}
//悲观读
private void read() {
if (stampedLock != null) {
long readLock = 0;
try {
readLock = stampedLock.readLock();
Log.d("lock", Thread.currentThread().getName() +" read: "+ System.currentTimeMillis());
Thread.sleep(10);
Log.d("lock", Thread.currentThread().getName() +" read: "+ System.currentTimeMillis());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
stampedLock.unlockRead(readLock);
}
}
}
//乐观写
private void tryWrite() {
if (stampedLock != null) {
try {
stampedLock.tryWriteLock();
Log.d("lock", Thread.currentThread().getName() + " tryWrite: " + System.currentTimeMillis());
Thread.sleep(10);
Log.d("lock", Thread.currentThread().getName() + " tryWrite: " + System.currentTimeMillis());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
stampedLock.tryWriteLock();
}
}
}
//乐观读
private void tryRead() {
if (stampedLock != null) {
try {
stampedLock.tryReadLock();
Log.d("lock", Thread.currentThread().getName() +" tryRead: "+ System.currentTimeMillis());
Thread.sleep(10);
Log.d("lock", Thread.currentThread().getName() +" tryRead: "+ System.currentTimeMillis());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
stampedLock.tryUnlockRead();
}
}
}
}
乐观读写(基本上不会使用到)
10:33:33.250 D/lock: 写线程 ==> 0 tryWrite: 1684118013250
10:33:33.250 D/lock: 读线程 ==> 1 tryRead: 1684118013250
10:33:33.250 D/lock: 写线程 ==> 2 tryWrite: 1684118013250
10:33:33.250 D/lock: 读线程 ==> 3 tryRead: 1684118013250
10:33:33.251 D/lock: 写线程 ==> 4 tryWrite: 1684118013251
10:33:33.252 D/lock: 读线程 ==> 5 tryRead: 1684118013252
10:33:33.252 D/lock: 写线程 ==> 6 tryWrite: 1684118013252
10:33:33.254 D/lock: 写线程 ==> 8 tryWrite: 1684118013254
10:33:33.254 D/lock: 读线程 ==> 7 tryRead: 1684118013254
10:33:33.254 D/lock: 读线程 ==> 9 tryRead: 1684118013254
10:33:33.260 D/lock: 读线程 ==> 1 tryRead: 1684118013260
10:33:33.260 D/lock: 写线程 ==> 0 tryWrite: 1684118013260
10:33:33.260 D/lock: 写线程 ==> 2 tryWrite: 1684118013260
10:33:33.261 D/lock: 读线程 ==> 3 tryRead: 1684118013261
10:33:33.261 D/lock: 写线程 ==> 4 tryWrite: 1684118013261
10:33:33.263 D/lock: 读线程 ==> 5 tryRead: 1684118013263
10:33:33.263 D/lock: 写线程 ==> 6 tryWrite: 1684118013263
10:33:33.264 D/lock: 读线程 ==> 7 tryRead: 1684118013264
10:33:33.264 D/lock: 写线程 ==> 8 tryWrite: 1684118013264
10:33:33.264 D/lock: 读线程 ==> 9 tryRead: 1684118013264
悲观写,乐观读
10:36:16.152 D/lock: 写线程 ==> 0 write: 1684118176152
10:36:16.152 D/lock: 读线程 ==> 1 tryRead: 1684118176152
10:36:16.153 D/lock: 读线程 ==> 3 tryRead: 1684118176153
10:36:16.154 D/lock: 读线程 ==> 5 tryRead: 1684118176154
10:36:16.157 D/lock: 读线程 ==> 7 tryRead: 1684118176157
10:36:16.158 D/lock: 读线程 ==> 9 tryRead: 1684118176158
10:36:16.162 D/lock: 读线程 ==> 1 tryRead: 1684118176162
10:36:16.162 D/lock: 写线程 ==> 0 write: 1684118176162
10:36:16.163 D/lock: 写线程 ==> 2 write: 1684118176163
10:36:16.163 D/lock: 读线程 ==> 3 tryRead: 1684118176163
10:36:16.164 D/lock: 读线程 ==> 5 tryRead: 1684118176164
10:36:16.168 D/lock: 读线程 ==> 9 tryRead: 1684118176168
10:36:16.169 D/lock: 读线程 ==> 7 tryRead: 1684118176169
10:36:16.173 D/lock: 写线程 ==> 2 write: 1684118176173
10:36:16.173 D/lock: 写线程 ==> 6 write: 1684118176173
10:36:16.184 D/lock: 写线程 ==> 6 write: 1684118176183
10:36:16.184 D/lock: 写线程 ==> 4 write: 1684118176184
10:36:16.194 D/lock: 写线程 ==> 4 write: 1684118176194
10:36:16.194 D/lock: 写线程 ==> 8 write: 1684118176194
10:36:16.205 D/lock: 写线程 ==> 8 write: 1684118176205
StampedLock的实现必须在Android 7.0之后,这个在使用的时候需慎重。StampedLock相比与ReentrantReadWriteLock API更加复杂灵活,还引用了邮戳(Stamped),在获取锁的同时返回一个邮戳标识,用于释放当前锁。