Java 中的 synchronized 同步锁

        导致线程安全问题的根本原因在于,存在多个线程同时操作一个共享资源,要想解决这个问题,就需要保证对共享资源访问的独占性,因此人们在Java中提供了synchronized关键字,我们称之为同步锁,它可以保证在同一时刻,只允许一个线程执行某个方法或代码块。
        synchronized同步锁具有互斥性,这相当于线程由并行执行变成串行执行,保证了线程的安全性,但是损失了性能。下面我们先来看一下synchronized的使用方法。

synchronized 的使用方法

synchronized 的使用方法比较简单,修饰方式有如下两种。

  • 作用在方法级别,表示针对m1()方法加锁,当多个线程同时访问m1()方法时,同一时刻只有一个线程能执行。
public synchronized void m1(){
    //省略代码
}


作用在代码块级别,表示针对某一段线程不安全的代码加锁,只有访问到synchronized(this)这行代码时,才会去竞争锁资源。
 

public void m2( ){ 
    synchronized(this){
        //省略代码
    }
}

        了解了 synchronized的基本使用语法之后,我们来看如图所示的流程,它针对上面的案例增加了 synchronized 同步锁之后的执行流程。简单地说,当多个线程同时访问加synchronized关键字修饰的方法时,需要先抢占一个锁标记,只有抢到锁标记的线程才有资格调用incr()方法。这就使得在同一时刻只有一个线程执行i++操作,从而解决了原子性问题。

了解 synchronized 同步锁的作用范围

        我们对一个方法增加synchronized关键字后,当多个线程访问该方法时,整个执行过程会变成串行执行,这种执行方式很明显会影响程序的性能,那么如何做好安全性及性能的平衡呢?
        实际上,synchronized关键字只需要保护可能存在线程安全问题的代码,因此,我们可以通过控制同步锁的作用范围来实现这个平衡机制。在synchronized 中,提供了两种锁,一是类锁,二是对象锁。
        类锁
        类锁是全局锁,当多个线程调用不同对象实例的同步方法时会产生互斥,具体实现方式如下。  

  • 修饰静态方法:
public static synchronized void m1( ){
    //省略代码
}
  • 修饰代码块,synchronized 中的锁对象是类,也就是Lock.class。
public class Lock{ 
    public void m2(){
        synchronized(Lock.class){
            //省略代码
        }
    }
}


        下面这段程序使用类锁来实现跨对象实例,从而实现互斥的功能。

public class SynchronizedExample{

    public void m1( ) {
        synchronized(SynchronizedExample.class) {
            while (true){
                System.out.println("当前访问的线程:"+Thread.currentThread( ).getName());                         
                try{
                    Thread.sleep(1000);
                } catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args){
        SynchronizedExample se1=new SynchronizedExample(); 
        SynchronizedExample se2=new SynchronizedExample(); 
        new Thread(()->se1.m1(),"t1").start(); 
        new Thread(()->se2.m1(),"t2" ).start();
    }
}
  • 该程序中定义了一个m1()方法,该方法中实现了一个循环打印当前线程名称的逻辑,并且这段逻辑是用类锁来保护的。
  • 在 main()方法中定义了两个SynchronizedExample对象实例sel和se2,又分别定义了两个线程来调用这两个实例的m10方法。

        根据类锁的作用范围可以知道,即便是多个对象实例,也能够达到互斥的目的,因此最终输出的结果是:哪个线程抢到了锁,哪个线程就持续打印自己的线程名称。

        对象锁
        对象锁是实例锁,当多个线程调用同一个对象实例的同步方法时会产生互斥,具体实现方式如下。

  • 修饰普通方法:
public synchronized void m1( ){
    //省略代码
}
  • 修饰代码块,synchronized中的锁对象是普通对象实例。
public class Lock{
    Object lock=new 0bject( ); public void m2( ){
        synchronized(lock){
            //省略代码
        }
    }
}

        下面这段程序演示了对象锁的使用方法,代码如下。

public class SynchronizedForobjectExample {
    Object lock=new 0bject(); 
    public void m1( ){
        synchronized (lock){
            while(true){
                System.out.println("当前获得锁的线程:"+Thread.currentThread().getName());
                try{
                    Thread.sleep(1000);
                } catch (InterruptedException e){
                    e.printStackTrace();}
                }
        }
    }

    public static void main(String[] args) {
        SynchronizedFor0bjectExample se1=new SynchronizedForobjectExample();             
        SynchronizedForObjectExample se2=new SynchronizedForobjectExample(); 
        new Thread(()->se1.m1(),"t1").start(); 
        new Thread(()->se2.m1(),"t2" ).start();
    }
}

我们先来看一下打印结果。

当前获得锁的线程:t1

当前获得锁的线程:t2

当前获得锁的线程:t2

当前获得锁的线程:t1

当前获得锁的线程:t1

当前获得锁的线程:t2

当前获得锁的线程:t1

当前获得锁的线程:t2


        从以上结果中我们发现,对于几乎相同的代码,在使用对象锁的情况下,当两个线程分别访问两个不同对象实例的m10方法时,并没有达到两者互斥的目的,看起来似乎锁没有生效,实际上并不是锁没有生效,问题的根源在于synchronized(lock)中锁对象lock的作用范围过小。
        Class是在JVM启动过程中加载的,每个.class文件被装载后会产生一个Class对象,Class对象在JVM进程中是全局唯一的。通过static修饰的成员对象及方法的生命周期都属于类级别,它们会随着类的定义被分配和装载到内存,随着类被卸载而回收。
        实例对象的生命周期伴随着实例对象的创建而开始,同时伴随着实例对象的回收而结束。

        因此,类锁和对象锁最大的区别是锁对象lock的生命周期不同,如果要达到多个线程互斥,那么多个线程必须要竞争同一个对象锁。
        在上述代码中,通过Objectlock-new Object();构建的锁对象的生命周期是由Synchronized- ForObjectExample 对象的实例来决定的,不同的SynchronizedForObjectExample 实例会有不同的 lock锁对象,由于没有形成竞争,所以不会实现互斥的效果。如果想要让上述程序达到同步的目的,那么我们可以对lock锁对象增加.static关键字。

static Object lock=new 0bject();

最后,留下一个问题去思考,关于 synchronized 同步锁的思考?同步锁的核心特性是排他,要达到这个目的,多线程必须抢占同一个资源。。。。。。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

明雨星云

感谢,我会继续创作更优质作品

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

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

打赏作者

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

抵扣说明:

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

余额充值