java 线程下常用的几种锁机制以及CAS无锁

常见锁使用、信号量、无锁

简介

在日常开发中,多线程并发使用为了保证数据的一致性,jdk下引入了锁的机制,如我们常见的synchronized关键字和Java.util.concurrent包中的lock接口和ReentrantLock都可以实现锁的功能。

一.synchronized关键字(独享,非公平锁)

synchronized主要作用是对同步的代码加锁,使每次只有一个线程可以进入同步块。从而保证线程间的安全性。
synchronized的主要用法:
1.可以指定对象加锁

 static AccountSync instance = new AccountSync();
//volatile 使得int i的更改对所有线程进行共享
 static volatile int i = 0;
 @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            /**
             * synchronized的作用是实现线程间的同步,对同步的代码加锁,使得每一次都只能有一个线程进入同步块从而保证线程间的安全性.
             */
            synchronized (instance) {
                i++;
            }
        }
    }

改代码为对对象AccountSync进行加锁操作保证线程的一致性

2.直接用在实例方法上加锁如:

  public synchronized void increase(){
        i++;
    }
    @Override
    public void run() {
        for (int j = 0; j < 10000000; j++) {
            increase();
        }
    }

二.ReentrantLock(重入锁)

重入锁的实现是基于jdk中Java.util.concurrent包,该锁也可以取代synchronized关键字使用。
该锁有两个构造方法:

 public ReentrantLock(boolean fair);
        

改参数就表示设置该锁是否为公平锁,默认为false。
上文中的代码可以替换为

   @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            lock.lock();
            try {
                i++;
            } finally {
                lock.unlock();
            }

        }
    }

我们可以看出这种锁需要我们自己来指定何时加入锁和释放锁,因此该锁会显得更加灵活。

该锁的另一个特点就是可以多次上锁,这也是为什么叫重入锁的原因 如:

 @Override
    public void run() {
        for (int j = 0; j < 1000000; j++) {
            lock.lock();
            lock.lock();
            lock.lock();
            try {
                i++;
            } finally {
                lock.unlock();
                lock.unlock();
                lock.unlock();
            }

        }
    }

但要注意,当你加入多少个lock时,最后就要释放多少个lock,否则该线程依旧持有锁,会使得其他线程依旧阻塞。

ReentrantLock中几种常用的方法

public void lockInterruptibly() throws InterruptedException 
//锁中断 当线程等待中接收到thread.interrupt();指令时会放弃当前申请锁的等待抛出 InterruptedException

 public boolean tryLock(long timeout, TimeUnit unit)
 //设置锁申请等待的时间,超过时间返回false,如不设置时间,则立即返回true/false


Condition接口

Condition和重入锁关系密切。其中主要方法:

 void await() throws InterruptedException;
 
 void awaitUninterruptibly();
 
 long awaitNanos(long nanosTimeout) throws InterruptedException;

 boolean await(long time, TimeUnit unit) throws InterruptedException;

 void signal();

 void signalAll();

其方法和Object中的wait()/notify()用法基本一致,其中long awaitNanos()表示 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。

三.信号量(Semaphore)

信号量主要的作用就是控制多个线程同时访问某一资源的线程个数。其构造方法:

public Semaphore(int permits) //设置个数
public Semaphore(int permits, boolean fair)  //是否为公平

主要的方法有:
acquire() 获取准入许可,没有则等待,可以中断。

acquireUninterruptibly() 不响应中断,一直等下去。

tryAcquire() 尝试获取 成功返回true 失败返回false,可设置尝试时间。

release(): 访问结束后是否许可。

四.ReadWriteLock 读写锁

ReadWriteLock 读写分离锁,在读多写少的场景下可以有效的减少锁竞争,从而增加性能,
在这里插入图片描述该锁除了读与读直接不阻塞之外,其余的状态均阻塞。
上锁方式:

 private static Lock readLock = reentrantReadWriteLock.readLock();
 private static Lock writeLock = reentrantReadWriteLock.writeLock();

五. CAS 无锁

无锁机制是一种乐观的策略,认为线程之间对资源的获取不存在冲突,如果发生冲突,则重试当前操作直到没有冲突为止。
cas算法主要直接对cpu进行操作,包含了三个参数CAS(V,E,N),其中V标识更新的变量,E表示预期值,N标识新值,当E=V时,就将V设置为N,反之不进行操作。

1. 无锁类

java中无锁类在java.util.concurrent.atomic包下有许多这样的类
在这里插入图片描述

因为CAS主要是通过无限循环并通过compare对比的方式对多线程之间的数据进行选择是否修改,其中我们会注意到比较核心的代码

ublic final boolean compareAndSet(V expect, V update) {
        return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
    }

this:相当于当前的值
valueOffset:是偏移量
expect:期望值
update :要修改的新值
如果指定的值等于expect,则进行修改

unsafe.compareAndSwap是cas无锁机制的关键,unsafe类中大多方法都是用了native关键字,这就说明无锁机制的核心是操作另一个系统实现的。
有兴趣的朋友可以查阅《实战Java高并发程序设计》一书。里面很详细的介绍了并发知识以及使用。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值