线程安全问题分析

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.并发带来的风险

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

3.1.安全性问题:

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

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

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

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

3.2解决此类问题的几种方法

1.使用同步机制限制变量的访问:锁
比如:

synchronized (LOCK) {
    int count = params.getOrDefault("count", 0);
    params.put("count", ++count);
}

2.将变量设置为不可变

即将共享变量设置为final

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

编程的原则:首先编写正确的代码,然后在实现性能的提升
无状态的类一定是线程安全的

3.3 内置锁

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

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

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

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

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

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

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

2.活跃性问题

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

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的性能。

原文地址:https://www.cnblogs.com/vitasyuan/p/9313625.html

时间: 2024-09-30 19:09:42

线程安全问题分析的相关文章

servlet 与线程安全问题 探讨

http://www.open-open.com/bbs/view/1366457535515  servlet与Struts,action线程分析 http://blog.knowsky.com/253158.htm Servlet与Struts action线程安全问题分析 http://bbs.csdn.net/topics/390894585  Spring MVC的Controller是线程安全的么? http://bbs.csdn.net/topics/390891861#post-

java线程安全问题原理性分析

1.什么是线程安全问题? 从某个线程开始访问到访问结束的整个过程,如果有一个访问对象被其他线程修改,那么对于当前线程而言就发生了线程安全问题:如果在整个访问过程中,无一对象被其他线程修改,就是线程安全的. 2.线程安全问题产生的根本原因 首先是多线程环境,即同时存在有多个操作者,单线程环境不存在线程安全问题.在单线程环境下,任何操作包括修改操作都是操作者自己发出的,操作者发出操作时不仅有明确的目的,而且意识到操作的影响. 多个操作者(线程)必须操作同一个对象,只有多个操作者同时操作一个对象,行为

java线程安全问题之静态变量、实例变量、局部变量

Java多线程编程中,存在很多线程安全问题,至于什么是线程安全呢,给出一个通俗易懂的概念还是蛮难的,如同<java并发编程实践>中所说: 写道 给线程安全下定义比较困难.存在很多种定义,如:"一个类在可以被多个线程安全调用时就是线程安全的". 此处不赘述了,首先给出静态变量.实例变量.局部变量在多线程环境下的线程安全问题结论,然后用示例验证,请大家擦亮眼睛,有错必究,否则误人子弟! 静态变量:线程非安全. 静态变量即类变量,位于方法区,为所有对象共享,共享一份内存,一旦静态

javaweb回顾第六篇谈一谈Servlet线程安全问题

前言:前面说了很多关于Servlet的一些基础知识,这一篇主要说一下关于Servlet的线程安全问题. 1:多线程的Servlet模型 要想弄清Servlet线程安全我们必须先要明白Servlet实例是如何创建,它的模式是什么样的. 在默认的情况下Servlet容器对声明的Servlet,只创建一个Servlet实例,那么如果要是多个客户同时请求访问这个Servlet,Servlet容器就采取多线程.下面我们来看一幅图 从图中可以看出当客户发送请求的时候,Servlet容器通过调度者线程从线程池

(2.1)servlet线程安全问题

本文参考链接:http://www.yesky.com/334/1951334.shtml 摘 要:介绍了Servlet多线程机制,通过一个实例并结合Java 的内存模型说明引起Servlet线程不安全的原因,给出了保证Servlet线程安全的三种解决方案,并说明三种方案在实际开发中的取舍. Servlet/JSP技术和ASP.PHP等相比,由于其多线程运行而具有很高的执行效率.由于Servlet/JSP默认是以多线程模式执行的,所 以,在编写代码时需要非常细致地考虑多线程的安全性问题.然而,很

浅谈利用同步机制解决Java中的线程安全问题

我们知道大多数程序都不会是单线程程序,单线程程序的功能非常有限,我们假设一下所有的程序都是单线程程序,那么会带来怎样的结果呢?假如淘宝是单线程程序,一直都只能一个一个用户去访问,你要在网上买东西还得等着前面千百万人挑选购买,最后心仪的商品下架或者售空......假如饿了吗是单线程程序,那么一个用户得等前面全国千万个用户点完之后才能进行点餐,那饿了吗就该倒闭了不是吗?以上两个简单的例子,就说明一个程序能进行多线程并发访问的重要性,今天就让我们去了解一下Java中多线程并发访问这个方向吧. **第一

浅析Struts1和Struts2的Action线程安全问题

[问题描述]最近公司安排我面试Java的FreshMan,面试者一般是工作1年多点的新人(这里我就装老一下,其实我也才工作3年不到),在被问及Struts1和Struts2的Action的线程安全问题的时候,大多是支支吾吾,答不出所以然.所以在这里我整理一下我个人的理解. [问题答案] 这是由于Servlet的工作原理产生的.我们先来简单回顾一下Servlet的生命周期“初始化->init->service->destroy->卸载”. 这里大家都知道,我们在web.xml里面定义

stl空间配置器线程安全问题补充

摘要 在上一篇博客<STL空间配置器那点事>简单介绍了空间配置器的基本实现 两级空间配置器处理,一级相关细节问题,同时简单描述了STL各组件之间的关系以及设计到的设计模式等. 在最后,又关于STL空间配置的效率以及空间释放时机做了简单的探讨. 线程安全问题概述 为什么会有线程安全问题? 认真学过操作系统的同学应该都知道一个问题. first--进程是系统资源分配和调度的基本单位,是操作系统结构的基础,是一个程序的运行实体,同时也是一个程序执行中线程的容器 seconed--进程中作为资源分配基

关于CoreData和SQLite多线程访问时的线程安全问题

http://www.jianshu.com/p/95db3fc4deb3 关于CoreData和SQLite多线程访问时的线程安全问题 数据库读取操作一般都是多线程访问的.在对数据进行读取时,我们要保证其当前状态不能被修改,即读取时加锁,否则就会出现数据错误混乱.IOS中常用的两种数据持久化存储方式:CoreData和SQLite,两者都需要设置线程安全,在这里以FMDB来解释对SQLite的线程安全访问. 一:FMDB的线程安全:(以读取图片为例) 1.没有线程安全的执行方式: //****