美文网首页
【多线程问题的解决】

【多线程问题的解决】

作者: hello高world | 来源:发表于2017-01-21 23:28 被阅读0次

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

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

  • 结构冒险(CPU对某一个存储器读取资源为例):
    使用同步,依赖硬件提供同步指令。
  • 数据冒险:

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

  • <b>2.1 安全问题的代码</b>
 package com.tinygao.thread.safe;
public class UnSafe {
    private int value;

    public int getNext() {
        return value++;
    }
}
  • <b>2.2 为什么不安全</b>
A/B代表两个不同线程,不安全执行错误情况

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

  • <b>有可变的状态(以下三个地方代表类是有状态的特征)</b>
    1、有类变量
    2 、有实例变量(本例子中的value属于这类)
    3、有其他对象的引用(比如map.entry对象)
    </br>
  • <b>复合操作(value++包含三个原子操作)</b>
    1、读取value
    2、value+1计算
    3、将value写入主存
    </br>

  • <b>顺序没有控制</b>
    B线程没有等待A操作完,就读取了value。导致执行两次加法,但最终结果都是10
    </br>

  • <b>状态不可见</b>
    A线程中value值的改变,对B来说不可见。线程栈内存数据是互相隔离的,看不到!

  • 为什么不安全之提问

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就可以了。
  • 事实:两个线程都判断了mapkey不存在,都各自put了一把到map上。导致mapkey的值被覆盖了。打印结果(你的可能跟我不一样):↓
    [map-1] INFO com.tinygao.thread.safe.UnSafe - map map-1
    [map-0] INFO com.tinygao.thread.safe.UnSafe - map map-0

  • <b>2.3 解决安全问题</b>

对应上面的四个特性取反:
<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();
   }
  • 让状态做到不可变
    final inti num = 1
    再看一个不安全的
```

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
待续~

相关文章

  • 谈谈对于多线程的理解

    针对这个问题可以从以下几个点来说。 多线程用来解决什么问题 java怎么实现多线程 使用多线程会带来哪些问题 怎么...

  • ThreadLocal源码解析与运用(上)

    ThreadLocal需要解决的问题是什么 ThreadLocal被用来“解决多线程并发”问题 ThreadLoc...

  • iOS性能篇——并行开发其实很简单

    概览 1.多线程 1.1 简介 1.2 iOS 多线程 2.NSThread 2.1 解决多线程阻塞问题 2.2 ...

  • RxJava响应式编程

    常用名词说明 Schedulers(调度器) 解决Android主线程问题; 解决多线程线程问题 Observab...

  • 【多线程问题的解决】

    需要回顾之前博文《多线程的问题》 一、 CPU对于两个冒险的解决办法 结构冒险(CPU对某一个存储器读取资源为例)...

  • RxJava

    一、Schedulers(调度器)1.解决Android主线程问题【针对Android】2.解决多线程线程问题 二...

  • Java 基础 之 Synchronized 锁升级

    synchronize是平时用的比较多的多线程问题的解决方案,一般说存在多线程问题,加个锁,就用synchroni...

  • 中级09 - Java多线程初步

    中级09 - Java多线程初步 介绍多线程带来的问题,以及基本解决方案。 竞争条件带来的数据错误问题 死锁的原...

  • Java 基础 之 Synchronized 锁升级

    synchronize是平时用的比较多的多线程问题的解决方案,一般说存在多线程问题,加个锁,就用synchroni...

  • 创建多少线程才是合适的?

    多线程的应用场景要解决这个问题,首先要分析以下两个问题: 1 为什么要使用多线程? 使用多线程,本质上就是提升程序...

网友评论

      本文标题:【多线程问题的解决】

      本文链接:https://www.haomeiwen.com/subject/ompnbttx.html