【JavaEE】——线程的安全问题和解决方式

举报
三三是该溜子 发表于 2025/05/05 10:41:24 2025/05/05
【摘要】 ​阿华代码,不是逆风,就是我疯,你们的点赞收藏是我前进最大的动力!!希望本文内容能够帮助到你!目录一:问题引入二:问题深入三:synchronized关键字(加锁)一:问题引入用多线程,让计数器自增到1_0000package thread;public class ThreadDemon19 {private static long count = 0;public static void...


阿华代码,不是逆风,就是我疯,你们的点赞收藏是我前进最大的动力!!希望本文内容能够帮助到你!

目录

一:问题引入

二:问题深入

三:synchronized关键字(加锁)

一:问题引入
用多线程,让计数器自增到1_0000

package thread;

public class ThreadDemon19 {
private static long count = 0;

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(()->{
        for (int i = 0; i < 5000; i++) {
            count ++;
        }
    });

    Thread t2 = new Thread(()->{
        for (int i = 0; i < 5000; i++) {
            count ++;
        }
    });

    t1.start();
    t2.start();

    t1.join();
    t2.join();
    System.out.println("双线程的计算结果是:"+count);
}

}
package thread;

public class ThreadDemon20 {
private static long count = 0;

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(()->{
        for (int i = 0; i < 1_0000; i++) {
            count ++;
        }
    });



    t1.start();
    t1.join();
    System.out.println("单线程的计算结果是:"+count);
}

}

通过上述代码的举例,我们发现解决同一个问题,怎么最后的结果会不一样呢,真是奇了怪了。

二:问题深入
结果不一样,猜测是循环自增代码这一块出现问题

1:举例说明
我们知道cpu可以读取指令,解析指令,执行指令此处我们重点关注执行指令

count++,实际由三个指令构成的

(1)load:从内存中读取数据到cpu的寄存器当中

(2)add:把寄存器当中的值+1

(3)save:把寄存器当中的值写回到内存中

2:图解双线程计算

(以上可以画出无数种情况,比如t1线程自增了2次,3次,t2线程才执行了1次。)(这就是线程的随机调度和抢占式执行)

3:线程不安全原因的总结
(1)根本原因
是线程的“抢占式执行和随机调度”

(2)代码结构
多个线程可以修改同一变量

(3)直接原因
是上述多线程修改变量这一操作并不是“原子性”的,而是可拆分的(就像我们上面画的图),这里就是操作系统底层结构的问题了

(4)内存可见性问题
(5)指令重排序问题
(4)(5)条上述代码没有涉及,我们后续再详细引入

5:解决问题的思路
为了确保结果的正确,我们得确保第一个线程save了过后,第二个线程再去load。这时第二个线程load到的数据才是自增过后正确的数据

(1)针对根本原因解决
不可行。如果要修改线程的“抢占式执行和随机调度”这一机制的话,就得修改操作系统中的内核,相当于是重新写了一个“新的系统”

(2)针对代码结构的原因解决
有些地方,代码结构可以进行修改,但是有些地方不可以,视情况而论

(3)针对直接原因——加锁
count++,由三个指令完成,我们如果给这三个指令打包成一个整体的话就可以避免线程出现问题了,也就是“加锁”

三:synchronized关键字(加锁)
1:synchronized
关键字:synchronized(对象){加锁内容};

注:synchronized的加锁依赖于对象

2:核心内容
(1)含义
如果第一个线程对某个对象上锁之后,第二个线程要想对同一个对象上锁,就必须等第一个线程释放锁,此时第二个线程是处于BLOCKED阻塞状态

package thread;

/**

  • Created with IntelliJ IDEA.

  • Description:

  • User: Hua YY

  • Date: 2024-09-21

  • Time: 15:27
    */
    public class ThreadDemon21 {
    private static long count = 0;
    public static void main(String[] args) throws InterruptedException {
    Object locker = new Object();//创建一个object对象

     Thread t1 = new Thread(()->{
         for (int i = 0; i < 5000; i++) {
             synchronized (locker){  //锁到object这个对象上
                 count++;
             }
         }
     });
    
     Thread t2 = new Thread(()->{
         for (int i = 0; i < 5000; i++) {
             synchronized(locker){   //锁到object这个对象上
                 count++;
             }
    
         }
     });
    
     t1.start();
     t2.start();
    
     t1.join();
     t2.join();
     System.out.println("计算结果是:" + count);
    

    }

}

(2)代码解释:

①“锁竞争”

通过“锁竞争”让让第二个线程无法插入到第一个线程的执行指令当中。

②“加锁”

“加锁”就是把count++中三个指令(load,add,save)打包成一个“原子性”的操作(最小单位的操作,再也不可分割了)

③“同一对象”

加锁的对象不同,“锁竞争”就不会发生,线程安全问题依旧存在

④“都要加锁”

如果第一个线程加锁,第二个线程不加锁,“锁竞争”也不会发生,线程安全问题依旧存在

3:变式
变式①:给this加锁
this指向的还是同一个对象,t1和t2依旧会产生“锁竞争”

package thread;

/**

  • Created with IntelliJ IDEA.
  • Description:
  • User: Hua YY
  • Date: 2024-09-21
  • Time: 15:27
    */
    class Test{
    public long count = 0;
    public void add(){
    synchronized (this){
    count++;
    }
    }

}
public class ThreadDemon22 {

public static void main(String[] args) throws InterruptedException {
    Test test = new Test();
    Thread t1 = new Thread(()->{
        for (int i = 0; i < 5000; i++) {
                test.add();

        }
    });

    Thread t2 = new Thread(()->{
        for (int i = 0; i < 5000; i++) {
                test.add();
        }
    });
    
    t1.start();
    t2.start();

    t1.join();
    t2.join();
    System.out.println("计算结果是:" + test.count);
}

}
变式②: 类名.class
获取到当前类的对象,类对象包含了这个类的各种信息(类名字,属性,方法…)

变式③:public前加synchronized
等价写法

变式④:给类对象加锁(static)
static方法前加synchronized(少见)

如果synchronized加到static方法上,就相当于给类对象加锁了

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。