把出现线程安全问题的核心代码给上锁:(每次只能进入一个线程,相当于给线程排队,一个一个进)
    synchronized [ˈsɪŋkrənaɪzd] 同步的意思 这个锁没有特别的意思,就是一个flag的标志
    作为锁: ()里面写“itheima”也可以,因为是在字符串常量池中,只有一份
    image.pngimage.png
    上锁快捷键:ctrl + t 选第9个
    image.png

    使用共享资源来定义锁对象, 不要使用唯一的对象来作为锁的对象
    实例方法用this代表锁对象(实现共享,是调用就是哪个对象)
    image.png
    静态方法使用 :类名.class(将其锁住) 类名.class 对于所有线程来说都是唯一 一个
    image.png
    image.png

    1. package com.itheima.d3_thread_safe;
    2. /**
    3. * 需求: 模拟取钱案例。
    4. */
    5. public class ThreadDemo {
    6. public static void main(String[] args) {
    7. // 1. 定义线程类,创建一个共享的账户对象
    8. Account acc = new Account("ICBC-111",100000);
    9. // 创建2个线程对象,代表小明和小红同时进来了
    10. // 线程启动
    11. new DrawThread(acc,"小明").start(); // 这是一种匿名写法(不要写对象名,直接创建出来对象后调用其方法)
    12. new DrawThread(acc,"小红").start(); // 这是一种匿名写法(不要写对象名,直接创建出来对象后调用其方法)
    13. // 相当于定义了两家人 (如果定义的锁对象一样,相当于所有的线程都走一样对象的锁,账户没有分开,每个账户公用一把锁)
    14. // 会影响其他无关线程的执行
    15. // 1. 定义线程类,创建一个共享的账户对象
    16. Account acc2 = new Account("ICBC-112",100000);
    17. // 创建2个线程对象,代表小明和小红同时进来了
    18. // 线程启动
    19. new DrawThread(acc2,"小黑").start(); // 这是一种匿名写法(不要写对象名,直接创建出来对象后调用其方法)
    20. new DrawThread(acc2,"小白").start(); // 这是一种匿名写法(不要写对象名,直接创建出来对象后调用其方法)
    21. }
    22. }
    1. package com.itheima.d3_thread_safe;
    2. /**
    3. * 取钱的线程类
    4. */
    5. public class DrawThread extends Thread{
    6. // 接收处理的账户对象
    7. private Account acc;
    8. // 定义有参和无参构造器
    9. // 无参一般要自己写
    10. public DrawThread(){
    11. }
    12. public DrawThread(Account acc,String name) { // 利用有参构造器为父类Thread线程类取名字
    13. super(name); // 这里的super相当于是父类Thread线程类,直接为该线程类取名
    14. this.acc = acc;
    15. }
    16. // 继承了线程类后:一定要重写run方法
    17. @Override
    18. public void run() {
    19. // 该线程定义: 小红,小明: 取钱
    20. acc.drawMoney(100000); // 该drawMoney方法定义在Account账户类里面
    21. }
    22. }
    1. package com.itheima.d3_thread_safe;
    2. public class Account {
    3. private String cardId;
    4. private double money; // 账户余额
    5. public Account() {
    6. }
    7. public Account(String cardId, double money) {
    8. this.cardId = cardId;
    9. this.money = money;
    10. }
    11. public String getCardId() {
    12. return cardId;
    13. }
    14. public void setCardId(String cardId) {
    15. this.cardId = cardId;
    16. }
    17. public double getMoney() {
    18. return money;
    19. }
    20. public void setMoney(double money) {
    21. this.money = money;
    22. }
    23. // 100个人调用该线程,只有一个线程能占用这个类 -- 其他99个线程在外面等他 (因为调用该方法要用到类名.方法,然而类名被锁住了,所以只有一个人能解锁锁)
    24. public static void run(){ // 由于这个方法是拿账户(Account类)调用,对于所有线程来说都是唯一的方法
    25. // 所以直接把类名锁起来就可以了,
    26. synchronized (Account.class){
    27. }
    28. }
    29. /**
    30. * 在账户类里面定义一个取钱方法drawMoney
    31. * @param money 代表用户取的钱
    32. */
    33. public void drawMoney(int money) {
    34. // 0. 先获取是谁来取钱,线程的名字就是人嘛
    35. // 使用Thread.currentThread() 获取当前线程对象(谁调用该线程,就获取哪个线程对象),然后使用getNmae方法获取该线程名字
    36. String name = Thread.currentThread().getName();
    37. // 同步代码块: 给会出现线程安全问题的代码块给上锁
    38. // synchronized ("heima") 一样的,都是唯一对象,这个字符串对象都在常量池中,表唯一
    39. // 将字符串对象,改成 this == acc(表示当前对象,当前调用drawMoney方法的对象,这样就不会让账户冲突
    40. // this == acc 共享账户 不会让账户冲突 -- 如果是小明小红账户取钱(调用drawMoney方法),那this代表小明小红(这样就实现了共享)
    41. synchronized (this) { // 直接双引号:因为会在字符串常量池中产生(唯一的一份),满足了锁的唯一性(锁用任意唯一的对象不好)
    42. // 1. 判断账户是否够钱
    43. if (this.money >= money){
    44. // 2. 取钱
    45. System.out.println(name + "来取钱成功,吐出:" + money);
    46. // 3. 更新余额
    47. this.money -= money;
    48. System.out.println(name + "取钱后余额剩余:" + this.money);
    49. }else {
    50. // 余额不足
    51. System.out.println(name + "来取钱,余额不足");
    52. }
    53. }
    54. }
    55. }