读为什么要加锁?
1、有些同学认为读锁没有用,他们的理由是:读操作又不会修改数据,想读就读呗,无论读的是就值还是新值,反正能读到。
2、也有同学认为读锁是为了防止多线程读到的数据不一致。
我认为不是这个原因,只需要问两个问题就知道了,首先问不一致的是什么?然后反问不一致会导致什么问题呢?
有些同学认为不一致就是有些线程读的是旧值,有些读的是新值,所以不一致。但是反问导致什么问题,就不是很好回答了,可能回答说为了保险吧,哈哈哈。
实际上即使加读锁,还是会存在有的线程读旧值,有的线程读新值,甚至非公平锁情况下,先开始的线程反而读到新值,而后开始的线程反而读到旧值,所以读锁并不是为了保证多线程读到的值是一样的。
3、那么读锁的作用是什么呢?
任何锁表面上是互斥,但本质是都是为了避免原子性问题(如果程序没有原子性问题,那只用volatile来避免可见性和有序性问题就可以了,效率更高),读锁自然也是为了避免原子性问题,比如一个long型参数的写操作并不是原子性的,如果允许同时读和写,那读到的数很可能是就是写操作的中间状态,比如刚写完前32位的中间状态。long型数都如此,而实际上一般读的都是复杂的对象,那中间状态的情况就更多了。
所以读锁是防止读到写的中间值
。
测试不加读锁
public class Demo {
public static final int MAX_VALUE = 2;
//值,且值不大于 MAX_VALUE
long value;
//返回值
long get(){
CommonMethod.sleep(10);
return value;
}
//值加1,模拟非原子的写操作
void add(){
//加法结果类似写操作的中间状态
value++;
CommonMethod.sleep(10);
//重置为0
if (value > MAX_VALUE){
value = 0;
}
}
public static void main(String[] args) {
TestMethod.test(new Demo());
}
}
公共代码:
public class CommonMethod {
private static AtomicLong UNIQ_ID = new AtomicLong();
public static void sleep(long time){
try {
TimeUnit.MILLISECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void log(String log){
System.out.println(new SimpleDateFormat("yyyyMMdd HH:mm:ss.SSS").format(new Date())+ " "+ Thread.currentThread().getName() + " " + log);
}
public static void start(Collection<Thread> threads){
threads.forEach(t->t.start());
}
public static void join(Thread thread){
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void join(Collection<Thread> threads){
threads.forEach(t-> join(t));
}
public static int randomInt(int high){
return new Random().nextInt(high);
}
public static Long getUniqId(){
return UNIQ_ID.getAndIncrement();
}
}
测试程序如下:
public class TestMethod {
public static void test(Demo demo) {
long start = System.currentTimeMillis();
List<Thread> threads = new LinkedList<>();
for (int i=0;i<10000;i++){
//读线程
threads.add(new Thread(() -> {
long value = demo.get();
if (value > Demo.MAX_VALUE){
CommonMethod.log("读到错误的数据了");
System.exit(-1);
}
CommonMethod.log("get " + value);
}, "thread-get-" + i));
//写线程
threads.add(new Thread(() -> demo.add() ,"thread-add-" + i));
}
CommonMethod.start(threads);
CommonMethod.join(threads);
CommonMethod.log(""+ demo.get());
CommonMethod.log("耗时:"+(System.currentTimeMillis()-start));
}
}
测试结果如下图:
使用读写锁
/**
* 读写锁示例
*/
public class ReadWriteLockDemo extends Demo {
private final ReadWriteLock rw = new ReentrantReadWriteLock();
private final Lock rl = rw.readLock();
private final Lock wl = rw.writeLock();
@Override
public long get(){
rl.lock();
try{
return super.get();
}finally {
rl.unlock();
}
}
@Override
public void add(){
wl.lock();
try{
super.add();
}finally{
wl.unlock();
}
}
public static void main(String[] args) {
TestMethod.test(new ReadWriteLockDemo());
}
}
- 31
测试结果:
测试环境:4核 8G