java的线程安全问题原因及解决办法

  • 更新时间:2022-11-23 09:29:51
  • 编辑:阚丹丹
这是一篇关于java相关的编程问答内容,被603位程序员关注,内容涉及到java的线程安全问题原因及解决办法、java的线程安全问题分析等,由姚半芹编辑补充,一起来看下大家的回答。我们把答案参考的资料分享在下方,想深入学习的读者们可以下载学习下。

java的线程安全问题分析

今天码农之家java学院小编为大家介绍java的线程安全问题原因及解决办法,希望对各位java程序员有帮助,下面就随小编一起看看java的线程安全问题原因及解决办法吧。

1、为什么会出现线程安全问题

计算机系统资源分配的单位为进程,同一个进程中允许多个线程并发执行,并且多个线程会共享进程范围内的资源:例如内存地址。当多个线程并发访问同一个内存地址并且内存地址保存的值是可变的时候可能会发生线程安全问题,因此需要内存数据共享机制来保证线程安全问题。

对应到java服务来说,在虚拟中的共享内存地址是java的堆内存,比如以下程序中线程安全问题:

public class ThreadUnsafeDemo {

    private static final ExecutorService EXECUTOR_SERVICE;

    static {

        EXECUTOR_SERVICE = new ThreadPoolExecutor(100, 100, 1000 * 10,

                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(100), new ThreadFactory() {

            private AtomicLong atomicLong = new AtomicLong(1);

            @Override

            public Thread newThread(Runnable r) {

                return new Thread(r, "Thread-Safe-Thread-" + atomicLong.getAndIncrement());

            }

        });

    }

    public static void main(String[] args) throws Exception {

        Map<String, Integer> params = new HashMap<>();

        List<Future> futureList = new ArrayList<>(100);

        for (int i = 0; i < 100; i++) {

            futureList.add(EXECUTOR_SERVICE.submit(new CacheOpTask(params)));

        }

        for (Future future : futureList) {

            System.out.println("Future result:" + future.get());

        }

        System.out.println(params);

    }

    private static class CacheOpTask implements Callable<Integer> {

        private Map<String, Integer> params;

        CacheOpTask(Map<String, Integer> params) {

            this.params = params;

        }

        @Override

        public Integer call() {

            for (int i = 0; i < 100; i++) {

                int count = params.getOrDefault("count", 0);

                params.put("count", ++count);

            }

            return params.get("count");

        }

    }

}

创建100个task,每个task对map中的元素累加100此,程序执行结果为:

{count=9846}

而预期的正确结果为:

{count=10000}

至于出现这种问题的原因,下面会具体分析。

判断是否有线程安全性的一个原则是:

是否有多线程访问可变的共享变量

2、多线程的优势

发挥多处理器的强大能力,提高效率和程序吞吐量。

3、并发带来的风险

使用并发程序带来的主要风险有以下三种:

(1)安全性问题:

竞态条件:由于不恰当的执行时序而出现不正确的结果。

对于1中的线程安全的例子就是由于竞态条件导致的最终结果与预期结果不一致。关键代码块如下:

int count = params.getOrDefault("count", 0);

params.put("count", ++count);

当多个线程同时取的count的值的时候,每个线程计算之后,在写入到count,这时候会出现多个线程值被覆盖的情况,最终导致结果不正确。如下图所示:

(2)解决此类问题的几种方法

1)使用同步机制限制变量的访问:锁

比如:

synchronized (LOCK) {

    int count = params.getOrDefault("count", 0);

    params.put("count", ++count);

}

2)将变量设置为不可变

即将共享变量设置为final

3)不在线程之间共享此变量ThreadLocal

编程的原则:首先编写正确的代码,然后在实现性能的提升。

无状态的类一定是线程安全的

(3)内置锁

内置锁:同步代码块( synchronized (this) {})

进入代码块前需要获取锁,会有性能问题。内置锁是可重入锁,之所以每个对象都有一个内置锁,是为了避免显示的创建锁对象。

常见的加锁约定:将所有的可变状态都封装在对象内部,并使用内置锁对所有访问可变状态的代码进行同步。例如:Vector等

同步的另一个功能:内存可见性,类似于volatile。

非volatile的64位变量double、long:

JVM允许对64位的操作分解为两次32位的两次操作,可变64位变量必须用volatile或者锁来保护。

加锁的含义不仅在于互斥行为,还包括内存可见性,为了所有线程都可以看到共享变量的最新值,所有线程应该使用同一个锁。

原则:除非需要跟高的可见性,否则应该将所有的域都声明为私有的,除非需要某个域是可变的,否则应该讲所有的域生命为final的。

活跃性问题:线程活跃性问题主要是由于加锁不正确导致的线程一直处于等待获取锁的状态,比如以下程序:

public class DeadLock {

    private static final Object[] LOCK_ARRAY;

    static {

        LOCK_ARRAY = new Object[2];

        LOCK_ARRAY[0] = new Object();

        LOCK_ARRAY[1] = new Object();

    }

    public static void main(String[] args) throws Exception {

        TaskOne taskOne = new TaskOne();

        taskOne.start();

        TaskTwo taskTwo = new TaskTwo();

        taskTwo.start();

        System.out.println("finished");

    }

    private static class TaskOne extends Thread {

        @Override

        public void run(){

            synchronized (LOCK_ARRAY[0]) {

                try {

                    Thread.sleep(3000);

                } catch (Exception e) {

                }

                System.out.println("Get LOCK-0");

                synchronized (LOCK_ARRAY[1]) {

                    System.out.println("Get LOCK-1");

                }

            }

        }

    }

    private static class TaskTwo extends Thread {

        @Override

        public void run() {

            synchronized (LOCK_ARRAY[1]) {

                try {

                    Thread.sleep(1000 * 3);

                } catch (Exception e) {

                }

                System.out.println("Get LOCK-1");

                synchronized (LOCK_ARRAY[0]) {

                    System.out.println("Get LOCK-0");

                }

            }

        }

    }

}

在两个线程持有一个锁,并在在锁没有释放之前,互相等待对方持有的锁,这时候会造成两个线程会一直等待,从而产生死锁。在我们使用锁的时候应该考虑持有锁的时长,特别是在网络I/O的时候。

在使用锁的时候要尽量避免以上情况,从而避免产生死锁。

3、性能问题

在使用多线程执行程序的时候,在线程间的切换以及线程的调度也会消耗CPU的性能。

以上就是码农之家java学院小编介绍的“java线程安全问题原因及解决办法”的内容,希望对各位java程序员有帮助,如有疑问,请在线咨询,有专业老师随时为你服务。

相关问题

  • java多线程动态传参数吗(java多线程传递数据)

    在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据。 Java多线程:向线程传递参数的三种方法 一、通过构造方法传递数据 在创建线程时,必须要建立一个Thread类的或其子

    发布时间:2021-04-26

  • 【java 多线程】守护线程与非守护线程示例效果(【java 多线程】守护线程与非守护线程的详解)

    这篇文章主要介绍了java守护线程与非守护线程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

    发布时间:2020-01-15

  • Java守护线程与用户线程示例代码(详解Java线程-守护线程与用户线程)

    这篇文章主要介绍了Java守护线程与用户线程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

    发布时间:2020-03-14

  • Java Swing 多线程加载图片实例详解(Java Swing 多线程加载图片(保证顺序一致))

    这篇文章主要为大家详细介绍了Java Swing 多线程加载图片,保证顺序一致,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

    发布时间:2020-03-08

  • java中如何实现两个线程同时写一个文件(java两个线程同时写一个文件)

    这篇文章主要为大家详细介绍了java两个线程同时写一个文件,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

    发布时间:2020-03-11

  • java线程中可以再开线程吗(Java中在线程中可以再开线程吗)

    Java中在线程中可以再开线程,因为main方法内就是一条主线程,我们可以在mian()方法内可以创建多条线程,多条线程都可以顺利执行,所以,在线程内是可以创建多线程的。Java 线程中创建线程的两种方法 1、使用Runnable接口创建线程 1.可以将CPU,代码和数据分开,形成清晰的模型 2.线程体run()方法所在的类可以从其它类中继承一些有用的属性和方法 3.有利于保持程序的设计风格一致 2、直接继承Thread类创建对象 1.Thread子类无法再从其它类继承(java语言单继

    发布时间:2020-11-28

  • 解析Java线程池创建过程(了解Java线程池创建过程)

    那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?在Java中可以通过线程池来达到这样的效果。下面我们来详细了解一下吧

    发布时间:2020-03-14

  • java实现多线程交替打印两个数(java多线程交替打印两个数)

    给网友们整理关于java 多线程的教程,这篇文章主要为大家详细介绍了java实现多线程交替打印两个数,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

    发布时间:2022-09-23

  • java中的线程可以共享数据吗(在java中的线程能共享数据吗)

    多个线程对共同数据的访问的实现,要根据情况而定 (1)当访问共同的代码的时候:可以使用同一个Runnable对象,这个Runnable对象中有这个共享数据,比如卖票系统就可以这么做。或者这个共享数据封装在一个对象当中,然后对这个对象加锁,也可以实现数据安全访问。 (2)当各个线程访问的代码不同时:这时候要使用不同的Runnable对象,有两种实现方式: a)将共享数据封装在另一个对象当中,然后将这个对象逐一的转递给各个Runnable对象。操作数据的

    发布时间:2020-12-04

用户回答