4.JAVA线程同步机制:锁

1.概述

分类

按照 Java 虚拟机对锁的实现方式划分,Java 平台中的锁包括内部锁 (Intrinsic Lock)和显式锁 (Explicit Lock)。内部锁是通过synchronized关键字实现的;显式锁是通过java.concurrent.locks.Lock 接口的实现类(如java.concurrent.locks.ReentrantLock 类)实现的。

作用

锁能够保护共享数据以实现线程安全,其作用包括保障原子性、保障可见性和保障有序性 。

  • 锁是通过互斥保障原子性的。所谓互斥 (Mutual Exclusion), 就是指一个锁一次只能被一个线程持有。因此一个线程持有一个锁的时候,其他线程无法获得该锁,而只能等待其释放该锁后再申请。这就保证了临界区代码一次只能够被一个线程执行。因此,一个线程执行临界区期间没有其他线程能够访问相应的共享数据,这使得临界区代码所执行的操作自然而然地具有不可分割的特性,即具备了原子性 。
  • 可见性的保障是通过写线程冲刷处理器缓存和读线程刷新处理器缓存这两个动作实现的。在Java平台中,锁的获得隐含着刷新处理器缓存这个动作,这使得读线程在执行临界区代码前(获得锁之后)可以将写线程对共享变最所做的更新同步到该线程执行处理器的高速缓存中;而锁的释放隐含着冲刷处理器缓存这个动作,这使得写线程对共享变量所做的更新能够被“推送”到该线程执行处理器的高速缓存中,从而对读线程可同步。因此,锁能够保障可见性。
    锁的互斥性及其对可见性的保障合在一起,可保证临界区内的代码能够读取到共享数据的最新值。由于锁的互斥性,同一个锁所保护的共享数据一次只能够被一个线程访问,因此线程在临界区中所读取到共享数据的相对新值(锁对保障可见性的结果)同时也是最新值。
  • 锁能够保障有序性。写线程在临界区中所执行的一系列操作在读线程所执行的临界区看起来像是完全按照源代码顺序执行的,即读线程对这些操作的感知顺序与源代码顺序一致。这是锁对原子性和可见性的保障的结果。

2.内部锁:synchronized

内部锁是通过synchronized关键字实现的。线程对内部锁的申请与释放的动作由 Java 虚拟机负责代为实施,这也正是 synchronized 实现的锁被称为内部锁的原因。synchronized关键字可以用来修饰方法以及代码块(花括号"{}"包裹的代码)。synchronized关键字修饰的方法就被称为同步方法,如:

public synchronized short nextSequence(){
    if(sequence>=999){
        sequence=0;
    }else{
        sequence++;    
    }
    return sequence;
}

synchronized关键字修饰的代码块被称为同步块 (Synchronized Block), 其语法如下所示:

synchronized(锁句柄){
//在此代码块中访问共享数据
}

锁句柄是一个对象的引用(或者能够返回对象的表达式)。例如,锁句柄可以填写为this 关键字(表示当前对象)。作为锁句柄的变量通常采用final修饰 。 这是因为锁句柄变量的值一旦改变,会导致执行同一个同步块的多个线程实际上使用不同的锁,从而导致竞态。有鉴于此,通常我们会使用private修饰作为锁句柄的变量 。如:private final Object lock = new Object();

3.显式锁:Lock接口

显式锁 (Explicit Lock) 是java.util.concurrent.lcoks.Lock 接口的实例。该接口对显式锁进行了抽象,其定义的方法如图所示:
在这里插入图片描述
类java.util.concurrent.lcoks.ReentrantLock 是Lock 接口的默认实现类 。显式锁的使用方法如下:

private final Lock lock=...;//创建一个Lock接口实例
......

lock.lock();//申请lock
try{
    //在此对共享数据进行访问
    ......
}finally{
    //总是在finally块中释放锁,以避免锁泄露
    lock.unlock();//释放锁lock
}

4.显式锁与内部锁的比较

  • 内部锁是基于代码块的锁,基本无灵活性可言:要么使用它,要么不使用它,除此之外别无他选。而显式锁是基于对象的锁,其使用可以充分发挥面向对象编程的灵活性。而内部锁从代码角度看仅仅是一个关键字,它无法充分发挥面向对象编程的灵活性。比如,内部锁的申请与释放只能是在一个方法内进行(因为代码块无法跨方法),而显式锁支持在一个方法内申请锁,却在另外一个方法里释放锁。
  • 内部锁基于代码块的这个特征也使其具有一个优势:简单易用,且不会导致锁泄漏。
  • 显式锁支持了一些内部锁所不支持的特性,例如:在锁的调度方面,内部锁仅支持非公平锁,而显式锁既支持非公平锁,又支持公平锁 ;显式锁提供了一些接口(指方法)可以用来对锁的相关信息进行监控,而内部锁不支持这种特性 。
  • 性能方面:Java 1.6/1. 7 对内部锁做了一些优化,这些优化在特定情况下可以减少锁的开销 。而这些优化在 Java 1.6/1.7 中并没有运用到显式锁上 。

5.锁的适用场景

  • check-then-act 操作:一个线程读取共享数据并在此基础上决定其下一个操作是什么。
  • read-modify-write 操作:一个线程读取共享数据并在此基础上更新该数据。
  • 多个线程对多个共享数据进行更新:如果这些共享数据之间存在关联关系,那么为了保障操作的原子性我们可以考虑使用锁。例如,关于服务器的配置信息可能包括主机 IP 地址、端口号等。一个线程如果要对这些数据进行更新,则必须要保障更新操作的原子性,即主机 IP 地址和端口号总是一起被更新的,否则其他线程可能看到一个并不真实存在的主机IP地址和端口号组合所代表的服务器。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值