显式锁基础知识

ObjectMonitor

锁类型

1.可重入锁

  • 同一个线程多次获取同一把锁不用切上下文

2.公平锁

  • 多个线程按照申请锁的顺序获得锁
  • 线程会直接进入队列去排队
  • 永远都是队列的第一位才能得到锁
  • 排队不插队
  • 优点
所有的线程都能得到资源,不会饿死在队列中
  • 缺点
吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大
  • 源码
    在这里插入图片描述
    在这里插入图片描述

3.非公平锁

  • 多个线程申请锁时,直接尝试获取锁,获取不到进入队列去排队
  • 优点
减少CPU唤醒线程的开销,整体的吞吐效率会高点,减少CPU唤起线程的数量
  • 缺点
导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死

4.自旋锁

//需求: 利用CAS完成一个自旋锁,以安全的自增
public class SafeIncrement {
    private AtomicInteger atomicI = new AtomicInteger(0);
    
    public void increament() {
        for (;;) {//自旋
            int i = atomicI.get();//另外线程  可能修改过i值
            boolean suc = atomicI.compareAndSet(i, ++i);
            if (suc) {//i修改成功则跳出循环
                break;
            }
        }
    }
}

显式锁

1.Lock

  • Lock与synchronized比较
    在这里插入图片描述
  • synchronized可重入证明
public class Re_synchronized {
    public synchronized void method1() {
        //main线程获取锁
        System.out.println("11111");
        method2();//若是非可重入,由于method1有锁,再去method2也需要锁,方法会阻塞在此
    }

    public synchronized void method2() {
        //main线程获取锁
        System.out.println("22222");
    }

    public static void main(String[] args) {
        Re_synchronized re = new Re_synchronized();
        re.method1();
    }
}
  • lock可重入证明
public class Re_reentrantLock implements Runnable {
    //Lock接口       ReentrantLock可重入锁
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        set();
    }

    public void set() {
        try {
            lock.lock();
            System.out.println("set");
            get();//同一个线程在set中获取锁又调用get重新获取同一把锁
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//必须释放锁
        }
    }

    public void get() {
        try {
            lock.lock();
            System.out.println("get");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Re_reentrantLock re = new Re_reentrantLock();
        new Thread(re).start();
    }
}
  • 方法
1.void lock():相当synchronized,获得锁
2.void unlock():释放锁,用于finally3.lockInterruptibly()4.boolean tryLock():锁可用立即返回true
5.boolean tryLock(long time, TimeUnit unit)
6.newCondition():看下面

2.Condition

  • 方法
1.await():相当Object.wait()
2.singal():相当Object.notify(),随机唤醒
3.singalAll():相当Object.notifyAll()
  • newCondition()使用
//模拟生产消费过程
class AppleBox {
    int index = 0;
    Apple[] apples = new Apple[5];

    //*******************************************
    //创建锁,替换synchronized
    private final Lock lock = new ReentrantLock();
    //一个锁可以有多个等待队列
    private final Condition produceCondition = lock.newCondition();
    private final Condition consumeCondition = lock.newCondition();
    //*******************************************

    public void deposite(Apple apple) throws InterruptedException {
        //*******************************************
        lock.lock();

        try {
            //存满了
            while (index == apples.length) {//不要if,醒来之后又判断一次,防止index越界===多次容错
	            //原方案
	            /*try {
	                this.wait();//存满了睡一下  没有加时间   唤醒才会结束等待     加上时间自己醒就不用别人激活了
	                //但醒来的时间是不可知的   别人通知一下比较好
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	            }*/

                //*******************************************
                produceCondition.await();
            }
            //this.notifyAll();//通知其他线程激活   就算都活了也只有有锁的才可以运行
            //*******************************************
            consumeCondition.signal();
            apples[index] = apple;
            index++;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //*******************************************
            lock.unlock();
        }
    }

    public Apple withdraw() {
        //*******************************************
        lock.lock();

        try {
            //容器是空的
            while (index == 0) {//不要if 醒来之后又判断一次   防止index越界
	            /*try {
	                this.wait();//释放锁
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	            }*/

                //*******************************************
                consumeCondition.await();
            }
            //this.notifyAll();//将所有在ab锁中的所有等待状态全部唤醒  继续

            //*******************************************
            produceCondition.signal();
            index--;
            return apples[index];
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //*******************************************
            lock.unlock();
        }
        return null;
    }
}

3.ReentrantLock

  • 非公平锁
  • 可重入锁
  • 排它锁

4.ReentrantReadWriteLock

  • 读多写少情况使用
  • 读锁与写锁不能同时拥有
  • 读锁在读操作间共享
  • 写锁在读写操作间都独占(排他锁)
  • 案例
/**
 * 需求:两个读线程,两个写线程,4个线程同时启动(CyclicBarrier),测试这4个线程的读写顺序
 * 1.当有读锁时,不能加写锁,且读锁在读操作共享
 * 2.当有写锁时,不能加读锁,且写锁排他,其他读操作也不能进行
 */
public class ReentrantReadWriteLockTest {
    //读写锁
    private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(4);
    private static int i = 100;

    public static void read(Thread thread) {
        //在此处等待,等待同时运行信号
        try {
            cyclicBarrier.await();
        } catch (Exception e) {
            e.printStackTrace();
        }

        //读锁:抢到读锁,则两个读锁线程可以同时向下运行-->读锁共享
        reentrantReadWriteLock.readLock().lock();

        //读线程 pool-1-thread-3 开始执行  i=100
        //读线程 pool-1-thread-2 开始执行  i=100
        //读线程 pool-1-thread-2 结束
        //读线程 pool-1-thread-3 结束

        try {
            System.out.println("读线程 " + thread.getName() + " 开始执行  i=" + i);
            Thread.sleep(1000);
            System.out.println("读线程 " + thread.getName() + " 结束");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantReadWriteLock.readLock().unlock();
        }
    }

    public static void write(Thread thread) {
        //在此处等待,等待同时运行信号
        try {
            cyclicBarrier.await();
        } catch (Exception e) {
            e.printStackTrace();
        }

        //写锁:只能有一个在写,且不能读
        reentrantReadWriteLock.writeLock().lock();

        //写线程 pool-1-thread-1 开始执行  i=101
        //写线程 pool-1-thread-1 结束
        //写线程 pool-1-thread-2 开始执行  i=102
        //写线程 pool-1-thread-2 结束

        try {
            i++;
            System.out.println("写线程 " + thread.getName() + " 开始执行  i=" + i);
            System.out.println("写线程 " + thread.getName() + " 结束");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantReadWriteLock.writeLock().unlock();
        }
    }

    public static void main(String[] args) {
        //提交任务到线程池
        executor.execute(() -> {
            write(Thread.currentThread());
        });
        executor.execute(() -> {
            write(Thread.currentThread());
        });
        executor.execute(() -> {
            read(Thread.currentThread());
        });
        executor.execute(() -> {
            read(Thread.currentThread());
        });
        executor.shutdown();
    }
}

LockSupport

1.作用

  • 阻塞一个线程
  • 唤醒一个线程
  • 构建同步组件的基础工具

2.方法

  • park()
线程没有许可证,将当前线程阻塞,直到unpark赋予许可证
默认情况下调用线程不持有许可证,所以被阻塞
  • unpark(Thread t)
让t持有线程与LockSupper关联的许可证,t线程从park阻塞位置回复运行,解除线程阻塞
  • 解锁的顺序灵活
若unpark前,该线程因park被挂起,unpark后该线程被唤醒
若unpark前,该线程没有调用park,则unpark后调用park会立即返回
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值