synchronized锁和ConcurrentHashMap

锁的基本使用

1.synchronized的使用:


import com.springboot.demo.common.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 锁的基本使用
 * synchronized锁的是对象
 */
@RestController
@RequestMapping("lock")
@Slf4j
public class LockController {
    Map<String ,Object>  mutexConcurrentHashMap=new ConcurrentHashMap<>();
    Map<String ,Object>  mutexHashMap=new HashMap<>();


    /**
     * 方法一
     * 将orderId放入字符常量池中,这样就可以锁住  相同字符的被认定为一样的
     * 如果是只锁string类型的orderId 每次进来都是新的对象 所以是锁不住的
     * 可以实现需求
     * 不推荐!!!
     */
    @GetMapping("/wayOne")
    public Result originalOne(String orderId){
        synchronized (orderId.intern()){
           service(orderId);
        }
        return new Result();
    }


    /**
     *   方法二
     * @param orderId
     * @return
     */
    @GetMapping("/wayTwo")
    public Result originalTwo(String orderId){

        synchronized (this){
            //如果当前代码块不加synchronized,在高并发情况下有可能同时执行当前块,导致重复消费该orderId
            Object value= mutexHashMap.get(orderId);
            if (value==null){
                value=new Object();
                mutexHashMap.put(orderId,value);

            }
        }

        synchronized (mutexHashMap){
            service(orderId);
            mutexHashMap.remove(orderId);
        }

        return new Result();
    }


    /**
     * 方法三 推荐方法
     */
    @GetMapping("/{orderId}")
    public Result placeOrder(@PathVariable String orderId){

        exec(orderId,()->{
            service(orderId);
        });

        return new Result().setCode(Result.CODE.OK);
    }


    // 推荐方法
    public void exec(String key,Runnable statement){

        //如果mutexMap中有这个key,就返回这个key对应的value,如果没有就新建这个键值对并且返回这个value
        Object mutexOrderId= mutexConcurrentHashMap.computeIfAbsent(key,k->new Object());

        synchronized (mutexOrderId){
            try{
                statement.run();
            }  finally {

                mutexConcurrentHashMap.remove(key);
            }

        }
    }

    //业务方法
    public void service(String orderId){
        try {
            log.info("正在执行编号为{}的业务",orderId);
            Thread.sleep(2000);
            log.info("执行完毕编号为{}的业务",orderId);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

2.ConcurrentHashMap的优势(分JDK1.7 和JDK1.8)

(1)JDK1.7
和 HashMap 非常类似,唯一的区别就是其中的核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性。虽然 HashEntry 中的 value 是用 volatile 关键词修饰的,但是并不能保证并发的原子性,所以 put 操作时仍然需要加锁处理。

原理上来说:ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 ReentrantLock。不会像 HashTable 那样不管是 put 还是 get 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。

(2)JDK1.8
其中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。也将 1.7 中存放数据的 HashEntry 改为 Node,但作用都是相同的。其中的 val next 都用了 volatile 修饰,保证了可见性。

put方法:

    根据 key 计算出 hashcode 。

    判断是否需要进行初始化。

    f 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。

    如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。

    如果都不满足,则利用 synchronized 锁写入数据。

    如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。

get方法

    根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。

    如果是红黑树那就按照树的方式获取值。

    就不满足那就按照链表的方式遍历获取值。

1.8 在 1.7 的数据结构上做了大的改动,采用红黑树之后可以保证查询效率(O(logn)),甚至取消了 ReentrantLock 改为了 synchronized,这样可以看出在新版的 JDK 中对 synchronized 优化是很到位的。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值