原创

java线程安全问题

温馨提示:
本文最后更新于 2021年12月15日,已超过 862 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

临界资源

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

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

竞态条件

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

导致竞态条件发生的代码区称作临界区。
在临界区中使用适当的同步操作就可以避免竞态条件,如使用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++;
    }
}

运行结果:

仙士可博客

本文参考:https://blog.csdn.net/weixin_41947378/article/details/112245369

正文到此结束
本文目录