Java同步关键字synchronized详解

前言

多线程编程可以极大地提高了效率,但也会带来线程安全问题。比如说多个线程向数据库插入数据,就可能会导致数据库中数据重复。

什么时候会引发线程安全问题

首先我需要了解什么是临界资源?有这样一种资源,在某一时刻只能被一个线程所使用,这种资源可以是各种类型的的资源:一个变量、一个对象、一个文件、一个数据库表等。

举个简单的例子:

现在有两个线程分别从网络上读取数据,然后插入一张数据库表中,要求不能插入重复的数据。那么必然在插入数据的过程中存在两个操作:

1)检查数据库中是否存在该条数据;

2)如果存在,则不插入;如果不存在,则插入到数据库中。假如两个线程分别用thread-a和thread-b表示,某一时刻,thread-a和thread-b都读取到了数据X,那么可能会发生这种情况:thread-a去检查数据库中是否存在数据X,然后thread-b也接着去检查数据库中是否存在数据X,如果数据x不存在,就会导致重复插入。

如何解决线程安全问题

基本上所有的并发模式在解决线程安全问题时,都采用“序列化访问临界资源”的方案,即在同一时刻,只能有一个线程访问临界资源,也称作同步互斥访问。通常来说,是在访问临界资源的代码前面加上一个锁,当访问完临界资源后释放锁,让其他线程继续访问,即通过加锁实现

synchronized同步方法或者同步块

在了解synchronized关键字的使用方法之前,我们先来看一个概念:互斥锁,顾名思义:能到达到互斥访问目的的锁。举个简单的例子:如果对临界资源加上互斥锁,当一个线程在访问该临界资源时,其他线程便只能等待。 在Java中,每一个对象都拥有一个锁标记(monitor),也称为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。

在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

下面通过几个简单的例子来说明synchronized关键字的使用:

1.synchronized方法

下面这段代码中两个线程分别调用insertData对象插入数据:

1public class Test {

2 public static void main(String[] args) {

3 final InsertData insertData = new InsertData();

4 new Thread() {

5 public void run() {

6 insertData.insert(Thread.currentThread());

7 };

8 }.start();

9 new Thread() {

10 public void run() {

11 insertData.insert(Thread.currentThread());

12 };

13 }.start();

14 }

15}

16class InsertData {

17 private ArrayList arrayList = new ArrayList();

18 public void insert(Thread thread){

19 for(int i=0;i<5;i++){

20 System.out.println(thread.getName()+"在插入数据"+i);

21 arrayList.add(i);

22 }

23 }

24 }

输出结果

说明两个线程在同时执行insert方法。而如果在insert方法前面加上关键字synchronized的话,运行结果为:

1class InsertData {

2 private ArrayList arrayList = new ArrayList();

3 public synchronized void insert(Thread thread){

4 for(int i=0;i<5;i++){

5 System.out.println(thread.getName()+"在插入数据"+i);

6 arrayList.add(i);

7 }

8 }

9}

从上输出结果说明,Thread-1插入数据是等Thread-0插入完数据之后才进行的。说明Thread-0和Thread-1是顺序执行insert方法的。

这就是synchronized方法。

不过有几点需要注意:

1)当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。

2)当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的,

3)如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型),也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。

 2.synchronized代码块

synchronized代码块类似于以下这种形式:

1synchronized(synObject) {

2 // 代码逻辑

3}

当在某个线程中执行这段代码块,该线程会获取对象synObject的锁,从而使得其他线程无法同时访问该代码块。synObject可以是this,代表获取当前对象的锁,也可以是类中的一个属性,代表获取该属性的锁。

比如上面的insert方法可以改成以下两种形式:

1class InsertData {

2 private ArrayList arrayList = new ArrayList();

3 public void insert(Thread thread){

4 synchronized (this){

5 for(int i=0;i<100;i++){

6 System.out.println(thread.getName()+"在插入数据"+i);

7 arrayList.add(i);

8 }

9 }

10 }

11}

12

13class InsertData {

14 private ArrayList arrayList = new ArrayList();

15 private Object object = new Object();

16 public void insert(Thread thread){

17 synchronized (object){

18 for(int i=0;i<100;i++){

19 System.out.println(thread.getName()+"在插入数据"+i);

20 arrayList.add(i);

21 }

22 }

23 }

24}

从上面可以看出,synchronized代码块使用起来比synchronized方法要灵活得多。因为也许一个方法中只有一部分代码只需要同步,如果此时对整个方法用synchronized进行同步,会影响程序执行效率。而使用synchronized代码块就可以避免这个问题,synchronized代码块可以实现只对需要同步的地方进行同步。

另外,每个类也会有一个锁,它可以用来控制对static数据成员的并发访问。并且如果一个线程执行一个对象的非static synchronized方法,另外一个线程需要执行这个对象所属类的static synchronized方法,此时不会发生互斥现象,因为访问static synchronized方法占用的是类锁,而访问非static synchronized方法占用的是对象锁,所以不存在互斥现象。

看下面这段代码就明白了:

1public class Test {

2 public static void main(String[] args) {

3 final InsertData insertData = new InsertData();

4 new Thread(){

5 @Override

6 public void run() {

7 insertData.insert();

8 }

9 }.start();

10 new Thread(){

11 @Override

12 public void run() {

13 insertData.insert1();

14 }

15 }.start();

16 }

17}

18class InsertData{

19 public synchronized void insert(){

20 System.out.println("执行insert");

21 try {

22 Thread.sleep(5000);

23 } catch (InterruptedException e){

24 e.printStackTrace();

25 }

26 System.out.println("执行insert完毕");

27 }

28 public synchronized static void insert1() {

29 System.out.println("执行insert1");

30 System.out.println("执行insert1完毕");

31 }

32 }

输出结果

 第一个线程里面执行的是insert方法,不会导致第二个线程执行insert1方法发生阻塞现象。

下面我们看一下synchronized关键字到底做了什么事情,我们来反编译它的字节码看一下,下面这段代码反编译后的字节码为: 1public class InsertData {

2 private Object object = new Object();

3 public void insert(Thread thread){

4 synchronized (object) {

5 }

6 }

7 public synchronized void insert1(Thread thread){

8

9 }

10 public void insert2(Thread thread){

11

12 }

13}

从反编译获得的字节码可以看出,synchronized代码块实际上多了monitorenter和monitorexit两条指令。monitorenter指令执行时会让对象的锁计数加1,而monitorexit指令执行时会让对象的锁计数减1,其实这个与操作系统里面的PV操作很像,操作系统里面的PV操作就是用来控制多个线程对临界资源的访问。对于synchronized方法,执行中的线程识别该方法的 method_info 结构是否有 ACC_SYNCHRONIZED 标记设置,然后它自动获取对象的锁,调用方法,最后释放锁。如果有异常发生,线程自动释放锁。

有一点要注意:对于synchronized方法或者synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。

以上就是小编今天的分享,感谢各位能看完文章,最后我再分享一个我自己的后端技术群,群里自己收集了很多Java架构资料,大家可以进群免费领取哦,群号:680075317,也可以进群一起交流,比如遇到技术瓶颈、面试不过的,大家一些交流学习!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,542评论 6 504
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,822评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,912评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,449评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,500评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,370评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,193评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,074评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,505评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,722评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,841评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,569评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,168评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,783评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,918评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,962评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,781评论 2 354

推荐阅读更多精彩内容

  • 摘要: 我们已经知道,synchronized 是Java的关键字,是Java的内置特性,在JVM层面实现了对临界...
    kingZXY2009阅读 1,830评论 0 20
  • 一、基础知识:1、JVM、JRE和JDK的区别:JVM(Java Virtual Machine):java虚拟机...
    杀小贼阅读 2,378评论 0 4
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,957评论 1 18
  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,454评论 1 15
  • 一:java概述: 1,JDK:Java Development Kit,java的开发和运行环境,java的开发...
    慕容小伟阅读 1,788评论 0 10