解决并发问题的方法有哪些呢

2021-06-15  本文已影响0人  G__yuan

用一张图说明

image.png

分析上面的方式为何能解决并发问题

局部变量

其实是善用局部变量可以避免出现线程安全问题,因为局部变量是仅仅存在于每个线程的工作内存中。例如:

public void test(){
   int i = 0;
   i++;
   System.out.println(i);
}

当每个线程执行到int i = 0;这句的时候,就会在各自的工作内存中创建这个变量。如图所示:


image.png

这就是每个线程都在各自的工作内存中操作变量i,各个线程之间的i根本没有任何交集,所以也不存在并发问题

不可变对象

所谓的不可变对象,就是只要一创建,就对外的状态就是不会改变的对象。如果一个对象是恒古不变的,那么它自然就不存在多线程并发问题了。比如:String s = "hello",这里的字符串指的是 "hello",而不是指引用"hello"的这个字符串变量"s",即使这个hello字符串变量和另一个字符串world组成新的字符串:hello world ,那字符串hello也没有发生变化,这就是咱说的不可变对象。

ThreadLocal

ThreadLocal本质上也就是每个线程有自己的副本,每个线程之间是没有任何关系的互不影响。


image.png

一个命名为"i"的ThreadLocal类,它会在每个线程线程都有一个Integer对象,虽然每个线程都会从主存中把integer对象拷贝到工作内存中,但是拷贝过来的是两个对象,并不是同一个对象,其中每个对象只会被一个线程操作,所以也不存在所谓的共享变量,也就不存在线程安全问题。

CAS原子类

CAS的意思是:Compare And Swap,比较并置换,CAS机制中使用了三个基本操作数:内存地址V,旧的预期值A,要修改的新值B,只有当内存地址V所对应的值和旧的预期值A相等的时候,才会将内存地址V所对应的值修改为新值B。在java中通常以Atomic为前缀的类,基本都是采用CAS的思想。
Atomic系列使用的是一种无锁化的CAS操作,是基于乐观锁的,它的并发性能很高,可以多个线程同时执行,并且还不会出现线程安全问题。看看AtomicInteger的简单使用。

private AtomicInteger counter = new AtomicInteger(0);
    public void  atomicAdd(){
        counter.incrementAndGet();
    }

atomicAdd该方法即使多个线程调用,也不会出现线程安全问题。

看看AtomicInteger的源码:


image.png

可以看到在源码内部存在一个Unsafe的实例,Unsafe提供了硬件级别的原子操作,因为java无法直接访问到操作系统的底层的硬件,为此java使用native方法来扩展这部分的功能,其中Unsafe类就是一个操作入口。Unfase提供了几种功能,其中包括分配和释放内存,挂起和恢复线程,定位对象字段内存地址,修改对象的字段值,CAS操作。

incrementAndGet(),这方法主要用了Unsafe的getAndAddInt()方法


image.png

可以看到先用变量var 5 获取到旧的值,然后调用compareAndSwapInt()方法,通过CAS操作来对数据进行比较并置换,如果操作失败了,会进入while循环,直到操作成功。其中compareAndSwapInt方法是一个native方法,底层是通过C++实现的,它可以保证整个操作是原子性的,避免并发问题。

利用Atmoic系类的原子类进行统计计数,就不会有线程安全的问题了;例如:

public static class AccessCounter{
      AtmoicInteger account = new AtmoicInteger(0);
      public void access(){
          account.incrementAndGet();
          System.out.println("reslut:"+account.get())
      }     
}

运行情况如下:


image.png

synchronized和ReentrantLock锁的运行原理

image.png
上一篇下一篇

猜你喜欢

热点阅读