栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java 锁

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java 锁

java 锁
  • 1、 synchronized
    • 1.1 、 使用方法
    • 1.2 、概念
      • 1.2.1 、偏向锁
      • 1.2.2 、轻量锁
      • 1.2.3 、重量锁
      • 1.2.4 、重入锁
      • 1.2.4 、公平、非公平锁
  • 2、 ReentrantLock
    • 2.1、 使用方法
    • 2.2 、ReentrantLock 源码
      • 2.2.1、 AbstractQueuedSynchronizer
      • 2.2.2 、非公平锁的实现
      • 2.2.3 公平锁的实现
      • 2.2.4 锁释放
      • 2.2.5 Condition

1、 synchronized

本文是基于jdk1.8,其他版本的jdk的差异暂不考虑。

1.1 、 使用方法
// 锁实例对象或者锁class
void fun(){
	synchronized (object) {
		// todo
	}
	synchronized (A.class) {
		// todo
	}
}
锁方法
void synchronized  fun(){
	 // todo
}

如果是锁对像object,则要求object是全局不可变对象,比如下面代码,虽然锁了全局的count ,但count是Integer 型,每次操作后都是一个新的对象(-128~127 是缓存的同一个对象,见 Integer valueOf(int i)) :

public class IntLockTest {
    Integer count = 0;

    public static void main(String[] args) throws InterruptedException {

        IntLockTest test = new IntLockTest();
        test.doTest();

    }

    private void doTest() throws InterruptedException {
        new Thread(new IntLockTest.IntegerRunnable()).start();
        new Thread(new IntLockTest.IntegerRunnable()).start();

        countDownLatch.await();

        System.out.println(count);
    }

    CountDownLatch countDownLatch = new CountDownLatch(2);

    class IntegerRunnable implements Runnable {

        @Override
        public void run() {
            for (int j = 0; j < 100000; j++) {
               // synchronized (IntegerRunnable.class) {
                synchronized (count) {
                    count = count + 1;
                }
            }
            countDownLatch.countDown();
        }
    }
}
1.2 、概念 1.2.1 、偏向锁

虽然在代码中使用了锁,但只有一个线程在调用。此时只会在对像的数据头中标识锁的标记,不会调用内核级别的锁。

1.2.2 、轻量锁

虽然在代码中使用了锁,也有多个线程在交替调用,但同一时间只有一个线程调用该方法,此时只会在对象的数据头中标识锁的地址,不会调用内核级别的锁。

1.2.3 、重量锁

在代码中使用了锁,有多个线程在同时调用,此时,会调用内核级别的锁。

1.2.4 、重入锁

支持多次上锁,示例

void fun(){
	synchronized (object) {
		// todo
		synchronized (object) {
			// todo
		}
	} 
}
1.2.4 、公平、非公平锁

synchronized 是java关键字,是在虚拟机内部机制处理的,本人一些小测试认为是非公平锁,不权威。

2、 ReentrantLock 2.1、 使用方法

官方示例

   class X {
        private final ReentrantLock lock = new ReentrantLock();
        // ...
        public void m() {
            lock.lock();  // block until condition holds
            try {
                // ... method body
            } finally {
                lock.unlock();
            }
        }
    }
2.2 、ReentrantLock 源码
public class ReentrantLock implements Lock, java.io.Serializable {
	private final Sync sync;
    // 内部类,实现AQS同步队列
	abstract static class Sync extends AbstractQueuedSynchronizer {
		...
	}
	
	//公平锁
	static final class FairSync extends Sync {
	}
	
	//非公平锁
	static final class NonfairSync extends Sync {
	}
	
	//默认使用非公平锁
    public ReentrantLock() {
        sync = new NonfairSync();
    } 
    
    //也可以通过策略来选择公平或者非公平锁
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    } 
}

2.2.1、 AbstractQueuedSynchronizer

AQS同步队列,是基于FIFO队列实现同步基础框架,是CLH lock 思想的一个变种。

2.2.2 、非公平锁的实现

我们默认使用 ReentrantLock lock = new ReentrantLock()得到的是非公平锁,在上锁过程中有个很重要思想:在入等待队列前,有机会就去看看能否抢到锁。
在调用lock.lock();拿锁时,会去请求非公平锁的lock();

public void lock() {
    //sync 对象目前是非公平锁对象,所以调用非公平锁类方法
	sync.lock();
}
 
    
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        
        final void lock() {
            
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                //去抢锁,抢不到,进队列,这里的1,可以看成是个信号量,
                //可重入锁每次上锁的时候,status值加1,释放锁的时候,status值减1
                acquire(1);
        }

        //AbstractQueuedSynchronizer.acquire方法调用过来,尝试抢锁
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

 public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
        //子类 Sync 调用过来
	    public final void acquire(int arg) {
  			 
	        if (!tryAcquire(arg) &&
	            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                //如果当前线程是被唤醒的,则触发下设置在本线程中的中断点,就是一个唤醒信号,通知
	            selfInterrupt();
	    }
}

 abstract static class Sync extends AbstractQueuedSynchronizer {
 
        
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
            
             
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
             
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

我们在看下 addWaiter 方法的实现

// 这个mode 就是是个null的节点
     private Node addWaiter(Node mode) {
     
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        //这个if就是通过一个cas 把当前节点设置到锁列表的尾部,设置成功就返回,失败就进入到enq方法自旋设置,直到成功
        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 (;;) {
            //首次执行的时候,tail没有值,会循环2遍,
            //第一遍是初始化head和tail为一个空的Node,
            //第二遍是把当前node 放到队列尾部
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

最后看进队列后,真正锁睡眠前以及锁被唤醒的操作

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            //中断标识,如果这个方法返回false 则不执行外面if里的触发点
            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);
        }
    }

到此,非公平锁加锁流程完了。

2.2.3 公平锁的实现

我们使用 ReentrantLock lock = new ReentrantLock(true);就能创建一个公平锁了,上锁的方式也是一样,对业务代码是无感的。

与非公平锁的区别部分,后续流程都一样

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

        final void lock() {
            //抢锁的时候,不考虑当前锁状态是否空闲,直接去往队列撞
            acquire(1);
        }

        
        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;
        }
    }
2.2.4 锁释放

我们会调用锁的unlock 方法释放锁

    public void unlock() {
        sync.release(1);
    }
    public final boolean release(int arg) {
        // tryRelease 如果释放的锁状态为初始0 了,就会去唤醒下一个Node
        if (tryRelease(arg)) { 
            //唤醒下一个Node
            Node h = head;
            // waitStatus 初始状态为0 ,在他后面的节点进入队列前会更新这个值,标识需要被唤醒
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

private void unparkSuccessor(Node node) {
        
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
         
        // 有需要唤醒的节点,唤醒它
        if (s != null)
            LockSupport.unpark(s.thread);
    }

锁队列结构

head 中不包含线程信息,在某个节点成功头的时候,代表着当前节点的线程已经在执行了,同时,在抢锁的时候,如果就一个线程,则不创建锁队列。 还有一种情况,就是锁队列里还有节点在等待,但是上个锁释放瞬间,被新进来的一个线程抢走了,那么本次唤醒就会抢不到锁,继续等待,且把head的唤醒标识符重置,等下下次唤醒。

    private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }
2.2.5 Condition

锁队列结构
head和tail 对锁队列
firstWaiter 和 lastWaiter 是等待队列

当使用Condition的 await(),节点进入等待队列,signal()唤醒,进入锁队列 等待抢锁,抢到锁后才真正执行。

await()等待源码解析

   public final void await() throws InterruptedException {
            //当前线程被中断了,直接中断异常
            if (Thread.interrupted())
                throw new InterruptedException();
            //在等待队列里,新增一个节点
            Node node = addConditionWaiter();
            
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                //检测下当前节点有没有进入锁队列,没有就挂起,等待唤醒
                LockSupport.park(this);
                // 唤醒后执行进队列操作
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //当前线程已经被唤醒,用savedState去还原锁状态,这里是已经进入了锁队列,抢锁
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

示例代码


public class ReentrantLockTest3 {
    Integer count = 0;

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(1);

        ReentrantLockTest3 test = new ReentrantLockTest3();
        test.doReentrantTest();

        countDownLatch.await();
    }

    
    private void doReentrantTest() throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        Condition consumerCondition = lock.newCondition();
        Condition producerCondition = lock.newCondition();

        //生产者,给count一个非0值,等待消费者消费,
        new Thread(() -> {
            for (; ; ) {
                try {
                    lock.lock();
                    while (count > 0) {
                        producerCondition.await();
                    }
                    count++;
                    System.out.println("生产者生产:count=" + count);

                    Thread.sleep(1000);
                    consumerCondition.signal();   
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }

        }).start();

        //消费者,消费count ,且置0 ,等待生产者生产,
        new Thread(() -> {
            for (; ; ) {
                try {
                    lock.lock();
                    while (count == 0) {
                        consumerCondition.await();
                    }
                    count--;
                    System.out.println("消费者消费:count=" + count);

                    Thread.sleep(1000);
                    producerCondition.signal();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }).start(); 
    } 
}

打印结果

生产者生产:count=1
消费者消费:count=0
生产者生产:count=1
消费者消费:count=0
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/332417.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号