Java 锁机制总结

锁的种类

  • 独享锁 VS 共享锁

    • 独享锁:锁只能被一个线程持有(synchronized)
    • 共享锁:锁可以被多个程序所持有(读写锁)
  • 乐观锁 VS 悲观锁
    • 乐观锁:每次去拿数据的时候都乐观地认为别人不会修改,所以不进行加锁操作。乐观锁适用于多读的应用类型。(CAS,Atomic)

      • CAS(Compare And Swap),其思想是:我认为V的值应该为 A,如果是,那么将 V 的值更新为 B,否则不修改并告诉V的值实际为多少。这样一来当有多个线程尝试修改同一个对象时,只有一个线程能够成功修改,因为一旦有一个线程修改成功了,那么其他线程就没法满足 V 的值是 A 了。其他线程修改失败之后不会被挂起,而是再次尝试修改。
    • 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
  • 公平锁 VS 非公平锁
    • 公平锁:对于等待锁队列中的线程,按照先来先得的原则,给予锁。
    • 非公平锁:一个线程一执行到需要锁的地方,就尝试去要锁,失败了再进等待队列。这样一来,就能够大大减少唤醒线程的开销,但有可能会出现某线程饿死的情况。
      • synchronized 是非公平锁,lock 可以是公平也可以非公平。
  • 其他概念
    • 可重入锁(synchronized 和 lock):可重入性表明了锁的分配不是基于方法而是基于线程的。如:synchronized 了两个方法 method1, method2,method1 中调用了 method2,那么只要线程获得了 method1 的锁,那么它就一定能够进入 method2 而不需再申请锁。
    • 可中断锁(lockInterruptible()):对于等待锁的线程,若等待时间过长,想中断该等待过程,让该线程去做其他的事,那么就需要可中断锁。

Java 锁的状态

Java 锁状态的级别从低到高为:无锁状态、偏向锁状态、轻量级锁状态、重要级锁状态,而锁的状态是被记录在 Java 对象的对象头中。

  • Java 的对象头:由 Mark Word、指向类的指针、数组长度,其中 Mark Word 负责记录有关锁的信息

    • 32 位 JVM 中 Mark Word 存储的内容为

      锁状态 25bit 4bit 1bit (是否是偏向锁) 2bit (锁标志位)
      无锁 对象的 HashCode 分代年龄 0 01
      偏向锁 线程 ID 与 Epoch 分代年龄 1 01
      轻量级锁 指向栈中的锁记录指针 同前 同前 00
      重量级锁 指向重量级锁的指针 同前 同前 10
      GC 标记 11
  • 偏向锁、轻量级锁概念的出现,都是为了减少同步唤醒的代价,若所有锁都为重量级的,那么所有等待锁的线程必须进入阻塞态
    • 偏向锁:大多数情况下,锁并不存在被大量线程竞争,而且总是由一个线程多次重复申请,那么这时只需要让偏向锁记录该线程 ID,这样当该线程又来声请锁的时候,就能快速获得锁了。
    • 轻量级锁:存在竞争,但是不强力且持有锁的线程会很快释放锁,在这种情况下,等待锁的线程可以处于自旋状态,而不必进入阻塞状态。
  • Java 中锁的状态是一级一级往上升的,具体情况为:
    • 当未遇到同步代码块(即没有出现锁的时候),处于无锁状态。
    • 当对象被当作了同步锁,但当前只有一个线程 A 申请了该锁(没有竞争产生),那么对象的锁状态就会升级为偏向锁。
    • 当又有另一线程 B 来申请锁时,这时会检查当前占用该锁的线程 A 是否处于活跃状态或者仍然需要该锁,即是否有竞争会产生。若无,则偏向锁偏向 B,A 释放偏向锁。
    • 若有,那么偏向锁就会升级会轻量级锁。但若竞争进一步加大(有很多线程需求锁,且占用时间较长),那么锁就会进一步升级为重量级锁。

Java 锁机制的实现


synchronized

从上一节我们可以看出来,Java 中能够作为锁的只能是对象,所以 synchronized 的实现,也是通过对对象进行加锁实现的。

  • synchronized 的三种用法

    • synchronized aMethod,锁一个类的方法,可以防止多个线程同时调用同一个对象的这个方法,但是能够同时调用。
    • static synchronized aMethod,锁一个类的静态方法,可以防止多个线程同时这个类的该静态方法。
    • synchronized (object){代码块},把 object 对象当作该代码块的锁
  • synchronized 使用起来简单,但是有如下缺点
    • 不支持中断,可能造成其他线程等待很长的时间,甚至死锁。
    • 不支持读写锁,即读读操作是能够同时进行的,但是 synchronized 没法实现。

Lock

Lock 是 java.util.concurrent.locks 包里的一个接口,对应的 ReentrantLock 类实现了该接口。

  • 一般使用方法

    public class LockTest {
        private Lock alock = new ReentrantLock(); // 锁需要定义在具体使用锁的栈帧的上一层,即若在线程里定义的话,不同的线程会 new 出不同的锁,这样就不能实现同步了。
    
        public static void main(String[] args) {
            Runnable r = () -> {
                alock.lock(); // 获得锁
                try {
                    // do something
                } catch(Exception e){
    
                } finally{
                    alock.unlock();
                }
            }
            Thread test = new Thread(r);
            test.start();
        }
    }
  • 获得锁的方法
    • lock(): 获取锁,若失败,则等待
    • tryLock(long time, TimeUnit unit): 尝试获得锁,返回一个布尔值,表明是否获得到了锁。
    • lockInterruptibly(): 若某一个线程使用这个方法等待获取锁,那么可以通过 thread.interrupt() 的方法去让他中断等待,干别的事。
  • 除此之外,java.util.concurrent.locks 包内还定义有 ReadWriteLock 接口,并有多种读写锁的实现类。

volatile

一种轻量级同步机制,能够保证 volatile 修饰的变量具有可见性,但不具备原子性

  • 原子性与可见性

    • 原子性:在多线程并发的条件下,对于变量的操作是线程安全的,不会受到其他线程的干扰。Atomatic 基于底层硬件处理器提供的原子指令,保证并发时线程安全。
    • 可见性:在多线程并发的条件下,对于变量的修改,其他线程中能获取到修改后的。volatile 通过对于值的操作,会立即更新到主存中,当其他线程获取该值会从主存中获取。
  • 使用方法与 synchronized 一样,都是 Java 关键词。

Atomatic

前面提到过 Atomatic 是一种乐观锁,它是通过操作的原子性来保证自己的线程安全的,即操作是不可被打断的,且具有排他性。当某一线程进行该原子操作时,其他想要执行该操作的线程只能处于自旋状态,等待该操作完成。

  • 其实,这个给人的感觉,atomatic 有点像轻量级锁,不过这种线程的等待,不是通过软件实现的,而是通过硬件实现的(硬件上的原子操作)。
  • 不过这种依靠硬件上的原子操作实现的锁机制,导致 atomatic 的相关操作都比较基础。
  • 使用方法:参见 java.util.concurrent.atomic

原文地址:https://www.cnblogs.com/Melles/p/10657611.html

时间: 2024-10-10 17:03:52

Java 锁机制总结的相关文章

Java锁机制

JAVA并发笔记: JDK发展史: JDK1.0:提供了一个纯解释的Java虚拟机实现 JDK1.3:把Java技术体系拆分为3个方向,J2SE,J2EE,J2ME,并且Java虚拟机第一次内置了JIT JDK1.4:增加正则表达式,异常链,NIO,日志类,XML解析器和XSLT转换器等 JDK1.5:自动装箱,泛型,动态注解,枚举,可变长参数,遍历循环等,在虚拟机和API层面上,这个版本改进了Java的内存模型JMM,提供了java.util.concurrent并发包的部分 JDK1.6:对

Java锁机制(二)

CAS无锁机制 CAS:Compare and Swap,即比较再交换. Java内存模型:JMM(Java Memory Model) 在内存模型当中定义了一个主内存,所有声明的实例变量都存在于主内存当中,主内存的数据会共享给所有线程,每一个线程有一块工作内存,工作内存当中主内存数据的副本 当更新数据时,会将工作内存中的数据同步到主内存当中 CAS无锁机制:本身无锁,采用乐观锁的思想,在数据操作时对比数据是否一致,如果一致代表之前没有线程操作该数据,那么就会更新数据,如果不一致代表有县城更新则

java 锁机制

公平锁/非公平锁 可重入锁 独享锁/共享锁 互斥锁/读写锁 乐观锁/悲观锁(实现秒杀的一种解决方案) (select * from product p where  p.type=’xxxxx’  for update) 分段锁 偏向锁/轻量级锁/重量级锁 自旋锁 这些分类并不是全是指锁的状态,有的指锁的特性,有的指锁的设计, 公平锁/非公平锁 公平锁是指多个线程按照申请锁的顺序来获取锁. 非公平锁是指多个线程获取锁的顺序并不按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁.有可能,

Java锁机制总结

锁是用于控制多线程对共享资源的访问. Java中的锁可以分为内置锁与显式锁Lock.其中内置锁指synchronized关键字. Synchronized synchronized可以修饰方法或代码块(在修饰代码块时,会在编译后在代码块前后加入monitorenter和monitorexit指令,修饰方法时会在方法上加入ACC_SYNCHRONIZED访问标志),在修饰静态方法时获取到的是类锁,否则是对象锁.线程在访问方法或代码块时,必须先获得锁,否则会进入阻塞状态. synchronized在

Java多线程(二) 多线程的锁机制

当两条线程同时访问一个类的时候,可能会带来一些问题.并发线程重入可能会带来内存泄漏.程序不可控等等.不管是线程间的通讯还是线程共享数据都需要使用Java的锁机制控制并发代码产生的问题.本篇总结主要著名Java的锁机制,阐述多线程下如何使用锁机制进行并发线程沟通. 1.并发下的程序异常 先看下下面两个代码,查看异常内容. 异常1:单例模式 1 package com.scl.thread; 2 3 public class SingletonException 4 { 5 public stati

Java锁(一)之内存模型

想要了解Java锁机制.引发的线程安全问题以及数据一致性问题,有必要了解内存模型,机理机制了解清楚了,这些问题也就应声而解了. 一.主内存和工作内存 Java内存模型分为主内存和工作内存,所有的变量都存储在主内存中.每条线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量.不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要主内存来完成. 二.线程.工作内存和主内存 下面是

JAVA synchronized关键字锁机制(中)

synchronized 锁机制简单的用法,高效的执行效率使成为解决线程安全的首选. 下面总结其特性以及使用技巧,加深对其理解. 特性: 1. Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码.       2. 当一个线程同时访问object的一个synchronized(this)同步代码块时,其它线程仍然可以访问非修饰的方法或代码块.       3. 当多个线程同时访问object的synchronized(this)同步代码

Java并发编程:Concurrent锁机制解析

.title { text-align: center } .todo { font-family: monospace; color: red } .done { color: green } .tag { background-color: #eee; font-family: monospace; padding: 2px; font-size: 80%; font-weight: normal } .timestamp { color: #bebebe } .timestamp-kwd

深入浅出 Java Concurrency (9): 锁机制 part 4[转]

本小节介绍锁释放Lock.unlock(). Release/TryRelease unlock操作实际上就调用了AQS的release操作,释放持有的锁. public final boolean release(int arg) {    if (tryRelease(arg)) {        Node h = head;        if (h != null && h.waitStatus != 0)            unparkSuccessor(h);