用示例说明synchronized

yeyeyeyeyyeyeye

一 .简单介绍synchronized

关键字 synchronized可以在多线程并发中使用,保证同步。
非常重要的一点,保证线程安全的三个特性:原子性、可见性、有序性。而synchronized可以保证原子性和可见性。
synchronized主要有3种用法:
(1)修饰普通方法:作用于实例对象
(2)修饰静态方法:作用于类对象
(3)修饰代码块:可以指定作用的对象

二. 用Demo说明synchronized主要的用法

1.模拟多线程环境
Thread thread = new Thread(runnable); // runnable里面做操作
thread.start();  // 用Thread才能模拟

基本来说,synchronized修饰一个方法,多线程调用这个方法的时候,肯定会同步,这个大家都知道,就不用写Demo来说明了

2.多个普通方法使用synchronized 修饰(示例1)

假设有两个方法
#####(1)一个方法用synchronized修饰,一个不用

                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        test.one("AA");
                    }
                });
                thread.start();
                test.two();
    public synchronized void one(String tag){
        for (int i = 0; i < 100; i++) {
            Log.v("mmp","one 【"+tag+" "+i);
        }
    }

    public void two(){
        for (int i = 0; i < 100; i++) {
            Log.v("mmp","two "+i);
        }
    }

这样得到的结果是两个方法不会同步,打印的结果是乱的。

#####(2)两个方法都加锁
两个方法都用synchronized 修饰

    public synchronized void one(String tag){
        for (int i = 0; i < 10000; i++) {
            Log.v("mmp","one 【"+tag+" "+i);
        }
    }

    public synchronized void two(){
        for (int i = 0; i < 10000; i++) {
            Log.v("mmp","two "+i);
        }
    }

得到的结果是数据没有乱,两个方法实现了同步

(3)synchronized修饰的方法调用不被synchronized修饰的方法
    public synchronized void one(String tag){
        for (int i = 0; i < 10000; i++) {
            Log.v("mmp","one 【"+tag+" "+i);
        }
        three();
    }

    public synchronized void two(){
        for (int i = 0; i < 10000; i++) {
            Log.v("mmp","two "+i);
        }
    }

    public void three(){
        for (int i = 0; i < 10000; i++) {
            Log.v("mmp","three "+i);
        }
    }

这种情况数据也不会乱,和上面的一样,调用two的时候,one都没进去,更不可能调用three
从示例1可以有一种感觉,synchronized 作用的是对象,一个对象只有一个锁。

3.静态方法用synchronized修饰

#####(1)一个线程调普通的方法,主线程调这个类的静态方法,两个方法都用synchronized修饰(示例2)

                Thread thread = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        test.one("AA");
                    }
                });
                thread.start();
                Test.three();
    public synchronized void one(String tag){
        for (int i = 0; i < 100; i++) {
            Log.v("mmp","one 【"+tag+" "+i);
        }
    }

    public synchronized static void three(){
        for (int i = 0; i < 100; i++) {
            Log.v("mmp","three "+i);
        }
    }


可以看到打印的数据是乱的,这就示例1的不同,上面synchronized修饰两个普通方法,这个是synchronized修饰一个普通方法和一个静态方法。这样就能证明修饰普通方法和修饰静态方法不是作用在同一个对象。
那如果要静态方法和普通方法里面的操作实现同步怎么做?
说实话我好像基本没见过这样的要求,那是不能做吗?当然能做,没什么是不能做的。

静态方法和基本方法保持同步(示例3)
    public void one(String tag){
        synchronized (Test.class) {
            for (int i = 0; i < 100; i++) {
                Log.v("mmp", "one 【" + tag + " " + i);
            }
        }
    }

    public synchronized static void three(){
        for (int i = 0; i < 100; i++) {
            Log.v("mmp", "three " + i);
        }
    }

这样就行了,这样数据就不会乱了。这里用到了同步代码块,把锁的对象设置成类,两个都是对类的锁,就会产生互斥。

4.同步代码块(示例4)

如果我们对某个方法,不想让这个方法的全部代码都同步,只想让部分代码同步,在上面的栗子中加入一个循环打印(循环次数改小方便截图)

    public void one(String tag){
        for (int i = 0; i < 10; i++) {
            Log.v("mmp", "非同步块:" + tag + " " + i);
        }
        synchronized (Test.class) {
            for (int i = 0; i < 10; i++) {
                Log.v("mmp", "one 【" + tag + " " + i);
            }
        }
    }

    public synchronized static void three(){
        for (int i = 0; i < 10; i++) {
            Log.v("mmp", "three " + i);
        }
    }


可以从结果中看出,只有synchronized 同步块里面的代码才会同步。

5.小结

示例1演示了synchronized 作用于类的实例对象。
示例2演示了synchronized 作用于类对象。
示例3和示例4演示了synchronized 使用同步代码块作用于指定的对象。
比如你想作用于当前的类的实例对象,可以这样写

        synchronized (this) {
            .......
        }

如果你想作用于类对象,可以这样写

        synchronized (类名.class) {
            .......
        }

三. 原理

简单来看看synchronized的原理,为什么说简单呢,因为底层的代码我也看不懂,所以大概就只能去看别人总结的,大致了解一下,详细的话以后看得懂代码再来详细说吧。
将synchronized反编译之后
网上找的图
我自己也试着反编译了下,发现找到的不是monitorenter和monitorexit,我这边看到的是monitor-enter和monitor-exit

可以看出,锁的进入和退出都有和monitor有关。

1.对象头

这就要涉及到java的对象头了,那对象头是什么。
Java对象保存在内存中时,由以下三部分组成: 对象头、实例数据、 对齐填充字节。也就是可以把JAVA对象抽象的认为是: 对象头 + 实例数据 + 对象填充
那这个对象头反过来就是Java对象的一部分,那肯定是用来存储某部分的数据。
对象头又由两部分组成,一部分用于存储自身的运行时数据,称之为 Mark Word,另外一部分是类型指针(先了解就好)。
所以 对象头 = Mark Word + 类型指针

Mark Word主要用来存储对象自身的运行时数据,如hashcode、gc分代年龄等。当然这个我也是看别人说的。然后可以看看别人画的结构图,会比较清晰。
网上的图

// 32位系统的
|-------------------------------------------------------|--------------------|
|                  Mark Word (32 bits)                  |       State        |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 |       Normal       |
|-------------------------------------------------------|--------------------|
|  thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 |       Biased       |
|-------------------------------------------------------|--------------------|
|               ptr_to_lock_record:30          | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
|               ptr_to_heavyweight_monitor:30  | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
|                                              | lock:2 |    Marked for GC   |
|-------------------------------------------------------|--------------------|
// 64位系统的
|------------------------------------------------------------------------------|--------------------|
|                                  Mark Word (64 bits)                         |       State        |
|------------------------------------------------------------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |       Normal       |
|------------------------------------------------------------------------------|--------------------|
| thread:54 |       epoch:2        | unused:1 | age:4 | biased_lock:1 | lock:2 |       Biased       |
|------------------------------------------------------------------------------|--------------------|
|                       ptr_to_lock_record:62                         | lock:2 | Lightweight Locked |
|------------------------------------------------------------------------------|--------------------|
|                     ptr_to_heavyweight_monitor:62                   | lock:2 | Heavyweight Locked |
|------------------------------------------------------------------------------|--------------------|
|                                                                     | lock:2 |    Marked for GC   |
|------------------------------------------------------------------------------|--------------------|

网上的图
大概了解下就行。

2.Monitor

Monitor 是一种同步工具,也是一个对象。
Object 类本身就是监视者对象,可以想象成他们本身就带了一把看不见的锁,可以看看Java Monitor 工作原理的图。
网上的图
这里也是简单理解一下,详细的流程就没这么简单了。

3.锁的状态

之前的synchronized是重量级的锁,在JAVA 6之后做了优化,锁的状态有4种,看上面的图就能看出,分为无锁状态、偏向锁、轻量级锁和重量级锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值