解决并发问题的方法有哪些呢
用一张图说明
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