• 并发编程学习笔记之线程安全(一)


    最近在复习、整理之前学习的多线程的知识,本着燃烧自己,照亮他人的想法,把自己整理的一些关于多线程的学习笔记、心得分享给大家.

    博主准备把自己关于多线程的学习笔记写成三个部分分享给大家: 基础、实战、测试&优化

    这三个部分是一环扣一环的.

    1.基础: 多线程操作的对象必须是线程安全的,所以构建线程安全的对象是一切的基础.这一部分讲的就是如何构建线程安全的类,和一些多线程的基础知识.

    2. 实战: 构建好了线程安全的类,我们就可以用线程/线程池,去构建我们的并发程序了,如何执行任务?如何关闭线程池?如何扩展线程池?这里都会给你答案

    3. 测试&优化: 构建好的程序会不会发生死锁? 如何优化程序? 如何知道运行的结果是否正确? 这一部分会 一 一为你解答.

    好了废话不多说,本篇博客是系列的第一篇,我们来讲述一下线程安全.

     

     

    线程安全

    在多线程环境下,保证线程访问的数据的安全格外重要.编写线程安全的代码,本质上就管理状态的访问,而且通常是共享的可变的状态. 

    状态:可以理解为对象的成员变量.

    共享: 是指变量可以被多个线程访问

    可变: 是指变量的值在生命周期内可以改变.

    保证线程安全就是要在不可控制的并发访问中保护数据.

    如果对象在多线程环境下无法保证线程安全,就会导致脏数据和其他不可预期的后果

    有很多在单线程环境下运行良好的代码,在多线程环境下却有问题.例如自增操作:

    public class Increment {
        private int num = 0;
    
        public void doSomething(){
            //do something
            num++;
        }
    }

    每次调用doSomething()方法的时候,num都会执行自增操作.但是在多线程环境下,这段代码是有问题的.

    原因在于num++并不是原子操作,而是由三个离散操作组合而来的:"读-改-写",读取当前的值,加1,写入变量.

    可能会出现某一时刻,两个线程同时读到num的数值,然后分别+1,分别写入.这样其中一次计数就不存在了.

    有一个专门形容这类情况的名词,叫竞争条件

    当计算的正确性依赖于运行时相关的时序或者多线程的交替时,会产生竞争条件.

    我对竞争条件的理解就是,多个线程同时访问一段代码,因为顺序的问题,可能导致结果不正确,这就是竞争条件.

     

    "检查-再运行",也是一种竞争条件.

    public class Singleton {
        private Singleton singleton;
    
        private Singleton() {
        }
    
        public Singleton getSingleton(){
            if(singleton == null){
                singleton = new Singleton();
            }
            return singleton;
        }
    }

    看这个例子,我们把构造方法声明为private的这样就只能通过getSingleton()来获得这个对象的实例了,先检查这个对象是否被实例化了,如果没有,那就实例化并返回,但是可能同一时刻两个线程同时通过了条件判断,这样就产生了两个对象的实例.

    问题已经很清楚了,那么如何解决问题呢?

    Java提供了synchronized(同步)关键字.只要是使用了synchronized关键字修饰的方法,就被加锁了,synchronized锁是互斥锁,同一时间只能有一个线程占有锁,其他对象想要获得锁只能等到占有锁的线程释放锁.


    我们来修改一下上面的代码,使它们成为线程安全的:

    private int num = 0;
    
        public synchronized void doSomething(){
            //do something
            num++;
        }
    public synchronized void test(){
            if (state){
                //做一些事
            }else{
                // 做另外一些事
            }
        }

    好了,现在它们又是线程安全的了.

    这种方式虽然很简单,但是由于synchronized块包住的代码都会顺序的执行,有时会导致令人无法忍受的响应速度

    决定synchronized块的大小需要权衡各种设计要求,包括安全性简单性性能,其中安全性是绝对不能妥协的,而简单性和性能又是互相影响的(将整个方法声明为synchronized很简单,但是响应速度不太好,将同步块的代码缩小,可能很麻烦,但是性能变好了).

    那么在简单性和性能之间我们要如何取舍呢? 这里有个原则:  通常简单性与性能之间是相互牵制的,实现一个同步策略时,不要过早地为了性能而牺牲简单性(这是对安全性潜在的妥协).

    如有耗时长的操作(I/O啊,长时间的计算啊),切记不能放在锁里,否则可能引发活跃度(死锁)与性能(响应慢)的风险.

    下面我们再看一段代码:

     

     1 public class Employees {
     2     //程序员的等级
     3     private int level;
     4     //技能库
     5     public Map<String,String> skills;
     6 
     7     //工资
     8     private int sal;
     9 
    10     public void updateSal(String multithreading){
    11         // 如果有会多线程这个技术
    12         if (multithreading.equals(skills.get(multithreading))){
    13             //根据你的等级升职加薪操作..
    14             sal = level * sal;
    15         }else{
    16             //如果不会多线程,学习多线程,更改等级为中级
    17             skills.put(multithreading,multithreading);
    18             level = 2;
    19             //根据等级加薪,..
    20             updateSal(multithreading);
    21         }
    22     }
    23 }

     员工类有个方法,根据你会不会多线程技术来提高你的薪水,如果你的技能库里有多线程技术,执行加薪操作,如果没有会让你学习,给你的技能库加上这个技能,并且提高你的等级,但是在一些极端的情况下会出现问题,线程A走到17行添加完技能又没修改等级的时候,可能有另一个线程重新调用方法,通过了12行的验证,但是等级没有改变,执行加薪操作的时候是按照等级的过期值执行的.

    这里我们就要注意了,当不变约束涉及到多个变量的时候,要原子的更新它们.在这个方法上加锁就又可以保证这个方法是线程安全的了.

     最后给大家介绍一下原子变量atomic,使用原子变量也可以把自增操作变为原子的.

    private AtomicLong num = new AtomicLong(0);
    
        public void doSomething(){
            //do something
            num.incrementAndGet();
        }

    好了关于线程安全和锁就为大家简单的介绍到这里,博主下一篇会更新关于安全发布对象的知识,这两篇结合起来就可以帮助我们构建线程安全的类了.

    如果大家有任何疑问或者对博主有什么建议,欢迎大家留下评论.楼主一定会尽快回复.本期分享就到这里,我们下期再见吧!

                

  • 相关阅读:
    test
    【转载】ASP.NET MVC 3 —— Model远程验证
    【转载】富有客户端技术之——jQuery EasyUI
    【转载】基于ASP.NET Web Application的插件实现,附DEMO
    【转载】浅谈C#中的延迟加载(1)——善用委托
    【转载】Winform开发框架之权限管理系统
    【转载】基于我的Winform开发框架扩展而成的WCF开发框架
    [转载]10大优秀的移动Web应用程序开发框架推荐
    [转载]C#泛型列表List<T>基本用法总结
    [转载]推荐一个被大家忽视的微软的反跨站脚本库AntiXSS V3.1
  • 原文地址:https://www.cnblogs.com/xisuo/p/9761217.html
Copyright © 2020-2023  润新知