synchronized和ReentrantLock

 多用例子测试,写一下,很好理解。

synchronized和ReentrantLock都是什么?

可参考

https://stackoverflow.com/questions/11821801/why-use-a-reentrantlock-if-one-can-use-synchronizedthis

https://juejin.im/post/5aeb0a8b518825673a2066f0

https://juejin.im/post/594a24defe88c2006aa01f1c

这是一个线程不安全的例子,因为每个线程都在公用一个state,并且没有锁

public class ThreadSafeSimple {

    public int sharedState;
    public void noSafeAction() {
        while (sharedState < 10000) {
            int former = sharedState++;
            int latter = sharedState;
            if (former != latter - 1) {
                System.out.println("former " + former + " latter " + latter);
            }
        }
    }

    public static void main(String[] args) {
        ThreadSafeSimple simple = new ThreadSafeSimple();
        Thread threadA = new Thread(() -> simple.noSafeAction());

        Thread threadB = new Thread(() -> simple.noSafeAction());

        threadA.start();
        threadB.start();

    }
}

加上一个方法锁,也叫同步代码块;还有一种在非静态方法上加synchronized。显然高并发并不合适

    public void noSafeAction() {
        while (sharedState < 10000) {
            synchronized (this) {
                int former = sharedState++;
                int latter = sharedState;
                if (former != latter - 1) {
                    System.out.println("former " + former + " latter " + latter);
                }
            }

        }
    }

再来看看ReentrantLock。什么是再入?它是表示当一个线程试图获取一个它已经获取的锁时,这个获取动作就自动成功,这是对锁获取粒度的一个概念,也就是锁的持 有是以线程为单位而不是基于调用次数。Java锁实现强调再入性是为了和pthread的行为进行区分。

再入锁可以设置公平性(fairness),我们可在创建再入锁时选择是否是公平的。

ReentrantLock fairLock = new ReentrantLock(true);

这里所谓的公平性是指在竞争场景中,当公平性为真时,会倾向于将锁赋予等待时间最久的线程。公平性是减少线程“饥饿”(个别线程长期等待锁,但始终无法获取)情况发生的一 个办法。

如果使用synchronized,我们根本无法进行公平性的选择,其永远是不公平的,这也是主流操作系统线程调度的选择。通用场景中,公平性未必有想象中的那么重要,Java默认的调 度策略很少会导致 “饥饿”发生。与此同时,若要保证公平性则会引入额外开销,自然会导致一定的吞吐量下降。所以,我建议只有当你的程序确实有公平性需要的时候,才有必要指 定它。

什么叫可重入,举个例子

Lock lock = new ReentrantLock();

long f(int n) throws InterruptedException
{
    lock.lock();
    try
    {
        if (n <= 1) return 1;
        return f(n - 1) + 1;
    }
    finally {
        lock.unlock();
    }
}

The reentrantlock will return 2, if you call f(2)

实现原理

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //1. 如果该锁未被任何线程占有,该锁能被当前线程获取
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //2.若被占有,检查占有线程是否是当前线程
    else if (current == getExclusiveOwnerThread()) {
        // 3. 再次获取,计数加一
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

释放锁

protected final boolean tryRelease(int releases) {
    //1. 同步状态减1
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        //2. 只有当同步状态为0时,锁成功被释放,返回true
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 3. 锁未被完全释放,返回false
    setState(c);
    return free;
}

如何实现公平锁

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;
  }
}

这里主要是hasQueuedPredecessors(),这个主要是避免刚释放锁的线程又取到,for公平。如果强调公平锁,那就不可避免的必须频繁切换线程,上下分切换很费时间的,所以不如在逻辑上控制。

synchronized实例测试

synchronized(this|object) {} 代码块和 synchronized 修饰非静态方法获取的锁是同一个锁,即该类的对象的对象锁。

两个线程访问不同对象的 synchronized(this|object) {} 代码块和 synchronized 修饰非静态方法是异步的,同一个类的不同对象的对象锁互不干扰。

在同一对象的情况下,synchronized(类.class) {} 代码块或 synchronized 修饰静态方法和 synchronized(this|object) {} 代码块和 synchronized 修饰非静态方法的行为一致。

两个线程访问不同对象的 synchronized(类.class) {} 代码块或 synchronized 修饰静态方法还是同步的,类中 synchronized(类.class) {} 代码块和 synchronized 修饰静态方法获取的锁是类锁。对于同一个类的不同对象的类锁是同一个。
对象锁和类锁是独立的,互不干扰。
 
 

猜你喜欢

转载自www.cnblogs.com/CherryTab/p/12177995.html