模拟银行账户

很重要,对于理解 锁的是哪个对象很重要以及volatile的可见性并不能代表数据的一致性以及数据的原子性,因此volatile并非是数据安全的。

模拟银行账户读写,数据是否一致

public class Account_01 {

    private String name;
    private int balance;

    void set(String name, int balance) {
        // 此处睡两秒是为了保证 main 主线程先执行一次getBalance方法
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.name = name;
        this.balance = balance;
    }

    int getBalance(String name) {
        return balance;
    }

    public static void main(String[] args) throws InterruptedException {
        Account_01 a = new Account_01();
        new Thread(() -> a.set("zhangsan", 2), "t1").start();

        System.out.println("thread t1 exe set before balance value :  " + a.getBalance("zhangsan"));

        // 此处睡3 秒是为了保证线程 t1 set 方法 执行完毕
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread t1 exe set after balance value:  " + a.getBalance("zhangsan"));
    }
}
输出结果:
thread t1 exe set before balance value :  0
thread t1 exe set after balance value:  2
    

出现了脏读现象,现在为了保持 写入的数据和读到的数据保持一致,代码做以下更改。

加synchronized

public class Account_01 {

    private String name;
    private int balance;

    synchronized void set(String name, int balance) {
        // 此处睡两秒是为了保证 main 主线程先执行一次getBalance方法
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.name = name;
        this.balance = balance;
    }

    synchronized int getBalance(String name) {
        return balance;
    }

    public static void main(String[] args) throws InterruptedException {
        Account_01 a = new Account_01();
        new Thread(() -> a.set("zhangsan", 2), "t1").start();

        System.out.println("thread t1 exe set before balance value :  " + a.getBalance("zhangsan"));

        // 此处睡3 秒是为了保证线程 t1 set 方法 执行完毕
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread t1 exe set after balance value:  " + a.getBalance("zhangsan"));


    }
}

输出结果:
thread t1 exe set before balance value :  2
thread t1 exe set after balance value:  2

读和写结果是一致的,此处说明了 synchronized 锁定的是当前对象,当 我们去调用get 方法时提示有锁,需要等待set方法执行完毕之后才可以执行 get方法。 重点,方法上的锁是对象的。

为啥不用volatile 呢, 试试看下输出结果。

public class Account_01 {

    private String name;
    private volatile int balance;

    synchronized void set(String name, int balance) {
        // 此处睡两秒是为了保证 main 主线程先执行一次getBalance方法
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.name = name;
        this.balance = balance;
    }

     int getBalance(String name) {
        return balance;
    }

    public static void main(String[] args) throws InterruptedException {
        Account_01 a = new Account_01();
        new Thread(() -> a.set("zhangsan", 2), "t1").start();

        System.out.println("thread t1 exe set before balance value :  " + a.getBalance("zhangsan"));

        // 此处睡3 秒是为了保证线程 t1 set 方法 执行完毕
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("thread t1 exe set after balance value:  " + a.getBalance("zhangsan"));


    }
}

输出结果:
thread t1 exe set before balance value :  0
thread t1 exe set after balance value:  2

输出结果 是读和写不一致,再一次说明了 volatile 只能保持数据的可见性,并不能说明数据的一致性,synchronized 才可以保证数据的原子性和一致性。

用了 synchronized 之后还需要用volatile 吗? 不需要。****synchronized 已经保证了多线程之间数据的一致性了。

模拟多线程写,用map存

public class Account_01 {

    private String name;
    private volatile int balance;

    private Map<String, Integer> map = new HashMap<>();

    synchronized void set(String name, int balance) {
        balance = (Objects.isNull(map.get(name)) ? 0 : map.get(name)) + balance;
        map.put(name, balance);

    }

    synchronized int getBalance(String name) {
        return (Objects.isNull(map.get(name)) ? 0 : map.get(name));
    }

    public static void main(String[] args) throws InterruptedException {
        Account_01 a = new Account_01();

        CountDownLatch start = new CountDownLatch(1);
        CountDownLatch end = new CountDownLatch(20);
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                try {
                    start.await();
                    a.set("zhangsan", 2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    end.countDown();
                }
            }, "t1").start();
        }

        start.countDown();
        end.await();
        System.out.println("输出结果: " + a.getBalance("zhangsan"));

    }
}

输出结果:
输出结果:  40  // get 方法加锁了,等到 set 完成释放锁之后才拿到锁执行get 方法获取到值

是否为同一把锁

@SuppressWarnings("all")
public class Account_01 {

    private String name;
    private volatile int balance;

    private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

    void set(String name, int balance) {
        synchronized (this) {
            balance = (Objects.isNull(map.get(name)) ? 0 : map.get(name)) + balance;
        }
        map.put(name, balance);
    }

    synchronized int getBalance(String name) {
        return (Objects.isNull(map.get(name)) ? 0 : map.get(name));
    }

    public static void main(String[] args) throws InterruptedException {
        for (int n = 0; n < 500000; n++) {
            Account_01 a = new Account_01();
            CountDownLatch start = new CountDownLatch(1);
            CountDownLatch end = new CountDownLatch(20);
            for (int i = 0; i < 20; i++) {
                new Thread(() -> {
                    try {
                        start.await();
                        a.set("zhangsan", 2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        end.countDown();
                    }
                }, "t1").start();
            }

            start.countDown();
            end.await();
            if (a.getBalance("zhangsan") != 40) {
                System.out.println("Incorrect result output: " + a.getBalance("zhangsan"));
            }
            System.out.println("Correct result output: " + a.getBalance("zhangsan"));
        }
    }
}

输出结果:
Correct result output: 40
Correct result output: 40
Incorrect result output: 38
Correct result output: 38
Correct result output: 40
Correct result output: 40
Incorrect result output: 38
Correct result output: 38
Correct result output: 40
Correct result output: 40
Correct result output: 40
Correct result output: 40
Correct result output: 40
Correct result output: 40
Correct result output: 40
Correct result output: 40‘

很显然输出结果里面有读写不一致的情况。这个代码里面我们用到了ConcurrentHashMap 为啥还读写不一致呢,个人认为 读和写并不是同一把锁导致的。所以要体现出读写一致,必定需要使用同一把锁
上一篇:第一部分:并发理论基础05->死锁了怎么办


下一篇:blog总结3