Java中的关键字synchronized
1. 介绍
在Java
并发系列的文章中,这个是第二篇文章。在前面的一篇文章中,我们学习了Java
中的Executor
池和Excutors
的各种类别。
在这篇文章中,我们会学习synchronized
关键字以及我们在多线程的环境中如何使用。
2. 什么是同步?
在一个多线程的环境中,多个线程同时访问相同的资源的情况是存在的。例如,两个线程试图写入同一个文本文件。它们之间没有任何的同步,当两个或多个线程对同一文件具有写访问权时,写入该文件的数据可能会损坏。
同理,在JVM
中,每个线程在各自的栈上存储了一份变量的副本。某些其他线程可能会更改这些变量的实际值。但是更改后的值可能不会刷新到其他线程的本地副本中。
这可能导致程序执行错误和非确定性行为。
为了避免这种问题,Java
给我们提供了synchronized
这有助于实现线程之间的通信,使得只有一个线程访问同步资源,而其他线程等待资源变为空闲。
synchronized
关键字可以被用在下面一些不同的方式中,比如一个同步块:
synchronized(someobject){
//thread-safe code here
}
对方法进行同步:
public synchronized void someMethod(){
//thread-safe code here
}
3.在JVM中synchronized是如何实现的
当一个线程试图进入一个同步块或者同步方法中的时候,它必须先获得一个同步对象上的锁。一次只可以有一个线程获取锁,并且执行块中的代码。
如果其他线程尝试访问该同步块,则必须等待,直到当前线程执行完同步块的代码。当前线程退出后,锁将被自动释放,其它线程可以获取锁并进入同步代码块。
- 对于一个
synchronized
块来说,在synchronized
关键字后的括号中指定的对象上获取锁; - 对于一个
synchronized static
方法,锁是在.class对象上获取的; - 对于
synchronized
实例方法来说,锁定是在该类的当前实例上获得的,即该实例(this
);
4.同步方法
定义同步方法就像在返回类型之前简单地包含关键字一样简单。我们定义一个顺序打印数字1-5之间的方法。会有两个线程来访问这个方法,所以让我们来看看在没有使用synchronized
关键字它们的运行情况, 和我们使用关键字来锁住共享对象会发生什么:
public class NonSynchronizedMethod {
public void printNumbers() {
System.out.println("Starting to print Numbers for " + Thread.currentThread().getName());
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
System.out.println("Completed printing Numbers for " + Thread.currentThread().getName());
}
}
现在,让我们实现两个访问该对象并希望运行 printNumbers()
方法的自定义线程:
class ThreadOne extends Thread {
NonSynchronizedMethod nonSynchronizedMethod;
public ThreadOne(NonSynchronizedMethod nonSynchronizedMethod) {
this.nonSynchronizedMethod = nonSynchronizedMethod;
}
@Override
public void run() {
nonSynchronizedMethod.printNumbers();
}
}
class ThreadTwo extends Thread {
NonSynchronizedMethod nonSynchronizedMethod;
public ThreadTwo(NonSynchronizedMethod nonSynchronizedMethod) {
this.nonSynchronizedMethod = nonSynchronizedMethod;
}
@Override
public void run() {
nonSynchronizedMethod.printNumbers();
}
}
这些线程共享一个相同的对象NonSynchronizedMethod
,它们会在这个对象上同时去调用非同步的方法printNumbers()
。
为了测试这个,写一个main
方法来做测试:
public class TestSynchronization {
public static void main(String[] args) {
NonSynchronizedMethod nonSynchronizedMethod = new NonSynchronizedMethod();
ThreadOne threadOne = new ThreadOne(nonSynchronizedMethod);
threadOne.setName("ThreadOne");
ThreadTwo threadTwo = new ThreadTwo(nonSynchronizedMethod);
threadTwo.setName("ThreadTwo");
threadOne.start();
threadTwo.start();
}
}
运行上面的代码,我们会得到下面的结果:
Starting to print Numbers for ThreadOne
Starting to print Numbers for ThreadTwo
ThreadTwo 0
ThreadTwo 1
ThreadTwo 2
ThreadTwo 3
ThreadTwo 4
Completed printing Numbers for ThreadTwo
ThreadOne 0
ThreadOne 1
ThreadOne 2
ThreadOne 3
ThreadOne 4
Completed printing Numbers for ThreadOne
虽然ThreadOne
先开始执行的,但是ThreadTwo
先结束的。
当我们再次运行上面的程序的时候,我们会得到一个不同的结果:
Starting to print Numbers for ThreadOne
Starting to print Numbers for ThreadTwo
ThreadOne 0
ThreadTwo 0
ThreadOne 1
ThreadTwo 1
ThreadOne 2
ThreadTwo 2
ThreadOne 3
ThreadOne 4
ThreadTwo 3
Completed printing Numbers for ThreadOne
ThreadTwo 4
Completed printing Numbers for ThreadTwo
这些输出完全是偶然的,完全不可预测。每次运行都会给我们一个不同的输出。因为可以有更多的线程,我们可能会遇到问题。在实际场景中,在访问某种类型的共享资源(如文件或其他类型的IO)时,这一点尤为重要,而不是仅仅打印到控制台。
下面我们采用同步的方法,使用synchronized
关键字:
public synchronized void printNumbers() {
System.out.println("Starting to print Numbers for " + Thread.currentThread().getName());
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
System.out.println("Completed printing Numbers for " + Thread.currentThread().getName());
}
代码中只是给方法添加了一个synchronized
关键字,没有其它的改动。现在我们运行上面的代码,得到如下所示的结果:
Starting to print Numbers for ThreadOne
ThreadOne 0
ThreadOne 1
ThreadOne 2
ThreadOne 3
ThreadOne 4
Completed printing Numbers for ThreadOne
Starting to print Numbers for ThreadTwo
ThreadTwo 0
ThreadTwo 1
ThreadTwo 2
ThreadTwo 3
ThreadTwo 4
Completed printing Numbers for ThreadTwo
在这里,我们看到即使两个线程同时运行,只有一个线程一次进入synchronized
方法,在这种情况下是ThreadOne
。一旦完成执行,ThreadTwo
就可以执行printNumbers()
方法
5.同步块
多线程的主要目的是尽可能并行地执行任意数量的任务。但是,同步限制了必须执行同步方法或块的线程的并行性。
但是,我们可以尝试通过在同步范围内保留尽可能少的代码来减少以同步方式执行的代码量。可能有许多场景,不是在整个方法上同步,而是可以在方法中同步几行代码。
我们可以使用synchronized
块来包含代码的那部分而不是整个方法。也就是说对于需要同步的代码块进行同步,而不是对整个方法进行同步。
由于在同步块内部执行的代码量较少,因此每个线程都会更快地释放锁定。结果,其他线程花费更少的时间等待锁定并且代码吞吐量大大增加。
让我们修改前面的例子,只同步for循环打印数字序列,实际上,它是我们示例中应该同步的唯一代码部分:
public class SynchronizedBlockExample {
public void printNumbers() {
System.out.println("Starting to print Numbers for " + Thread.currentThread().getName());
synchronized (this) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
System.out.println("Completed printing Numbers for " + Thread.currentThread().getName());
}
}
运行结果:
Starting to print Numbers for ThreadOne
Starting to print Numbers for ThreadTwo
ThreadOne 0
ThreadOne 1
ThreadOne 2
ThreadOne 3
ThreadOne 4
Completed printing Numbers for ThreadOne
ThreadTwo 0
ThreadTwo 1
ThreadTwo 2
ThreadTwo 3
ThreadTwo 4
Completed printing Numbers for ThreadTwo
尽管ThreadTwo
在ThreadOne
完成其任务之前“开始”打印数字似乎令人担忧,这只是因为我们在停止ThreadTwo
锁之前,允许线程通过System.out.println("Completed printing Numbers for " + Thread.currentThread().getName())
语句。
这很好,因为我们只想同步每个线程中的数字序列。我们可以清楚地看到两个线程只是通过同步for
循环以正确的顺序打印数字。
6.结论
在这个例子中,我们看到了如何在Java
中使用synchronized
关键字来实现多个线程之间的同步。我们还通过例子了解了何时可以使用synchronized
方法和块。
作者:Chandan Singh
译者:lee
Java中的关键字synchronized的更多相关文章
- Java中的关键字--synchronized
在并发编程中,synchronized关键字是常出现的角色.之前我们都称呼synchronized关键字为重量锁,但是在JDK1.6中对synchronized进行了优化,引入了偏向锁.轻量锁.本篇介 ...
- 【转】java中volatile关键字的含义
java中volatile关键字的含义 在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言 ...
- Java中Volatile关键字详解
一.基本概念 先补充一下概念:Java并发中的可见性与原子性 可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值, ...
- 转:java中volatile关键字的含义
转:java中volatile关键字的含义 在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言 ...
- java中volatile关键字的理解
一.基本概念 Java 内存模型中的可见性.原子性和有序性.可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有 ...
- Java中Volatile关键字详解(转载)
转载自:https://www.cnblogs.com/zhengbin/p/5654805.html 一.基本概念 先补充一下概念:Java 内存模型中的可见性.原子性和有序性. 可见性: 可见性是 ...
- Java中Volatile关键字详解 (转自郑州的文武)
java中volatile关键字的含义:http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html 一.基本概念 先补充一下概念:J ...
- 【高并发】面试官:Java中提供了synchronized,为什么还要提供Lock呢?
写在前面 在Java中提供了synchronized关键字来保证只有一个线程能够访问同步代码块.既然已经提供了synchronized关键字,那为何在Java的SDK包中,还会提供Lock接口呢?这是 ...
- Java 中的关键字
Java 中有多少个关键字,有大小写之分吗? Java 中有 48 个关键字在使用 + 两个保留关键字未使用,共 50 个关键字. Java 关键字全部都由是小写组成. Java 中保留关键字分别是哪 ...
随机推荐
- 常用cl编译命令参数解释
紧接前文,第一行cl命令如下: 1> cl /c /IC:\...\include /ZI /nologo- /W3 /WX- /sdl /Od /Oy- /D WIN32 /D _DEBUG ...
- .NET Core 中使用 Humanizer 显示友好时间格式
今天在将一个 .net framework 项目迁移至 .net core 的过程中,在迁移到显示友好时间格式(比如“1分钟前”,“1小时前”)的代码时,找了找看有没有对应的开源库,结果找到了 Hum ...
- Tinyhttpd for Windows(学习型的项目,才500多行代码)
前言 TinyHTTPd是一个开源的简易学习型的HTTP服务器,项目主页在:http://tinyhttpd.sourceforge.net/,源代码下载:https://sourceforge.ne ...
- SAP和去哪儿网面试感悟
这两天先后面了两家公司,个人感觉挺有意思,就写下来.现在是4月份,校招基本没有了,去哪儿网刚好有春招,我就去试试.SAP是同学推荐的. 去哪儿.参加笔试,个人感觉还行.半个月后收到面试通知.到了面试现 ...
- VS2012 调试Web项目 遭遇 HTTP 错误 500.23 - Internal Server Error
原文:VS2012 调试Web项目 遭遇 HTTP 错误 500.23 - Internal Server Error 在使用vs2012 调试Web站点时 报错 500.23,详细如图 此错误是因为 ...
- 水晶报表异常“CrystalDecisions.ReportSource.ReportSourceFactory”的类型初始值设定项引发异常,未能加载文件或程序集“log4net
System.TypeInitializationException: “CrystalDecisions.ReportSource.ReportSourceFactory”的类型初始值设定项引发异常 ...
- Qt4.85静态编译配置VS动态编译(非常详细的图文教程)
http://www.qter.org/forum.php?mod=viewthread&tid=1409&extra=page%3D1&page=1
- Zookeeper zkui-zookeeper图形化管理工具
zkui zkui是一个Zookeeper可视化管理工具. Github:https://github.com/DeemOpen/zkui zkui安装 1.Git拉取代码 #git clone ht ...
- 使用PNG实现半透明的窗体(使用GDI+)
Delphi中标准控件是不支持png图片的,据说从Window2000后增加gdiplus.dll库处理更多的gdi图像,其中包括png. 关键的几个api GdipCreateBitma ...
- 浅谈Linux(Centos7.4)环境下NTP服务器的构建
一.软件环境 1.操作系统版本 [root@Geeklp201 etc]# cat /etc/redhat-release CentOS Linux release 7.4.1708 (Core ...