如何在JAVA中使用Synchronized

《编程思想之多线程与多进程(1)——以操作系统的角度述说线程与进程》一文详细讲述了线程、进程的关系及在操作系统中的表现,这是多线程学习必须了解的基础。本文将接着讲一下Java线程同步中的一个重要的概念synchronized.

在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;

2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;

3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;

4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

修饰一个代码块

1.一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。我们看下面一个例子:
【Demo1】:synchronized的用法

/**
* 同步线程
*/
class SyncThread implements Runnable {
    private static int count;

    public SyncThread() {
      count = 0;
    }

    public void run() {
      synchronized(this) {
        for (int i = 0; i < 5; i++) {
          try {
            System.out.println(Thread.currentThread().getName() + ":" + (count++));
            Thread.sleep(100);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }

    public int getCount() {
      return count;
    }
}

SyncThread的调用:

 SyncThread syncThread = new SyncThread();
 Thread thread1 = new Thread(syncThread, "SyncThread1");
 Thread thread2 = new Thread(syncThread, "SyncThread2");
 thread1.start();
 thread2.start();

结果如下:

SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9

当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。
我们再把SyncThread的调用稍微改一下:

Thread thread1 = new Thread(new SyncThread(), "SyncThread1");
 Thread thread2 = new Thread(new SyncThread(), "SyncThread2");
 thread1.start();
 thread2.start();

结果如下:

SyncThread1:0
SyncThread2:1
SyncThread1:2
SyncThread2:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread1:7
SyncThread1:8
SyncThread2:9

不是说一个线程执行synchronized代码块时其它的线程受阻塞吗?为什么上面的例子中thread1和thread2同时在执行。这是因为synchronized只锁定对象,每个对象只有一个锁(lock)与之相关联,而上面的代码等同于下面这段代码:

SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();

这时创建了两个SyncThread的对象syncThread1和syncThread2,线程thread1执行的是syncThread1对象中的synchronized代码(run),而线程thread2执行的是syncThread2对象中的synchronized代码(run);我们知道synchronized锁定的是对象,这时会有两把锁分别锁定syncThread1对象和syncThread2对象,而这两把锁是互不干扰的,不形成互斥,所以两个线程可以同时执行。

2.当一个线程访问对象的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该对象中的非synchronized(this)同步代码块。

【Demo2】:多个线程访问synchronized和非synchronized代码块

class Counter implements Runnable {
    private int count;

    public Counter() {
      count = 0;
    }

    public void countAdd() {
      synchronized(this) {
        for (int i = 0; i < 5; i ++) {
          try {
            System.out.println(Thread.currentThread().getName() + ":" + (count++));
            Thread.sleep(100);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }

    //非synchronized代码块,未对count进行读写操作,所以可以不用synchronized
    public void printCount() {
      for (int i = 0; i < 5; i ++) {
        try {
          System.out.println(Thread.currentThread().getName() + " count:" + count);
          Thread.sleep(100);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }

    public void run() {
      String threadName = Thread.currentThread().getName();
      if (threadName.equals("A")) {
        countAdd();
      } else if (threadName.equals("B")) {
        printCount();
      }
    }
}

调用代码:

 Counter counter = new Counter();
 Thread thread1 = new Thread(counter, "A");
 Thread thread2 = new Thread(counter, "B");
 thread1.start();
 thread2.start();

结果如下:

A:0
B count:1
A:1
B count:2
A:2
B count:3
A:3
B count:4
A:4
B count:5

上面代码中countAdd是一个synchronized的,printCount是非synchronized的。从上面的结果中可以看出一个线程访问一个对象的synchronized代码块时,别的线程可以访问该对象的非synchronized代码块而不受阻塞。

指定要给某个对象加锁

【Demo3】:指定要给某个对象加锁

/**
* 银行账户类
*/
class Account {
    String name;
    float amount;

    public Account(String name, float amount) {
      this.name = name;
      this.amount = amount;
    }
//存钱
    public void deposit(float amt) {
      amount += amt;
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
//取钱
    public void withdraw(float amt) {
      amount -= amt;
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }

    public float getBalance() {
      return amount;
    }
}

/**
* 账户操作类
*/
class AccountOperator implements Runnable {
    private Account account;
    public AccountOperator(Account account) {
      this.account = account;
    }

    public void run() {
      synchronized (account) {
        account.deposit(500);
        account.withdraw(500);
        System.out.println(Thread.currentThread().getName() + ":" + account.getBalance());
      }
    }
}

调用代码:

Account account = new Account("zhang san", 10000.0f);
AccountOperator accountOperator = new AccountOperator(account);

final int THREAD_NUM = 5;
Thread threads[] = new Thread[THREAD_NUM];
for (int i = 0; i < THREAD_NUM; i ++) {
threads[i] = new Thread(accountOperator, "Thread" + i);
threads[i].start();
}

结果如下:

Thread3:10000.0
Thread2:10000.0
Thread1:10000.0
Thread4:10000.0
Thread0:10000.0

在AccountOperator 类中的run方法里,我们用synchronized 给account对象加了锁。这时,当一个线程访问account对象时,其他试图访问account对象的线程将会阻塞,直到该线程访问account对象结束。也就是说谁拿到那个锁谁就可以运行它所控制的那段代码。

当有一个明确的对象作为锁时,就可以用类似下面这样的方式写程序。

public void method3(SomeObject obj) {
//obj 锁定的对象
  synchronized(obj) {
// todo
  }
}

当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:

class Test implements Runnable {
    private byte[] lock = new byte[0]; // 特殊的instance变量
    public void method() {
      synchronized(lock) {
// todo 同步代码块
      }
    }

    public void run() {

    }
}

说明:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

修饰一个方法

Synchronized修饰一个方法很简单,就是在方法的前面加synchronized,public synchronized void method(){//todo}; synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块是大括号括起来的范围,而修饰方法范围是整个函数。如将【Demo1】中的run方法改成如下的方式,实现的效果一样。

*【Demo4】:synchronized修饰一个方法

public synchronized void run() {
  for (int i = 0; i < 5; i ++) {
    try {
      System.out.println(Thread.currentThread().getName() + ":" + (count++));
      Thread.sleep(100);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

Synchronized作用于整个方法的写法。

写法一:

 public synchronized void method() {
 // todo
 }

写法二:

 public void method() {
   synchronized(this) {
 // todo
   }
 }

写法一修饰的是一个方法,写法二修饰的是一个代码块,但写法一与写法二是等价的,都是锁定了整个方法时的内容。

在用synchronized修饰方法时要注意以下几点:

1. synchronized关键字不能继承。

虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。这两种方式的例子代码如下:

在子类方法中加上synchronized关键字

class Parent {
    public synchronized void method() { }
}
class Child extends Parent {
    public synchronized void method() { }
}

在子类方法中调用父类的同步方法

class Parent {
    public synchronized void method() { }
}
class Child extends Parent {
    public void method() {
      super.method();
    }
}

2.在定义接口方法时不能使用synchronized关键字。

3.构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
修饰一个静态的方法

Synchronized也可修饰一个静态方法,用法如下:

 public synchronized static void method() {
 // todo
 }

我们知道静态方法是属于类的而不属于对象的。同样的,synchronized修饰的静态方法锁定的是这个类的所有对象。我们对Demo1进行一些修改如下:

【Demo5】:synchronized修饰静态方法

/**
* 同步线程
*/
class SyncThread implements Runnable {
    private static int count;

    public SyncThread() {
      count = 0;
    }

    public synchronized static void method() {
      for (int i = 0; i < 5; i ++) {
        try {
          System.out.println(Thread.currentThread().getName() + ":" + (count++));
          Thread.sleep(100);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }

    public synchronized void run() {
      method();
    }
}

调用代码:

SyncThread syncThread1 = new SyncThread();
SyncThread syncThread2 = new SyncThread();
Thread thread1 = new Thread(syncThread1, "SyncThread1");
Thread thread2 = new Thread(syncThread2, "SyncThread2");
thread1.start();
thread2.start();

结果如下:

SyncThread1:0
SyncThread1:1
SyncThread1:2
SyncThread1:3
SyncThread1:4
SyncThread2:5
SyncThread2:6
SyncThread2:7
SyncThread2:8
SyncThread2:9

syncThread1和syncThread2是SyncThread的两个对象,但在thread1和thread2并发执行时却保持了线程同步。这是因为run中调用了静态方法method,而静态方法是属于类的,所以syncThread1和syncThread2相当于用了同一把锁。这与Demo1是不同的。

修饰一个类

Synchronized还可作用于一个类,用法如下:

class ClassName {
    public void method() {
      synchronized(ClassName.class) {
// todo
      }
    }
}

我们把Demo5再作一些修改。

【Demo6】:修饰一个类

/**
* 同步线程
*/
class SyncThread implements Runnable {
    private static int count;

    public SyncThread() {
      count = 0;
    }

    public static void method() {
      synchronized(SyncThread.class) {
        for (int i = 0; i < 5; i ++) {
          try {
            System.out.println(Thread.currentThread().getName() + ":" + (count++));
            Thread.sleep(100);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }

    public synchronized void run() {
      method();
    }
}

其效果和【Demo5】是一样的,synchronized作用于一个类T时,是给这个类T加锁,T的所有对象用的是同一把锁。

总结:

A. 无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。

B. 每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。

C. 实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

以上就是如何在JAVA中使用Synchronized的详细内容,更多关于JAVA Synchronized用法的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java synchronized锁升级jol过程详解

    jol(java object layout)需要的依赖 <dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.10</version> </dependency> 一.synchronized锁对象的升级(膨胀)过程主要如下: 1.膨胀过程:无锁(锁对象初始化时)-> 偏向

  • Java使用synchronized实现互斥锁功能示例

    本文实例讲述了Java使用synchronized实现互斥锁功能.分享给大家供大家参考,具体如下: 代码 package per.thread; import java.io.IOException; public class Test { private int i = 0; private Object object = new Object(); public static void main(String[] args) throws IOException { Test test =

  • Java中synchronized正确使用方法解析

    生活中随处可见并行的例子,并行 顾名思义就是一起进行的意思,同样的程序在某些时候也需要并行来提高效率,在上一篇文章中我们了解了 Java 语言对缓存导致的可见性问题.编译优化导致的顺序性问题的解决方法,下面我们就来看看 Java 中解决因线程切换导致的原子性问题的解决方案 -- 锁 . 说到锁我们并不陌生,日常工作中也可能经常会用到,但是我们不能只停留在用的层面上,为什么要加锁,不加锁行不行,不行的话会导致哪些问题,这些都是在使用加锁语句时我们需要考虑的. 来看一个使用 32 位的 CPU 写

  • JAVA面试题 简谈你对synchronized关键字的理解

    面试官:sychronized关键字有哪些特性? 应聘者: 可以用来修饰方法; 可以用来修饰代码块; 可以用来修饰静态方法; 可以保证线程安全; 支持锁的重入; sychronized使用不当导致死锁; 了解sychronized之前,我们先来看一下几个常见的概念:内置锁.互斥锁.对象锁和类锁. 内置锁 在Java中每一个对象都可以作为同步的锁,那么这些锁就被称为内置锁.线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁.获得内置锁的唯一途径就是进入这个锁的保护的同

  • Java Synchronized锁失败案例及解决方案

    synchronized关键字,一般称之为"同步锁",用它来修饰需要同步的方法和需要同步代码块,默认是当前对象作为锁的对象. 同步锁锁的是同一个对象,如果对象发生改变,则锁会不生效. 锁失败的代码: public class IntegerSynTest { //线程实现Runnable接口 private static class Worker implements Runnable{ private Integer num; public Worker(Integer num){

  • java中SynchronizedList和Vector的区别详解

    前言 Vector是java.util包中的一个类. SynchronizedList是java.util.Collections中的一个静态内部类. 在多线程的场景中可以直接使用Vector类,也可以使用Collections.synchronizedList(List list)方法来返回一个线程安全的List. 那么,到底SynchronizedList和Vector有没有区别,为什么java api要提供这两种线程安全的List的实现方式呢? 首先,我们知道Vector和Arraylis

  • 如何在JAVA中使用Synchronized

    <编程思想之多线程与多进程(1)--以操作系统的角度述说线程与进程>一文详细讲述了线程.进程的关系及在操作系统中的表现,这是多线程学习必须了解的基础.本文将接着讲一下Java线程同步中的一个重要的概念synchronized. 在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行. synchronized是Java中的关键字,是一种同步锁.它修饰的对象有以下几种: 1. 修饰一个代码块,被修饰的代码块称

  • Java中提供synchronized后为什么还要提供Lock

    目录 一.为何提供Lock接口? 二.死锁问题 三.synchronized的局限性 四.解决问题  摘要: 在Java中提供了synchronized关键字来保证只有一个线程能够访问同步代码块.既然已经提供了synchronized关键字,那为何在Java的SDK包中,还会提供Lock接口呢?这是不是重复造轮子,多此一举呢?今天,我们就一起来探讨下这个问题. 在Java中提供了synchronized关键字来保证只有一个线程能够访问同步代码块.既然已经提供了synchronized关键字,那为

  • 实例讲解Java中的synchronized

    一.使用场景 在负责后台开发的时候,很多时候都是提供接口给前端开发人员去调用,会遇到这样的场景: 需要提供一个领奖接口,每个用户名只能领取一次,我们可以将成功领取的用户在数据库用个标记保存起来.如果这个用户再来领取的时候,查询数据库看该用户是否领取过. 但是问题来了,假设用户手速很快,极短时间内点了两次领奖按钮(前端没有进行控制,我们也不能依赖前端去控制).那么可能掉了两次领奖接口,而且有可能第二次调用的时候查询数据库的时候,第一次领奖还没有执行完成更新领奖标记. 这种场景就可以使用到synch

  • 如何在java中使用SFTP协议安全的传输文件

    本文介绍在Java中如何使用基于SSH的文件传输协议(SFTP)将文件从本地上传到远程服务器,或者将文件在两个服务器之间安全的传输.我们先来了解一下这几个协议 SSH 是较可靠,专为远程登录会话和其他网络服务提供安全性的协议.比如:我们购买的云服务器登陆的时候使用的协议都是ssh. ftp协议通常是用来在两个服务器之间传输文件的,但是它本质上是不安全的. 那么SFTP是什么?SFTP可以理解为SSH + FTP,也就是安全的网络文件传输协议. 一般来说,SFTP和FTP服务都是使用相应的客户端软

  • 详解如何在Java中调用Python程序

    Java中调用Python程序 1.新建一个Maven工程,导入如下依赖 <dependency> <groupId>org.python</groupId> <artifactId>jython-standalone</artifactId> <version>2.7.0</version> </dependency> 2.在java中直接执行python代码片段 import org.python.util

  • Java中的synchronized关键字

    目录 1.synchronized锁的底层实现原理 2.基于synchronized实现单例模式 3.利用类加载实现单例模式(饿汉模式) 1.synchronized锁的底层实现原理 JVM基于进入和退出Monitor对象来实现方法同步和代码块同步.代码块同步是使用monitorenter和monitorexit指令实现的,monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处.任何对象都有一个monitor与之关联,当且一个moni

  • 如何在java中使用Jython

    目录 一.Jython是什么 二.使用步骤 1.引入依赖 2.调用代码 2.python脚本 三.问题 1.报错:ImportError:Nomodulenamedpasslib 2.报错:CannotcreatePyStringwithnon-bytevalue 前言: 由于项目中需要用到Java调用Python的脚本,来实现一些功能,就对jython做了一些了解,通过jython可以实现java对python脚本的调用. 一.Jython是什么 Jython 是 Python 的纯 Java

  • 详解如何在Java中实现堆排序算法

    目录 算法描述 实现代码 测试代码 算法描述 堆排序算法的描述如下: 将待排序的数组调整为最大堆,此时未排序的长度 N 为数组的长度,调整的过程就是倒序将数组的前 N/2 个元素下沉的过程,每次下沉都会将较大的元素带到上面,最终将数组变为最大堆: 弹出最大堆的堆顶元素并将其移动到数组的最后面,将原本最后面的元素放到堆顶,然后将未排序的长度 N - 1,调整数组的前 N 个元素为最大堆: 重复步骤 2 直到未排序的长度为 0. 实现代码 package com.zhiyiyo.collection

  • 如何在Java中实现一个散列表

    目录 前言: 优化1 优化2 优化3 如何实现 总结 前言: 假设现在有一篇很长的文档,如果希望统计文档中每个单词在文档中出现了多少次,应该怎么做呢? 很简单! 我们可以建一个HashMap,以String类型为Key,Int类型为Value: 遍历文档中的每个单词 word ,找到键值对中key为 word 的项,并对相关的value进行自增操作. 如果该key= word 的项在 HashMap中不存在,我们就插入一个(word,1)的项表示新增. 这样每组键值对表示的就是某个单词对应的数量

  • 如何在Java中调用python文件执行详解

    目录 一.Java内置Jpython库(不推荐) 1.1 下载与使用 1.2 缺陷 二.使用Runtime.getRuntime()执行脚本⽂件 2.1 使用 2.2 缺陷 三.利用cmd调用python文件 3.1 使用 3.2 优化 总结 一.Java内置Jpython库(不推荐) 1.1 下载与使用 可以在官网下载jar包,官网:http://ftp.cuhk.edu.hk/pub/packages/apache.org/ 或者使用maven进行jar包下载 <dependency> &

随机推荐