java线程安全问题

简介: java线程安全问题

临界资源

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

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

竞态条件

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

导致竞态条件发生的代码区称作临界区。

在临界区中使用适当的同步操作就可以避免竞态条件,如使用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,所以循环不会中断

image.png


增加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;
    }
}


image.png

volatile无法解决原子性问题:

image.png


主要原因为:

线程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++;
    }
}

运行结果:

image.png

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

目录
相关文章
|
13天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
35 3
|
5月前
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
Java并发编程实战:使用synchronized关键字实现线程安全
72 0
|
10天前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
10天前
|
Java 调度
【JavaEE】——线程的安全问题和解决方式
【JavaEE】——线程的安全问题和解决方式。为什么多线程运行会有安全问题,解决线程安全问题的思路,synchronized关键字的运用,加锁机制,“锁竞争”,几个变式
|
2月前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
2月前
|
SQL 安全 Java
安全问题已经成为软件开发中不可忽视的重要议题。对于使用Java语言开发的应用程序来说,安全性更是至关重要
在当今网络环境下,Java应用的安全性至关重要。本文深入探讨了Java安全编程的最佳实践,包括代码审查、输入验证、输出编码、访问控制和加密技术等,帮助开发者构建安全可靠的应用。通过掌握相关技术和工具,开发者可以有效防范安全威胁,确保应用的安全性。
58 4
|
2月前
|
SQL 安全 Java
Java 异常处理:筑牢程序稳定性的 “安全网”
本文深入探讨Java异常处理,涵盖异常的基础分类、处理机制及最佳实践。从`Error`与`Exception`的区分,到`try-catch-finally`和`throws`的运用,再到自定义异常的设计,全面解析如何有效管理程序中的异常情况,提升代码的健壮性和可维护性。通过实例代码,帮助开发者掌握异常处理技巧,确保程序稳定运行。
47 0
|
3月前
|
存储 安全 Java
Java-如何保证线程安全?
【10月更文挑战第10天】
|
3月前
|
安全 Java 编译器
Java 泛型深入解析:类型安全与灵活性的平衡
Java 泛型通过参数化类型实现了代码重用和类型安全,提升了代码的可读性和灵活性。本文深入探讨了泛型的基本原理、常见用法及局限性,包括泛型类、方法和接口的使用,以及上界和下界通配符等高级特性。通过理解和运用这些技巧,开发者可以编写更健壮和通用的代码。
|
4月前
|
安全 Java API
java安全特性
java安全特性
34 8