java并发编程中少不了中的资源共享并发问题及解决办法

问题场景

在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();
        }
    }

图片[1]-java并发编程中少不了中的资源共享并发问题及解决办法-唐朝资源网

}

以上结果为:

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毫秒。所以我们可以得出结论:多个线程可同时获得读锁、多线程一次只能一个线程获取写锁。

最后小编告诉大家读写锁使用的时候,存在写锁时无法获取读锁,存在读锁时也无法获取写锁。

并发问题解决方案解决方案有很多,以上只是个别解决方案。

© 版权声明
THE END
喜欢就支持一下吧
点赞184赞赏 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容