java锁相关(CAS的问题)

CAS的三大问题

上面我们在学习多线程的原子操作时提到了可以使用java里面的atomic来完成多线程的计算,但是cas也有一些问题:

1、循环+CAS,自旋的实现让所有线程处于高速运行,争抢cpu执行时间的状态。如果操作长时间不成功,会带来很大的cpu资源消耗。

2、仅针对单个变量的操作,不能用于多个变量来实现原子操作。

3、ABA问题。

ABA问题,线程1 从内存当中获取到变量V的值是A,线程2也从内存当中获取到V的值A,然后线程2将V的值修改成B, 然后线程2又将变量V的值改成了A,这时候线程1 判断变量V的值也是A,然后就进行修改成功了。

这个时候可能就会有个疑问即使我中间变化了,可我最终还是把值给改成我所期待的值,并不会有影响,这种想法只是在修改一个Integer类型时,可能中间修改过多少次并没有太大的影响,但是如果是链表的话,那么这个影响是很大的:

package com.study.cas.aba;

// 存储在栈里面元素 -- 对象
public class Node {
    public final String value;
    public Node next;

    public Node(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "value=" + value;
    }
}
package com.study.cas.aba;

import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;

// 实现一个 栈(后进先出)
public class Stack {
    // top cas无锁修改
    AtomicReference<Node> top = new AtomicReference<Node>();

    public void push(Node node) { // 入栈
        Node oldTop;
        do {
            oldTop = top.get();
            node.next = oldTop;
        }
        while (!top.compareAndSet(oldTop, node)); // CAS 替换栈顶
    }


    // 出栈 -- 取出栈顶 ,为了演示ABA效果, 增加一个CAS操作的延时
    public Node pop(int time) {

        Node newTop;
        Node oldTop;
        do {
            oldTop = top.get();
            if (oldTop == null) {   //如果没有值,就返回null
                return null;
            }
            newTop = oldTop.next;
            if (time != 0) {    //模拟延时
                LockSupport.parkNanos(1000 * 1000 * time); // 休眠指定的时间
            }
        }
        while (!top.compareAndSet(oldTop, newTop));     //将下一个节点设置为top
        return oldTop;      //将旧的Top作为值返回
    }
}
package com.study.cas.aba;

import java.util.concurrent.locks.LockSupport;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Stack stack = new Stack();

        stack.push(new Node("B"));      //B入栈
        stack.push(new Node("A"));      //A入栈

        Thread thread1 = new Thread(() -> {
            Node node = stack.pop(800);
            System.out.println(Thread.currentThread().getName() +" "+ node.toString());

            System.out.println("done...");
        });
        thread1.start();

        Thread thread2 = new Thread(() -> {
            LockSupport.parkNanos(1000 * 1000 * 300L);

            Node nodeA = stack.pop(0);      //取出A
            System.out.println(Thread.currentThread().getName()  +" "+  nodeA.toString());

            Node nodeB = stack.pop(0);      //取出B,之后B处于游离状态
            System.out.println(Thread.currentThread().getName()  +" "+  nodeB.toString());

            stack.push(new Node("D"));      //D入栈
            stack.push(new Node("C"));      //C入栈
            stack.push(nodeA);                    //A入栈

            System.out.println("done...");
        });
        thread2.start();

        LockSupport.parkNanos(1000 * 1000 * 1000 * 2L);


        System.out.println("开始遍历Stack:");
        Node node = null;
        while ((node = stack.pop(0))!=null){
            System.out.println(node.value);
        }
    }
}

可以看到,线程1想要将栈顶的A替换成B,但是线程2比1快,线程2先行将A的next替换成C、D,然后线程1进行比较,发现栈顶都是A,然后将next元素替换成B,这样,C、D就出栈了,这样就造成了数据丢失的情况。

这是因为比较条件不够充分的原因(只比较旧值),使用AtomicStampedReference加版本号可以解决这个问题,代码如下:

package com.study.cas.aba;

import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.LockSupport;

public class ConcurrentStack {
    // top cas无锁修改
    //AtomicReference<Node> top = new AtomicReference<Node>();
    AtomicStampedReference<Node> top =
            new AtomicStampedReference<>(null, 0);

    public void push(Node node) { // 入栈
        Node oldTop;
        int v;
        do {
            v = top.getStamp();
            oldTop = top.getReference();
            node.next = oldTop;
        }
        while (!top.compareAndSet(oldTop, node, v, v+1)); // CAS 替换栈顶
    }


    // 出栈 -- 取出栈顶 ,为了演示ABA效果, 增加一个CAS操作的延时
    public Node pop(int time) {

        Node newTop;
        Node oldTop;
        int v;

        do {
            v = top.getStamp();
            oldTop = top.getReference();
            if (oldTop == null) {   //如果没有值,就返回null
                return null;
            }
            newTop = oldTop.next;
            if (time != 0) {    //模拟延时
                LockSupport.parkNanos(1000 * 1000 * time); // 休眠指定的时间
            }
        }
        while (!top.compareAndSet(oldTop, newTop, v, v+1));     //将下一个节点设置为top
        return oldTop;      //将旧的Top作为值返回
    }
}

 java中锁的概念

自旋锁:是指一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断是否能够被成功获取,直到获取到锁才会退出循环。

乐观锁:假定没有冲突,在修改数据时如果发现数据和之前获取的不一致,则读最新数据,修改后重试修改。

悲观锁:假定发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁。

独享锁(写):给资源上锁,线程可以修改资源,其他线程不能再枷锁;(单写)。

共享锁(读):给资源上读锁后只能读不能写,其他线程也只能加读锁,不能加写锁(多读)

可重入锁、不可重入锁:绝大部分的锁都是可重入锁,同一个线程可以重复获得锁,可以自由进入同一把锁所同步的其他代码,不可重入相反

公平锁、非公平锁:争抢锁的顺序,如果是按先来后到,视为公平。

同步关键字synchronized

1、用于实例方法、静态方法时,隐式指定锁对象

2、用于代码块时,显式指定锁对象

3、锁的作用域:对象锁、类锁、分布式锁

4、引申:如果是多个进程,怎么办?

特性:可重入、独享、悲观锁

锁优化:锁消除(开启锁消除的参数:-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks

例如:stringbuffer用了synchronized,是线程安全的,在单线程下,重复多次append会触发jit运行时编译器的性能优化,会认为重复地加锁解锁是没有意义的,就会消除锁。

锁粗化:是一种使用锁的思想,有些情况下我们希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗,jvm也会帮我做优化(重复执行很多次)。

加锁的状态如何记录?

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值