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

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

  1. public class Account_01 {
  2. private String name;
  3. private int balance;
  4. void set(String name, int balance) {
  5. // 此处睡两秒是为了保证 main 主线程先执行一次getBalance方法
  6. try {
  7. Thread.sleep(2000);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. this.name = name;
  12. this.balance = balance;
  13. }
  14. int getBalance(String name) {
  15. return balance;
  16. }
  17. public static void main(String[] args) throws InterruptedException {
  18. Account_01 a = new Account_01();
  19. new Thread(() -> a.set("zhangsan", 2), "t1").start();
  20. System.out.println("thread t1 exe set before balance value : " + a.getBalance("zhangsan"));
  21. // 此处睡3 秒是为了保证线程 t1 set 方法 执行完毕
  22. try {
  23. TimeUnit.SECONDS.sleep(3);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. System.out.println("thread t1 exe set after balance value: " + a.getBalance("zhangsan"));
  28. }
  29. }
  30. 输出结果:
  31. thread t1 exe set before balance value : 0
  32. thread t1 exe set after balance value: 2

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

加synchronized

  1. public class Account_01 {
  2. private String name;
  3. private int balance;
  4. synchronized void set(String name, int balance) {
  5. // 此处睡两秒是为了保证 main 主线程先执行一次getBalance方法
  6. try {
  7. Thread.sleep(2000);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. this.name = name;
  12. this.balance = balance;
  13. }
  14. synchronized int getBalance(String name) {
  15. return balance;
  16. }
  17. public static void main(String[] args) throws InterruptedException {
  18. Account_01 a = new Account_01();
  19. new Thread(() -> a.set("zhangsan", 2), "t1").start();
  20. System.out.println("thread t1 exe set before balance value : " + a.getBalance("zhangsan"));
  21. // 此处睡3 秒是为了保证线程 t1 set 方法 执行完毕
  22. try {
  23. TimeUnit.SECONDS.sleep(3);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. System.out.println("thread t1 exe set after balance value: " + a.getBalance("zhangsan"));
  28. }
  29. }
  30. 输出结果:
  31. thread t1 exe set before balance value : 2
  32. thread t1 exe set after balance value: 2

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

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

  1. public class Account_01 {
  2. private String name;
  3. private volatile int balance;
  4. synchronized void set(String name, int balance) {
  5. // 此处睡两秒是为了保证 main 主线程先执行一次getBalance方法
  6. try {
  7. Thread.sleep(2000);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. this.name = name;
  12. this.balance = balance;
  13. }
  14. int getBalance(String name) {
  15. return balance;
  16. }
  17. public static void main(String[] args) throws InterruptedException {
  18. Account_01 a = new Account_01();
  19. new Thread(() -> a.set("zhangsan", 2), "t1").start();
  20. System.out.println("thread t1 exe set before balance value : " + a.getBalance("zhangsan"));
  21. // 此处睡3 秒是为了保证线程 t1 set 方法 执行完毕
  22. try {
  23. TimeUnit.SECONDS.sleep(3);
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. System.out.println("thread t1 exe set after balance value: " + a.getBalance("zhangsan"));
  28. }
  29. }
  30. 输出结果:
  31. thread t1 exe set before balance value : 0
  32. thread t1 exe set after balance value: 2

输出结果 是读和写不一致,再一次说明了 volatile 只能保持数据的可见性,并不能说明数据的一致性,synchronized 才可以保证数据的原子性和一致性。
用了 synchronized 之后还需要用volatile 吗? 不需要。synchronized 已经保证了多线程之间数据的一致性了。

模拟多线程写,用map存

  1. public class Account_01 {
  2. private String name;
  3. private volatile int balance;
  4. private Map<String, Integer> map = new HashMap<>();
  5. synchronized void set(String name, int balance) {
  6. balance = (Objects.isNull(map.get(name)) ? 0 : map.get(name)) + balance;
  7. map.put(name, balance);
  8. }
  9. synchronized int getBalance(String name) {
  10. return (Objects.isNull(map.get(name)) ? 0 : map.get(name));
  11. }
  12. public static void main(String[] args) throws InterruptedException {
  13. Account_01 a = new Account_01();
  14. CountDownLatch start = new CountDownLatch(1);
  15. CountDownLatch end = new CountDownLatch(20);
  16. for (int i = 0; i < 20; i++) {
  17. new Thread(() -> {
  18. try {
  19. start.await();
  20. a.set("zhangsan", 2);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. } finally {
  24. end.countDown();
  25. }
  26. }, "t1").start();
  27. }
  28. start.countDown();
  29. end.await();
  30. System.out.println("输出结果: " + a.getBalance("zhangsan"));
  31. }
  32. }
  33. 输出结果:
  34. 输出结果: 40 // get 方法加锁了,等到 set 完成释放锁之后才拿到锁执行get 方法获取到值

是否为同一把锁

  1. @SuppressWarnings("all")
  2. public class Account_01 {
  3. private String name;
  4. private volatile int balance;
  5. private ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
  6. void set(String name, int balance) {
  7. synchronized (this) {
  8. balance = (Objects.isNull(map.get(name)) ? 0 : map.get(name)) + balance;
  9. }
  10. map.put(name, balance);
  11. }
  12. synchronized int getBalance(String name) {
  13. return (Objects.isNull(map.get(name)) ? 0 : map.get(name));
  14. }
  15. public static void main(String[] args) throws InterruptedException {
  16. for (int n = 0; n < 500000; n++) {
  17. Account_01 a = new Account_01();
  18. CountDownLatch start = new CountDownLatch(1);
  19. CountDownLatch end = new CountDownLatch(20);
  20. for (int i = 0; i < 20; i++) {
  21. new Thread(() -> {
  22. try {
  23. start.await();
  24. a.set("zhangsan", 2);
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. } finally {
  28. end.countDown();
  29. }
  30. }, "t1").start();
  31. }
  32. start.countDown();
  33. end.await();
  34. if (a.getBalance("zhangsan") != 40) {
  35. System.out.println("Incorrect result output: " + a.getBalance("zhangsan"));
  36. }
  37. System.out.println("Correct result output: " + a.getBalance("zhangsan"));
  38. }
  39. }
  40. }
  41. 输出结果:
  42. Correct result output: 40
  43. Correct result output: 40
  44. Incorrect result output: 38
  45. Correct result output: 38
  46. Correct result output: 40
  47. Correct result output: 40
  48. Incorrect result output: 38
  49. Correct result output: 38
  50. Correct result output: 40
  51. Correct result output: 40
  52. Correct result output: 40
  53. Correct result output: 40
  54. Correct result output: 40
  55. Correct result output: 40
  56. Correct result output: 40
  57. Correct result output: 40
  58. 很显然输出结果里面有读写不一致的情况。这个代码里面我们用到了ConcurrentHashMap 为啥还读写不一致呢,个人认为 读和写并不是同一把锁导致的。所以要体现出读写一致,必定需要使用同一把锁