Multi-threaded and highly concurrent (2)

(1)Volatile

The characteristics of volatile

volatile variables have the following characteristics:

  1. Visibility. Reading of a volatile variable, always able to see (any thread) last write this volatile variable.
  2. And instruction execution time of rearrangement prevents the compiler.
  3. Atomicity. Referred to here is the atomic read of any single variable volatile / write, but similar to such composite operations are not volatile ++ atomic.
package day02;

import java.util.concurrent.TimeUnit;

/ ** 
 * @author : zdc
 * @date: 2020-03-19
 */
public class _1VolatileTest {
   /* volatile*/ boolean  flag = true;
    public void m(){
        System.out.println("m start");
        while (flag){

        }
        System.out.println("m end");
    }

    public static void main(String[] args) {
        _1VolatileTest v = new _1VolatileTest();
        new Thread(v::m,"t").start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace ();
        }
        v.flag = false ;
    }
}

What is visibility?

Visibility means that when a thread modifies a shared variable, another thread can read the value of this modification.

volatile is lightweight synchronized, it ensures that the shared variable "visibility", we can simply be understood as a single reading of the volatile variables / write in a multi-processor development, as is using the same lock on these a single read / write operation done in sync. If volatile variable modifier properly, and it is lower than the cost of execution of synchronized, because it does not cause the thread context switching and scheduling. Performing the following example the effect of two classes are the same.

public class VolatileFeatureExample {
    volatile long v1 = 0L;

    public void set(long l) {
        v1 = l;
    }

    public  void getAndIncrement () {
        v1++;
    }

    public long get() {
        return v1;
    }
}
----------------------------------------------
public class VolatileFeatureExample {
    long v1 = 0L;

    public synchronized void set(long l) {
        v1 = l;
    }

    public void getAndIncrement() {
        long temp = get();
        temp += 1L;
        set(temp);
    }

    public synchronized long get() {
        return v1;
    }
}

How volatile is to achieve visibility of it?

Java memory model:

Instruction is executed in the CPU, run faster CPU, thus to reduce the overhead of reading and writing data from memory frequently between cpu and memory operation, there is a cache region

Data acquisition process:

  • Data acquired from the cache
  • Cache exists, the direct return
  • The cache does not exist
    • Data acquired data from memory
    • Data is written to the data cache
    • Data returned data

The above process, the first step will lead to consistency problems, are analyzed as follows

If memory Data have been updated, but the cache data is not updated, then return to Data cache, old data is returned.

solution:

  • Plus LOCK # lock on bus
    • Since for the CPU and other components are performed by the communication bus, if the bus plus lock LOCK #, then the other CPU that is blocking access to other components (such as memory), so that only one CPU can use this variable memory
  • Cache coherency protocol
    • In-memory data after the change occurs, while making all data in the cache of failure, when accessing the data cache, priority has failed to determine whether, if memory from the failure to obtain the latest data is written back to the cache, and returns

volatile

Shared variables are stored in the main memory between threads, each thread has a private local memory, local memory to save a copy of the thread to read and write shared variables. Therefore, there is consistency above, namely how to ensure that after the thread changes to shared variables, other threads have access to the latest shared variables.

Instruction reordering

Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性

举例说明

int i;
boolean ans;

i = 10;
ans = true;

上面的代码中,ians的赋值先后顺序由于指令重排,可能会出现ans=true时,i依然为0的情况。

 Volatile关键字

用法

  • 在变量前面加上volatile即可

作用

  • 确保共享变量的修改,对其他线程都是立即可见的
  • 禁止指令重排(即当访问or修改volatile修饰的共享变量时,确保前面的代码都执行完了)

原理和实现机制

  • 修改volatile声明的共享变量,会强制要求修改后的值写入内存,并失效其他线程的本地内存中的副本
  • 汇编之后,加入volatile关键字时,会多出一个lock前缀指令
  • 它确保指令重排序时不会把其后面的指令排到lock指令之前,也不会把前面的指令排到lock指令之后

例子::Java并发-懒汉式单例设计模式加volatile的原因

class SingletonClass{
 2     private static  SingletonClass instance = null;
 3  
 4     private SingletonClass() {}
 5  
 6     public static  SingletonClass getInstance() {
 7         if(instance==null) {
 8             synchronized ( SingletonClass.class) {
 9                 if(instance==null)
10                     instance = new  SingletonClass();//语句1
11             }
12         }
13         return instance;
14     }
15 }

上面的代码在多线程下调用可能会报错,具体报错原因:

在语句1中并不是一个原子操作,在JVM中其实是3个操作:
1.给instance分配空间、
2.调用 Singleton 的构造函数来初始化、
3.将instance对象指向分配的内存空间(instance指向分配的内存空间后就不为null了);
在JVM中的及时编译存在指令重排序的优化,也就是说不能保证1,2,3执行的顺序,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是 1-3-2,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
  通过添加volatile就可以解决这种报错,因为volatile可以保证1、2、3的执行顺序,没执行玩1、2就肯定不会执行3,也就是没有执行完1、2instance一直为空

 

锁优化:

  锁细化:不应该把锁加在整个方法上。

  锁粗化:在征用特别频繁的地方。

以对象做锁时,为使它不发生改变,应该加final。

 

(2)CAS --无锁优化 或称自旋。

package day02;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author: zdc
 * @date: 2020-03-19
 */
public class _2ActomicInteger {
    //int count=0;
    AtomicInteger count = new AtomicInteger(0);
    void m(){
        for (int i = 0; i < 10000; i++) {
         count.incrementAndGet();
         //   count++;
        }
    }

    public static void main(String[] args) {
        _2ActomicInteger test = new _2ActomicInteger();
        List<Thread> threads = new ArrayList<Thread>();
        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(test::m,"thread_"+i));
        }

        threads.forEach((t)->t.start());
        //让主线程最后运行 得到结果
        threads.forEach((t)->{
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(test.count);
    }
}

CAS算法理解 https://www.jianshu.com/p/ab2c8fce878b

对CAS的理解,CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

CAS比较与交换的伪代码可以表示为:

do{

备份旧数据;

基于旧数据构造新数据;

}while(!CAS( 内存地址,备份的旧数据,新数据 ))

 

 因为t1和t2线程都同时去访问同一变量56,所以他们会把主内存的值完全拷贝一份到自己的工作内存空间,所以t1和t2线程的预期值都为56。

假设t1在与t2线程竞争中线程t1能去更新变量的值,而其他线程都失败。(失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次发起尝试)。t1线程去更新变量值改为57,然后写到内存中。此时对于t2来说,内存值变为了57,与预期值56不一致,就操作失败了(想改的值不再是原来的值)。

CAS(比较并交换)是CPU指令级的操作,只有一步原子操作,所以非常快。而且CAS避免了请求操作系统来裁定锁的问题,不用麻烦操作系统,直接在CPU内部就搞定了.
CAS操作是CPU指令级别上的支持,中间不会被打断。
 
ABA问题:

 

 

package day02;

import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author: zdc
 * @date: 2020-03-19
 */
public class _3ABATest {
    private static AtomicInteger count = new AtomicInteger(10);

    public static void main(String[] args) {
      //10-》11-》10
new Thread(()->{ System.out.println(Thread.currentThread().getName()+"预期值是10?"+count.compareAndSet(10,11)); System.out.println(Thread.currentThread().getName()+"预期值是11?"+count.compareAndSet(11,10)); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ System.out.println (. Thread.currentThread () getName () + "expected value is 10?" + Count.compareAndSet (10, 12 )); },"B").start(); } }
An AtomicStampedReference <Integer> AtomicStampedReference = new new An AtomicStampedReference <> ( 10 , . 1 ) ; ABA solution may be added to the version number

 

Guess you like

Origin www.cnblogs.com/zdcsmart/p/12524785.html