锁(synchronized和Lock)

  • Post author:
  • Post category:其他


使用锁是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只能修饰代码



版权声明:本文为mzpqq原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。