问题场景
在java并发编程中,少不了使用IO进行数据持久化。今天遇到的问题就是文件读写中,并发导致的数据错乱,原因与大多数的并发问题大致相同。
线程A和线程B同时工作,它们都需要将文件num.txt中的数值读取出来,在原有值的基础上进行+1,然后写回num.txt。使得num.txt中的数值实现自增。
程序开始时,num.txt文件中的数值为0,A线程首先读取到了num.txt文件中的数值0,然后A线程给数值进行运算(0 + 1 = 1),最后在将结果值写入num.txt之前,B线程也进行了对num.txt的数值读取java打开文件读取行数,B线程得到的数值为0,之后A线程将结果值写入num.txt(此时num.txt中的值为1),B线程进行了业务换算,将读取到的值0进行运算后,将结果值写入num.txt中。
到此大家应该发现了问题所在,num.txt中的值经过了两次换算从0变成了1。而不是理想中的数值2。这就是我们今天要解决的多线程中的资源共享的问题。
解决办法
大家都应该知道,并发中的资源共享导致最终结果不一致,我们解决方案无非两种,就是乐观锁和悲观锁。我们今天采用比较方便快速的办法解决悲观锁。
java5.0之前我们使用Synchronized关键字进行线程阻塞,一次只让一个线程进行文件的读写操作
synchronized (this){
//文件读
//值加一
//文件写
}
以上办法可以看出,加锁和释放锁都是由Synchronized关键字自动完成,优点是使用方便,缺点是不够灵活。于是java5.0之后,我们可以使用并发包下的Lock进行手动加锁和释放锁。
Lock lock =new ReentrantLock();// 锁
try {
lock.lock();// 取得锁
//文件读
//值加一
//文件写
} finally {
lock.unlock();// 释放锁
}
细心的同学可以发现,在上面的加锁和释放锁的时候使用的try…finally…。但是这又是为什么呢?原因很简单java打开文件读取行数,为了防止业务代码在获取锁之后发生异常,导致lock.unlock()永远无法执行。这个道理与IO使用中,要在finally块中关闭流是一个道理。
优化方案
在文件的读写中,其实更加好的办法是使用ReentrantReadWriteLock锁进行控制,从名字我们可以看出,它是一个可重入的读写锁。当我们业务既存在读取,又存在增加修改或删除时。使用读写锁更加贴合业务使用。
public class ReadWriteLockTest {
public static final ReentrantReadWriteLock LOCK=new ReentrantReadWriteLock();
public static void main(String[] args) {
for (int i = 0; i < 5 ; i++ ){
Thread t1 = new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " | 准备锁");
LOCK.readLock().lock();
System.out.println(Thread.currentThread().getName() + " | 获得锁 : " + System.currentTimeMillis());
Thread.sleep(1000);
} catch (Exception e){
e.printStackTrace();
} finally {
LOCK.readLock().unlock();
}
});
t1.start();
}
}
}
以上结果为:
Thread-0 | 准备锁
Thread-4 | 准备锁
Thread-3 | 准备锁
Thread-2 | 准备锁
Thread-1 | 准备锁
Thread-3 | 获得锁 : 1558579415355
Thread-2 | 获得锁 : 1558579415355
Thread-0 | 获得锁 : 1558579415355
Thread-4 | 获得锁 : 1558579415355
Thread-1 | 获得锁 : 1558579415355
至此我们可以看出,获取锁的时间为同一时刻,并未受Thread.sleep(1000)影响。所以读锁可以多线程同时获得。单个线程获取读锁不会阻塞其他线程获取读锁。
当我们把上面main方法中的所有LOCK.readLock()改成LOCK.writeLock(),再次来看结果
Thread-0 | 准备锁
Thread-3 | 准备锁
Thread-2 | 准备锁
Thread-3 | 获得锁 : 1558579649131
Thread-1 | 准备锁
Thread-4 | 准备锁
Thread-2 | 获得锁 : 1558579650134
Thread-0 | 获得锁 : 1558579651138
Thread-1 | 获得锁 : 1558579652141
Thread-4 | 获得锁 : 1558579653145
每次获得锁的间隔大约为1000毫秒。所以我们可以得出结论:多个线程可同时获得读锁、多线程一次只能一个线程获取写锁。
最后小编告诉大家读写锁使用的时候,存在写锁时无法获取读锁,存在读锁时也无法获取写锁。
并发问题解决方案解决方案有很多,以上只是个别解决方案。
暂无评论内容