一起学并发编程

来源:互联网 发布:星空卫视直播软件 编辑:程序博客网 时间:2024/05/01 14:09

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

概述

  • 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 (Bank3.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(欢迎调戏)

原创粉丝点击