多线程并发编程
线程安全主要是由于多线程并发、同时操作共享变量导致的数据不一致。
至于共享变量,需要涉及到计算机体系结构的内容:
因为现代计算机都一般是设置了两级甚至三级cache。
以两级cache为例:
假设此时有两个CUP,
线程1 线程2
| |
v v
CUP1 CUP2
| |
v v
Cache1-1 Cache2-1
|
V
公用cache2
|
V
内存
假设有一个变量A,且初始只在内存中存在
当线程1读写A时,则会先读取内存中的A,并在cache1-1、cache2中都留下一个副本。
接下来,线程2对A进行操作:此时线程2只需要找cache2即可找到A,再对A进行操作,并在Cache2、cache2-1中留下副本。
这下就有问题:cache1-1与cache2-1中,A的值不同。
这就是数据不一致问题。
在java中的解决方案是:
①synchronized关键字:
在前面关于线程的创建中已经认识、使用过这个关键字(只不过那里是进行PV原子操作,这里是用其来解决共享变量不一致问题):
加上这个关键字的对象或者方法,在线程运行时,运行线程会获得该对象/方法的一个监视器锁(又称内部锁)。
只有在:(1)正常退出synchronized语句块。(2)发生异常。(3)synchronized语句块内部调用wait()方法。
这三种情况下,这个公用的监视器锁才会被释放。而且从“公用”可以看出,该锁是独占性质的。
先前只是使用,现在记录在java中为什么使用这个关键字可以解决共享变量的不一致问题:
要解决问题,先看看问题的原因是什么:不一样的并发线程读取不一样的cache。
因此,在java设计中,如果一个变量或者方法被关键字synchronized修饰,那么:
在进入synchronized语句块的时候,系统会清除所有cache中的关于该变量的信息,只有内存中保有该变量。此时使用该变量时,则只能从内存中读取。
在退出synchronized语句块的时候,系统不是将修改后的变量保存到运行cache中,而是保存到内存中。
致命缺点:
由于synchronized的作用机理(不管是用于PV原子操作,还是共享变量),所以必定会导致像线程挂起、cache读不中进而多次读写IO。
因此也就必定导致了:synchronized关键字会引起上下文切换,增大进程开销。
所以,synchronized锁一般被称作:重锁
②使用volatile关键字:
与synchronized方式的笨重名声不同,volatile是一种弱形式的同步,也可称为轻锁。
规则是:如果一个变量被声明为volatile,那么硬性规定读取时只会从内存读取,写入时直接写入到内存中。
这种硬性规定是通过底层硬件操作来实现。
与synchronized类比,volatile变量读取时相当于进入synchronized块,写入相当于退出synchronized块。
两者差异:
1)
synchronized既可用于原子操作控制,又可用于解决共享变量问题
而volatile只能用于解决共享变量问题
2)
synchronized锁上的变量同一时间只能有一个线程使用,因此上下文切换很多,很重。
volatile不会锁上变量。因此其不能用于解决原子操作问题。
volatile一般用于:
1)写入变量不依赖与当前变量本身。如果依赖,则需要取-计算-存多步操作,无法保证原子性。
2)没有锁的时候。显而易见。
③ 非阻塞式CAS:
CAS,Compare and swap
首先记录该算法作用:使用非阻塞(与synchronized对照)的方法保证原子操作,是通过硬件实现的。
可以看出,该方法主要用于实现原子操作。
在JDK中提供若干个CompareAndSwap*()方法,如
CompareAndSwapLong(Object object, long valueOffset, long expect, long update)
参数含义分别为:对象内存位置, 对象中变量的偏移量, 变量预期值, 变量新值
tip:由于在线程中不允许修改变量,所以只能通过修改对象中的变量的方式操作。
使用该操作时的具体含义为:
如果对象object中内存偏移量(可以理解为第几个)为valueOffset的变量值为except,则使用update替换旧的expect值。
因为是硬件实现的原子指令,所以一定可以保证操作过程中的原子性。
CAS中的ABA问题:
如果线程1使用了变量X,其初值为A,并且将值其修改为B。使用CAS后最后结果也的确是B。但是:
如果在线程1执行CAS操作之前、读取X之后,有一个线程2也读取了变量X并且将其先该为B,再改为A。
此时线程1再执行CAS操作,结果肯定正确,只是变量X已经被改变过了。
这就叫CAS中的ABA问题。
JDK中使用添加时间戳的方式解决这个问题。
ps:由于在使用CAS时,需要用到对象中变量的偏移量,所以需要借助使用Unsafe类。
而事实上,CAS对应的java中的所有方法都属于Unsafe类下。并且,Unsafe类中都是原子方法。
Unsafe类不能直接拿来用,需要使用特殊的方法:反射机制得到Unsafe实例来使用,直接使用会报security错误。
详情见demo2.java
④ 伪共享:
与共享相似,但不是。
在操作系统中的共享是指:变量可以供多个线程并发使用。
很显然,当一个线程在使用这个变量时,系统会将该变量锁住。而在机器中,实际上就是锁住了该变量所在的内存单元,即此时限制其他线程对
该内存单元的读写。
而在操作系统中,有字长的概念,一次取的位数,即为字长。
可以理解为:虽然我只是想锁住这个变量本身,但是实际上系统会锁住这个变量所在的一整个内存块。
假设,变量x,y共处于一块内存块中,当线程1读写x时,按理来说,线程2应该是可以读写变量y的,但是不能。
这就是伪共享的含义。
在解释,为什么x,y会放在一块:因为程序是按照定义的顺序来存放,比如一个数组,基本就是连续的几个存放在一块,这也符合程序局部性原理,
在单线程的时候,这种设计可以加速程序运行(因为可以直接用上次读的);但是在多线程中,这种设计就成为伪共享的源头。
⑤ 其他锁:
1)
乐观锁和悲观锁
乐观锁:总是不加锁,总是假设并发时不会出错。但是检测出错以及出错恢复方法有,只是不加锁
悲观锁:总是认为并发时会出错,因此直接加上锁,但是会降低并发度(如本可以同时读却因为加了悲观锁而受限)
2)
公平锁与非公平锁
公平锁是指:按照线程来的先后顺序给锁。
非公平锁:则不以先后顺序给锁。
在java中ReentrantLock类就是一种默认为非公平锁。
实例化的时候如果传入true则为公平锁。
3)
独占锁与共享锁
synchronized锁是一种独占锁,独占锁是一种悲观锁
共享锁是一种乐观锁。
4)
可重入锁
即:如果一个线程获得了一个锁,此时如果再去获取这个锁,并且可以获取到,就说明该锁是可重入锁。
synchronized就是一种可重入锁。
具体实现的时候:如果线程1获取了一个锁,则在该锁上加上一个使用者标记,并且将计数器加一(初始为0,表示,无线程占有)。
再后如果线程1还想获取该锁,则会该锁会先比较当前使用者标记与线程1是否一致,一致则准入,并让计数器加一;否则不准入。
5)
自旋锁
因为当一个线程去试图获取一个锁的时候,如果该锁正好当前被占,则该线程阻塞放弃cpu,需要执行上下文的切换。
如果该锁是自旋锁,那么该该线程不会在知道该锁被占后立即放弃cpu,而是会尝试多次之后才阻塞。
以CPU时间换取上下文切换的开销。
⑥ java中的Atomic*变量:
像AtomicLong、AtomicInt等,这些是原子变量。
称为变量,事实上它们是借助Unsafe类来实现其原子性的类。
具体是:
以AtomicLong为例,此时内部有一个long类型的value,并且使用volatile修饰(保持仅内存可见性)。
对AtomicLong的修改实际上是修改其内部的value域,
而在修改时,则是通过Unsafe中的CAS操作来实现。
可见,Atomic*类型通过将volatile与CAS结合起来,达到synchronized的效果,并且不会像synchronized那样过重:因为CAS的非阻塞性以及自旋
java中的*Adder类型:
比如LongAdder类型。
LongAdder类型的基本原理和AtomicLong一样,只不过解决了在AtomicLong中的:
高并发的时候,很多线程竞争一个Atomic*引发的频繁自旋和上下文切换。
具体设计与ThreadLocal设计时的思想一致,只不过ThreadLocal中是在每个线程内部copy一份,而Adder是在主存中开辟新的(因为只用于自加减)。
这样,在线程竞争时,不仅只是只能竞争单个base,而且还可以去竞争cell数组。
base是Atomic*中那样的设计,并且在并发度不高的时候,cell数组为null,只会竞争base,此时与Atomic*无二致。
当并发度高时,会初始化cell数组,此时就还可以竞争cell数组中的对象。最后计算总结果时是cell以及base之和。
至于Cell类型,每个Cell又也是Atomic*的设计,即通过volatile与CAS保证仅内存可见性与原子性。
① volatile关键字在解决共享变量数据不一致问题
package multiConcurrentprogram;/*
name: demo01
user: ly
Date: 2020/5/29
Time: 22:11
*/
/此示例用于:说明volatile关键字在解决共享变量数据不一致问题上的用法
public class demo01 {
static class ThreadNotSafted{
private int value;
private int time;
public void setValue(int value){
this.value = value;
}
public int getValue(){
return this.value;
}
public int getTime(){
return time;
}
public void addValue(int add){
this.value += add;
}
public void increaseSelf(){
this.time++;
}
}
static class ThreadSafted{
private volatile int value;
private volatile int time;
public void setValue(int value){
this.value = value;
}
public int getValue(){
return this.value;
}
public int getTime(){
return time;
}
public void addValue(int add){
this.value += add;
}
public void increaseSelf(){
this.time++;
}
}
public static void test1() throws InterruptedException{
int time = 10;
final ThreadNotSafted threadNotSafted = new ThreadNotSafted();
threadNotSafted.setValue(0);
for(int i = 0;i < time;i++){
Thread thread = new Thread(new Runnable() {
public void run() {
threadNotSafted.increaseSelf();
int j = ((int)(Math.random()*10));
threadNotSafted.setValue(threadNotSafted.getTime());
threadNotSafted.setValue(j);
// System.out.println("this time is "+threadNotSafted.getTime()+", the add number is "+j);
// threadNotSafted.addValue(j);
System.out.println(Thread.currentThread()+"after :"+threadNotSafted.getValue());
}
});
// thread.join();
int ran = (int)(Math.random()*10);
if(ran%2 == 0){
Thread.sleep(1000);
}
thread.start();
}
// System.out.println(threadNotSafted.getValue());
}
public static void test2(){
int time = 10;
final ThreadSafted threadSafted = new ThreadSafted();
threadSafted.setValue(0);
for(int i = 0;i < time;i++){
Thread thread = new Thread(new Runnable() {
public void run() {
synchronized (threadSafted){
threadSafted.increaseSelf();
threadSafted.setValue(threadSafted.getTime());
System.out.println(Thread.currentThread()+"after :"+threadSafted.getValue());
}
}
});
thread.start();
}
}
public static void main(String []args) throws InterruptedException{
test1();
}
}
② 操纵unsafe类。(unsafe类是实现CAS的基础)
package multiConcurrentprogram;/*
name: demo02
user: ly
Date: 2020/5/29
Time: 23:53
*/
import sun.misc.Unsafe;
import java.lang.reflect.Field;
// 测试:Unsafe类
public class demo02 {
// static final Unsafe unsafe = Unsafe.getUnsafe(); //获取UnSafe类实例 用于 test1()
private static final Unsafe unsafe; //用于test2()
private static final long stateOffSet; //变量在demo02类中的偏移量。CAS方法中要用
private volatile long state = 0; //要执行CAS操作的变量
static{
try{
// stateOffSet = unsafe.objectFieldOffset(demo02.class.getDeclaredField("state")); //利用反射机制获取声明的域 //用于test1()
//用于test2()
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true); //设置访问权限
unsafe = (Unsafe)field.get(null);
stateOffSet = unsafe.objectFieldOffset(demo02.class.getDeclaredField("state"));
}catch (Exception e){
System.out.println(e.getLocalizedMessage());
throw new Error(e);
}
}
//这种情况下会报错:SecurityException。报错位置是UnSafe实例获取的位置。原因可以查看源码。
public static void test1(){
demo02 demo02 = new demo02();
boolean success = unsafe.compareAndSwapLong(demo02, stateOffSet, 0, 1); //如果是0,就换成1
System.out.println(success);
}
//要让不报错,不能使用UnSafe静态方法获取实例的方法,要使用反射。
public static void test2(){
demo02 demo02 = new demo02();
boolean success = unsafe.compareAndSwapLong(demo02, stateOffSet, 0, 1); //如果是0,就换成1
System.out.println(success);
}
public static void main(String []args){
test2();
}
}
③ AtomicLong类使用
package multiConcurrentprogram;/*
name: demo03
user: ly
Date: 2020/5/30
Time: 9:54
*/
import org.omg.CORBA.INITIALIZE;
import java.lang.reflect.Array;
import java.util.concurrent.atomic.AtomicLong;
// 实例:AtomicLong类使用
public class demo03 {
private static AtomicLong atomicLong = new AtomicLong();
private static Integer []array1 = new Integer[]{0,1,5,0,6,0,8,7,0,6};
private static Integer []array2 = new Integer[]{8,5,0,4,6,3,8,9,1,0,0};
public static void main(String []args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
public void run() {
for(int i = 0;i < array1.length;i++){
if(array1[i] == 0){
atomicLong.incrementAndGet();
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
public void run() {
for(int j = 0;j < array2.length;j++){
if(array2[j] == 0){
atomicLong.incrementAndGet();
}
}
}
});
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("the result is :"+atomicLong.get());
}
}
④ 比较Atomic*与*Adder的性能
package multiConcurrentprogram;/*
name: demo04
user: ly
Date: 2020/5/30
Time: 10:42
*/
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
// 比较Atomic*与*Adder的性能差距:
// test1()测试Atomic*
// test2()测试*Adder
public class demo04 {
private static AtomicLong atomicLong = new AtomicLong();
@SuppressWarnings("unchecking")
private static LongAdder longAdder = new LongAdder();
public static void test1(){
for(int i = 0;i < 1000000;i++){
Thread thread = new Thread(new Runnable() {
public void run() {
atomicLong.incrementAndGet();
}
});
thread.start();
}
}
public static void test2(){
for(int i = 0;i < 1000000;i++){
Thread thread = new Thread(new Runnable() {
public void run() {
longAdder.increment();
}
});
}
}
public static void main(String []args) throws InterruptedException{
long start = System.currentTimeMillis();
Thread thread1 = new Thread(new Runnable() {
public void run() {
test1();
}
});
// Thread thread2 = new Thread(new Runnable() {
// public void run() {
// test2();
// }
// });
thread1.start();
// thread2.start();
thread1.join();
long end1 = System.currentTimeMillis();
// thread2.join();
// long end2 = System.currentTimeMillis();
System.out.println("Atomic* :"+(end1-start));
// System.out.println("*Adder :"+(end2-start));
System.out.println("main thread is over");
}
}