JUC高并发与线程安全(1)

我是研究僧i 提交于 2020-01-22 20:24:30

JUC高并发

1、谈谈你对volatile的理解

1.1volatile是Java虚拟机提供的轻量级的同步机制

1.1.1保证可见性

在内存模型中,线程将自身需要的对象数据从主机物理内存拷贝到线程工作内存(实际仍在物理内存,虚拟的划分约定),当线程对数据的修改经历三个步骤:

  • 从物理内存拷贝数据到工作内存
  • 在执行线程中修改数据
  • 将数据写回物理内存

数据写完后,需要及时的通知其他需要此对象的线程,“该数据已更改,请重新加载”,这就是数据可见性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8gEIEbLQ-1577715678426)(C:\Users\Kin\AppData\Roaming\Typora\typora-user-images\1577408098733.png)]

验证可见性

class Mydata{
    int num=0;
	//添加volatile关键字再次测试
    //volatile int num=0;
    public void initTo10(){
        this.num=10;
    }

    public void numPlusPlus(){
        this.num++;
    }
}

public class VolatileDemo {
    public static void main(String[] args) {
        seeOkVolatile();
    }

    public static void seeOkVolatile(){
        Mydata mydata = new Mydata();

        new Thread(()->{
            System.out.println(String.format("start thread:%s",Thread.currentThread().getName()));
            try {
                Thread.sleep(3000);
                mydata.initTo10();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(String.format("end thread:%s",Thread.currentThread().getName()));
        }).start();

        while (mydata.num==0){

        }
        System.out.println(mydata.num);
    }

}

1.1.2不保证原子性

什么是原子性?

不可分割,保证数据完整一致性,要么成功要么失败

public static void notAtomic(){
        Mydata mydata = new Mydata();

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
               for(int j=0;j<1000;j++){
                   mydata.numPlusPlus();
               }
            }).start();
        }

        while (Thread.activeCount()>2){
			Thread.yield();
        }
        System.out.println("The end result:"+mydata.num);
    }

//The end result:9192 输出一个小于10000的随机值

分析:

javap -c Mydata.class//反汇编字节码
public void numPlusPlus();
    Code:
       0: aload_0
       1: dup
       //从主内存取值
       2: getfield      #2                  // Field num:I
       5: iconst_1
       //执行加一操作
       6: iadd
       //将值写回主内存
       7: putfield      #2                  // Field num:I
      10: return
}
当多个线程执行时,拿到的初始值一致,各执执行加一操作,但是当数据写回主内存时,系统对线程执行调度,当A线程写入时B线程被挂起,此时数值更新还没有更新到B线程,紧接着B线程就覆盖刚刚更改的值,这样原子性就被破坏了

如何解决原子性?

  • 加sync(重量级)
  • 使用class AtomicInteger的方法
class Mydata{
    volatile int num=0;

    public void initTo10(){
        this.num=10;
    }

    public void numPlusPlus(){
        this.num++;
    }
    AtomicInteger atomicInteger=new AtomicInteger();
    public void addAtomic(){
        atomicInteger.getAndIncrement();
    }
}

public class VolatileDemo {
    public static void main(String[] args) {
        notAtomic();
    }
    public static void notAtomic(){
        Mydata mydata = new Mydata();

        for (int i = 0; i < 10; i++) {
            new Thread(()->{
               for(int j=0;j<1000;j++){
                   mydata.numPlusPlus();
                   mydata.addAtomic();
               }
            }).start();
        }

        while (Thread.activeCount()>2){
            Thread.yield();
        }
        System.out.println("The end result:"+mydata.num);
        System.out.println("The Atomic result:"+mydata.atomicInteger);
    }

    /*
    The end result:9254
	The Atomic result:10000
    */

为什么AtomicInteger可以解决这个问题?

1.1.3禁止指令重排

内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,作用有二:

  • 保证特定操作的执行顺序
  • 强制刷出各种CPU中的内存数据,保证某些变量的内存可见性

Volatile变量进行写操作

  • 会在写操作后面加入一条store屏障指令,禁止上面的普通写和下面的volatile写重排序
  • 将工作内存中的共享变量值刷新回到主内存(getfield),执行写操作

Volatile变量进行读操作

  • 在读操作前加入load屏障指令
  • 从主内存读取

插入屏障就是住址编译器对屏障前后进行重排

1.2谈谈JMM

  • 可见性

  • 原子性

  • 有序性

    在多线程交替执行下,编译器会对指令进行重排,这样会影响最终结果

    public class ResortSeqDemo{
        int a=0;
        boolean flag=false;
        public void method1(){
            a=1;
            flag=true;
            //多线程中,可以先执行flag,也可能先执行a=1
        }
        public void method2(){
            if(flag){//如果先执行flag=true,那么此时a=0
                a=a+5;
            }
        }
        
    }
    

1.3你在什么地方用过volatile

单例模式

public class SinglestonDemo {

    private static SinglestonDemo instance1=null;
    private static SinglestonDemo instance2=null;
    private static SinglestonDemo instance3=null;
    private volatile static SinglestonDemo instance4=null;
    public SinglestonDemo() {
        System.out.println(Thread.currentThread().getName()+"\t 这是一个构造函数");
    }

    /**
     *@Method 
     *@Description
     * 单机程序单例模式 
     *@author pandas
     *@create 2019/12/27 11:48
     *@Param []
     *@return volatile_demo.SinglestonDemo
     */
    public static SinglestonDemo getInstance1(){
        if (instance1==null){
            instance1=new SinglestonDemo();
        }
        return instance1;
    }

    //synchronized将整个函数阻塞
    public static synchronized SinglestonDemo getInstance2(){
        if (instance2==null){
            instance2=new SinglestonDemo();
        }
        return instance2;
    }

    /**
     *@Method getInstance3
     *@Description
     * DCL 双端检测锁机制,如果存在指令重排也有可能出错
     * 因为某个线程执行第一次检测时,读取到instance不为null,
     * 但是此时instance的引用对象可能没有完全初始化
     * instance3=new SinglestonDemo();的三个步骤
     * 1、memory=allocate();分配内存空间
     * 2、instance(memory);初始化对象
     * 3、instance=memory;instance指向分配的地址,此时instance!=null
     * 由于2、3之间不存在数据依赖关系,所以如果出现先执行3再执行2就会出现异常
     *@author pandas
     *@create 2019/12/27 11:39
     *@Param []
     *@return volatile_demo.SinglestonDemo
     */
    public static SinglestonDemo getInstance3(){
        if (instance3==null){
            synchronized (SinglestonDemo.class){
                if (instance3==null){
                    instance3=new SinglestonDemo();
                }
            }
        }
        return instance3;
    }
    public static SinglestonDemo getInstance4(){
        if (instance4==null){
            synchronized (SinglestonDemo.class){
                if (instance4==null){
                    instance4=new SinglestonDemo();
                }
            }
        }
        return instance4;
    }
    public static void main(String[] args) {
        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                SinglestonDemo.getInstance1();
                SinglestonDemo.getInstance2();
                SinglestonDemo.getInstance3();
            },String.valueOf(i)).start();
        }
    }
}

1.4如何保证线程安全

  • 工作内存与主内存同步延迟现象导致的可见性问题

    可以使用synchronized或volatile关键字解决

  • 指令重排导致的可见性问题和有序性问题

    利用volatile关键字解决

2、CAS你知道吗

CAS->Unsafe->cas底层原理->ABA->原子应用更新->如何规避?

CAS(atomicInteger.compareAndSet(expect,uodate))比较并交换。如果当前的值与expect期望值一致,就更新update的数据。

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);
        System.out.println(atomicInteger.compareAndSet(5,2019)+"\t this data is"+atomicInteger);
        System.out.println(atomicInteger.compareAndSet(5,2020)+"\t this data is"+atomicInteger);

    }
}
//result
true	 this data is2019
false	 this data is2019

底层原理

  • 自旋锁

  • Unsafe类,本地方法类,属于系统原语,不允许中途被中断,类中的方法直接调用操作系统底层

    //AtomicInteger l类
    public class AtomicInteger extends Number implements java.io.Serializable {
        private static final long serialVersionUID = 6214790243416807050L;
    
        // setup to use Unsafe.compareAndSwapInt for updates
        //unsafe是一个native类
        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;
    
        static {
            try {
                //获取当前对象内存偏移地址,可以直接操作
                valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }
    	//通过volatile修饰,保证数据可见性
        private volatile int value;
        
    }
    //Atomic调用Unsafe
    public final int getAndIncrement() {
            return unsafe.getAndAddInt(this, valueOffset, 1);
        }
    //unsafe类中的方法
    public final int getAndAddInt(Object var1, long var2, int var4) {
            int var5;
            do {
                //unsafe 获取当前对象,对应偏移量地址中的值
                var5 = this.getIntVolatile(var1, var2);
                //再次去取值进行比较,如果相等直接相加,否则循环去取
                //思考?会不会因为多线程抢占导致循环太久?
            } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    
            return var5;
        }
    

CAS缺点

  • while循环时间长,自旋可能会造成额外开销
  • 只能保证一个共享变量的原子操作,加锁则可以实现多语句操作
  • 引出ABA问题

3、原子类Atomiclntegerde ABA问题谈谈?原子更新引用知道吗?

在多线程中,每个线程的执行时间是不能确定的,假设A线程执行时间为10ns,B线程执行时间为2ns,在同一时间两个线程拿到相同的初始值Y,B线程在10ns内将Y改成了X更新到主内存,又将X重新改成了Y更新到主内存,此时A线程比较主内存发现等于Y。A、B线程都将执行成功,但这个过程存在问题。

如何解决ABA问题

时间戳原子引用

public class ABADemo {

    static AtomicReference<Integer> atomicReference=new AtomicReference<>(100);
    static AtomicStampedReference<Integer> atomicStampedReference=new AtomicStampedReference<>(100,1);
    public static void main(String[] args) {
        new Thread(()->{
            atomicReference.compareAndSet(100,101);
            atomicReference.compareAndSet(101,100);
        },"t1").start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+atomicReference.compareAndSet(100,2019)+"\t"+atomicReference);
        },"t2").start();

        //ABA解决方案
        new Thread(()->{
            int stamp=atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t第一次版本号"+stamp);
            //暂停3线程
            try {
                TimeUnit.SECONDS.sleep(1);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t第二次版本号"+atomicStampedReference.getStamp());

            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t第三次版本号"+atomicStampedReference.getStamp());

        },"t3").start();

        new Thread(()->{
            int stamp=atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t第一次版本号"+stamp);
            try {
                TimeUnit.SECONDS.sleep(3);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            boolean b = atomicStampedReference.compareAndSet(100, 203, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"修改成功否:"+b+"/t当前实际版本号"+atomicStampedReference.getStamp());
            System.out.println(Thread.currentThread().getName()+"\t当前最新值:"+atomicStampedReference.getReference());
        },"t4").start();


    }
}


result
t4	第一次版本号1
t3	第一次版本号1
t3	第二次版本号2
t2true	2019
t3	第三次版本号3
t4修改成功否:false/t当前实际版本号3
t4	当前最新值:100

4、集合类不安全问题

ArrayList

public class ArraylistDemo {
    public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,4));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}
0	[null, 3e1f, fac4]
1	[null, 3e1f, fac4]
9	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd]
4	[null, 3e1f, fac4, bed1, cef7, f47d]
6	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0]
12	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab]
13	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232]
14	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2]
2	[null, 3e1f, fac4, bed1]
3	[null, 3e1f, fac4]
18	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813]
5	[null, 3e1f, fac4, bed1, cef7]
17	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10]
16	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f]
Exception in thread "19" Exception in thread "22" 15	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17]
11	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c]
10	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d]
24	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20, 3c4a, b478, b91a, 02fb]
8	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011]
25	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20, 3c4a, b478, b91a, 02fb, b9fe]
26	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20, 3c4a, b478, b91a, 02fb, b9fe, 01ae]
java.util.ConcurrentModificationException
7	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c]
27	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20, 3c4a, b478, b91a, 02fb, b9fe, 01ae, b9e5]
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
23	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20, 3c4a, b478, b91a]
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
	at java.lang.String.valueOf(String.java:2994)
	at java.lang.StringBuilder.append(StringBuilder.java:131)
	at ArrayListUnsafe.ArraylistDemo.lambda$main$0(ArraylistDemo.java:19)
	at java.lang.Thread.run(Thread.java:748)
21	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20, 3c4a]java.util.ConcurrentModificationException

	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at java.util.AbstractCollection.toString(AbstractCollection.java:461)
	at java.lang.String.valueOf(String.java:2994)
20	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20]
	at java.lang.StringBuilder.append(StringBuilder.java:131)
	at ArrayListUnsafe.ArraylistDemo.lambda$main$0(ArraylistDemo.java:19)
	at java.lang.Thread.run(Thread.java:748)
29	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20, 3c4a, b478, b91a, 02fb, b9fe, 01ae, b9e5, da18, bab6]
28	[null, 3e1f, fac4, bed1, cef7, f47d, 94b0, bc6c, 1011, f3bd, a19d, ed2c, 66ab, a232, f2e2, ec17, 947f, 8813, ed10, 0c77, fa20, 3c4a, b478, b91a, 02fb, b9fe, 01ae, b9e5, da18]

故障现象

java.util.ConcurrentModificationException

故障原因

集合类并发修改异常。

如何解决

1、List list=new Vector(),数据保证,并发性能下降

public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

2、List safelist= Collections.synchronizedList(new ArrayList<>());//包装不安全的list

如何实现?

3、List safelist= new CopeAndWriteArrayList() 写时复制,读写分离思想

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
    	//加锁
        lock.lock();
        try {
            //复制原有的Array
            Object[] elements = getArray();
            //计算长度
            int len = elements.length;
            //新增一个长度的array
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //赋值
            newElements[len] = e;
            //更改当前array的引用地址
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
//完整实例
public class ArraylistDemo {
    public static void main(String[] args) {
        //不安全
        List<String> list=new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,4));
                System.out.println(Thread.currentThread().getName()+"\t"+list);
            },String.valueOf(i)).start();
        }

        //安全
        List<String> safelist= Collections.synchronizedList(new ArrayList<>());
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                safelist.add(UUID.randomUUID().toString().substring(0,4));
                System.out.println(Thread.currentThread().getName()+"\t"+safelist);
            },String.valueOf(i)).start();
        }

        List<String> copyOnWriteArrayList= new CopyOnWriteArrayList<>();
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                copyOnWriteArrayList.add(UUID.randomUUID().toString().substring(0,4));
                System.out.println(Thread.currentThread().getName()+"\t"+copyOnWriteArrayList);
            },String.valueOf(i)).start();
        }
    }
}

ArraySet

底层就是Arraylist

HashSet

底层是HashMap,Hashset关注key而非value

/**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
        map = new HashMap<>();
    }

  // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
/**
     * Adds the specified element to this set if it is not already present.
     * More formally, adds the specified element <tt>e</tt> to this set if
     * this set contains no element <tt>e2</tt> such that
     * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
     * If this set already contains the element, the call leaves the set
     * unchanged and returns <tt>false</tt>.
     *
     * @param e element to be added to this set
     * @return <tt>true</tt> if this set did not already contain the specified
     * element
     */
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

HashMap

使用ConcurrentHashMap

5、Java锁

5.1公平锁与非公平锁

/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

公平锁

多个线程按照申请锁的顺序获取锁


非公平

线程通过抢占的方式尝试获取锁,如果抢占失败就采用类似公平锁方式

优点:吞吐量比公平锁大

缺点:有可能造成 优先级反转或饥饿现象

5.2可重入锁/递归锁

指的是同一线程外层函数获取锁之后,内层递归函数任然能获取改锁的代码,在同一线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。

public class ReentranLockDemo {
    public static void main(String[] args) {

        Phone phone = new Phone();

        new Thread(()->{
            phone.sendSms();
            System.out.println("-------");
        },"t1").start();

        new Thread(()->{
            phone.sendSms();
            System.out.println("-------");
        },"t2").start();

        new Thread(()->{
            phone.sendSms_lock();
            System.out.println("-------");
        },"t3").start();

        new Thread(()->{
            phone.sendSms_lock();
            System.out.println("-------");
        },"t4").start();
    }


}

class Phone{
    //synchronized 可重入锁
    public  synchronized void sendSms(){
        System.out.println(Thread.currentThread().getId()+"\t invoke sendSms()");
        sendEmail();
    }

    public  synchronized void sendEmail(){
        System.out.println(Thread.currentThread().getId()+"\t invoke sendEmail()");
    }

    //ReentrantLock 可重入锁
    Lock lock=new ReentrantLock();
    public  synchronized void sendSms_lock(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getId()+"\t invoke sendSms()");
            sendEmail();
        }catch (Exception e){
            e.printStackTrace();
        }
        lock.unlock();
    }

    public  synchronized void sendEmail_lock(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getId()+"\t invoke sendEmail()");

        }catch (Exception e){
            e.printStackTrace();
        }
        lock.unlock();
    }
}

/*
14	 invoke sendSms()
14	 invoke sendEmail()
-------
15	 invoke sendSms()
15	 invoke sendEmail()
-------
16	 invoke sendSms()
16	 invoke sendEmail()
-------
17	 invoke sendSms()
17	 invoke sendEmail()
-------
*/

作用:避免死锁

5.3自旋锁

尝试获取锁的线程不会立即阻塞(比如wait),而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

//unsafe类中的自旋锁应用
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            //unsafe 获取当前对象,对应偏移量地址中的值
            var5 = this.getIntVolatile(var1, var2);
            //再次去取值进行比较,如果相等直接相加,否则循环去取
            //思考?会不会因为多线程抢占导致循环太久?
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
}

//
public class SpinLockDemo {
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock(){
        Thread thread=Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"\t try get lock");
        while (!atomicReference.compareAndSet(null,thread));{

        }
        System.out.println(Thread.currentThread().getName()+"\t get lock");
    }
    public void muUnlock(){
        Thread thread=Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"\t unlock");
        atomicReference.compareAndSet(thread,null);
    }
    public static void main(String[] args) {
        SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(()->{
            spinLockDemo.myLock();
            System.out.println("<<<<<");
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(">>>>>");
            spinLockDemo.muUnlock();
        },"t1").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            spinLockDemo.myLock();
            System.out.println("-----");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.muUnlock();
        },"t2").start();
    }
}
/*
t1	 try get lock
t1	 get lock
<<<<<
t2	 try get lock
>>>>>
t1	 unlock
t2	 get lock
-----
t2	 unlock

*/


5.4独占锁、共享锁、读写锁

独占锁:该锁一次只能被一个线程所有

共享锁:该锁可以被多个线程所有

读写锁:读锁可以被共享,写锁必须独占,保证一致性与并发性

class myCache{

   private volatile HashMap<String,Object> map=new HashMap<>();

   private ReentrantReadWriteLock rwlock=new ReentrantReadWriteLock();
    public void put(String key,Object value){
        System.out.println("start write:"+key);
        map.put(key, value);
        System.out.println("end write");
    }
    public void get(String key){
        System.out.println("start read");
        Object o = map.get(key);
        System.out.println("end read:"+o);
    }

    public void put_rw(String key,Object value){
        try {
            rwlock.writeLock().lock();
            System.out.println("rw start write:"+key);
            TimeUnit.MILLISECONDS.sleep(300);
            map.put(key, value);
            System.out.println("rw end write");

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            rwlock.writeLock().unlock();
        }
    }

    public void get_rw(String key){
        try {
            rwlock.readLock().lock();
            System.out.println("rw start read");
            TimeUnit.MILLISECONDS.sleep(300);
            Object o = map.get(key);
            System.out.println("rw end read:"+o);

        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            rwlock.readLock().unlock();
        }

    }
}
public class ReadWriteLock {


    public static void main(String[] args) throws InterruptedException {
        myCache myCache = new myCache();
        for (int i = 0; i <5 ; i++) {
            final int index=i;
            new Thread(()->{
                myCache.put(String.valueOf(index),index);
                myCache.get(String.valueOf(index));
            }).start();

        }

        TimeUnit.SECONDS.sleep(1);
        for (int i = 0; i <5 ; i++) {
            final int index=i;
            new Thread(()->{
                myCache.put_rw(String.valueOf(index),index);
                myCache.get_rw(String.valueOf(index));
            }).start();

        }
    }
}

/*
//没有锁的情况出现了加塞现象
start write:0
start write:2
end write
start write:1
end write
start read
start read
end read:2
end write
start write:4
start write:3
end write
start read
end read:1
end read:3
end write
start read
end read:4
start read
end read:0

//加锁后写入过程被严格控制
rw start write:0
rw end write
rw start write:1
rw end write
rw start write:2
rw end write
rw start write:3
rw end write
rw start write:4
rw end write
//加锁读过程可以实现并发
rw start read
rw start read
rw start read
rw start read
rw start read
rw end read:3
rw end read:2
rw end read:0
rw end read:4
rw end read:1
*/

5.5synchronized与lock的区别?使用新的lock有什么好处?

1、原始构成

​ synchronized是关键字属于JVM层面,monitorenter(底层是通过monitor对象来完成,wait、notify等方法也依赖与monitor对象,只有在 同步块或方法中才能调用wait、notify等方法monitorexit)

​ lock是具体类属于API层面

2、使用方法

synchronized不需要手动释放,执行完成会自动释放

ReentranLock必须手动释放锁

3、等待是否可中断

synchronized不可中断,除非抛出异常或正常结束

ReentranLockkeh可中断,设置超时方法tryLock(long timeout,TimeUnit unit),将lockInterruptibly()放入代码块中调用interrupt()方法触发中断。

4、加锁是否公平

synchronized非公平锁

ReentranLock两者都可以

5、锁绑定多个条件Condition

synchronized没有,要么随机唤醒一个,要么全部唤醒

ReentranLock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒

题目:多线程之间顺序调用,实现A->B->C的顺序启动,要求如下:

AA打印3次,BB打印2次,CC打印3次;

循环5次

class ShareOPtion{

    private int number=0;
    private ReentrantLock lock=new ReentrantLock();
    private Condition[] conditions;

    public ShareOPtion(int thread) {
        conditions=new Condition[thread];
        for (int i = 0; i < thread; i++) {
            conditions[i]=lock.newCondition();
        }
    }

    public void option(int thread,int printNum){
        try {
            lock.lock();
            while (number!=thread){
                conditions[thread].await();
            }
            number=(thread+1)%3;
            for (int i = 0; i < printNum; i++) {
                System.out.print(Thread.currentThread().getName()+"\t"+i+"\t");

            }
            System.out.println();
            conditions[(thread+1)%3].signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
public class SynchronizedDemo {

    public static void main(String[] args) {
        ShareOPtion shareOPtion = new ShareOPtion(3);

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                shareOPtion.option(0,3);
            },"AA").start();
            new Thread(()->{
                shareOPtion.option(1,2);
            },"BB").start();
            new Thread(()->{
                shareOPtion.option(2,3);
            },"CC").start();
        }

    }
}
/*
AA	0	AA	1	AA	2	
BB	0	BB	1	
CC	0	CC	1	CC	2	
AA	0	AA	1	AA	2	
BB	0	BB	1	
CC	0	CC	1	CC	2	
AA	0	AA	1	AA	2	
BB	0	BB	1	
CC	0	CC	1	CC	2	
AA	0	AA	1	AA	2	
BB	0	BB	1	
CC	0	CC	1	CC	2	
AA	0	AA	1	AA	2	
BB	0	BB	1	
CC	0	CC	1	CC	2	
*/
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!