Java并发编程实战(chapter_1)原子性

混混噩噩看了很多多线程的书籍,一直认为自己还不够资格去阅读这本书。有种要高登大堂的感觉,被各种网络上、朋友、同事一顿外加一顿的宣传与传颂,多多少少再自我内心中产生了一种敬畏感。2月28好开始看了之后,发现,其实完全没这个必要。除了翻译的烂之外(一大段中文下来,有时候你就会骂娘:这tm想说的是个shen me gui),所有的,多线程所必须掌握的知识点,深入点,全部涵盖其中,只能说,一书在手,万线程不愁那种!当然,你必须要全部读懂,并融汇贯通之后,才能有的效果。我推荐,看这本书的中文版本,不要和哪一段的冗长的字句在那过多的纠缠,尽量一段一段的读,然后获取这一段中最重要的那句话,否则你会陷入中文阅读理解的怪圈,而怀疑你的高中语文老师是不是体育老师客串的!!我举个例子:13页第八段,我整段读了三遍硬是没想明白前面那么多的文字,是干什么用的,就是最后一句话才是核心:告诉你,线程安全性,最正规的定义应该是什么!(情允许我,向上交的几个翻译此书的,所谓的“教授”致敬,在你们的引领下,使我们的意志与忍受力更上了一个台阶,人生更加完美!)

一、多线程开发所要平衡的几个点

看了很多次的目录,外加看了第一部分,发现,要想做好多线程的开发,无非就是平衡好以下的几点

  • 安全性
  • 活跃性
    • 无限循环问题
    • 死锁问题
    • 饥饿问题
    • 活锁问题(这个还没具体的了解到)
  • 性能要求
    • 吞吐量的问题
    • 可伸缩性的问题

二、多线程开发所要关注的开发点

要想平很好以上几点,书中循序渐进的将多线程开发最应该修炼的几个点,娓娓道来:

  • 原子性
    • 先检查后执行
    • 原子类
    • 加锁机制
  • 可见性
    • 重排
    • 非64位写入问题
    • 对象的发布
    • 对象的封闭
  • 不变性

在一本国人自己写的,介绍线程工具api的书中,看到了这么一句话:外练原子,内练可见。感觉这几点如果在多线程中尤为重要。我在有赞,去年还记得上线多门店的那天凌晨,最后项目启动报一个类加载的错误,一堆人过来看问题,基德大神站在攀哥的后面,最后淡淡的说了句:已经很明显是可见性问题了,加上volatile,不行的话,我把代码吃了!!可以见得,多线程这几个点,在“居家旅行”,生活工作中是多么的常见与重要!不出问题不要紧,只要一出,就会是头痛的大问题,因为你根本不好排查根本原因在这。所以我们需要平时就练好功底,尽量避免多线程问题的出现!而不是一味的用框架啊用框架、摞代码啊摞代码!

三、原子性下面的安全问题

1. 下面代码有什么问题呢?

public class UnsafeConuntingFactorizer implements Servlet{
    private long count = ;
    private long getCount(){
        return count;
    }
    public void service(ServletRequest req, ServletResponse resp){
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        ++count;
        encodeIntoResponse(resp,factor);
    }
}

思考:如何让一个普普通通的类变得线程安全呢?一个类什么叫做有状态,而什么又叫做无状态呢?

2. 解答上面代码

  • 一个请求的方法,实例都是一个,所以每次请求都会访问同一个对象
  • 每个请求,使用一个线程,这就是典型的多线程模型
  • count是一个对象状态属性,被多个线程共享
  • ++count并非一次原子操作(分成:复制count->对复制体修改->使用复制体回写count,三个步奏)
  • 多个线程有可能多次修改count值,而结果却相同

3. 使用原子类解决上面代码问题

public class UnsafeConuntingFactorizer implements Servlet{
    private final AtomicLong count = new AtomicLong();
    private long getCount(){
        return count.get();
    }
    public void service(ServletRequest req, ServletResponse resp){
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = factor(i);
        count.incrementAndGet();//使用了新的原子类的原子方法
        encodeIntoResponse(resp,factor);
    }
}

4. 原子类也不是万能的

//在复杂的场景下,使用多个原子类的对象
public class UnsafeConuntingFactorizer implements Servlet{
    private final AtomicReference<BigInteger> lastNumber 
        = new AtomicReference<BigInteger>();
    private final AtomicReference<BigInteger[]> lastFactors 
        = new AtomicReference<BigInteger[]>();


    public void service(ServletRequest req, ServletResponse resp){
        BigInteger i = extractFromRequest(req);
        if(i.equals(lastNumber.get())){//先判断再处理,并没有进行同步,not safe!
            encodeIntoResponse(resp,lastFactors.get());
        }else{
            BigInteger[] factors = factor(i);
            lastNumer.set(i);
            lastFactors.set(factors);
            encodeIntoResponse(resp, factors);
        }
    }
}

思考:什么叫做复合型操作?

5. 先列举一个我们常见的复合型操作

public class LazyInitRace {
    private ExpensiveObject instace = null;
    public ExpensiveObject getInstace(){
        if(instace == null){
            instace = new ExpensiveObject();
        }
        return instace;
    }
}

看好了,这就是我们深恶痛绝的一段代码!如果这段代码还分析不了的,对不起,出门左转~

6. 提高“先判断再处理”的警觉性

  • 如果没有同步措施,直接对一个状态进行判断,然后设值的,都是不安全的
  • if操作和下面代码快中的代码,远远不是原子的
  • 如果if判断完之后,接下来线程挂起,其他线程进入判断流程,又是同样的状态,同样进入if语句块
  • 当然,只有一个线程执行的程序,请忽略(那还叫能用的程序吗?)

7. 性能的问题来了

//在复杂的场景下,使用多个原子类的对象
public class UnsafeConuntingFactorizer implements Servlet{
    private final AtomicReference<BigInteger> lastNumber 
        = new AtomicReference<BigInteger>();
    private final AtomicReference<BigInteger[]> lastFactors 
        = new AtomicReference<BigInteger[]>();

    //这下子总算同步了!
    public synchronized void service(ServletRequest req, ServletResponse resp){
        BigInteger i = extractFromRequest(req);
        if(i.equals(lastNumber.get())){//先判断再处理,并没有进行同步,not safe!
            encodeIntoResponse(resp,lastFactors.get());
        }else{
            BigInteger[] factors = factor(i);
            lastNumer.set(i);
            lastFactors.set(factors);
            encodeIntoResponse(resp, factors);
        }
    }
}

思考:有没有种“关公挥大刀,一砍一大片”的感觉?

8. 上诉代码解析

  • 加上了synchronized关键字的确解决了多线程访问,类安全性问题
  • 可是每次都是一个线程进行计算,所有请求变成了串行
  • 请求量低于100/s其实都还能接受,可是再高的话,这就完全有问题的代码了
  • 性能问题,再网络里面,是永痕的心病~

9. 一段针对原子性、性能问题的解决方案

//在复杂的场景下,使用多个原子类的对象
public class CacheFactorizer implements Servlet{
    private BigInteger lastNumber;
    private BigInteger[] lastFactors ;
    private long hits;
    private long cacheHits;

    public synchronized long getHits(){
        return hits;
    }
    public synchronized double getCacheHitRadio(){
        return (double) cacheHits / (double) hits;
    }

    public void service(ServletRequest req, ServletResponse resp){
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = null;
        synchronized (this){
            ++hits;
            if(i.equals(lastNumber)){
                ++cacheHits;
                factors = lastFactors.clone();
            }
        }
        if (factors == null){
            factors = factor(i);
            synchronized (this){
                lastNumer = i;
                lastFactors = factors.clone();
            }
        }
        encodeIntoResponse(resp, factors);
    }
}

在修改状态值的时候,才进行加锁,平时对状态值的读操作可以不用加锁,当然,最耗时的计算过程,也是要同步的,这种情况下,才会进一步提高性能。

相关推荐