很重要,对于理解 锁的是哪个对象很重要以及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 : 0thread 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 : 2thread 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 : 0thread 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: 40Correct result output: 40Incorrect result output: 38Correct result output: 38Correct result output: 40Correct result output: 40Incorrect result output: 38Correct result output: 38Correct result output: 40Correct result output: 40Correct result output: 40Correct result output: 40Correct result output: 40Correct result output: 40Correct result output: 40Correct result output: 40‘很显然输出结果里面有读写不一致的情况。这个代码里面我们用到了ConcurrentHashMap 为啥还读写不一致呢,个人认为 读和写并不是同一把锁导致的。所以要体现出读写一致,必定需要使用同一把锁
