线程安全性问题

目录

一、线程安全的源头问题

二、JMM(Java Memory Model)java内存模型提供的方案


一、线程安全的源头问题

1、可见性

cpu增加了高速缓存,均衡与内存的速度差异,这使得多个线程拿到共享资源后,分别在不同的cpu上执行,每个cpu都拥有自己的高速缓存,每个线程在不同的高速缓存里,互相之间并不可见,如:俩个线程同时拿到i=0,第一个线程在自己的缓存里写i=1,第二个线程并不知道

2、原子性

初始i=0 i++实际上分3步操作,1.获取值2.加一 3.写回内存

这使得第一个线程没有执行完+1的完整操作,就有可能切换的第二个线程操作 此时因为线程1并没有将+1写到内存,则第二个线程读取的仍是0,则最终得到的结果可能是1或2(不可能超过2)

3、有序性:编译程序优化指令的执行顺序,使得能够更加合理的利用缓存

a=b;x=1 有可能真正的执行顺序为x=1先执行

二、JMM(Java Memory Model)java内存模型提供的方案

1、Synchronize可保证原子性,可见性,有序性

  三种应用方式

Java中每个对象都可以作为锁,这是synchronized实现同步的基础

  1. 对于普通同步方法,锁是当前实例对象
  2. 对于静态同步方法,锁是当前类的Class对象
  3. 对于同步方法快,锁是Synchronized括号里配置的对象

2、Volatitle 可保证可见性,有序性,无法保证原子性

     对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。

     如果是 count++操作,使用如下实现:AtomicInteger count = new AtomicInteger();  count.addAndGet(1);
  1. 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(实现可见性)
  2. 禁止进行指令重排序。(实现有序性)
  3. volatile 不能保证原子性。x++ 这种操作不能保证原子性。虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而volatile不能提供必须的原子特性。实现正确的操作需要使x的值在操作期间保持不变(如果一个线程修改x加了1但还写入,另一个拿到后还是原来没加1之前的),而volatile变量无法实现这点。

   保证可见性:

public class ThreadTest {
    public static volatile boolean stop = false;
//加上volatile后保证可见性,子线程可以立即读到主线程更改后的值,之后结束,
//不加volatile则有可能线程一直运行着

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                while (!stop) {
                    i++;
                }
            }
        });
        thread.start();
        Thread.sleep(1000);
        stop = true;
    }
}

在 volatile 修饰的共享变量进行写操作的时候会多出 lock 前缀的指令

  • lock 前缀的指令在多核处理器下会引发两件事情。
  • 1)将当前处理器缓存行的数据写回到系统内存。
  • 2)写回内存的操作会使在其他 CPU 里缓存了该内存地址的额数据无效。

JMM提供内存屏障禁止重排序:

为了性能优化,JMM 在不改变正确语义的前提下,会允许编译器和处理器对指令序列进行重排序。JMM 提供了内存屏障阻止这种重排序。volatile 写是在前面和后面分别插入内存屏障,而 volatile 读操作是在后面插入两个内存屏障。

内存屏障说明
StoreStore 屏障禁止上面的普通写和下面的 volatile 写重排序。
StoreLoad 屏障防止上面的 volatile 写与下面可能有的 volatile 读/写重排序。
LoadLoad 屏障禁止下面所有的普通读操作和上面的 volatile 读重排序。
LoadStore 屏障禁止下面所有的普通写操作和上面的 volatile 读重排序。

 

3、Final关键字

对于final域,编译器和处理器要最受俩个重排序规则

  1. 在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这俩个操作不能重排序
  2. 初次读一个包含final域的对象引用,与随后初次读这个final域,这俩个操作不能重排序

4、Atomic保证原子性

多线程下将属性设置为atomic可以保证读取数据的一致性。因为他将保证数据只能被一个线程占用,也就是说一个线程对属性进行写操作时,会使用自旋锁锁住该属性。不允许其他的线程对其进行读取操作了

 

public class ThreadTest {
    public static AtomicInteger atomicInteger = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(atomicInteger.incrementAndGet());//最后结果一定是1000
                }
            }).start();
        }
    }
}

5、ThreadLocal线程局部变量

public class ThreadTest {
    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public static void main(String[] args) {
        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    Integer integer = threadLocal.get();
                    System.out.println(integer + 5);//运行结果永远是5,r如果公用变量是普通变量肯定会有线程安全问题
                }
            }, "Thread" + i);
        }
        for (Thread thread : threads) {
            thread.start();
        }
    }
}

ThreadLocal源码简单分析

每个Thread都维护一个只属于本线程的ThreadLocalMap,当在一个线程中使用ThreadlLocal类型变量则这个变量就被纳入

当前线程ThreadLocalMap管理,之后对此变量的操作,都在属于本线程的ThreadLocalMap里面

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);//返回当前线程的ThreadLocalMap
    if (map != null)//如果已经被初始化过,则将threadlocal对象作为key,value做为值放入ThreadLocalMap
        map.set(this, value);
    else//未被初始化过,则初始化ThreadLocalMap
        createMap(t, value);
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];//创建一个初始容量为16的Entry数组用来存放所有threadlocal类型变量
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//通过hash值确定位置存放
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

//Thread类里有个全局变量ThreadLocalMap用来存放当前线程的所有ThreadLocal类型变量
ThreadLocal.ThreadLocalMap threadLocals = null;

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {//第二个threadlocal类型的变零
        ThreadLocal<?> k = e.get();

        if (k == key) {//key相同则覆盖
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);//不存在hash冲突直接存放
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值