java线程安全问题

举报
仙士可 发表于 2023/06/26 17:06:48 2023/06/26
【摘要】 临界资源临界资源是一次仅允许一个进程使用的共享资源。各进程采取互斥的方式,实现共享的资源称作临界资源。属于临界资源的硬件有,打印机,磁带机等;软件有消息队列,变量,数组,缓冲区等。诸进程间采取互斥方式,实现对这种资源的共享。竞态条件当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。导致竞态条件发生的代码区称作临界区。 在临界区中使用适当的同步操作就可以避免竞态条件,如使用...

临界资源

临界资源是一次仅允许一个进程使用的共享资源。各进程采取互斥的方式,实现共享的资源称作临界资源。属于临界资源的硬件有,打印机,磁带机等;

软件有消息队列,变量,数组,缓冲区等。诸进程间采取互斥方式,实现对这种资源的共享。

竞态条件

当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。

导致竞态条件发生的代码区称作临界区。 在临界区中使用适当的同步操作就可以避免竞态条件,如使用synchronized或者加锁机制。

线程安全

允许被多个线程同时执行的代码称作线程安全的代码。线程安全的代码不包含竞态条件。

线程安全出现问题的例子:

当多个线程同时操作一个变量时,可能会造成变量的脏读脏写(类似于mysql)

package com.company;

public class Main {
    public static void main(String[] args) {
        Test test = new Test();
        //创建20个线程
        for (int i =1;i<=20;i++){
            new Thread(()->{
                for (int j =1;j<=1000;j++){
                    test.incA();
                }
            },"测试"+i).start();
        }
        while(Thread.activeCount() > 2){ //main, gc,说明还存在其他线程在执行
            Thread.yield();//线程礼让
        }
        System.out.println(Thread.currentThread().getName() + "\t int类型的number最终值:" + test.a());
    }
}

class Test{
    public int a;

    public int a(){
        return a;
    }

    public void incA(){
        a++;
    }
}
复制

执行结果:

/Users/tioncico/Library/Java/JavaVirtualMachines/openjdk-14.0.1/Contents/Home/bin/java -javaagent:/Applications/IDE/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=56786:/Applications/IDE/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/tioncico/IdeaProjects/test/out/production/untitled104 com.company.Main
main	 int类型的number最终值:19893
复制

可看到 本来是20个线程*1000次递增,但是实际值却小于20000,这个情况就属于非线程安全的一种

如何实现线程安全?

volatile关键字

通过volatile修饰属性,此属性将直接修改内存,不经过线程内部缓存和重排序

volatile关键字可以保证属性操作的可见性和有序性,但是不能保证原子性

可见性

指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改一个共享变量时,另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。

有序性

有序性是指在单线程环境中, 程序是按序依次执行的.

而在多线程环境中, 程序的执行可能因为指令重排而出现乱序

指令重排

指令重排是指在程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序.

原子性

子性是指一个操作是不可中断的. 即使是在多个线程一起执行的时候,

一个操作一旦开始,就不会被其它线程干扰.

volatile可见性案例:

package com.company;

import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) {
        Test test = new Test();
        //创建1个线程
        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t 正在执行");

            try {
                TimeUnit.SECONDS.sleep(3);//留出时间使得主线程代码执行
                test.setA(100);
                System.out.println(Thread.currentThread().getName() + "\t int类型的值为:" + test.a());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"演示").start();
        while(test.a==0){//如果一直为0,则一直循环

        }
        System.out.println(Thread.currentThread().getName() + "\t int类型的number值为:" + test.a());
    }
}

class Test{
    public int a=0;

    public int a(){
        return a;
    }

    public void incA(){
        a++;
    }
    public void setA(int a){
        this.a = a;
    }
}
复制

由于没有volatile关键字,主线程main一直获取到的值是0,所以循环不会中断

增加volatile关键字:

class Test{
    public volatile int a=0;

    public int a(){
        return a;
    }

    public void incA(){
        a++;
    }
    public void setA(int a){
        this.a = a;
    }
}
复制

volatile无法解决原子性问题:

主要原因为:

线程1拿到了a=0的值,并且0++变成了1 但是其实在同一时刻,线程1-20都拿到了a=0的值,都++变成了1,就会导致线程写入覆盖,最后就会导致值小于20000;

AtomicIntegrer原子类

虽然volatile无法实现原子性,但是可以通过java.util.concurrent.AtomicInteger 类   保存数据实现原子性操作:

class Test{
    public AtomicInteger a = new AtomicInteger();

    public int a(){
        return a.get();
    }

    public void incA(){
        a.getAndIncrement();
    }
}
复制

结果:

/Users/tioncico/Library/Java/JavaVirtualMachines/openjdk-14.0.1/Contents/Home/bin/java -javaagent:/Applications/IDE/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=62725:/Applications/IDE/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8 -classpath /Users/tioncico/IdeaProjects/test/out/production/untitled104 com.company.Main
main	 int类型的number最终值:20000
复制

synchronized关键字

synchronized关键字可对某个方法进行加锁,使得该方法同一时刻只能一个线程访问:

class Test {
    public int a;

    public int a() {
        return a;
    }

    public synchronized void incA() {
        a++;
    }
}
复制

运行结果:

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

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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