image.png
    自己定义一个锁
    image.png
    上锁,解锁
    image.png

    1. package com.itheima.d3_thread_safe;
    2. import java.util.concurrent.locks.Lock;
    3. import java.util.concurrent.locks.ReentrantLock;
    4. public class Account {
    5. private String cardId;
    6. private double money; // 账户余额
    7. // 自己定义一个锁
    8. // 在这里创建一个锁对象(外面new了一个Account(账户类)就会自动创建锁对象)
    9. // final修饰后:锁对象是唯一和不可替换的,非常专业 (每次new一个账户,就会生成一个新的锁对象),自己拿自己的锁
    10. private final Lock lock = new ReentrantLock(); // 加final的好处就是变成一个常量(常量对象)只能赋值一次
    11. public Account() {
    12. }
    13. public Account(String cardId, double money) {
    14. this.cardId = cardId;
    15. this.money = money;
    16. }
    17. public String getCardId() {
    18. return cardId;
    19. }
    20. public void setCardId(String cardId) {
    21. this.cardId = cardId;
    22. }
    23. public double getMoney() {
    24. return money;
    25. }
    26. public void setMoney(double money) {
    27. this.money = money;
    28. }
    29. /**
    30. * 在账户类里面定义一个取钱方法drawMoney
    31. * @param money 代表用户取的钱
    32. *
    33. * // 方式二: 同步方法 : 把出现线程安全问题的核心方法给上锁,这样每次只能有一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行
    34. */
    35. public void drawMoney(int money) {
    36. // 0. 先获取是谁来取钱,线程的名字就是人嘛
    37. // 使用Thread.currentThread() 获取当前线程对象(谁调用该线程,就获取哪个线程对象),然后使用getNmae方法获取该线程名字
    38. String name = Thread.currentThread().getName();
    39. // 同步代码块: 给会出现线程安全问题的代码块给上锁
    40. // synchronized ("heima") 一样的,都是唯一对象,这个字符串对象都在常量池中,表唯一
    41. // 将字符串对象,改成 this == acc(表示当前对象,当前调用drawMoney方法的对象,这样就不会让账户冲突
    42. // this == acc 共享账户 不会让账户冲突 -- 如果是小明小红账户取钱(调用drawMoney方法),那this代表小明小红(这样就实现了共享)
    43. // 直接双引号:因为会在字符串常量池中产生(唯一的一份),满足了锁的唯一性(锁用任意唯一的对象不好)
    44. // 在
    45. // 方式三:自定义锁,在会出现线程安全问题的代码块给上锁
    46. lock.lock(); // 上锁
    47. try {
    48. // 1. 判断账户是否够钱
    49. if (this.money >= money){
    50. // 2. 取钱
    51. System.out.println(name + "来取钱成功,吐出:" + money);
    52. // 3. 更新余额
    53. this.money -= money;
    54. System.out.println(name + "取钱后余额剩余:" + this.money);
    55. // System.out.println(10/0); // 如果这里出异常了,那么后面的锁则不会解开,所以锁最好定义在finally代码块中
    56. }else {
    57. // 余额不足
    58. System.out.println(name + "来取钱,余额不足");
    59. }
    60. } catch (Exception e) {
    61. e.printStackTrace();
    62. }finally { // finally 如果异常与否,都会执行这行代码
    63. lock.unlock(); // 解锁
    64. }
    65. }
    66. }