锁(synchronized和lock)
使用锁是JAVA中解决线程安全问题的最主要的手段。
JAVA中的锁主要有以下两种:
1.内存锁 synchronized
2.可重入锁 Lock(ReentrantLock)
1.synchronized
① synchronized 基本使用
synchronized 的基本用法有以下 3 种:
1.修饰静态方法
2.修饰普通方法
3.修饰代码块()
import org.omg.CORBA.PUBLIC_MEMBER;
import java.net.PortUnreachableException;
/**
* 修饰静态方法
*/
public class ThreadSynchronized {
private static int number=0;
static class Counter{
//循环次数
private static int MAX_COUNT=1000000;
//++方法
public synchronized static void incr(){
for (int i = 0; i < MAX_COUNT; i++) {
number++;
}
}
//--方法
public synchronized static void decr(){
for (int i = 0; i < MAX_COUNT; i++) {
number--;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(()->{
Counter.incr();
});
t1.start();
Thread t2=new Thread(()->{
Counter.decr();
});
t2.start();
//等待线程执行结束
t1.join();
t2.join();
System.out.println("最终结果:"+number);
}
}
import org.omg.CORBA.PUBLIC_MEMBER;
import java.net.PortUnreachableException;
/**
* 修饰普通方法
*/
public class ThreadSynchronized2 {
private static int number=0;
static class Counter{
//循环次数
private static int MAX_COUNT=1000000;
//++方法
public synchronized void incr(){
for (int i = 0; i < MAX_COUNT; i++) {
number++;
}
}
//--方法
public synchronized void decr(){
for (int i = 0; i < MAX_COUNT; i++) {
number--;
}
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter=new Counter();
Thread t1=new Thread(()->{
counter.incr();
});
t1.start();
Thread t2=new Thread(()->{
counter.decr();
});
t2.start();
//等待线程执行结束
t1.join();
t2.join();
System.out.println("最终结果:"+number);
}
}
import org.omg.CORBA.PUBLIC_MEMBER;
import java.net.PortUnreachableException;
/**
* Synchronized修饰代码块
*/
public class ThreadSynchronized3{
private static int number=0;
static class Counter{
//循环次数
private static int MAX_COUNT=1000000;
//++方法
public void incr() {
for (int i = 0; i < MAX_COUNT; i++) {
synchronized (this) {
number++;
}
}
}
//--方法
public void decr() {
for (int i = 0; i < MAX_COUNT; i++) {
synchronized (this){
number--;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter=new Counter();
Thread t1=new Thread(()->{
counter.incr();
});
t1.start();
Thread t2=new Thread(()->{
counter.decr();
});
t2.start();
//等待线程执行结束
t1.join();
t2.join();
System.out.println("最终结果:"+number);
}
}
注意事项:
1.synchronized时,一定要注意,对于同一个业务的多个线程加锁对象,一定要是同一个对象(加同一把锁)。
2.synchronized修饰代码块时,代码块在静态方法块中时,不能使用this对象。
② synchronized 特性
-
互斥(排他性)
synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到同一个对象 synchronized 就会
阻塞等待
●进入 synchronized 修饰的代码块, 相当于
加锁
●退出 synchronized 修饰的代码块, 相当于
解锁
-
刷新内存(解决内存可见性问题)
synchronized 的工作过程:
- 获得互斥锁
- 从主内存拷贝变量的最新副本到工作的内存
- 执行代码
- 将更改后的共享变量的值刷新到主内存
-
释放互斥锁
所以 synchronized 也能保证内存可见性. 具体代码参见后面 volatile 部分.
-
可重入
synchronized 同步块对同一条线程来说是可重入的,不会出现自己把自己锁死的问题。
/**
* Synchronized可重入性测试
*/
public class ThreadSynchronized4 {
public static void main(String[] args) {
synchronized (ThreadSynchronized4.class){
System.out.println("当前主线程已经得到了锁");
synchronized (ThreadSynchronized4.class){
System.out.println("当前主线程再次得到了锁");
}
}
}
}
synchronized是如何实现的?(面试问题)
JVM层面synchronized是依靠监视器Monitor实现的,从操作系统的层面来看,synchronized是基于(Mutex)来实现的。
每创建一个对象,它都会内置一个隐藏对象头和ID,类里面也有对象头和ID;每一个对象都有一个内置的隐藏的对象头,对象头里面都会包含至少两个属性:是否加锁的标识、拥有当前锁的线程id。
说一下synchronized底层实现和运行原理?(非公平锁)(重量级锁)
无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁(锁升级)
监视器
监视器是一个概念或者说是一个机制,它用来保障在任何时候,只有一个线程能够执行指定区域的代码。
严格意义来说监视器和锁的概念是不同的,但很多地方也把二者相互指代。
执行流程
在 Java 中,synchronized 是
非公平锁
,也是可以重入锁。
所谓的非公平锁是指,线程获取锁的顺序不是按照访问的顺序先来先到的,而是由线程自己竞争,随机获取到锁。
可重入锁指的是,一个线程获取到锁之后,可以重复得到该锁。这些内容是理解接下来内容的前置知识。
在 HotSpot 虚拟机中,Monitor 底层是由 C++实现的,它的实现对象是ObjectMonitor,ObjectMonitor 结构体的实现如下:
ObjectMonitor::ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0; //线程的重入次数
_object = NULL;
_owner = NULL; //标识拥有该monitor的线程
_WaitSet = NULL; //等待线程组成的双向循环链表,_WaitSet是第一个节点
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; //多线程竞争锁进入时的单向链表
FreeNext = NULL ;
_EntryList = NULL ; //_owner从该双向循环链表中唤醒线程结点,_EntryList
是第一个节点
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
}
在以上代码中有几个关键的属性:
_count:记录该
线程获取锁的次数
(也就是前前后后,这个线程一共获取此锁多少次)。
_recursions:锁的
重入次数
。
_owner:The Owner 拥有者,是持有该 ObjectMonitor(监视器)对象的线程;
_EntryList:EntryList 监控集合,存放的是处于阻塞状态的线程队列,在多线程下,竞争失败的线程会进入 EntryList 队列。
_WaitSet:WaitSet 待授权集合,存放的是处于 wait 状态的线程队列,当线程执行了 wait() 方法之后,会进入 WaitSet 队列。
监视器的执行流程:
公平锁
一定执行的步骤:
1.上一个线程释放锁之后执行唤醒
2.最前面的线程从阻塞状态又切换运行
2.Lock
Lock 实现步骤:
1.创建Lock
2.加锁 lock.lock()
3.释放锁 lock.unlock()
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 手动锁(可重入锁)的基本使用
*/
public class ThreadLock {
public static void main(String[] args) {
//1.创建锁对象
Lock lock=new ReentrantLock();
//2.加锁 lock.lock()
lock.lock();
try {
System.out.println("你好,ReentrantLock");
}finally {//unlock一定要放在try...finally中
//3.释放锁 lock.unlock()
lock.unlock();
}
}
}
Lock指定锁的类型(非公平锁和公平锁)
默认情况下,会创建一个非公平锁(性能高)
传递参数会创建一个公平锁
Lock注意事项:
1.unlock操作一定要放在finally里面,因为如果不在finally里面,可能会导致锁资源永久占用的问题
2. Lock()一定要放在try之前,或者是try的首行
问题:
1.未加锁却执行了释放锁的操作
2.释放锁的错误信息会覆盖业务报错信息,从而增加调试程序和修复的复杂性
synchronized VS Lock
1.Lock更灵活。有更多方法,比如tryLock();
2.锁类型不同:Lock默认的是非公平锁,但可以指定为公平锁;Synchronized只能为非公平锁
3.调用lock方法和Synchronized 线程等待锁状态不同,lock方法会变为WATING;Synchronized 会变为BLOCKED
4.Synchronized 是JVM层面提供的锁,他是自订进行加锁和释放锁的,对于开发者是无感的;而Lock需要开发者自己进行加锁和释放锁的操作
5.Synchronized 可以修饰方法(静态方法/普通方法)和代码块;而Lock只能修饰代码