手写java锁

手写非公平可重入锁

公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。

  • 优点:所有的线程都能得到资源,不会饿死在队列中。
  • 缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。

非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。

  • 优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
  • 缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。

不可重入锁:只判断这个锁有没有被锁上,只要被锁上申请锁的线程都会被要求等待。实现简单

可重入锁:不仅判断锁有没有被锁上,还会判断锁是谁锁上的,当就是自己锁上的时候,那么他依旧可以再次访问临界资源,并把加锁次数加一。

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;

public class MyLock implements Lock {

    // 锁的状态 0 - 未占用 1 - 占用
    AtomicInteger state = new AtomicInteger();
    // 当前占用的线程
    Thread ownerThread = null;
    // 当前等待抢锁的线程
    LinkedBlockingDeque<Thread> waiters = new LinkedBlockingDeque<>();

    @Override
    public void lock() {
        if (!tryLock()) {
            waiters.add(Thread.currentThread()); // 1. 先排队
            for (;;) {
                if (tryLock()) {
                    // 如果某一个线程抢到了锁,自己把自己从队列里面挪出来
                    waiters.poll();
                    return;
                } else {
                    LockSupport.park(); // 2. 等呗 -- 等待到何时再执行?唤醒目的是继续抢锁
                }
            }
        }

    }

    @Override
    public void unlock() {
        if (ownerThread != Thread.currentThread()) {
            throw new RuntimeException("非法调用---当前这个锁不属于你");
        }
        if (state.decrementAndGet() == 0) { //减1等0 - 0代表锁未被占用
            ownerThread = null;
            // 通知其他正在等待的线程
            Thread waiterThread = waiters.peek();// 取出队列的第一个(但队列中仍然存在)FIFO先进先出
            LockSupport.unpark(waiterThread);
        }

    }

    @Override
    public boolean tryLock() {
        // 把 0 - 1
        if (state.get() == 0) {
            // CAS 底层c++实现 保证N个线程同事操作,只有一个线程操作成功
            if (state.compareAndSet(0, 1)) {
                ownerThread = Thread.currentThread();
                return true;
            }
        } else if (ownerThread == Thread.currentThread()) { // 如果占用锁就是当前线程,则代表重入
            state.set(state.get() + 1);
            return true;
        }

        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public Condition newCondition() {
        return null;
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

}
import java.util.Date;

import java.util.concurrent.locks.ReentrantLock;

public class LockDemo {
//   static Lock lock = new ReentrantLock(); //默认非公平
    static MyLock lock = new MyLock();
//  new ReentrantLock(true); 公平锁
    public static void main(String[] args) throws InterruptedException {
        // 主线程获取到锁
        lock.lock(); // 其他没有获取到锁 阻塞 卡住不动
        System.out.println("主线程获取到锁 其他线程卡住不动 " + Thread.currentThread().getName() + new Date());
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "---子线程获取到锁---");
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "---子线程第2次获取到锁---");
                lock.unlock();
                System.out.println(Thread.currentThread().getName() + "---子线程释放锁---");
                lock.unlock();
                System.out.println(Thread.currentThread().getName() + "---子线程第2次释放锁---");
            }
        });
        thread.start();
        // 主线程睡眠3s
        Thread.sleep(3000L);
        System.out.println("3s后主线程释放锁 其他线程可以开始抢锁 " + Thread.currentThread().getName() + new Date());
        lock.unlock();
    }
}

打印结果

主线程获取到锁 其他线程卡住不动 mainSat May 23 23:16:22 CST 2020
3s后主线程释放锁 其他线程可以开始抢锁 mainSat May 23 23:16:26 CST 2020
Thread-0---子线程获取到锁---
Thread-0---子线程第2次获取到锁---
Thread-0---子线程释放锁---
Thread-0---子线程第2次释放锁---

可重入锁使用案例

/**
* 文件所有的文件名增加一个前缀
* @param dir
* @param prefix
*/
public void fileRename(String dir, String prefix) {
lock.lock();
try {
File file = new File(dir);
File[] files = file.listFiles();
for (File f : files) {
if (f.isDirectory()) {
fileRename(f.getPath(), prefix);
} else {
file.renameTo(new File("新名字"));
}
}
} finally {
lock.unlock();
}
}

 

posted @ 2020-05-23 16:50  nancodex  阅读(303)  评论(0)    收藏  举报