Java人的第一把锁——synchronized

楔子

上一篇文章,我们学习了volatile关键字,这边文章我们继续学习并发编程中常用的另一个关键字——synchronized。

synchronized使用方法

首先我们来看synchronized常见的几种使用方法。

使用synchronized(this)修饰代码块

class SyncThread implements Runnable {
    private static int count;
    public SyncThread() {
        count = 0;
    }
    public  void run() {
       synchronized (this){
            for (int i = 0; i < 50; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        System.out.println("同一个对象,使用synchronized关键字");
        SyncThread syncThread = new SyncThread();
        Thread thread1 = new Thread(syncThread, "同一个对象:线程一");
        Thread thread2 = new Thread(syncThread, "同一个对象:线程二");
        thread1.start();
        thread2.start();
    }
}

执行结果

同一个对象:线程一:0
......
同一个对象:线程二:99

使用synchronized修饰普通方法

我们修改SyncThread类,为run方法加上synchronized关键字

class SyncThread implements Runnable {
    private static int count;
    public SyncThread() {
        count = 0;
    }
    public synchronized void run() {
        for (int i = 0; i < 50; i++) {
            try {
                System.out.println(Thread.currentThread().getName() + ":" + (count++));
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果

同一个对象:线程一:0
......
同一个对象:线程二:99

使用synchronized修饰静态方法

我们修改SyncThread类,提取方法为静态方法,并且为提取的方法加上synchronized关键字

class SyncThread implements Runnable {
    private static int count;
    public SyncThread() {
        count = 0;
    }
    public void run() {
        doSome();
    }
    private synchronized static void doSome() {
        for (int i = 0; i < 50; i++) {
            try {
                System.out.println(Thread.currentThread().getName() + ":" + (count++));
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

执行结果

同一个对象:线程一:0
......
同一个对象:线程二:99

使用synchronized(xxx.class)修饰一个类

class SyncThread implements Runnable {
    private static int count;
    public SyncThread() {
        count = 0;
    }
    public void run() {
        synchronized (SyncThread.class){
            for (int i = 0; i < 50; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

执行结果

同一个对象:线程一:0
......
同一个对象:线程二:99

小结

通过以上基础使用,我们可以大胆猜想synchronized其实本质上是对对象进行加锁的,无论是实例对象还是类对象,那为什么他会对对象加锁呢?其实synchronized底层的原理,是跟jvm指令和monitor有关系的。

synchronized底层原理

每个对象都有一个monitor,你如果用到了synchronized关键字,在底层编译后的jvm指令中,会有monitorenter和monitorexit两个指令。

monitor里面有一个从0开始的计数器。如果一个线程要获取monitor的锁,需要先看看对象的计数器是不是0,如果是0的话,那么说明没人获取锁,他就可以获取锁了,然后对计数器加1;如果计数器是1,那么说明当前锁已经被别人占有了,那么此时他就会等待一定时间后,再去尝试获取锁;如果出了synchronized修饰的范围,那么就会有一个monitorexit指令,那么该对象的monitor计数器就会减1。值的一提的是,monitor是可重入的,也就是说一个对象可以被加多次锁,加几次锁,计数器就递增几,释放锁的时候也递减,最后变为0。

下面是一个简单的例子

public class SynchronizedDemo {
    public static void main(String[] args) {
        synchronized (SynchronizedDemo.class) {
        }
        method();
    }
 
    private static void method() {
    }
}

编译上面文件,并在target对应目录下执行javap -v SynchronizedDemo.class,你可以看到对应字节码如下:
在这里插入图片描述

wait与notify

首先我们看以下简单例子

class ThreadA extends Thread{
    public ThreadA(String name) {
        super(name);
    }
    public void run() {
        synchronized (this) {
            try {
                //  使当前线阻塞 1 s,确保主程序的 t1.wait(); 执行之后再执行 notify()
                Thread.sleep(5000);
            } catch (Exception e) {
                e.printStackTrace();
            }           
            System.out.println("等待5s结束 "+Thread.currentThread().getName()+" 被唤醒");
            // 唤醒当前的wait线程
            this.notify();
        }
    }
}

public class WaitTest {
    public static void main(String[] args) {
        ThreadA t1 = new ThreadA("t1");
        synchronized(t1) {
            try {
                // 启动“线程t1”
                System.out.println(Thread.currentThread().getName()+" 执行t1线程");
                t1.start();
                // 主线程等待t1通过notify()唤醒。
                System.out.println(Thread.currentThread().getName()+" 开始等待");
                //  不是使t1线程等待,而是当前执行wait的线程等待
                t1.wait();
                System.out.println(Thread.currentThread().getName()+" 等待结束,继续执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

结果为:

main 执行t1线程
main 开始等待
等待5s结束 t1 被唤醒
main 等待结束,继续执行

wait与notify的底层原理,同样是基于monitor的,底层分别调用了wait和set方法,所以必须对同一个对象进行加锁、wait、notify,这样的话,他们才是通一个对象里的monitor相关的计数器、wait、set进行操作的,才能够有效果。

思考

思考一

下面的例子count的值是99吗?为什么?

public static void main(String[] args) throws InterruptedException {
    System.out.println("不同的对象,使用synchronized关键字");
    Thread thread3 = new Thread(new SyncThread(), "不同的对象:线程三");
    Thread thread4 = new Thread(new SyncThread(), "不同的对象:线程四");
    thread3.start();
    thread4.start();
}

class SyncThread implements Runnable {
    private static int count;
    public SyncThread() {
        count = 0;
    }
    public void run() {
        synchronized (this){
            for (int i = 0; i < 50; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.notify();
        }
    }
}

思考二

下面的例子count的值是99吗?为什么?

public static void main(String[] args) throws InterruptedException {
    System.out.println("不同的对象,使用synchronized关键字");
    Thread thread3 = new Thread(new SyncThread(), "不同的对象:线程三");
    Thread thread4 = new Thread(new SyncThread(), "不同的对象:线程四");
    thread3.start();
    thread4.start();
}

class SyncThread implements Runnable {
    private static int count;
    public SyncThread() {
        count = 0;
    }
    public void run() {
        synchronized (SyncThread.class){
            for (int i = 0; i < 50; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ":" + (count++));
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

第七人格

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值