多线程之对象及变量的并发访问(五) - 数据脏读

一、前述

在前面文章的学习中,我们一直都在围绕 synchronized 来讲述,在方法没有同步处理的时候,多个线程访问一个实例变量的时候,这个值已经被其他的线程更改过了,所以会出现数据脏读(dirtyRead),这个数据脏读也出现了好多次,那么本文就来看看到底是什么东西

二、数据脏读

2.1 数据脏读

线程数据脏读跟数据库的事务脏读又有一点不一样,关于事务脏读可以了解下另一篇文章:mysql 事务特性以及隔离级别
这里示例一个脏读:

User.java:

public class User {
    public String username = "usernameA";
    public String password = "passwordA";

    synchronized public void setValue(String username, String password) {
        try {
            this.username = username;
            Thread.sleep(5000);
            this.password = password;
            System.out.println("Thread Name = " + Thread.currentThread().getName() + ", Date = " + new Date() + ", setValue method , username=" + username + ", password=" + password);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void getValue() {
        System.out.println("Thread Name = " + Thread.currentThread().getName() + ", Date = " + new Date() + ", getValue method , username = " + username + ", password = " + password);
    }
}

UserThread.java:

public class UserThread extends Thread{
    private User user;

    public UserThread(User user){
        super();
        this.user = user;
    }
    @Override
    public void run(){
        user.setValue("usernameB","passwordB");
    }
}

UserService:

public class UserService {
    public static void main(String[] args) throws Exception{
        System.out.println("Thread Start.... Thread Name = " + Thread.currentThread().getName() + ", Date = " + new Date());
        User user = new User();
        UserThread userThread = new UserThread(user);
        userThread.start();
        Thread.sleep(1000);
        user.getValue();
    }
}

Result:

在这里插入图片描述
在这里插入图片描述
说明:

1)T1 时刻,开始主线程进入
(2)T2 时刻,创建的新的线程 Thread-0 启动,进入内部逻辑,将 username 赋值为 usernameB,然后开始睡眠 5s,同时 main 线程进入,开始睡眠 1s
(3)T3 时刻 = T2 时刻,两个线程同时睡眠
(4)T4 时刻,Thread-0 仍然在睡,main 唤醒往下执行代码,user 调用 getValue 输出值: `username = usernameB, password = passwordA`
(5)T5 时刻,也就是在 T4 4s 后,继续执行下面代码,将 password 赋值为 passwordB ,而后输出:`username=usernameB, password=passwordB`
由上,可以看到,main 线程读取到了 Thread-0 线程执行代码到一半的数据,造成了数据脏读

三、解决办法

解决上述脏读的办法简单有两个:

1)Thread-0 睡眠的时候,不让 Main 线程调用这个对象的方法
(2)Thread-0 睡眠的时候,让 Main 也继续睡眠,睡眠时间直到 Thread-0 执行完毕

3.1 针对第一个办法:

在 User 类里,将 getValue 方法也加上 synchronized 同步锁,对对象锁住

User.java:

public class User {
    public String username = "usernameA";
    public String password = "passwordA";

    synchronized public void setValue(String username, String password) {
        try {
            this.username = username;
            Thread.sleep(5000);
            this.password = password;
            System.out.println("Thread Name = " + Thread.currentThread().getName() + ", Date = " + new Date() + ", setValue method , username=" + username + ", password=" + password);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    synchronized public void getValue() {
        System.out.println("Thread Name = " + Thread.currentThread().getName() + ", Date = " + new Date() + ", getValue method , username = " + username + ", password = " + password);
    }
}

Result:

在这里插入图片描述
可以看到 Thread-0 线程获取到对象锁以后, main 无法执行 getValue 只有等 Thread-0 睡眠五秒以后,唤醒重新执行下面的代码, main 线程才执行 getValue 方法,这样就不会造成数据脏读

3.2 针对第二种方法

将 main 线程的睡眠时间增加到 7s ,也就是至少要大于 Thread-0 的睡眠时间

UserService.java

public class UserService {
    public static void main(String[] args) throws Exception{
        System.out.println("Thread Start.... Thread Name = " + Thread.currentThread().getName() + ", Date = " + new Date());
        User user = new User();
        UserThread userThread = new UserThread(user);
        userThread.start();
        Thread.sleep(1000);
        user.getValue();
    }
}

Result

在这里插入图片描述
如图所示,增加 main 线程的睡眠时间以后,当 Thread-0 睡眠五秒以后唤醒, main 线程仍然在睡眠,因此 Thread-0 继续往下执行代赋值,第七秒时, main 线程唤醒执行 getValue 方法,输出 username = usernameB, password = passwordB,没有出现数据脏读,是理想的结果

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值