一起学并发编程 - synchronized详解

synchronized是JAVA语言的一个关键字,使用 synchronized 来修饰方法或代码块的时候,能够保证多个线程中最多只有一个线程执行该段代码 ...

<!-- more -->

概述

  • synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就同步方法同步代码块块。细分为 instance variable(实例变量)、object reference(对象引用)、static method(静态方法) 和 class literals(常量类)。
  • 无论·synchronized·关键字加在方法上还是对象上,它获取的都是对象锁,而不是将一段代码或一个函数当作锁,而且同步方法很可能还会被其他线程的对象访问。
  • 每个对象只有一个锁(lock)与之相关联。
  • 实现同步则是以系统开销作为代价,甚至可能造成死锁,所以尽量避免滥用。

同步方法: 使用 synchronized 标记的方法,只有获得该方法类实例的锁才能执行,否则所属线程将被阻塞,方法一旦执行,就独占该锁,直到该方法执行完毕将锁释放,被阻塞的线程才能获得锁从而执行。这种机制确保了同一时刻该类实例,所有声明为 synchronized 的函数中只有一个方法处于可执行状态,从而有效避免了类成员变量访问冲突。

同步方法缺陷:若将一个大的方法声明为 synchronized 将会大大的影响效率,典型的,若将线程类的方法 run() 声明为 synchronized,由于在线程的整个生命期中它一直在运行,因此将导致对本类任何 synchronized 方法的调用都不会成功。因此在这种环境下,可以使用同步代码块的方式

同步代码块: 除了方法前用synchronized关键字,还可以用于方法中的某个区块中,表示只对该区域内的资源进行互斥操作。用法是: synchronized(this){/区块/},它的作用域是当前对象。也可以创建一个特殊的instance变量(它得是一个对象)来充当锁

写法

类的范围写法,防止多个线程同时访问这个类中的 synchronized method,它可以对类的所有对象实例起作用
static synchronized void transferAccount() {
    //...
}
//等同
static void transferAccount() {
    synchronized(Bank.class) {
        //...
    }
}
对象实例内写法,多个线程同时访问该对象的 synchronized 方法,如果该对象实例有多个 synchronized方法,任意线程访问了其中的一个 synchronized方法,剩余线程则不能并发访问该对象中任何一个 synchronized方法(不同对象实例的 synchronized方法是互不干扰的。其它线程依然可以并发访问 相同对象类不同实例中的 synchronized方法,如果想做到在 不同对象实例同步需要使用 class literal的方式)
synchronized void transferAccount() {
    //...
}

void transferAccount() {
    synchronized(this) {
        //...
    }
}

private final byte[] LOCK = new byte[0]; // 特殊的实例化对象
void transferAccount() {
    synchronized(LOCK) {
        //...
    }
}

案例一:静态同步方法

class Bank1 {
     synchronized static void transferAccount() {
        System.out.println("开始转账:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("转账完毕");
    }

     synchronized static void debit() {
        System.out.println("开始扣款:" + Thread.currentThread().getName());
        try {
            Thread.sleep(3 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("扣款完毕");
    }
}

public class BankMain {

    public static void main(String[] args) {
        new Thread(Bank1::transferAccount, "北京银行").start();
        new Thread(Bank1::debit, "上海银行").start();
    }
}
日志
开始转账:北京银行
转账完毕
开始扣款:上海银行
扣款完毕
日志

分析:通过日志看到在使用synchronized后,虽然是调用的不同方法,但是线程还是同步去执行的(不加并发执行,结果你懂得(^▽^))

案例二:同步方法单一对象锁

class Bank2 implements Runnable {

    @Override
    public synchronized void run() {
        System.out.println("查询数据:" + Thread.currentThread().getName());
        System.out.println("开始转账:" + Thread.currentThread().getName());
        try {
            Thread.sleep(5 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("转账完毕");
    }
}

public class BankMain {

    public static void main(String[] args) {
        Bank2 bank2 = new Bank2();
        new Thread(bank2, "北京银行").start();
        new Thread(bank2, "上海银行").start();
    }
}
日志
查询数据:北京银行
开始转账:北京银行
转账完毕
查询数据:上海银行
开始转账:上海银行
转账完毕
日志

分析:方法同步执行,谁获得锁谁先执行

案例三:Lock对象锁

class Bank3 implements Runnable {
    private final byte[] LOCK = new byte[0]; // 特殊的实例化变量

    @Override
    public void run() {
        System.out.println("查询数据:" + Thread.currentThread().getName());
        synchronized (LOCK) {//该种方式只能锁
            System.out.println("开始转账:" + Thread.currentThread().getName());
            try {
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("转账完毕");
        }
    }
}

public class BankMain {

    public static void main(String[] args) {
        Bank3 bank = new Bank3();
        new Thread(bank, "北京银行").start();
        new Thread(bank, "上海银行").start();
    }
}
日志
查询数据:北京银行
查询数据:上海银行
开始转账:北京银行
转账完毕
开始转账:上海银行
转账完毕
日志

分析:互斥部分上锁,查询数据部分则并发执行

案例四:同步到多个对象锁

前文说过一个实例对象一把锁,在案例三案例四中,都只实例化了一个对象,当对象为多实例化的时候,需使用class literal 的方式,它和synchronized static method方式产生的结果一样,取得的锁很特别,为当前调用该方法对象所属的类(而不再是由这个Class产生的某个具体对象了)。

class Bank4 implements Runnable {
    @Override
    public void run() {
        System.out.println("查询数据:" + Thread.currentThread().getName());
        synchronized (Bank4.class) {
            System.out.println("开始转账:" + Thread.currentThread().getName());
            try {
                Thread.sleep(5 * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("转账完毕");
        }
    }
}

public class BankMain {

    public static void main(String[] args) {
        new Thread(new Bank4(), "北京银行").start();
        new Thread(new Bank4(), "上海银行").start();
    }
}

日志
查询数据:北京银行
查询数据:上海银行
开始转账:北京银行
转账完毕
开始转账:上海银行
转账完毕
日志

可以推断:如果一个类中定义了一个synchronized static methodA,也定义了一个 synchronized 的 instance methodB,该类同一个对象在多线程中分别访问A和B两个方法时,并不会构成同步,因为它们的锁都不一样。methodA的锁是它的所属Class,而methodB的锁是当前对象(该部分代码未贴出,可以自己实现或者看GIT

- 说点什么

全文代码:https://gitee.com/battcn/battcn-concurent/tree/master/Chapter1-1/battcn-thread/src/main/java/com/battcn/chapter5

  • 个人QQ:1837307557
  • battcn开源群(适合新手):391619659

微信公众号:battcn(欢迎调戏)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值