Java——深刻理解锁(边边奋斗史)

   日期:2020-10-13     浏览:150    评论:0    
核心提示:引入今天我们将通过几个场景来深刻的理解几个问题。如下:什么是锁?是谁的锁?锁的是谁?场景1import java.util.concurrent.TimeUnit;public class test1 { public static void main(String[] args) throws I

引入

今天我们将通过几个场景来深刻的理解几个问题。

如下:

  • 什么是锁?
  • 是谁的锁?
  • 锁的是谁?

场景1

import java.util.concurrent.TimeUnit;


public class test1 { 
    public static void main(String[] args) throws InterruptedException { 
        Phone phone = new Phone();
        new Thread(()->{ 
            phone.sendMsm();
        },"A").start();
        //等待1秒钟
        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{ 
            phone.call();
        },"B").start();
    }
}
class Phone{  //资源类
    public synchronized void sendMsm(){ 
        System.out.println("发短信");
    }
    public synchronized void call(){ 
        System.out.println("打电话");
    }
}

简单说明一下:一个资源类里面有两个同步方法(发短信 和 打电话),然后主线程创建了一个资源类对象,并开了两个线程去分别执行这个对象的两个同步方法。

执行结果:

发短信
打电话

分析

线程A先拿到了 phone 这个对象的锁,所以他先执行。
这里 要认为是主线程先调用了A线程所以他先执行。

不理解的话,我们再看一个场景

场景2

import java.util.concurrent.TimeUnit;


@SuppressWarnings("ALL")
public class test1 { 
    public static void main(String[] args) throws InterruptedException { 
        Phone phone = new Phone();
        new Thread(()->{ 
            try { 
                phone.sendMsm();
            } catch (InterruptedException e) { 
                e.printStackTrace();
            }
        },"A").start();
        //等待1秒钟
        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{ 
            phone.call();
        },"B").start();
    }
}
class Phone{ 
    public synchronized void sendMsm() throws InterruptedException { 
        //等待3秒钟
        TimeUnit.SECONDS.sleep(3);
        System.out.println("发短信");
    }
    public synchronized void call(){ 
        System.out.println("打电话");
    }
}

我们让 “发短信”方法 持续 3 秒钟。
执行结果:

发短信
打电话

分析
结果还是先 发短信 后 打电话,就像我 刚刚说的一样,发短信是持续3秒,并不影响他先得到锁,因为主线程过了一秒才去调用线程B,所以线程A 99.9%的几率是先拿到锁的。

synchronized 锁的对象是方法的调用者。

线程A和B 都是调用的同一个对象(phone)的方法,所以他们都是去竞争同一把锁。谁先拿到锁谁先执行,没拿到就得等。


看下一个场景:

如果没有锁竞争关系,那么应该就是拼的速度了。

场景3

import java.util.concurrent.TimeUnit;


@SuppressWarnings("ALL")
public class test1 { 
    public static void main(String[] args) throws InterruptedException { 
        Phone phone = new Phone();
        new Thread(()->{ 
            try { 
                phone.sendMsm();
            } catch (InterruptedException e) { 
                e.printStackTrace();
            }
        },"A").start();
        //等待1秒钟
        TimeUnit.SECONDS.sleep(1);

        new Thread(()->{ 
            phone.hello();
        },"B").start();
    }
}
class Phone{ 
    public synchronized void sendMsm() throws InterruptedException { 
        //等待3秒钟
        TimeUnit.SECONDS.sleep(3);
        System.out.println("发短信");
    }
    public synchronized void call(){ 
        System.out.println("打电话");
    }
    public void hello(){ 
        System.out.println("Hello World!");
    }
}

这次我们让线程B调用 对象phone的 hello()方法,注意:hello()方法不是同步方法!!

执行结果:

Hello World!
发短信

分析
因为 hello()方法不是同步方法,所以压根就没有锁竞争这一说,虽然 线程B 比 线程A 晚启动了一秒钟,但 “发短信” 要等三秒钟才打印,这样一来,Hello World!还会比 “发短信” 先打印 2 秒钟。

同样没有锁竞争的场景

如下:

场景4

import java.util.concurrent.TimeUnit;


@SuppressWarnings("ALL")
public class test1 { 
    public static void main(String[] args) throws InterruptedException { 
        Phone phone = new Phone();
        Phone phone1 = new Phone();
        new Thread(() -> { 
            try { 
                phone.sendMsm();
            } catch (InterruptedException e) { 
                e.printStackTrace();
            }
        }, "A").start();
        //等待1秒钟
        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> { 
            phone1.call();
        }, "B").start();
    }
}

class Phone { 
    public synchronized void sendMsm() throws InterruptedException { 
        //等待3秒钟
        TimeUnit.SECONDS.sleep(3);
        System.out.println("发短信");
    }

    public synchronized void call() { 
        System.out.println("打电话");
    }
}

这次我们让 线程A 调用对象phone的“发短信”方法,而让 线程B 调用对象phone1的“打电话”方法。

执行结果:

打电话
发短信

分析
这场景和上一个场景类似,虽然 “打电话”方法也是同步方法,但它是属于 对象 phone1 的方法,和对象 phone 的 “发短信” 方法 没有 锁竞争 关系。

还是那句话:

synchronized 锁的对象是方法的调用者。

显然,这次的这两个方法不是同一个调用者。而是两个,对象phone 和 对象phone1。

对象可以锁,那么类可不可以锁?

可以的,看如下场景:

场景5

import java.util.concurrent.TimeUnit;


@SuppressWarnings("ALL")
public class test1 { 
    public static void main(String[] args) throws InterruptedException { 
        Phone phone = new Phone();
        Phone phone1 = new Phone();
        new Thread(() -> { 
            try { 
                phone.sendMsm();
            } catch (InterruptedException e) { 
                e.printStackTrace();
            }
        }, "A").start();
        //等待1秒钟
        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> { 
            phone1.call();
        }, "B").start();
    }
}

class Phone { 
    public static synchronized void sendMsm() throws InterruptedException { 
        //等待3秒钟
        TimeUnit.SECONDS.sleep(3);
        System.out.println("发短信");
    }

    public static synchronized void call() { 
        System.out.println("打电话");
    }
}

基本没什么变化,就是把 Phone 资源类 里的两个方法都加上了 static ,让其变成了 静态方法。

执行结果:

发短信
打电话

分析
被 static 关键字修饰的,会在类加载的时候被执行,他们会和类信息一样,被存到 方法区,是独一无二的存在。
所以不管方法的调用者是不是同一个对象,他们都会存在 锁竞争关系,因为他们都是同一个类的静态方法。所以,这里的锁,锁的是 Class。

判断 锁竞争关系的关键,就是去看两个同步方法是不是去拿同一把锁。

场景6

import java.util.concurrent.TimeUnit;


@SuppressWarnings("ALL")
public class test1 { 
    public static void main(String[] args) throws InterruptedException { 
        Phone phone = new Phone();
        new Thread(() -> { 
            try { 
                phone.sendMsm();
            } catch (InterruptedException e) { 
                e.printStackTrace();
            }
        }, "A").start();
        //等待1秒钟
        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> { 
            phone.hello();
        }, "B").start();
    }
}

class Phone { 
    public static synchronized void sendMsm() throws InterruptedException { 
        //等待3秒钟
        TimeUnit.SECONDS.sleep(3);
        System.out.println("发短信");
    }

    public static synchronized void call() { 
        System.out.println("打电话");
    }
    public static void hello(){ 
        System.out.println("Hello World!");
    }
}

这次我们又加上了 hello() 方法,我们把它定义成了静态方法,同样不是同步方法。

执行结果:

Hello World!
发短信

分析
非同步方法,根本没有锁竞争这一说,同一个类(Class)也没用。
这个场景其实挺没劲的,都老生常谈的事儿了。

那我们在来看下一个场景:

场景7

import java.util.concurrent.TimeUnit;


@SuppressWarnings("ALL")
public class test1 { 
    public static void main(String[] args) throws InterruptedException { 
        Phone phone = new Phone();
        new Thread(() -> { 
            try { 
                phone.sendMsm();
            } catch (InterruptedException e) { 
                e.printStackTrace();
            }
        }, "A").start();
        //等待1秒钟
        TimeUnit.SECONDS.sleep(1);

        new Thread(() -> { 
            phone.call();
        }, "B").start();
    }
}

class Phone { 
    public static synchronized void sendMsm() throws InterruptedException { 
        //等待3秒钟
        TimeUnit.SECONDS.sleep(3);
        System.out.println("发短信");
    }

    public synchronized void call() { 
        System.out.println("打电话");
    }
}

这次我们把 线程B 调用的 call()方法 定义成了 普通同步方法,由同一个对象phone 去调用这两个方法。

大家猜猜结果会是神马?

同一个 对象phone 调用的,都是同步方法。结果难道是…

好了,不误导大家了。

执行结果:

打电话
发短信

分析
还是那句话,看看他们是不是竞争同一把锁。
首先,“发短信” 是静态方法,他应该是要去找 Class锁,而 “打电话” 是非静态方法,所以他找的是对象锁。显然,他们竞争的不是同一把锁,所以没有竞争关系。

换句话说,静态同步块锁的是Class,而非静态的同步块 锁的是调用者。

That’s all,,Thank you !

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服