java多线程——并发数据不一致java中的解决方案

多线程并发编程

线程安全主要是由于多线程并发、同时操作共享变量导致的数据不一致
至于共享变量,需要涉及到计算机体系结构的内容:
因为现代计算机都一般是设置了两级甚至三级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

tips:volatile还可以解决读后读、写后写、读后写冲突。即解决指令重排序问题。

④ 伪共享:
与共享相似,但不是。
在操作系统中的共享是指:变量可以供多个线程并发使用。
很显然,当一个线程在使用这个变量时,系统会将该变量锁住。而在机器中,实际上就是锁住了该变量所在的内存单元,即此时限制其他线程对
该内存单元的读写。
而在操作系统中,有字长的概念,一次取的位数,即为字长。
可以理解为:虽然我只是想锁住这个变量本身,但是实际上系统会锁住这个变量所在的一整个内存块。
假设,变量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");
    }
}

 

你可能感兴趣的:(java多线程,多线程,java,并发编程)