前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >(十三)synchronized用法,四种锁范围

(十三)synchronized用法,四种锁范围

作者头像
HaC
发布2020-12-30 17:58:12
1.4K0
发布2020-12-30 17:58:12
举报
文章被收录于专栏:HaC的技术专栏

《深入理解Java虚拟机》一句话:

当多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替运行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获取正确的结果,那这个对象是线程安全的。

1. 开篇

内存分为主内存和工作内存,每个线程都有自己的工作内存,如何和主内存的数据同步,产生的数据不一致性,就是我们常说的线程安全,这就需要我们去了解Java内存模型了。

借用一张图:

如图为JMM抽象示意图,线程A和线程B之间要完成通信的话,要经历如下两步:

  1. 线程A从主内存中将共享变量读入线程A的工作内存后并进行操作,之后将数据重新写回到主内存中;
  2. 线程B从主存中读取最新的共享变量。

2. 作用

synchronized的字面意思,是同步的意思。

在多线程访问某行代码的时候,synchronized可以用来控制线程的同步,简单的说就是控制synchronized代码段不被多个线程同时执行,使其有序执行。

3. 用法

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

修饰范围

作用范围

代码块

大括号{}括起来的代码,作用的对象是调用这个代码块的对象

方法

整个方法,作用的对象是调用这个方法的对象

静态方法

整个静态方法,作用的对象是这个类的所有对象

synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象

借用一张图更好的理解:

上demo:

demo1 ——synchronized

代码语言:javascript
复制
public class TestDemoSynchronized implements Runnable {
    private Integer y = 0;

    private void setNumber() {
        y++;
    }

    private int getNumber() {
        return y;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            synchronized (y) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                setNumber();
                System.out.println(Thread.currentThread().getName() + " : " + i + "  --->>>" + getNumber());
            }
        }
    }
}

class TestDemo extends Thread {
    TestDemo(Runnable runnable, String name) {
        super(runnable, name);
    }

    public static void main(String[] args) {
        TestDemoSynchronized testDemoSynchronizedfor = new TestDemoSynchronized();
        for (int i = 1; i <= 2; i++) {
            TestDemo testDemofor = new TestDemo(testDemoSynchronizedfor, "Thread" + i);
            testDemofor.start();
        }

    }
}
代码语言:javascript
复制
Thread1 : 1  --->>>1
Thread2 : 1  --->>>2
Thread1 : 2  --->>>2
Thread2 : 2  --->>>3
Thread1 : 3  --->>>4
Thread2 : 3  --->>>4
Thread1 : 4  --->>>5
Thread2 : 4  --->>>6
Thread1 : 5  --->>>7
Thread2 : 5  --->>>8

所以虽然对象y 被synchronized住了,但是 y 的对象本身发生了改变,简而言之,两个线程在进行不同的操作时锁定的不是同一个对象 。(这里并不是原子性问题)

demo2:

代码语言:javascript
复制
private volatile static Integer y = 0;

对 y 加上 volatile ,结果:

代码语言:javascript
复制
Thread1 : 1  --->>>1
Thread2 : 1  --->>>2
Thread1 : 2  --->>>2
Thread2 : 2  --->>>3
Thread1 : 3  --->>>4
Thread2 : 3  --->>>5
Thread2 : 4  --->>>7
Thread1 : 4  --->>>7
Thread2 : 5  --->>>8
Thread1 : 5  --->>>9

因为volatile不能保证原子性。导致最后的结果错误。

下一篇文章再详细讲一下这个volatile的用法。

demo3 ——synchronized(this)、synchronized(class)

synchronized (this) 锁住this对象,即Object

代码语言:javascript
复制
public class TestDemoSynchronized implements Runnable {
    private Integer y = 0;

    private void setNumber() {
        y++;
    }

    private int getNumber() {
        return y;
    }

    @Override
    public void run() {
        synchronized (this) {
             for (int i = 1; i <= 5; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                setNumber();
                System.out.println(Thread.currentThread().getName() + " : " + i + "  --->>>" + getNumber());
            }
        }
    }
}

class TestDemo extends Thread {
    TestDemo(Runnable runnable, String name) {
        super(runnable, name);
    }

    public static void main(String[] args) {
        TestDemoSynchronized testDemoSynchronizedfor = new TestDemoSynchronized();
        for (int i = 1; i <= 2; i++) {
            TestDemo testDemofor = new TestDemo(testDemoSynchronizedfor, "Thread" + i);
            testDemofor.start();
        }

    }
}

结果:

代码语言:javascript
复制
Thread2 : 1  --->>>1
Thread2 : 2  --->>>2
Thread2 : 3  --->>>3
Thread2 : 4  --->>>4
Thread2 : 5  --->>>5
Thread1 : 1  --->>>6
Thread1 : 2  --->>>7
Thread1 : 3  --->>>8
Thread1 : 4  --->>>9
Thread1 : 5  --->>>10

因为第一个线程进入的时候,会拿到整个对象的锁,执行完5次循环才会释放锁。 Thread2先进入,拿到对象锁testDemoSynchronizedfor,Thread1发现自己也是testDemoSynchronizedfor,但是被Thread2先进入锁住了,只能等待。

看看synchronized (TestDemoSynchronized.class) 会怎么样:

代码语言:javascript
复制
public class TestDemoSynchronized implements Runnable {
    private Integer y = 0;

    private void setNumber() {
        y++;
    }

    private int getNumber() {
        return y;
    }

    @Override
    public void run() {
        synchronized (TestDemoSynchronized.class) {
            for (int i = 1; i <= 5; i++) {
                setNumber();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " : " + i + "  --->>>" + getNumber());
            }
        }
    }
}

class TestDemo extends Thread {
    TestDemo(Runnable runnable, String name) {
        super(runnable, name);
    }

    public static void main(String[] args) {
        TestDemoSynchronized testDemoSynchronizedfor = new TestDemoSynchronized();
        for (int i = 1; i <= 2; i++) {
            TestDemo testDemofor = new TestDemo(testDemoSynchronizedfor, "Thread" + i);
            testDemofor.start();
        }
    }
}

结果:

代码语言:javascript
复制
Thread1 : 1  --->>>1
Thread1 : 2  --->>>2
Thread1 : 3  --->>>3
Thread1 : 4  --->>>4
Thread1 : 5  --->>>5
Thread2 : 1  --->>>6
Thread2 : 2  --->>>7
Thread2 : 3  --->>>8
Thread2 : 4  --->>>9
Thread2 : 5  --->>>10

拿到的是类所有对象的锁,自然也锁住了。 类锁实际上是通过对象锁实现的,即类的 Class 对象锁。每个类只有一个 Class 对象,所以每个类只有一个类锁。

有人可能会问,为什么y的自增又正确了呢? 因为线程拿到的是整个对象,setNumber 也在synchronized里面,而且最重要的一点是:synchronize是 能保证原子性。(即setNumber()方法的 y++)

再看看下面的例子:

demo4——只锁一部分,不锁原子部分

代码语言:javascript
复制
public class TestDemoSynchronized implements Runnable {
    private volatile static Integer y = 0; //volatile也没有用

    private void setNumber() {
        y++;
    }

    private int getNumber() {
        return y;
    }

    @Override
    public void run() {
        setNumber();  //把这个方法放外面
        synchronized (this) {
            for (int i = 1; i <= 5; i++) {
            try {
                Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " : " + i + "  --->>>" + getNumber());
            }
        }
    }
}

class TestDemo extends Thread {
    TestDemo(Runnable runnable, String name) {
        super(runnable, name);
    }

    public static void main(String[] args) {
        TestDemoSynchronized testDemoSynchronizedfor = new TestDemoSynchronized();
        for (int i = 1; i <= 2; i++) {
            TestDemo testDemofor = new TestDemo(testDemoSynchronizedfor, "Thread" + i);
            testDemofor.start();
        }

    }
}

结果

代码语言:javascript
复制
Thread2 : 1  --->>>2
Thread2 : 2  --->>>3
Thread2 : 3  --->>>4
Thread2 : 4  --->>>5
Thread2 : 5  --->>>6
Thread1 : 1  --->>>6
Thread1 : 2  --->>>7
Thread1 : 3  --->>>8
Thread1 : 4  --->>>9
Thread1 : 5  --->>>10

setNumber()不使用锁,当两个线程进入的时候就会异步执行这个方法,导致y错误;但是for循环还是加锁的,2个线程只能同步执行。

demo5

代码语言:javascript
复制
public class TestDemoSynchronized implements Runnable {
    private volatile static Integer y = 0;

    private void setNumber() {
        y++;
    }

    private int getNumber() {
        return y;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) { //位置换了
            synchronized (this) { //位置换了
                setNumber();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " : " + i + "  --->>>" + getNumber());
            }
        }
    }
}

class TestDemo extends Thread {
    TestDemo(Runnable runnable, String name) {
        super(runnable, name);
    }

    public static void main(String[] args) {
        TestDemoSynchronized testDemoSynchronizedfor = new TestDemoSynchronized();
        for (int i = 1; i <= 2; i++) {
            TestDemo testDemofor = new TestDemo(testDemoSynchronizedfor, "Thread" + i);
            testDemofor.start();
        }
    }
}

结果

代码语言:javascript
复制
Thread1 : 1  --->>>1
Thread1 : 2  --->>>2
Thread1 : 3  --->>>3
Thread1 : 4  --->>>4
Thread2 : 1  --->>>5
Thread2 : 2  --->>>6
Thread2 : 3  --->>>7
Thread2 : 4  --->>>8
Thread2 : 5  --->>>9
Thread1 : 5  --->>>10  //注意

同步进入for里面的{},但是synchronized并没有锁住for,所以在运行的时候,线程不一定是同步的。

demo6——synchronized(this)、synchronized(class)、synchronized(object)

代码语言:javascript
复制
public class TestDemoSynchronized implements Runnable {
    public Integer y = 0;
    public Integer x = new Integer(1);
    public Integer z = 200;
    public Integer k = 100;

    public void setNumber() {
        y++;
    }

    public int getNumber() {
        return y;
    }

    @Override
    public void run() {
        synchronized (TestDemoSynchronized.class) { //能
//        synchronized (this) { //不能
//        synchronized (x) {  //不能,x 在堆, 1 在常量池,两个对象拥有的x 不一样,可以进入
//        synchronized (z) {  //不能 //-128-127 之间,还是同一个对象,否则会Intger创建一个新对象
//        synchronized (k) { //能,x 此时在常量池,常量池是线程共享的,
            for (int i = 1; i <= 5; i++) {
                setNumber();
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " : " + i + "  --->>> " + getNumber());
            }
        }
    }
}

class TestDemo extends Thread {
    TestDemo(Runnable runnable, String name) {
        super(runnable, name);
    }

    public static void main(String[] args) {
        TestDemoSynchronized testDemoSynchronizedfor = new TestDemoSynchronized();
        TestDemoSynchronized testDemoSynchronizedfor2 = new TestDemoSynchronized();
    
        TestDemo testDemo =new TestDemo(testDemoSynchronizedfor,"Thread1");
        TestDemo testDemo2 =new TestDemo(testDemoSynchronizedfor2,"Thread2");

        testDemo.start();
        testDemo2.start();

    }
}

输出:

代码语言:javascript
复制
Thread1 : 1  --->>> 1
Thread1 : 2  --->>> 2
Thread1 : 3  --->>> 3
Thread1 : 4  --->>> 4
Thread1 : 5  --->>> 5
Thread2 : 1  --->>> 1
Thread2 : 2  --->>> 2
Thread2 : 3  --->>> 3
Thread2 : 4  --->>> 4
Thread2 : 5  --->>> 5

注意这个demo是创建 两个不同的对象 testDemoSynchronizedfor、testDemoSynchronizedfor2 可以使用 synchronized (TestDemoSynchronized.class) 去同步,可以和demo3 比较一下。

如果把同步块 换成 synchronized (this)、synchronized (x) 就不能同步了。 synchronized (z) 、synchronized (k) 的话 因为这个 Integer在 [-128, 127] 之间时,会拆箱放在常量池,常量池是线程共享的,所以两个不同的TestDemoSynchronized 对象去创建 Integer(100) 会先判断常量池是否有100,有就不会创建,直接返回该对象;如果不在 [-128, 127],Integer会直接在堆创建一个对象,这时候两个TestDemoSynchronized对象就互不影响了。

建议尝试一下这个demo6。

参考:

https://blog.csdn.net/mockingbirds/article/details/51336105

https://blog.csdn.net/ya_1249463314/article/details/52571505

https://www.cnblogs.com/hgnulb/p/9942486.html

https://juejin.im/post/594a24defe88c2006aa01f1c#heading-0

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/07/26 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 开篇
  • 2. 作用
  • 3. 用法
  • demo1 ——synchronized
  • demo2:
  • demo3 ——synchronized(this)、synchronized(class)
  • demo4——只锁一部分,不锁原子部分
  • demo5
  • demo6——synchronized(this)、synchronized(class)、synchronized(object)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档