关于锁

我对于锁的初次接触和简单理解

定义一个商店Shop类

public class Shop {
    public void watch() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + ":" + "开始看商品");
        Thread.sleep(2000);
        System.out.println(Thread.currentThread().getName() + ":" + "看完了!");
        Thread.sleep(2000);
    }

    public void buy() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + ":" + "开始买商品!");
        Thread.sleep(2000);
        System.out.println(Thread.currentThread().getName() + ":" + "买完了");
        Thread.sleep(2000);
    }
}

这个类中有两个方法,每个方法都可以输出两次当前线程名和对应语句。为了方便区分,在这两个输出语句之间加上2秒的间隔。

public class About_LockTest {
    public static void main(String[] args) {
        //实例化一个Shop对象 sp
        Shop sp = new Shop();
        //匿名内部类使得t0和t1都执行sp中的watch方法
        Thread t0 = new Thread(() -> {
            try {
                sp.watch();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread t1 = new Thread(() -> {
            try {
                sp.watch();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t0.start();
        t1.start();
    }
}

运行该程序,发现t0和t1同时输出相应语句,这表明两个线程在同时运行。如果,对watch方法加上锁,那么再运行该程序会有什么结果呢?

public synchronized void watch() throws InterruptedException {//在public后面加上synchronized
        System.out.println(Thread.currentThread().getName() + ":" + "开始看商品");
        Thread.sleep(2000);
        System.out.println(Thread.currentThread().getName() + ":" + "看完了!");
        Thread.sleep(2000);
    }

此时,通过输出线程名可以发现,只有一个线程在运行,而另一个线程没有运行,也就是处于阻塞状态。直接在方法名前面加上synchronized关键词,其实等价于以下语句

public void watch() throws InterruptedException {
        synchronized (this) {//将整个方法体包裹在锁内,其中用this,本类对象作为钥匙
            System.out.println(Thread.currentThread().getName() + ":" + "开始看商品");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + ":" + "看完了!");
            Thread.sleep(2000);
        }
    }

​ 线程在执行这个方法的时候,被锁住的部分必须要钥匙才能进入,每个钥匙只能同时被一个线程持有。在这里钥匙是本类对象,而在上面都是利用sp对象调用方法,所以钥匙就是sp。这个持有sp的状态,会一直持续到线程在走出synchronized包裹的范围前,此时没拿到钥匙sp的线程只能在锁外排队,处于阻塞状态。等到一个线程走出synchro-nized的作用域之后,就会释放手中的钥匙sp,此时钥匙处于自由状态,而另一个线程会得到这把钥匙,于是可以进入这个锁住的区域,可以执行里面的代码。

​ 为了验证以上观点,我对Thread t1进行改写:

1、锁只作用于自己的作用域

Thread t1 = new Thread(() -> {
            try {
                sp.buy();//添加一个buy方法在watch方法前面
                sp.watch;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

运行程序后,发现t0在执行watch方法的同时,t1也在执行buy方法,当t0执行完watch且t1执行完buy之后,t1拿到t0的锁,继续执行watch方法。所以说,此处这把锁只是锁住了watch方法,对于buy方法而言,并无任何影响

2、同一把钥匙只能同时被同一个线程持有(忽略释放之后持有之前的自由状态)

将t0的watch方法删去,只留有buy方法:

Thread t1 = new Thread(() -> {
            try {
                sp.buy();//此处t1只有buy方法了
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

将Shop类中的buy也加上synchronized锁,这把锁的钥匙也是this本类对象,在目前认仍是sp

public synchronized void buy() throws InterruptedException {//buy方法加锁
        System.out.println(Thread.currentThread().getName() + ":" + "开始买商品!");
        Thread.sleep(2000);
        System.out.println(Thread.currentThread().getName() + ":" + "买完了");
        Thread.sleep(2000);
    }

运行程序后,发现t0执行watch方法时,线程t1没有任何动作,而等到t0结束watch方法后,才开始执行buy。由于之前已经证明锁只能作用于自己的作用域,所以排除了watch的锁影响了buy(相反亦然),这就说明此处钥匙只能同时被一个线程持有,只要等钥匙释放并拿到钥匙后,另一个线程才会执行。

3、只要持有钥匙就可以打开对应锁

(在2的基础上)在Shop类中实例化一个Object对象obj,这里将watch方法锁的钥匙改为obj:

Object obj = new Object();//新生成一个obj

    public void watch() throws InterruptedException {
        synchronized (obj) {//将obj作为钥匙
            System.out.println(Thread.currentThread().getName() + ":" + "开始看商品");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + ":" + "看完了!");
            Thread.sleep(2000);
        }
    }

运行程序发现,t0和t1分别同时运行watch和buy方法。由1和2可以知道,不会是二者同时持有钥匙,并且因为不同锁之间相互影响而导致线程同时运行。这里是因为,watch方法使用了obj作为钥匙,t0持有的是obj;buy使用了本类对象sp作为钥匙。这两个钥匙并不相同,所以t0和t1都能直接拿到各自的钥匙(没有其他线程竞争),打开对应的锁执行方法。

4、调用不同对象的多线程的同步

新创建一个Shop对象shp,让t0只调用shp的buy方法,t1只调用原来sp的方法,将3中两个锁的钥匙都改为this

public static void main(String[] args) {
        Shop sp = new Shop();
        Shop shp=new Shop();//再实例化一个shp
        Thread t0 = new Thread(() -> {
            try {
                shp.buy();//调用shp中的buy方法
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread t1 = new Thread(() -> {
            try {
                sp.buy();//调用sp中的buy方法
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t0.start();
        t1.start();
    }
/**两个方法都以this作为钥匙*/
public void watch() throws InterruptedException {
        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + ":" + "开始看商品");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + ":" + "看完了!");
            Thread.sleep(2000);
        }
    }

    public synchronized void buy() throws InterruptedException {
        System.out.println(Thread.currentThread().getName() + ":" + "开始买商品!");
        Thread.sleep(2000);
        System.out.println(Thread.currentThread().getName() + ":" + "买完了");
        Thread.sleep(2000);
    }

执行程序后发现t0和t1同时各自执行buy方法,因为他们的钥匙shp、sp都直接被持有,无需等待。如果在这种情况下同步t0和t1,可以在类中创建一个静态对象作为钥匙:

static Object obj = new Object();//静态对象
//都以静态对象为钥匙
    public void watch() throws InterruptedException {
        synchronized (obj) {
            System.out.println(Thread.currentThread().getName() + ":" + "开始看商品");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + ":" + "看完了!");
            Thread.sleep(2000);
        }
    }

    public void buy() throws InterruptedException {
        synchronized (obj) {
            System.out.println(Thread.currentThread().getName() + ":" + "开始买商品!");
            Thread.sleep(2000);
            System.out.println(Thread.currentThread().getName() + ":" + "买完了");
            Thread.sleep(2000);
        }
    }

静态对象是类所有对象共享的,所以不管有多少个对象,在这个类都只有一个静态对象可用,这个钥匙就唯一了,所以执行程序后一个线程先执行buy,之后另一个线程才执行buy。

总结

锁的作用范围是有限的(可自定义),不同锁对应的钥匙可以相同,相应的这把钥匙完全可以打开这些锁。同一把钥匙同时只能被同一个线程持有,并且只有这个持有钥匙的线程可以执行锁内代码,没有钥匙的线程就处于阻塞状态,等到持有钥匙的线程走出锁后,会释放钥匙,这时就有机会得到钥匙执行代码,没有得到就继续堵塞,等待下一次机会。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容