【学习笔记】juc并发学习+关于锁的面试题

查看锁的信息

jvisualvm
查看pid
在这里插入图片描述
死锁代码:


public class sisuoDemo4 {
    public static void main(String[] args) {
        Obj1 obj1 = new Obj1();
        Obj2 obj2 = new Obj2();

        Thread thread1 = new Thread(new SynAddRunalbe(obj1,obj2,1,2,true));
        Thread thread2 = new Thread(new SynAddRunalbe(obj1,obj2,2,1,false));
        thread1.setName("1");
        thread2.setName("线程2");
        thread1.start();
        thread2.start();
    }
    public static class SynAddRunalbe implements Runnable {
        Obj1 obj1;
        Obj2 obj2;
        int a,b;
        boolean flag;
        public SynAddRunalbe(Obj1 obj1, Obj2 obj2, int a, int b, boolean flag) {
            this.obj1 = obj1;
            this.obj2 = obj2;
            this.a = a;
            this.b = b;
            this.flag = flag;


        }

        @Override
        public void run() {
            if(flag)
            {
                synchronized (obj1){
                    try {
                        Thread.sleep(5);
                        synchronized (obj2){
                            System.out.println(a+b);
                            System.out.println(Thread.currentThread().getName());
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            else
            {
synchronized (obj2)
{
    try{
        Thread.sleep(5);
        synchronized (obj1){
            System.out.println(a+b);
            System.out.println(Thread.currentThread().getName());
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
            }
        }
    }
    public static class Obj1 {
    }
    public static class Obj2 {
    }
}

C:\hello>jstack -l 20488
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

饥饿死锁的例子

public class ExecutorLock {
private static ExecutorService single = Executors.newSingleThreadExecutor();
public static class AnotherCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("in AnotherCallable");
return "annother success";
}
}

public static class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("in MyCallable");
Future<String> submit = single.submit(new AnotherCallable());
return "success:" + submit.get();
}
}
public static void main(String[] args) throws ExecutionException,
InterruptedException {
MyCallable task = new MyCallable();
Future<String> submit = single.submit(task);
System.out.println(submit.get());
System.out.println("over");
single.shutdown();
}
}

Synchronized 其 原 理 是 什 么 ?

Synchronized 是 由 JVM 实 现 的 一 种 实 现 互 斥 同 步 的 一 种 方 式 , 如 果你查看被 Synchronized 修 饰 过 的 程 序 块 编 译 后 的 字 节 码 , 会 发 现 , 被Synchronized 修 饰 过 的 程 序 块 , 在 编 译 前 后 被 编 译 器 生 成了 monitorenter 和 monitorexit 两 个 字 节 码 指 令 。
这 两 个 指 令 是 什 么 意 思 呢 ?
在 虚 拟 机 执 行 到 monitorenter 指 令 时 , 首 先 要 尝 试 获 取 对 象 的 锁 :
如 果 这 个 对 象 没 有 锁 定 ,或 者 当 前 线 程 已 经 拥 有 了 这 个 对 象 的 锁 ,把 锁 的计数器 +1;当 执 行 monitorexit 指 令 时 将 锁 计 数 器 -1;当 计 数 器 为 0 时 , 锁 就 被 释 放 了 。
如 果 获 取 对 象 失 败 了 ,那 当 前 线 程 就 要 阻 塞 等 待 ,直 到 对 象 锁 被 另 外 一 个线 程 释 放 为 止 。
Java 中 Synchronize 通 过 在 对 象 头 设 置 标 记 , 达 到 了 获 取 锁 和 释 放 锁的目的。

你 刚 才 提 到 获 取 对 象 的 锁 ,这 个“ 锁 ”到 底 是 什 么 ? 如 何 确 定 对象的锁

“ 锁 ” 的 本 质 其 实 是 monitorenter 和 monitorexit 字 节 码 指 令 的 一 个
Reference 类 型 的 参 数 , 即 要 锁 定 和 解 锁 的 对 象 。 我 们 知 道 , 使 用Synchronized 可 以 修 饰 不 同 的 对 象 ,因 此 ,对 应 的 对 象 锁 可 以 这 么 确 定 。

  1. 如 果 Synchronized 明 确 指 定 了 锁 对 象 ,比 如 Synchronized( 变 量 名 )、Synchronized(this) 等 , 说 明 加 解 锁 对 象 为 该 对 象 。
  2. 如 果 没 有 明 确 指 定 :
    若 Synchronized 修 饰 的 方 法 为 非 静 态 方 法 ,表 示 此 方 法 对 应 的 对 象 为 锁对象;
    若 Synchronized 修 饰 的 方 法 为 静 态 方 法 ,则 表 示 此 方 法 对 应 的 类 对 象 为锁对象。
    注 意 , 当 一 个 对 象 被 锁 住 时 , 对象里面有用Synchronized 修饰的方法 都 将 产 生 堵 塞 , 而 对 象 里 非 Synchronized 修 饰 的 方 法 可 正 常 被 调 用 ,不 受 锁 影 响 。

什 么 是 可 重 入 性 , 为 什 么 说 Synchronized 是 可 重 入 锁 ?

可 重 入 性 是 锁 的 一 个 基 本 要 求 , 是 为 了 解 决 自 己 锁 死 自 己 的 情 况 。

比 如一 个 类 中 的 同 步 方 法 调 用 另 一 个 同 步 方 法 , 假 如
Synchronized 不 支 持 重 入 , 进 入 method2 方 法 时 当 前 线 程 获 得 锁 ,method2 方 法 里 面 执 行 method1 时 当 前 线 程 又 要 去 尝 试 获 取 锁 , 这时 如 果 不 支 持 重 入 , 它 就 要 等 释 放 , 把 自 己 阻 塞 , 导 致 自 己 锁 死 自 己 。

为 什 么 说 Synchronized 是 一 个 悲 观 锁 ? 乐 观 锁 的 实 现 原 理 又 是 什 么 ? 什 么 是 CAS, 它 有 什 么 特 性 ?

Synchronized 显 然 是 一 个 悲 观 锁 , 因 为 它 的 并 发 策 略 是 悲 观 的 :
不 管 是 否 会 产 生 竞 争 ,任 何 的 数 据 操 作 都 必 须 要 加 锁 、用 户 态 核 心 态 转 换 、维 护 锁 计 数 器 和 检 查 是 否 有 被 阻 塞 的 线 程 需 要 被 唤 醒 等 操 作 。
随 着 硬 件 指 令 集 的 发 展 ,我 们 可 以 使 用 基 于 冲 突 检 测 的 乐 观 并 发 策 略 。先进 行 操 作 , 如 果 没 有 其 他 线 程 征 用 数 据 , 那 操 作 就 成 功 了 ;如 果 共 享 数 据 有 征 用 ,产 生 了 冲 突 ,那 就 再 进 行 其 他 的 补 偿 措 施 。这 种 乐观 的 并 发 策 略 的 许 多 实 现 不 需 要 线 程 挂 起 , 所 以 被 称 为 非 阻 塞 同 步 。
乐 观 锁 的 核 心 算 法 是 CAS( Compareand Swap,比较并交换 ) , 它 涉及 到 三 个 操 作 数 :内 存 值 、预 期 值 、新 值 。当 且 仅 当 预 期 值 和 内 存 值 相 等时才将内存值改为新值,这 样 处 理 的 逻 辑 是 , 首 先 检 查 某 块 内 存 的 值 是 否 跟 之 前 我 读 取 时 的 一 样 ,如 不 一 样 则 表 示 期 间 此 内 存 值 已 经 被 别 的 线 程 更 改 过 ,舍 弃 本 次 操 作 ,否
则 说 明 期 间 没 有 其 他 线 程 对 此 内 存 值 操 作 , 可 以 把 新 值 设 置 给 此 块 内 存 。
CAS 具 有 原 子 性 ,它 的 原 子 性 由 CPU 硬 件 指 令 实 现 保 证 ,即 使 用 JNI 调 用 Native 方 法 调 用 由 C++ 编 写 的 硬 件 级 别 指 令 , JDK 中 提 供 了Unsafe 类 执 行 这 些 操 作 。

乐 观 锁 一 定 就 是 好 的 吗 ?

乐 观 锁 避 免 了 悲 观 锁 独 占 对 象 的 现 象 ,同 时 也 提 高 了 并 发 性 能 ,但 它 也 有缺点:

  1. 乐 观 锁 只 能 保 证 一 个 共 享 变 量 的 原 子 操 作 。如 果 多 一 个 或 几 个 变 量 ,乐 观锁 将 变 得 力 不 从 心 ,但 互 斥 锁 能 轻 易 解 决 ,不 管 对 象 数 量 多 少 及 对 象 颗 粒度大小。
  2. 长 时 间 自 旋 可 能 导 致 开 销 大 。 假 如 CAS 长 时 间 不 成 功 而 一 直 自 旋 , 会给 CPU 带 来 很 大 的 开 销 。
  3. ABA 问题。CAS 的 核 心 思 想 是 通 过 比 对 内 存 值 与 预 期 值 是 否 一 样 而 判 断内 存 值 是 否 被 改 过 , 但 这 个 判 断 逻 辑 不 严 谨 , 假 如 内 存 值 原 来 是 A, 后来 被 一 条 线 程 改 为 B, 最 后 又 被 改 成 了 A, 则 CAS 认 为 此 内 存 值 并 没有 发 生 改 变 ,但 实 际 上 是 有 被 其 他 线 程 改 过 的 ,这 种 情 况 对 依 赖 过 程 值 的情 景 的 运 算 结 果 影 响 很 大 。解 决 的 思 路 是 引 入 版 本 号 ,每 次 变 量 更 新 都 把版 本 号 加 一 。

可重入锁 ReentrantLock 及 其他 显 式 锁相 关 问题

跟 Synchronized 相 比 , 可 重 入 锁 ReentrantLock 其实现原理 有 什 么 不 同 ?

其 实 , 锁 的 实 现 原 理 基 本 是 为 了 达 到 一 个 目 的 :
让 所 有 的 线 程 都 能 看 到 某 种 标 记 。
Synchronized 通过在对象头中设置标记实现了这一目的,是一种 JVM
原 生 的 锁 实 现 方 式 , 而 ReentrantLock 以 及 所 有 的 基 于 Lock 接 口 的实 现 类 ,都 是 通 过 用 一 个 volitile 修饰的 int 型 变 量 ,并 保 证 每 个 线 程都 能 拥 有 对 该 int 的 可 见 性 和 原 子 修 改 ,其 本 质 是 基 于 所 谓 的 AQS 框架 。

AQS框 架 是 怎 么 回 事 儿 ?

AQS( AbstractQueuedSynchronizer 类 ) 是 一 个 用 来 构 建 锁 和 同 步 器的 框 架 , 各 种Lock 包 中 的 锁 ( 常 用 的 有ReentrantLock 、ReadWriteLock) , 以 及 其 他 如 Semaphore、 CountDownLatch, 甚至 是 早 期 的 FutureTask 等 , 都 是 基 于 AQS 来构建。
5. AQS 在 内 部 定 义 了 一 个 volatile int state 变 量 , 表 示 同 步 状 态 : 当 线 程调 用 lock 方法时 ,如 果 state=0,说 明 没 有 任 何 线 程 占 有 共 享 资 源 的 锁 ,可 以 获 得 锁 并 将 state=1;如果 state=1, 则 说 明 有 线 程 目 前 正 在 使 用 共享 变 量 , 其 他 线 程 必 须 加 入 同 步 队 列 进 行 等 待 。
6. AQS 通 过 Node 内 部 类 构 成 的 一 个 双 向 链 表 结 构 的 同 步 队 列 , 来 完 成 线程 获 取 锁 的 排 队 工 作 , 当 有 线 程 获 取 锁 失 败 后 , 就 被 添 加 到 队 列 末 尾 。
Node 类 是 对 要 访 问 同 步 代 码 的 线 程 的 封 装 , 包 含 了 线 程 本 身 及 其 状 态 叫waitStatus( 有 五 种 不 同 取 值 , 分 别 表 示 是 否 被 阻 塞 , 是 否 等 待 唤 醒 , 是否 已 经 被 取 消 等 ) , 每 个 Node 结 点 关 联 其 prev 结点和 next 结点,方 便 线 程 释 放 锁 后 快 速 唤 醒 下 一 个 在 等 待 的 线 程 , 是 一 个 FIFO 的过程。
Node 类 有 两 个 常 量 , SHARED 和 EXCLUSIVE, 分 别 代 表 共 享 模 式 和 独占 模 式 。 所 谓 共 享 模 式 是 一 个 锁 允 许 多 条 线 程 同 时 操 作 ( 信 号 量Semaphore 就 是 基 于 AQS 的 共 享 模 式 实 现 的 ) , 独 占 模 式 是 同 一 个 时间 段 只 能 有 一 个 线 程 对 共 享 资 源 进 行 操 作 , 多 余 的 请 求 线 程 需 要 排 队 等 待( 如 ReentranLock) 。
7. AQS 通 过 内 部 类 ConditionObject 构 建 等 待 队 列 ( 可 有 多 个 ) , 当Condition 调 用 wait() 方 法 后 , 线程将会加入等待队列中 , 而当Condition 调 用 signal() 方 法 后 , 线 程 将 从 等 待 队 列 转 移 动 同 步 队 列 中进 行 锁 竞 争 。

synchronized实例

1.synchronized:内置的Java关键字
LOCK:一个接口,下面有多个实现类,可以判断是否取得了锁
2.synchronized自动释放锁,lock必须手动释放锁
3.synchronized不可以中断,非公平锁,lock 可以设置公平还是非公平
前者适合锁少量代码同步问题,后者适合锁大量同步代码
一个用synchronized的例子1:


public class A {
    public static void main(String[] args) {
        Threadtest threadtest = new Threadtest();
        new Thread(
                ()->{
                    for(int i = 0;i < 5;i++)
                    {
                        try{
                            threadtest.increment();
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                    }
                },"A"
        ).start();

        new Thread(
                ()->{
                    for(int i = 0;i < 5;i++)
                    {
                        try{
                            threadtest.decrement();
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                    }
                },"B"
        ).start();
    }
}


public class Threadtest
{
    private int number = 0;
    Lock lock = new ReentrantLock();
    public synchronized void increment()throws InterruptedException{
       // lock.lock();
        try{
            //自己的业务代码
            if(number !=0)
                this.wait();
            number++;
            System.out.println(Thread.currentThread().getName()+"->"+number);
            this.notify();
        }catch (Exception e){
            e.printStackTrace();
        }

    }
    public synchronized void decrement()throws InterruptedException{
        try{
            if(number == 0)
                this.wait();
            number--;
            System.out.println(Thread.currentThread().getName()+"->"+number);
            this.notify();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

1this.notify()去掉this对结果无影响
以上例子如果再加一个线程C,改成notifyAll()会出现问题:
A->1
B->0
A->1
B->0
C->1
A->2
C->3
B->2
B->1
B->0
C->1
A->2

发现输出不正确了,是因为/if和while的虚假唤醒问题 ,改if为while
增加CD两个线程:


public class A {
    public static void main(String[] args) {
        Threadtest threadtest = new Threadtest();
        new Thread(
                ()->{
                    for(int i = 0;i < 5;i++)
                    {
                        try{
                            threadtest.increment();
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                    }
                },"A"
        ).start();

        new Thread(
                ()->{
                    for(int i = 0;i < 5;i++)
                    {
                        try{
                            threadtest.decrement();
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                    }
                },"B"
        ).start();
        new Thread(
                ()->{
                    for(int i = 0;i < 5;i++)
                    {
                        try{
                            threadtest.increment();
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                    }
                },"C"
        ).start();
        new Thread(
                ()->{
                    for(int i = 0;i < 5;i++)
                    {
                        try{
                            threadtest.decrement();
                        }catch (InterruptedException e){
                            e.printStackTrace();
                        }
                    }
                },"D"
        ).start();


    }
}



public class Threadtest
{
    private int number = 0;
    Lock lock = new ReentrantLock();
    public synchronized void increment()throws InterruptedException{
       // lock.lock();
        try{
            //自己的业务代码
            while(number !=0)
                this.wait();
            number++;
            System.out.println(Thread.currentThread().getName()+"->"+number);
           notifyAll();
        }catch (Exception e){
            e.printStackTrace();
        }

    }
    public synchronized void decrement()throws InterruptedException{
        try{
            while (number == 0)
                this.wait();
            number--;
            System.out.println(Thread.currentThread().getName()+"->"+number);
           notifyAll();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
    }
}

输出
A->1
B->0
A->1
B->0
A->1
B->0
A->1
B->0
A->1
B->0
C->1
D->0
C->1
D->0
C->1
D->0
C->1
D->0
C->1
D->0

五个为一轮

例子2:

public class Test {
    public static void main(String[] args) {
        Sell sell = new Sell();
        new Thread(() -> {
            sell.sell1();
        },"A"
        ).start();


         new Thread(() -> {
        sell.sell2();
    },"B"
            ).start();
}
}
class Sell{
    public synchronized void sell1(){
        System.out.println("卖衣服");
    }
    public synchronized void sell2(){
        System.out.println("卖包子");
    }
}

输出卖衣服
卖包子
如果sell2和sell1改一下顺序,则输出变成卖包子 卖衣服
说明:sychronized锁的对象是方法的调用者,由于上面两个方法用的是同一个锁,因此谁先拿到锁先执行谁
加了sleep方法:

public class Test {
    public static void main(String[] args) {
        Sell sell = new Sell();
        Sell mm = new Sell();
        new Thread(() -> {
            try {
                sell.sell2();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            mm.sell1();
        },"A"
        ).start();


         new Thread(() -> {
       mm.sell1();
             try {
                 mm.sell2();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         },"B"
            ).start();
}
}
class Sell{
    public synchronized void sell1(){
        System.out.println("卖衣服"+Thread.currentThread().getName());
    }
    public synchronized void sell2() throws InterruptedException {
        Thread.sleep(2);
        System.out.println("卖包子"+Thread.currentThread().getName());
    }
}

卖衣服B
卖包子A
卖包子B
卖衣服A
记住:非同步方法不受锁的影响
如果sychronized修饰的方法是static的,则锁的是整个class,此时即使即使new了不同的两个实例,仍然是同一把锁

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值