「并发编程」Synchronized的方法与同步块的区别

174 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第33天,点击查看活动详情

「并发编程」Synchronized的方法与同步块的区别

一、块与方法

二、同步方法

三、同步块

四、案例分析

五、总结

前言

一、块与方法

首先我们要明白什么是块、 什么是方法

代码块即java代码中用{ }括起来的代码段。

非静态代码块:

作用在方法当中,作用是控制变量的生命周期。

public class Test {
    {
        System.out.println("非静态代码块");//非静态代码块每次创建对象时都会执行
    }
​
    public static void main(String[] args) {
        new Test();
        new Test();
    }
}

静态代码块:

在类中的成员位置,{}前用static修饰,作用在于对类进行初始化。

public class Test {
    static {
        System.out.println("非静态代码块");//非静态代码块每次创建对象时都会执行
    }
​
    public static void main(String[] args) {
        new Test();
        new Test();
    }
}

方法

方法在很多地方被称为函数,方法是一段可以被重复执行的代码块。

方法用于定义类的某种行为或功能,其语法结构如下:

访问控制符 [修饰符] 返回值类型 方法名( [参数] )  {
           //方法体    
}

例如用于计算加法的方法

int add(int x, int y){
    return x + y;
}

本处只是简单介绍,具体......

进入正题

二、同步方法

由于我们可以通过private关键字来保证数据对象只能被方法访问,所以我们只需要针对方法提出一套机制,这套机制就是synchronized关键字,它包括两种用法: synchronized方法synchronized块

//同步方法
public synchronized void method(int args){}

synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个 synchronized,方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞。方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。

缺陷

若将一个大的方法申明为synchronized将会影响效率。如果方法有A代码(只读)和B代码(写、修改),使用同步方法锁住A代码会会影响效率。由此我们引入了同步块

三、同步块

synchronized(Obj){}//Obj为需要锁住的对象(会变化的量)

Obj

Obj称之为同步监视器

  • Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
  • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class[反射中讲解]

执行过程

同步监视器的执行过程

  • 第一个线程访问,锁定同步监视器,执行其中代码
  • 第二个线程访问,发现同步监视器被锁定,无法访问
  • 第一个线程访问完毕,解锁同步监视器
  • 第二个线程访问,发现同步监视器没有锁,然后锁定并访问

四、案例分析

线程同步问题大都使用synchronized解决,有同步代码块和同步方法的两种方式,主要记一下这两种的区别

package cn.frozenpenguin.test;
​
public class Test {
    public void main(String[] args) {
        new Thread(()->{
            System.out.println("showA..");
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            synchronized (this) {
                System.out.println("showB..");
            }
        }).start();
        new Thread(()->{
            String s = "1";
            synchronized (s) {
                System.out.println("showC..");
            }
        }).start();
    }
}

运行结果如下: 在这里插入图片描述

这段代码的打印结果是,showA…..showC…..会很快打印出来,showB…..会隔一段时间才打印出来,那么showB为什么不能像showC那样很快被调用呢?

  在启动线程1调用方法A后,接着会让线程1休眠3秒钟,这时会调用方法C,注意到方法C这里用synchronized进行加锁,这里锁的对象是s这个字符串对象。但是方法B则不同,是用当前对象this进行加锁,注意到方法A直接在方法上加synchronized,这个加锁的对象是什么呢?显然,这两个方法用的是一把锁。

由这样的结果,我们就知道这样同步方法是用什么加锁的了,由于线程1在休眠,这时锁还没释放,导致线程2只有在3秒之后才能调用方法B,由此,可知两种加锁机制用的是同一个锁对象,即当前对象。   另外,同步方法直接在方法上加synchronized实现加锁,同步代码块则在方法内部加锁,很明显,同步方法锁的范围比较大,而同步代码块范围要小点,一般同步的范围越大,性能就越差,一般需要加锁进行同步的时候,肯定是范围越小越好,这样性能更好。

五、总结

  • 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
  • 然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。 ed(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
  • 然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
  • 尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。

笔记参考

oldmonk真正的小明被占用了Java学到头秃