首页 > 编程语言 >java 锁

java 锁

时间:2022-11-10 11:58:41浏览次数:38  
标签:Node node 结点 java 线程 return final

1.独占锁,共享锁,

2.可重入锁,

3.公平锁,非公平锁,

4.乐观锁,悲观锁

5.互斥锁/读写锁,

6.分段锁

7.偏向锁/轻量级锁/重量级锁,

8.自旋锁

锁升级,锁降级

synchronized  中有偏向锁,轻量级锁,重量级锁,独占锁,可重入锁,乐观锁,悲观锁,互斥锁,自旋锁

synchronized的偏向锁、轻量级锁以及重量级锁是通过Java对象头实现的。

独占锁,可重入锁,乐观锁,悲观锁,互斥锁,自旋锁 解释

可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁

乐观锁在Java中的使用,是无锁编程,常常采用的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。

public class SynchronizedTest {
    private static Object lock = new Object();
    public static void main(String[] args) {
        method1();
        method2();
    }
    synchronized static void method1() {}
    synchronized static void method2() {}
}

当线程访问同步方法method1时,会在对象头(SynchronizedTest.class对象的对象头)和栈帧的锁记录中存储锁偏向的线程ID,下次该线程在进入method2,只需要判断对象头存储的线程ID是否为当前线程,而不需要进行CAS操作进行加锁和解锁(

偏向锁升级轻量级锁

当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。

轻量级锁升级为重量级锁

线程1获取轻量级锁时会先把锁对象的对象头MarkWord复制一份到线程1的栈帧中创建的用于存储锁记录的空间(称为DisplacedMarkWord),然后使用CAS把对象头中的内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址;

如果在线程1复制对象头的同时(在线程1CAS之前),线程2也准备获取锁,复制了对象头到线程2的锁记录空间中,但是在线程2CAS的时候,发现线程1已经把对象头换了,线程2的CAS失败,那么线程2就尝试使用自旋锁来等待线程1释放锁。

但是如果自旋的时间太长也不行,因为自旋是要消耗CPU的,因此自旋的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。

锁粗化

public void lockCoarseningMethod() {
    synchronized (Test.class) {
        sb.append("1");
    }

    synchronized (Test.class) {
        sb.append("2");
    }
    synchronized (Test.class) {
        sb.append("3");
    }
    synchronized (Test.class) {
        sb.append("4");
    }
}
public void lockCoarseningMethod(){
	synchronized (Test.class){
		sb.append("1");
		sb.append("2");
		sb.append("3");
		sb.append("4");
}

锁消除

    public String cancatString(String s1, String s2, String s3){
        StringBuffer sb = new StringBuffer();
        sb.append(s1);
        sb.append(s2);
        sb.append(s3);
        return sb.toString();
    }
源码:
	public synchronized StringBuffer append(String str) {
    	toStringCache = null;
    	super.append(str);
    	return this;
	}


reentrantlock  源码分析

AQS中同步等待队列的实现是一个带头尾指针(这里用指针表示引用是为了后面讲解源码时可以更直观形象,况且引用本身是一种受限的指针)且不带哨兵结点(后文中的头结点表示队列首元素结点,不是指哨兵结点)的双向链表。




/**
 * Head of the wait queue, lazily initialized.  Except for
 * initialization, it is modified only via method setHead.  Note:
 * If head exists, its waitStatus is guaranteed not to be
 * CANCELLED.
 */
private transient volatile Node head;//指向队列首元素的头指针

/**
 * Tail of the wait queue, lazily initialized.  Modified only via
 * method enq to add new wait node.
 */
private transient volatile Node tail;//指向队列尾元素的尾指针




head是头指针,指向队列的首元素;tail是尾指针,指向队列的尾元素。而队列的元素结点Node定义在AQS内部,主要有如下几个成员变量

volatile Node prev; //指向前一个结点的指针

volatile Node next; //指向后一个结点的指针
volatile Thread thread; //当前结点代表的线程
volatile int waitStatus; //等待状态




prev:指向前一个结点的指针

next:指向后一个结点的指针

thread:当前结点表示的线程,因为同步队列中的结点内部封装了之前竞争锁失败的线程,故而结点内部必然有一个对应线程实例的引用

waitStatus:对于重入锁而言,主要有3个值。0:初始化状态;-1(SIGNAL):当前结点表示的线程在释放锁后需要唤醒后续节点的线程;1(CANCELLED):在同步队列中等待的线程等待超时或者被中断,取消继续等待。

public ReentrantLock() {
    sync = new NonfairSync();
}

/**
 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
 *
 * @param fair {@code true} if this lock should use a fair ordering policy
 */
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}







static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}


final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();//获取当前线程实例
    int c = getState();//获取state变量的值,即当前锁被重入的次数
    if (c == 0) {   //state为0,说明当前锁未被任何线程持有
        if (compareAndSetState(0, acquires)) { //以cas方式获取锁
            setExclusiveOwnerThread(current);  //将当前线程标记为持有锁的线程
            return true;//获取锁成功,非重入
        }
    }
    else if (current == getExclusiveOwnerThread()) { //当前线程就是持有锁的线程,说明该锁被重入了
        int nextc = c + acquires;//计算state变量要更新的值
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);//非同步方式更新state值
        return true;  //获取锁成功,重入
    }
    return false;     //走到这里说明尝试获取锁失败
}










static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);
    }

    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */

	//不会授予访问权限,除非递归调用或没有队列没有等待的或这就是第一个加锁的。
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}
public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}








public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}




private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;//t指向当前队列的最后一个节点,队列为空则为null
        if (t == null) { // Must initialize  //队列为空
            if (compareAndSetHead(new Node())) //构造新结点,CAS方式设置为队列首元素,当head==null时更新成功
                tail = head;//尾指针指向首结点
        } else {  //队列不为空
            node.prev = t;
            if (compareAndSetTail(t, node)) { //CAS将尾指针指向当前结点,当t(原来的尾指针)==tail(当前真实的尾指针)时执行成功
                t.next = node;    //原尾结点的next指针指向当前结点
                return t;
            }
        }
    }
}







1.队列为空的情况:

因为队列为空,故head=tail=null,假设线程执行2成功,则在其执行3之前,因为tail=null,其他进入该方法的线程因为head不为null将在2处不停的失败,所以3即使没有同步也不会有线程安全问题。

2.队列不为空的情况:

假设线程执行5成功,则此时4的操作必然也是正确的(当前结点的prev指针确实指向了队列尾结点,换句话说tail指针没有改变,如若不然5必然执行失败),又因为4执行成功,当前节点在队列中的次序已经确定了,所以6何时执行对线程安全不会有任何影响,

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

标签:Node,node,结点,java,线程,return,final
From: https://www.cnblogs.com/hope-happy/p/16876571.html

相关文章

  • simpread-获取 JavaScript 对象的键 _ D 栈 - Delft Stack
    本文由简悦SimpRead转码,原文地址www.delftstack.com使用Object.keys()方法获取javascript对象的键Object.keys()函数返回一个包含javascript对象键的数组......
  • 记一次多个Java Agent同时使用的类增强冲突问题及分析
    摘要:JavaAgent技术常被用于加载class文件之前进行拦截并修改字节码,以实现对Java应用的无侵入式增强。本文分享自华为云社区《记一次多个JavaAgent同时使用的类增强冲突问......
  • 中文书籍对《人月神话》的引用(20211105更新161-165本):大师品软件、JavaScript开发框架
    ​​中文书籍对《人月神话》的引用(第001到160本)>>​​《人月神话》于1975年出版,1995年出二十周年版。自出版以来,该书被大量的书籍和文章引用,直到现在热潮不退。UMLChina摘录......
  • java :多线程实现的三种方式
    一、并行、串行、并发在了解java中多线程的三种实现方式之前,我们首先需要明白并行、串行、并发三个概念。1.并行:多个CPU同时处理多个任务;2.串行:单个CPU处理多个任务,当一......
  • 高精度加法(Java)
    题目描述高精度加法,相当于a+bproblem,不用考虑负数。输入格式分两行输入。a,b≤ 10^500输出格式输出只有一行,代表a+b 的值。思路使用数组进行模拟,如果......
  • 无法打开调试器端口(127.0.0.1:xxxx): java.net.BindException "Address already
    开启项目突然报错1099端口冲突和debug端口冲突,修改端口号没有用依旧冲突,查看端口进程为空,重新配置tomcat、重启电脑和关闭HyperV无效果。删除C:\Users\用户名\AppData\Lo......
  • 棋盘覆盖(java实现)
    棋盘覆盖问题描述在一个2k×2k个方格组成的棋盘中,恰有一个方格与其它方格不同,称该方格为一特殊方格,且称该棋盘为一特殊棋盘。在棋盘覆盖问题中,要用图示的4种不同形态的L......
  • Java对用户使用功能排序
    精确统计页面停留时长介绍页面停留时间(TimeonPage)简称Tp,是网站分析中很常见的一个指标,用于反映用户在某些页面上停留时间的长短,传统的Tp统计方法会存在一定的统计盲区......
  • ZooKeeper Java API
    ZooKeeper是一个分布式应用程序协调服务,主要用于解决分布式集群中应用系统的一致性问题。它能提供类似文件系统的目录节点树方式的数据存储,主要用途是维护和监控所存数据的......
  • Java输出SSL握手日志和查看cacerts路径
    在JAVA启动时添加下面的VM参数就可以启动握手日志了!!!-Djavax.net.debug=all另外,在debug日志中,有一个trustStoreis关键字,根据这个可以找到使用的是哪个truststor......