锁(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对象。
在这里插入图片描述
代码块在静态方法块中时,不能使用this对象

② synchronized 特性

  1. 互斥(排他性)
    synchronized 会起到互斥效果, 某个线程执行到某个对象的 synchronized 中时, 其他线程如果也执行到同一个对象 synchronized 就会阻塞等待
    ●进入 synchronized 修饰的代码块, 相当于加锁
    ●退出 synchronized 修饰的代码块, 相当于解锁
    在这里插入图片描述

  2. 刷新内存(解决内存可见性问题)
    synchronized 的工作过程:

  1. 获得互斥锁
  2. 从主内存拷贝变量的最新副本到工作的内存
  3. 执行代码
  4. 将更改后的共享变量的值刷新到主内存
  5. 释放互斥锁
    所以 synchronized 也能保证内存可见性. 具体代码参见后面 volatile 部分.
  1. 可重入
    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只能修饰代码

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值