Java Synchronized 关键字
本文内容
- Synchronized 关键字
- 示例
- Synchronized 方法
- 内部锁(Intrinsic Locks)和 Synchronization
- 参考资料
下载 Demo
Synchronized 关键字
Java 语言提供两个基本的同步机制:synchronized 方法(synchronized methods )和 synchronized 语句(synchronized statements)。
示例
先大概说一下 Java Synchronized 关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻只有一个线程执行该段代码。
- 当两个线程访问同一个对象 synchronized(this) 代码块时,只能有一个线程执行,另一个线程必须等待这个线程执行完后才能执行;
- 但是,当一个线程访问一个对象 synchronized(this) 同步代码块时,另一个线程仍能访问该对象的非 synchronized(this) 代码块;
- 尤其是,当一个线程访问一个对象的一个 synchronized(this) 代码块时,其他线程对该对象中其他所有 synchronized(this) 代码块的访问也将被阻塞;
- 以上规则对其它对象锁同样适用。
如下代码所示:
package cn.db.syncdemo;
public class NewClass {
/**
* 同步方法
*/
public void synchronizedMethod() {
synchronized (this) {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i
+ " synchronized method");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(e.toString());
}
}
}
}
/**
* 同步方法 2
*/
public void synchronizedMethod2() {
synchronized (this) {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i
+ " synchronized method 2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.toString());
}
}
}
}
/**
* 非同步方法
*/
public void nonSynchronizedMethod() {
int i = 5;
while (i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i
+ " nonSynchronized method");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println(e.toString());
}
}
}
public static void main(String[] args) {
final NewClass mClass = new NewClass();
// t1 和 t2 都要访问同一个同步方法 synchronizedMethod
Thread t1 = new Thread(new Runnable() {
public void run() {
mClass.synchronizedMethod();
}
}, "Thread 1");
Thread t2 = new Thread(new Runnable() {
public void run() {
mClass.synchronizedMethod();
}
}, "Thread 2");
// t3 要访问另一个同步方法 synchronizedMethod2
Thread t3 = new Thread(new Runnable() {
public void run() {
mClass.synchronizedMethod2();
}
}, "Thread 3");
// t4 要访问非同步方法 nonSynchronizedMethod
Thread t4 = new Thread(new Runnable() {
public void run() {
mClass.nonSynchronizedMethod();
}
}, "Thread 4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行结果:
Thread 4 : 4 nonSynchronized method
Thread 1 : 4 synchronized method
Thread 4 : 3 nonSynchronized method
Thread 4 : 2 nonSynchronized method
Thread 1 : 3 synchronized method
Thread 4 : 1 nonSynchronized method
Thread 4 : 0 nonSynchronized method
Thread 1 : 2 synchronized method
Thread 1 : 1 synchronized method
Thread 1 : 0 synchronized method
Thread 3 : 4 synchronized method 2
Thread 3 : 3 synchronized method 2
Thread 3 : 2 synchronized method 2
Thread 3 : 1 synchronized method 2
Thread 3 : 0 synchronized method 2
Thread 2 : 4 synchronized method
Thread 2 : 3 synchronized method
Thread 2 : 2 synchronized method
Thread 2 : 1 synchronized method
Thread 2 : 0 synchronized method
说明:
- synchronizedMethod 同步方法,每隔 2 秒输出一条信息;
- synchronizedMethod2 同步方法,每隔 1 秒输出一条信息;
- nonSynchronizedMethod 非同步方法,每隔 1 秒输出一条信息;
另外,
- 线程 t1 和线程 t2 都访问 synchronizedMethod 方法;
- 只有线程 t3 访问 synchronizedMethod2 方法;
- 只有线程 t4 访问 nonSynchronizedMethod 方法。
注意:
- t4 访问非同步方法 nonSynchronizedMethod2,无论何时都可以访问,所以“thread 4:……”出现的是不规律的,交错的;
- t1 和 t2 都要访问同一个同步方法 synchronizedMethod,所以“thread 1:……”和“thread 2:……”绝对不会交错显示,一个线程访问完后,另一个线程才能访问;
- 对于 t3,t3 自己访问另一个同步方法 synchronizedMethod2,并且没有其他线程再访问,而且当 t3 访问该方法时,其他同步方法也被锁了,所以,“thread 3:……”是连续出现的;
- 如果不看 t4,也就是没有该线程,那么结果会是:
Thread 1 : 4 synchronized method
Thread 1 : 3 synchronized method
Thread 1 : 2 synchronized method
Thread 1 : 1 synchronized method
Thread 1 : 0 synchronized method
Thread 3 : 4 synchronized method 2
Thread 3 : 3 synchronized method 2
Thread 3 : 2 synchronized method 2
Thread 3 : 1 synchronized method 2
Thread 3 : 0 synchronized method 2
Thread 2 : 4 synchronized method
Thread 2 : 3 synchronized method
Thread 2 : 2 synchronized method
Thread 2 : 1 synchronized method
Thread 2 : 0 synchronized method说明:
- “thread 1……”和”thread 2……” 绝对不会交错出现,只有当其中一个访问完,另一个才能访问;
- 调试时,“thread 1……”和”thread 2……” 谁先出现,貌似是不确定的,但如果“thread 1……”先执行,那“thread 1……”先出现的几率肯定会大很多。这得看场景,毕竟是并发,谁先出现,谁后出现,并不重要的;
- ”thread 3……“访问时,“thread 1……”和”thread 2……” 也不能访问。虽然没有线程跟”thread 3……“抢,但这三个线程访问的都是两个同步方法。
Synchronized 方法
要想使一个方法为同步方法,只需简单给它的声明加上 synchronized 关键字就行:
package cn.db.syncdemo;
public class SynchronizedCounter {
private int c = 0;
public synchronized void increment() {
c++;
}
public synchronized void decrement() {
c--;
}
public synchronized int value() {
return c;
}
}
如果 count 是 SynchronizedCounter 的一个实例,那么把这些方法变成 synchronized 方法会有两个效果:
- 首先,在同一个对象上,交错调用 synchronized 方法是不可能的。当一个线程正在执行一个对象的 synchronized 方法时,所有调用该 synchronized 方法的其他线程会被挂起,直到第一个线程完成;
- 其次,当一个 synchronized 方法退出时,它自动地建立一个对同一个对象的这个同步方法的任何后续调用的发生前关系(happens-before relationship)。这保证了对象状态的改变对所有线程都是可见的。
注意:构造函数不能是 synchronized,也就是说,
synchronized关键字修饰构造函数是一个语法错误。synchronized 构造函数没有意义,因为当对象创建时,只有创建对象的那个线程才能访问它。
警告:当构造一个对象在线程之间共享时,必须非常小心,一个对象的引用不能过早地“泄漏”。例如,假设你想维护一个名为
instances的 List,它包含类的每个实例。你可能会用下面代码在你的构造函数里:instances.add(this);但是,当对象构造完成后,其他线程使用
instances访问对象。
synchronized 方法用一个简单策略来阻止线程干扰和内存一致性错误:如果一个对象对多个线程可见,所有读或写该对象的变量都通过 synchronized 方法完成。 (一个重要的例外:final 字段,对象被构造后不能被修改,可以通过非 synchronized 方法来安全地读取,一旦对象被构造)。
内部锁(Intrinsic Locks)和 Synchronization
同步是建立在被称为内部锁(Intrinsic Locks)或监视器锁定的一个内部实体。(API 规范往往指的是作为一个“显示器”的实体。)内在锁扮演两方面同步的角色:执行独占访问对象的状态,建立发生前关系(happens-before relationships )必要的可见性。
每个对象都有一个与之关联的内部锁。通常,一个需要独占和一致的访问对象字段的线程必须在访问之前要求对象的内部锁,然后当完成后释放内部锁。线程在要求和释放锁之间的时间拥有内部锁。只要一个线程拥有一个内部锁,其他线程就不能再要求这个锁。当其他线程试图要求锁时就会被阻塞。
当一个线程释放内部锁时,一个发生前关系(happens-before relationship)在该动作和后续相同锁的动作之间被建立。
Synchronized Methods 方法
当一个线程调用一个 synchronized 方法,它会自动获取该方法对象的内部锁,并且,当方法返回时,释放锁。即使返回由未捕获的异常发生时造成的锁释放。
你可能想知道,一个静态的 synchronized 方法被调用时发生了什么,因为一个静态方法与一个类关联,而不是一个对象。在这种情况下,线程获得与类有关的 Class 对象的内部锁。因此,访问类的静态字段是由锁控制的,而这个锁与任何类的实例的锁不同。
Synchronized 语句
创建 synchronized 代码的另一个方式是 synchronized 语句。与 synchronized 方法不同,synchronized 语句必须指定提供内部锁的对象,如下代码所示中的“this”:
public void addName(String name) {
synchronized(this) {
lastName = name;
nameCount++;
}
nameList.add(name);
}
本例中,addName 方法需要同步改变 lastName 和 nameCount,还需要避免其它对象的方法同步调用。要是没有 synchronized 语句,就必须有一个单独的、非 synchronized 方法用于调用 nameList.add 的目的。
Synchronized 语句对用细粒度同步来改进并发也很有用。假设,例如,类 MsLunch 有两个实例字段,c1 和 c2,它们绝不会一起使用。所有这些字段的更新都必须被同步,但没有理由阻止 c1 和 c2 交叉更新(通过创建一个没有必要的代码块,来减少并发)。因此,不是使用 synchronized(this),而是为两个对象提供单独的锁。如下所示:
public class MsLunch {
private long c1 = 0;
private long c2 = 0;
private Object lock1 = new Object();
private Object lock2 = new Object();
public void inc1() {
synchronized(lock1) {
c1++;
}
}
public void inc2() {
synchronized(lock2) {
c2++;
}
}
}
这么用要格外小心。你必须绝对确保它对交叉访问受影响字段是真的安全。
Reentrant Synchronization
回想一下,一个线程不能获取由另一个线程拥有的锁。但一个线程可以获取,它已经拥有的锁。让一个线程不只一次获取相同的锁会造成折返同步(reentrant synchronization)。这描述了一种情况,同步代码码直接或间接地调用一个方法,该方法也包含的同步代码,并且这两组代码都使用相同的锁。如果没有折返同步(reentrant synchronization),同步代码必须采取很多额外的预防措施,来避免线程阻塞自己。
参考资料
- https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html
- https://docs.oracle.com/javase/tutorial/essential/concurrency/syncrgb.html
- java 并发演示动画
Java Synchronized 关键字的更多相关文章
- Java synchronized 关键字详解
Java synchronized 关键字详解 前置技能点 进程和线程的概念 线程创建方式 线程的状态状态转换 线程安全的概念 synchronized 关键字的几种用法 修饰非静态成员方法 sync ...
- Java synchronized关键字用法(清晰易懂)
本篇随笔主要介绍 java 中 synchronized 关键字常用法,主要有以下四个方面: 1.实例方法同步 2.静态方法同步 3.实例方法中同步块 4.静态方法中同步块 我觉得在学习synchro ...
- java synchronized关键字浅探
synchronized 是 java 多线程编程中用于使线程之间的操作串行化的关键字.这种措施类似于数据库中使用排他锁实现并发控制,但是有所不同的是,数据库中是对数据对象加锁,而 java 则是对将 ...
- java synchronized关键字
引用其他人的一段话 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchro ...
- Java synchronized关键字的理解
转载自:http://blog.csdn.net/xiao__gui/article/details/8188833 在Java中,synchronized关键字是用来控制线程同步的,就是在多线程的环 ...
- java synchronized关键字浅析
synchronized这个关键字想必学Java的人都应该知道. 直接上例子: 方法级别实例 public class AtomicInteger { private int index; publi ...
- [java] java synchronized 关键字详解
Java语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码.当两个并发线程访问同一个对象object中的这个加锁同步代码块时,一 ...
- Java:synchronized关键字引出的多种锁
前言 Java 中的 synchronized关键字可以在多线程环境下用来作为线程安全的同步锁.本文不讨论 synchronized 的具体使用,而是研究下synchronized底层的锁机制,以及这 ...
- java synchronized关键字的底层实现
每个对象都有一个锁(Monitor,监视器锁),class对象也有锁,如果synchronized关键字修饰同步代码块,通过反编译可以看到,其实是有个monitorenter和monitorexit指 ...
随机推荐
- Leetcode 234 Palindrome Linked List 复杂度为时间O(n) 和空间(1)解法
1. 问题描写叙述 给定一个单链表,推断其内容是不是回文类型. 比如1–>2–>3–>2–>1.时间和空间复杂都尽量低. 2. 方法与思路 1)比較朴素的算法. 因为给定的数据 ...
- C#获取文件夹及文件的大小与占用空间的方法
本文详细介绍了利用C#实现根据路径,计算这个路径所占用的磁盘空间的方法 . 网上有很多资料都是获取文件夹/文件的大小的.对于占用空间的很少有完整的代码.这里介绍实现这一功能的完整代码,供大家参考一下. ...
- 使用Axure RP原型设计实践08,制作圆角文本框
本篇体验做一个简单圆角文本框,做到3个效果: 1.初始状态,圆角文本框有淡淡的背景色,边框的颜色为浅灰色2.点击圆角文本框,让其获取焦点,边框变成蓝色,背景色变成白色3.圆角文本框失去焦点,边框变成红 ...
- Javascript 中的arguments
arguments是当前正在执行的function的一个参数,它保存了函数当前调用的参数. 使用方法:function.arguments[i]. 其中function.是可选项,是当前正在执行的 ...
- INotifyPropertyChanged接口的实现
何时实现INotifyPropertyChanged接口 官方解释:INotifyPropertyChanged 接口用于向客户端(通常是执行绑定的客户端)发出某一属性值已更改的通知.官方解释的很模 ...
- Grid布局方式
wp7中Grid布局类似HTML中的表格,但是又不太一致! 为了测试新一个3行3列的Grid 方了方便,剔除掉其它XAML代码 [c-sharp:collapse] view plaincopy ...
- 转换java keytools的keystore证书到OPENSSL的PEM格式文件
背景:原先业务使用的前端为haproxy,直接端口转发至tomcat,后端进行ssl连接,所以当时生成的步骤如下 ? 1 2 •生成密钥对:keytool -genkey -alias tomcat- ...
- Java并发编程的艺术(十三)——锁优化
自旋锁 背景:互斥同步对性能最大的影响是阻塞,挂起和恢复线程都需要转入内核态中完成:并且通常情况下,共享数据的锁定状态只持续很短的一段时间,为了这很短的一段时间进行上下文切换并不值得. 原理:当一条线 ...
- Javascript 对象(object)合并
对象的合并 需求:设有对象 o1 ,o2,需要得到对象 o3 var o1 = { a:'a' }, o2 = { b:'b' }; // 则 var o3 = { a:'a', b:'b' } 方法 ...
- Spring Boot工程结构推荐程结构(最佳实践)
工程结构(最佳实践) Spring Boot框架本身并没有对工程结构有特别的要求,但是按照最佳实践的工程结构可以帮助我们减少可能会遇见的坑,尤其是Spring包扫描机制的存在,如果您使用最佳实践的工程 ...