JAVA开发(synchronized、lock、volatile的使用与区别)

在java开发中,使用锁的场景有很多,有时候我们必须保证某块业务的原子性操作,所以就有可能需要用到的锁。所谓锁就是独享一块内存和CPU计算空间。在java代码中,涉及到使用锁的有几个关键词synchronized、lock、volatile。本节我们就来梳理梳理这几种方法的使用和区别。

 

synchronized:


synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized 翻译为中文的意思是同步,也称之为”同步锁“。
synchronized的作用是保证在同一时刻, 被修饰的代码块或方法只会有一个线程执行,以达到保证并发安全的效果。

synchronized的使用方式有三种:代码块、成员方法、静态方式

1、作用在代码块上:

		synchronized(this) {
			if(null == this.searchByOrigPhone(encryPhone)) {
				//存在相同的openId,先删除原来的信息
				this.deleteByOpenId(mUser.getOpenId());
				this.save(mUser.encryptSelf());	
			};
		}

2、作用在成员方法上:

public synchronized void method(){
// 代码
}

3、作用在静态方法上:

public static synchronized void method(){
    // 代码
}

synchronized的特点:

原子性:确保线程互斥的访问同步代码。synchronized保证只有一个线程拿到锁,进入同步代码块操作共享资源,因此具有原子性。

可见性:保证共享变量的修改能够及时可见。执行 synchronized时,会对应执行 lock 、unlock原子操作。lock操作,就会清空工作空间该变量的值;执行unlock操作之前,必须先把变量同步回主内存中。

有序性:synchronized内的代码和外部的代码禁止排序,至于内部的代码,则不会禁止排序,但是由于只有一个线程进入同步代码块,因此在同步代码块中相当于是单线程的,根据 as-if-serial 语义,即使代码块内发生了重排序,也不会影响程序执行的结果。

悲观锁:synchronized是悲观锁。每次使用共享资源时都认为会和其他线程产生竞争,所以每次使用共享资源都会上锁。

独占锁(排他锁):synchronized是独占锁(排他锁)。该锁一次只能被一个线程所持有,其他线程被阻塞。

非公平锁:synchronized是非公平锁。线程获取锁的顺序可以不按照线程的阻塞顺序。允许线程发出请求后立即尝试获取锁。

可重入锁:synchronized是可重入锁。持锁线程可以再次获取自己的内部的锁。

Lock:

Lock是个接口,有个实现类是ReentrantLock。Lock是java 1.5中引入的线程同步工具,它主要用于多线程下共享资源的控制。 

接口源码:

public interface Lock {

    /**
     * 获取锁,调用该方法的线程会获取锁,当获取到锁之后会从该方法但会
     */
    void lock();

    /**
     * 可响应中断。即在获取锁的过程中可以中断当前线程
     */
    void lockInterruptibly() throws InterruptedException;

    /**
     * 尝试非阻塞的获取锁,调用该方法之后会立即返回,如果获取到锁就返回true否则返回false
     */
    boolean tryLock();

    /**
     * 超时的获取锁,下面的三种情况会返回
     * ①当前线程在超时时间内获取到了锁
     * ②当前线程在超时时间内被中断
     * ③超时时间结束,返回false
     */
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    /**
     * 释放锁
     */
    void unlock();

    /**
     * 获取等待通知组件,该组件和当前锁绑定,当前线程只有获取到了锁才能调用组件的wait方法,调用该方法之后会释放锁
     */
    Condition newCondition();
}

Lock的使用:

Lock lock = new ReentrantLock();
lock.lock();
try{
    //处理任务
}catch(Exception ex){
     
}finally{
    lock.unlock();   //释放锁
}

volatile

volatile是Java提供的一种轻量级的同步机制。与synchronized修饰方法、代码块不同,volatile只用来修饰变量。并且与synchronized、ReentrantLock等重量级锁不同的是,volatile更轻量级,因为它不会引起线程上下文的切换和调度。

volatile的使用,以单例为例:

public class Singleton {
    // 使用volatile修饰,赋值后,其他线程能立即感知到
    private static volatile Singleton instance;
 
    private Singleton() {
    }
 
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
 
}

各种同步机制的关系和区别:
原理:
synchronized关键字属于JVM层面的,通过monitorenter,monitorexit指令实现,底层是通过monitor对象来完成,其实wait notify等方法也依赖monitor对象,只有同步块或者同步方法中才能调用wait,notify等方法;
Lock是具体类(java.utils.concurrent.locks.Lock)是API层面的锁
使用方法:
synchronized不许要显式手动的获得和释放锁,系统会自动让线程释放对锁的占用
ReectrantLock:需要显式的获得和释放锁,如果没有正确的释放锁,有可能导致死锁;
等待是否可以中断
synchronized不可以中断,除非抛出异常或者正常运行完成
ReentrantLock可以中断,1.设置超时时间tryLock(timeout),2.lockInterruptibly放在代码块中,调用interrupter()方法可以中断
加锁是否公平:
synchronzed是非公平锁
ReentrantLock两者都可以,默认为非公平锁,构造方法传入boolean值,true为公平锁,false为
ReentrantLock:实现分组唤醒需要唤醒的线程们,可以实现精确唤醒,而不是像synchronized要么随机唤醒一个线程,要么唤醒所有的线程;

与synchronized修饰方法、代码块不同,volatile只用来修饰变量。并且与synchronized、ReentrantLock等重量级锁不同的是,volatile更轻量级,因为它不会引起线程上下文的切换和调度。

按锁的性质可以将锁分为以下几种类别:

悲观锁 or 乐观锁:是否一定要锁
共享锁 or 独占锁(排他锁):是否可以有多个线程同时拿锁
公平锁 or 非公平锁:是否按阻塞顺序拿锁
可重入锁 or 不可重入锁:拿锁线程是否可以多次拿锁

-------------------------------------------------------------

乐观锁:

顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。

悲观锁:

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。

传统的MySQL关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

共享锁:

共享锁指的就是对于多个不同的事务,对同一个资源共享同一个锁。相当于对于同一把门,它拥有多个钥匙一样。就像这样,你家有一个大门,大门的钥匙有好几把,你有一把,你女朋友有一把,你们都可能通过这把钥匙进入你们家,这个就是所谓的共享锁。

公平锁:

就是很公平,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。

非公平锁:

上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式。

非公平锁的优点是可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程。缺点是处于等待队列中的线程可能会饿死,或者等很久才会获得锁。

可重入锁:

当某个线程请求一个被其他线程所持有的锁的时候,该线程会被阻塞(后面的读写锁先不考虑在内),但是像synchronized这样的内置锁是可重入的,即一个线程试图获取一个已经被该线程所持有的锁,这个请求会成功。重入以为这锁的操作粒度是线程级别而不是调用级别。我们下面说到的ReentrantLock也是可重入的,而除了支持锁的重入之外,该同步组件也支持公平的和非公平的选择。

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奋力向前123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值