【多线程问题的解决】

2017-01-21  本文已影响0人  hello高world

需要回顾之前博文《多线程的问题》

一、 CPU对于两个冒险的解决办法

二、多线程对问题的解决办法

 package com.tinygao.thread.safe;
public class UnSafe {
    private int value;

    public int getNext() {
        return value++;
    }
}
A/B代表两个不同线程,不安全执行错误情况

<b>!!因为它具备4个特性。永远记住这四点,绝大部分只要这4点!!</b>

  • <b>有可变的状态(以下三个地方代表类是有状态的特征)</b>
    1、有类变量
    2 、有实例变量(本例子中的value属于这类)
    3、有其他对象的引用(比如map.entry对象)
    </br>

1、没有状态的类,是线程安全的 (√)
2、只要使用线程安全的类写出来的代码块一定是线程安全的(×)
==>多个安全类在一起成了复合操作了,加上没有控制顺序的手段,可能会出现不可预测的结果。
3、不可变对象一定是线程安全的(√)
==>什么是不可变对象?
1、对象创建之后其状态就不能修改。
2、对象的所有状态都是final类型
3、对象是正确创建的(this引用没有逸出)

↓对于第二问看concurrentMap是线程安全的,用了他的代码块可不是线程安全的,来举个栗子吧↓

package com.tinygao.thread.safe;

import com.google.common.collect.Maps;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * Created by gsd on 2017/1/26.
 */
@Slf4j
public class UnSafe {
    private int num;
    private Map<String, String> map = Maps.newConcurrentMap();

    public int getNumAdd() {
        return num++;
    }

    public String getMapValue() {
        if(!map.containsKey("tinygao")) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put("tinygao", Thread.currentThread().getName());
        }
        return map.get("tinygao");
    }

    public static void main(String[] args) {
        UnSafe unSafe = new UnSafe();
        ExecutorService es = Executors.newFixedThreadPool(
                2,
               new ThreadFactoryBuilder().setNameFormat("map-%d").build());

        es.submit(()->{
            log.info("map {}",unSafe.getMapValue());
        });
        es.submit(()->{
            log.info("map {}",unSafe.getMapValue());
        });
    }
}

  • 我们的本意:当第一个线程判断不存在mapkey的时候去填充这个key的值,之后的其他线程只要从map get出来这个key就可以了。

对应上面的四个特性取反:
<b>1、去可变状态</b>
<b>2、复合操作改成原子操作(记住两个常见的复合操作)</b>
-- if-then操作(像上面map的例子)
-- 取-读-写(像上面value++的例子)
<b>3、控制执行顺序,即保证有序性</b>
<b>4、若有状态,则让状态在线程间互相可见</b>
</br>

<b>2.3.1、怎么去可变状态</b>

 public int getNumAdd() {
        int num = 0;
        return num++;
    }
private ThreadLocal<Integer> num = new ThreadLocal<>();
   public int getNumAdd() {
       num.set(num.get()+1);
       return num.get();
   }
```

package com.tinygao.thread.safe;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
//这个是不安全的
@Slf4j
public class Unfinal {
private Integer lastNumber;
private Integer currentNumber;

public void setLastNumber(Integer i) {
    this.lastNumber = i;
}

public Integer getCurrentNumber(Integer i) {
    if(lastNumber == null || !lastNumber.equals(i)) {
        return null;
    }
    else {
        return 1;
    }
}

public static void main(String[] args) {
    Unfinal unfinal = new Unfinal();

    ExecutorService es = Executors.newFixedThreadPool(2);
    es.submit(()->{
        unfinal.setLastNumber(1);
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //预期打印是1,看看实际打印多少?
        log.info("get first {}", unfinal.getCurrentNumber(1));
    });

    es.submit(()->{
        unfinal.setLastNumber(2);
        //预期打印是null,看看实际打印多少?
        log.info("get seconde {}", unfinal.getCurrentNumber(1));
    });
    es.shutdown();
}

}

看一个final安全的,保证在构造函数中初始化一次状态后不可变了

package com.tinygao.thread.safe;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Slf4j
public class FinalClass {
private final Integer lastNumber;
private final Integer currentNumber;

public FinalClass(Integer lastNumber, Integer currentNumber) {
    this.lastNumber = lastNumber;
    this.currentNumber = currentNumber;
}

public Integer getCurrentNumber(Integer i) {
    if(lastNumber == null || !lastNumber.equals(i)) {
        return null;
    }
    else {
        return 1;
    }
}

public static void main(String[] args) {
    FinalClass finalclass = new FinalClass(1, 1);

    ExecutorService es = Executors.newFixedThreadPool(2);
    es.submit(()->{
        log.info("get first {}", finalclass.getCurrentNumber(1));
    });

    es.submit(()->{
        log.info("get seconde {}", finalclass.getCurrentNumber(1));
    });
    es.shutdown();
}

}


<b> 2.3.2、怎么将复合操作变成原子操作</b>
  - "读-操作-写" 使用Atomic类

private AtomicInteger num2 = new AtomicInteger(0);
public int getNumAdd2() {
return num2.incrementAndGet();
}

  - "if-then" 使用java自带的原子操作 

private Map<String, String> map = Maps.newConcurrentMap();
public void safeMap() {
map.putIfAbsent("tinygao", Thread.currentThread().getName());
}


<b> 2.3.3、怎么控制执行顺序</b>
   - 同步
    1、synchronized
    2、volatile类型的变量(但复合操作变量就有问题了)
    3、显式锁
    4、原子变量(long和double可能不是原子变量,看处理器架构)

<b> 2.3.4、怎么让状态在线程间可见</b>
   - 同步
    1、synchronized
    2、volatile类型的变量(但复合操作变量就有问题了)
    3、显式锁
    4、原子变量(long和double可能不是原子变量,看处理器架构)




采用互斥。对于共享资源访问的<b>代码片段</b>叫做临界区
有多种方法,比如锁变量(0/1)、严格轮换法

概念:管程、信号量、互斥量、同步、阻塞、协作、互斥。
https://zh.wikipedia.org/wiki/%E7%9B%A3%E8%A6%96%E5%99%A8_(%E7%A8%8B%E5%BA%8F%E5%90%8C%E6%AD%A5%E5%8C%96)
198.35.26.96 zh.wikipedia.org
待续~
上一篇 下一篇

猜你喜欢

热点阅读