java 并发之synchronized

前言

在多线程开发中,我们经常看到synchronized(this)、synchronized(*.class)、synchronized(Object o)这些玩意。

它们都围绕synchronized关键字,用于同步方法或同步代码块,解决线程安全问题,保证在同一时刻最多只有一个线程执行某段代码。

一、synchronized(this)

/**
 * synchronized测试
 */
public class ObjectService {
    /**
     * 方法A
     */
    public void serviceMethodA(){
        try {
            synchronized (this) {
                System.out.println(Thread.currentThread().getName()+" A begin time="+System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName()+" A end   time="+System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    /**
     * 方法B
     */
    public void serviceMethodB(){
        synchronized (this) {
            System.out.println(Thread.currentThread().getName()+" B begin time="+System.currentTimeMillis());
            System.out.println(Thread.currentThread().getName()+" B end   time="+System.currentTimeMillis());
        }
    }

    /**
     * 方法C
     */
    public void serviceMethodC(){
            System.out.println(Thread.currentThread().getName()+" C begin time="+System.currentTimeMillis());
            System.out.println(Thread.currentThread().getName()+" C end   time="+System.currentTimeMillis());
    }
}
/**
 * 测试线程A
 */
public class ThreadA extends Thread {
    private ObjectService objectService;
    public ThreadA(ObjectService objectService){
        //调用父类无参构造方法,完成线程初始化工作
        super();
        this.objectService=objectService;
    }
    @Override
    public void run() {
        //调用父类普通方法
        super.run();
        //执行方法A
        objectService.serviceMethodA();
    }
}
/**
 * 测试线程B
 */
public class ThreadB extends Thread {
    private ObjectService objectService;
    public ThreadB(ObjectService objectService){
        //调用父类无参构造方法,完成线程初始化工作
        super();
        this.objectService=objectService;
    }
    @Override
    public void run() {
        //调用父类普通方法
        super.run();
        //执行方法
//objectService.serviceMethodA();
        objectService.serviceMethodB();
    }
}
/**
 * 测试线程C
 */
public class ThreadC extends Thread {
    private ObjectService objectService;
    public ThreadC(ObjectService objectService){
        //调用父类无参构造方法,完成线程初始化工作
        super();
        this.objectService=objectService;
    }
    @Override
    public void run() {
        //调用父类普通方法
        super.run();
        //执行方法B
        objectService.serviceMethodC();
    }
}
public class MainTest {
    public static void main(String[] args) {
        ObjectService service=new ObjectService();
        ThreadA a=new ThreadA(service);
        a.setName("ThreadA");
        a.start();
        ThreadB b=new ThreadB(service);
        b.setName("ThreadB");
        b.start();
        ThreadC c=new ThreadC(service);
        c.setName("ThreadC");
        c.start();
    }
}

执行结果:

ThreadA A begin time=1622080816475
ThreadC C begin time=1622080816475
ThreadC C end   time=1622080816475
ThreadA A end   time=1622080818479
ThreadB B begin time=1622080818479
ThreadB B end   time=1622080818479

分析可知:

  1. 当一个线程访问对象实例的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象实例中的非synchronized(this)同步代码块。(线程A执行时间段内,另一个线程同时进入了普通方法,这里的同时都是宏观上的)

  2. 当多个并发线程访问同一个对象实例中的某个synchronized(this)同步代码块时,同一时刻只能有一个线程进入。另一个线程必须等待当前线程执行完这个代码块以后才能执行。(线程B执行方法A的开始时间在线程A执行方法A的结束时间之后,说明synchronized(this)同步方法同一时刻只有一个线程可以访问)

  3. 当一个线程访问对象实例的一个synchronized(this)同步代码块时,其他线程对于对象实例中的所有其它synchronized(this)同步代码块的访问都将被阻塞,包括此时访问的这个synchronized(this)同步代码块。(线程A执行同步代码块时,其它线程可以穿插执行非同步代码块,其它线程必须顺序执行其它同步代码块)

  4. synchronized (this) 锁的是当前对象实例,所有该对象实例中的同步方法都会被锁住。即某一时刻,只有一个线程能进入某个同步方法,锁住该方法,与此同时,其它所有synchronized (this)同步方法也会锁住。

二、静态synchronized同步方法

为了更清楚的测试静态同步方法锁类的效果,我们先测试一下锁某个实例对象的情况:

public class ObjectService {
    /**
     * 同步方法A
     */
    public synchronized  void methodA(Object o){
        try {
            System.out.println("static methodA begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
            Thread.sleep(2000);
            System.out.println("static methodA end   线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    /**
     * 同步方法B
     */
    public synchronized  void methodB(Object o){
        System.out.println("static methodB begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
        System.out.println("static methodB end   线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
    }
}
/**
 * 线程A
 */
public class ThreadA extends Thread {
    @Override
    public void run() {
        ObjectService objectService = new ObjectService();
        objectService.methodA(objectService);
    }
}
/**
 * 线程B
 */
public class ThreadB extends Thread {
    @Override
    public void run() {
        ObjectService objectService = new ObjectService();
        objectService.methodA(objectService);
    }
}
public class MainTest {
    public static void main(String[] args) {
        ThreadA a=new ThreadA();
        a.setName("A");
        a.start();
        ThreadB b=new ThreadB();
        b.setName("B");
        b.start();
    }
}

执行结果:

static methodA begin 线程名称:A time:1622085729602
static methodA begin 线程名称:B time:1622085729602
static methodA end   线程名称:B time:1622085731615
static methodA end   线程名称:A time:1622085731615

分析可知:

Synchronized(Object o)传入不同的实例对象,锁住的是不同的实例。多个线程对于同一个实例对象的同步方法会阻塞,对于不同实例对象的同步方法可以同时执行。

修改代码,线程A和B都访问同步方法A:

public class ThreadB extends Thread {
    @Override
    public void run() {
        ObjectService.methodA();
    }
}

执行结果:

static methodA begin 线程名称:A time:1622092770910
static methodA end   线程名称:A time:1622092772920
static methodA begin 线程名称:B time:1622092772920
static methodA end   线程名称:B time:1622092774928
public class ObjectService {
    /**
     * 静态同步方法A
     */
    public synchronized static void methodA(){
        try {
            System.out.println("static methodA begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
            Thread.sleep(2000);
            System.out.println("static methodA end   线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    /**
     * 静态同步方法B
     */
    public synchronized static void methodB(){
        System.out.println("static methodB begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("static methodB end   线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
    }

    public void methodC(){
        System.out.println("static methodC begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
        System.out.println("static methodC end   线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
    }

    public static void methodD(){
        System.out.println("static methodD begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
        System.out.println("static methodD end   线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
    }
}
/**
 * 线程A
 */
public class ThreadA extends Thread {
    @Override
    public void run() {
        new ObjectService().methodA();
    }
}
/**
 * 线程B
 */
public class ThreadB extends Thread {
    @Override
    public void run() {
        new ObjectService().methodB();
    }
}

执行结果:

static methodA begin 线程名称:A time:1622097007969
static methodA end   线程名称:A time:1622097009983
static methodB begin 线程名称:B time:1622097009983
static methodB end   线程名称:B time:1622097011990

综上,分析可知:

1)用类直接在两个线程中分别调用两个不同的静态同步方法,会顺次访问。

2)用类直接在两个线程中分别调用一个静态同步方法和一个静态方法,可以同时访问。

3)用类直接在一个线程中调用一个静态同步方法,另一个用实例对象调用一个静态方法(或普通方法),可以同时访问。

4)用类的不同实例对象分别调用两个不同的静态同步方法,会顺次访问。

 

结论:

synchronized static静态同步方法锁的是当前类,针对的是该类的所有实例对象,多个线程并发访问时,不论是直接通过类还是类的对象实例,访问同一个静态同步方法或访问不同的静态同步方法,必须顺序访问;访问同一个非静态方法或访问不同的非静态方法,可以同时访问。(即控制当前类下同步方法的并发访问)

三、synchronized(*.Class)同步方法

public class ObjectService {
    /**
     * 方法A
     */
    public void methodA(){
        try {
            synchronized (ObjectService.class) {
                System.out.println("methodA begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("methodA end   线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    /**
     * 方法B
     */
    public void methodB(){
        synchronized (ObjectService.class) {
            System.out.println("methodB begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
            System.out.println("methodB end   线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
        }
    }
    /**
     * 方法C
     */
    public void methodC(){
        System.out.println("static methodC begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
        System.out.println("static methodC end   线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
    }

    /**
     * 方法D
     */
    public static void methodD(){
        System.out.println("static methodD begin 线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
        System.out.println("static methodD end   线程名称:"+Thread.currentThread().getName()+" time:"+System.currentTimeMillis());
    }
}
/**
 * 线程A
 */
public class ThreadA extends Thread {
    private ObjectService objectService;
    public ThreadA(ObjectService objectService) {
        //调用父类无参构造,完成线程初始化
        super();
        this.objectService = objectService;
    }
    @Override
    public void run() {
        objectService.methodA();
    }
}
/**
 * 线程B
 */
public class ThreadB extends Thread {
    private ObjectService objectService;
    public ThreadB(ObjectService objectService) {
        //调用父类无参构造,完成线程初始化
        super();
        this.objectService = objectService;
    }
    @Override
    public void run() {
        objectService.methodB();
    }
}
public class MainTest {
    public static void main(String[] args) {
        ObjectService service = new ObjectService();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        a.start();
        service = new ObjectService();
        ThreadB b = new ThreadB(service);
        b.setName("B");
        b.start();
    }
}

四、同步代码块和同步方法的区别

我的理解是,同步代码块就是需要控制并发访问的代码片段(方法内),同步方法就是需要控制并发访问的方法(方法本身)。

五、最后小结

  1. synchronized(this) 只可以锁一个实例对象,synchronized(object)可以锁多个实例对象(多次调用,传入不同的实例即可)。

  2. synchronized(this)相当于当前实例的一把锁,synchronized(object)相当于N个实例对象的N把锁。

  3. synchronized(this)和synchronized (Object o)锁的都是实例对象,不是方法。都是控制实例对象下同步方法的访问。

  4. synchronized static静态同步方法锁的是当前类,针对的是该类的所有实例对象,多个线程并发访问时,不论是直接通过类还是类的对象实例,访问同一个静态同步方法或访问不同的静态同步方法,必须顺序访问;访问同一个非静态方法或访问不同的非静态方法,可以同时访问。(即控制当前类下同步方法的并发访问)

  5. synchronized锁的是实例对象,static synchronized 锁的是类对象。

  6. synchronized(*.class)同步代码块的作用和synchronized static静态同步方法一样,都是锁类对象。

  7. 使用synchronized (*.class)的方法,多线程并发访问,同一实例对象访问,会顺次访问;不同实例对象访问,会顺次访问。说明Class锁对类的所有对象实例起作用。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值