资讯专栏INFORMATION COLUMN

一起学并发编程 - synchronized详解

acrazing / 2381人阅读

摘要:每个对象只有一个锁与之相关联。实现同步则是以系统开销作为代价,甚至可能造成死锁,所以尽量避免滥用。这种机制确保了同一时刻该类实例,所有声明为的函数中只有一个方法处于可执行状态,从而有效避免了类成员变量访问冲突。

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

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67629.html

相关文章

  • 一起并发编程 - sleep与wait的差异

    摘要:一般差异简单来说,是一个用于线程同步的实例方法。暂停当前线程,不释放任何锁。用来线程间通信,使拥有该对象锁的线程等待直到指定时间或。执行对该对象加的同步代码块。 在JAVA的学习中,不少人会把sleep和wait搞混,认为都是做线程的等待,下面主要介绍下这俩者是什么,及了解它们之间的差异和相似之处。 一般差异 简单来说,wait()是一个用于线程同步的实例方法。因为定义在java.l...

    noONE 评论0 收藏0
  • 一起并发编程 - 死锁跟踪分析

    摘要:上一章介绍过关键字,使用它可以给程序互斥部分加上一把锁从而达到同步的效果,但错误的用法会导致多个线程同时被阻塞死锁死锁多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。 上一章介绍过synchronized关键字,使用它可以给程序互斥部分加上一把锁从而达到同步的效果,但错误的用法会导致多个线程同时被阻塞.... 死锁 死锁...

    ACb0y 评论0 收藏0
  • 一起并发编程 - 等待与通知

    摘要:如果有其它线程调用了相同对象的方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,从新争夺锁的拥有权。 wait,notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视,而本文则是对这些关键字的使用进行描述。 存在即合理 在java中,每个对象都有两个池,锁池(monitor)和等待池(waitset),每个...

    Meathill 评论0 收藏0
  • 者福音!可能是最适合你的Java习路线和方法推荐。

    摘要:学习完多线程之后可以通过下面这些问题检测自己是否掌握,下面这些问题的答案以及常见多线程知识点的总结在这里。可选数据结构与算法如果你想进入大厂的话,我推荐你在学习完基础或者多线程之后,就开始每天抽出一点时间来学习算法和数据结构。 我自己总结的Java学习的系统知识点以及面试问题,已经开源,目前已经 35k+ Star。会一直完善下去,欢迎建议和指导,同时也欢迎Star: https://...

    yanest 评论0 收藏0
  • 一起并发编程 - Volatile关键字详解

    摘要:比如用修饰的变量,就会确保变量在修改时,其它线程是可见的。。多核环境中,多个线程分别在不同的中运行,就意味着,多个线程都有可能将变量拷贝到当前运行的里。当线程读取变量时,它将能看见被线程写入的东西。 volatile是用来标记一个JAVA变量存储在主内存(main memory)中,多线程读写volatile变量会先从高速缓存中读取,但是写入的时候会立即通过内存总线刷到主存,同时内存总...

    vpants 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<