Java并发编程-synchronized指南
在多线程程序中,同步修饰符用来控制对临界区代码的访问。其中一种方式是用synchronized关键字来保证代码的线程安全性。在Java中,synchronized修饰的代码块或方法不会被多个线程并发访问。它强制要求线程在进入一个方法之前获得一个锁,在离开方法时释放该锁。它保证了在同一时刻只有一个线程能执行被其修饰的方法。
如果我们把一个方法或代码块定义为同步的,就意味着在同一个对象中,只会有一个对同步方法的调用。如果在一个线程内部调用了一个同步方法,则其他线程会一直阻塞,直到第一个线程完成方法调用。
在进入一个对象的同步方法之前,需要申请对该对象上锁,完成方法调用后释放锁供其他线程申请。同步方法遵循happens-before机制,它保证了对象状态的改变在其他线程中都是可见的。
当标记一个代码块为同步时,需要用一个对象作为参数。当一个运行线程执行到该代码块时,要等到其他运行线程退出这个对象的同步代码区。然而,一个线程可以进入另一个对象的同步代码区。但是同一个对象的非同步方法可以不用申请锁。
如果定义一个静态方法为同步,则是在类上同步,而不是在对象上同步。也即如果一个静态同步方法在执行时,整个类被锁住,对该类中的其他静态方法调用会阻塞。
1)当一个线程进入了一个实例的同步方法,则其他任何线程都不能进入该实例的任何一个同步方法。
2)当一个线程进入了一个类的静态同步方法,则其他任何线程都不能进入该类的任何一个静态同步方法。
注意:
- 同步的静态方法和非静态方法之间没有关系。也即静态同步方法和非静态同步方法可以同时执行,除非非静态同步方法显式在该类上同步(例如,synchronized(MyClass.class){…})
- 类的构造函数不能定义成同步的。
监视器或内部锁
锁限制了对某个对象状态的访问,同时保证了happens-before关系。
每个对象都有一个锁对象,一个线程在访问对象之前必须申请锁,完成以后释放锁。其他线程不能访问对象,知道获得该对象的锁。这保证了一个线程改变了对象的状态后,新的状态对其他在同一个监视器上线程可见。
当线程释放锁时,会将cache中的内容更新到主内存,这也就使得该对象的状态变化对其他线程是可见的——这就是happens-before关系。
synchronized和Volatile,包括Thread.start()和Thread.join()方法,都能保证happens-before关系。
同步语句和同步方法获取的锁相同,某个线程可以请求同一个锁多次。
一个线程获得了对象锁后,不会影响其他线程访问对象的字段或调用对象的非同步方法。
同步语句首先尝试获取对象的锁,获取成功后立即开始执行同步代码块,执行完后释放锁。
如果方法是对象成员或对象实例,线程将锁住该实例。如果方法是静态的,线程锁住的是该类对应的Class对象。同步方法用SYNCHRONIZED标记,该标记被方法调用指令识别。
原子变量
来看语句 int c++,它包含多个操作,e.g. 从内存读取c的值,将c的值加1,然后写回内存。这个操作对单线程来说是正确的,但是在多线程环境却可能出错。它存在竞态条件,在多线程环境中可能多个线程同时读取c的值
原子访问保证所有操作作为一个整体一次完成。一个原子操作要么完全执行要么完全不执行。
以下这些操作能认为是原子操作:
- 对引用类型和大部分基本数据类型(long和double类型除外)的读和写操作。
- 声明为volatile类型变量的读和写操作(包括long和double变量)。
Java并发包java.util.concurrent.atomic定 义了对单个变量进行原子操作的类。所有类都有get和set方法,就像对volatile变量的读写一样。这就意味着,一个写操作happens- before其他任何对该变量的读操作。原子方法compareAndSet同样有这些特性,就像对整型变量做原子的算术运算一样。
在Java 5.0的并发包中,定义了支持原子操作的类。Java虚拟机编译这些类时利用硬件提供的CAS(Compare and set)来实现。
- AtomicInteger
- AtomicLong
- AtomicBoolean
- AtomicReference
volatile变量
volatile只能用来修饰变量。用volatile修饰的变量可能被异步地修改,所以编译器会对它们特殊处理。
volatile修饰符保证读取某个字段的任何线程都能看到该变量最近被写入的值。
使用volatile修饰的变量降低了内存一致性的风险,因为任何对volatile变量的写操作都能被其他线程可见。另外,当一个线程访问volatile变量时,不止能看到对该变量最近的修改,还能修改该变量的代码所带来的其他影响。
在多线程环境中,对象在不同线程中都保存有副本。但是volatile变量却没有,它们在堆中只有一个实例。这样对volatile变量的修改就能立即对其他线程可见。另外,本地线程缓存没有完成后刷新的工作。
volatile能够保证可见性,但是也带来了竞态条件。它不会锁定等待完成某个操作。例如:
1 | volatile int i= 0 ; |
两 个线程同时执行 i +=5 时,会得到5-10之间的某个值(译者注:原文为i +=5 invoking by two simultaneously thread give result 5 or 10 but it guarantee to see immediate changes 感觉有问题)
使用场景:用一个volatile布尔变量作为一个线程终止的标志。
静态和volatile变量之间的差别
声明一个静态变量,意味着该类的多个实例将共享该变量,静态变量与类关联而不是与对象关联。线程可能会有静态变量的本地缓存值。
当两个线程同时更新静态(非volatile)变量的值时,可能有一个线程的缓存中是一个过期的值。虽然多线程能够访问的是同一个静态变量,每个线程还是可能会保存自己的缓存副本。
一个volatile变量则在内存中只保留一个副本,该副本在多个线程中共享。
volatile变量和同步之间的差别
在线程内存和主内存之间,volatile只是同步了一个变量的值,synchronized则同步了(synchronized块中)所有变量的值,并且会锁住和释放一个监视器。所以,synchronized比volatile会有更多的开销。
volatile变量不允许有一个本地副本与主内存中的值不同。一个声明为volatile的变量必须保证所有线程中的副本同步,不管哪个线程修改了变量的值,另外其他线程都能立即看到该值。
锁对象
锁对象的作用像synchronized代码使用的隐式锁一样。像隐式锁一样,同时只能有一个线程持有锁。锁还支持wait/notify机制,通过他们之间的condition对象。
锁对象相对于隐式锁最大的优点是,他们能从尝试获得锁的状态返回。如果锁当前不可用或者在一个超时时间之前,tryLock()方法能够返回。在获得锁之前,如果其他线程发送了一个中断,lockInterruptibly()方法能返回。
Java内存回收
在 Java中,创建的对象存放在堆中。Java堆被称为内存回收堆。内存收集不能强制执行。当内存收集器运行时,它释放掉那些不可达对象占用的内存。垃圾收集线程作为一个优先级较低的守护线程运行。你能通过System.gc()提示虚拟机进行垃圾回收,但是不能强迫其执行。
如何写一个死锁程序
在多线程环境中,死锁意味着两个或多个线程一直阻塞,等待其他线程释放锁。下面是死锁的一个示例:
public class DeadlockSample {
private final Object obj1 = new Object();
private final Object obj2 = new Object(); public static void main(String[] args) {
DeadlockSample test = new DeadlockSample();
test.testDeadlock();
} private void testDeadlock() {
Thread t1 = new Thread(new Runnable() {
public void run() {
calLock12();
}
});
Thread t2 = new Thread(new Runnable() {
public void run() {
calLock21();
}
});
t1.start();
t2.start(); }
private void calLock12() {
synchronized (obj1) {
sleep();
synchronized (obj2) {
sleep();
}
}
}
private void calLock21() {
synchronized (obj2) {
sleep();
synchronized (obj1) {
sleep();
}
}
}
private void sleep() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 垃圾收集器不会回收强引用。
- 在内存不足时才会回收软引用,所以用它实现缓存可以避免内存不足。
- 垃圾收集器将会在下一次垃圾收集时回收弱引用。弱引用能被用来实现特殊的map。java.util.WeakHashMap中的key就是弱引用。
- 虚引用会被立即回收。能被用来跟踪对象被垃圾回收的活动。
Java并发编程-synchronized指南的更多相关文章
- Java并发编程-synchronized
多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多个线程同时访问时可能出现的问题.同步机制可以使用synchronized关键字实现.synchronized关 ...
- java并发编程--Synchronized的理解
synchronized实现锁的基础:Java中每一个对象都可以作为锁,具体表现为3种形式. (1)普通同步方法,锁是当前实例对象 (2)静态同步方法,锁是当前类的Class对象 (3)同步方法块,锁 ...
- Java并发编程 | Synchronized原理与使用
Java提供了多种机制实现多线程之间有需要同步执行的场景需求.其中最基本的是Synchronized ,实现上使用对象监视器( Monitor ). Java中的每个对象都是与线程可以锁定或解锁的对象 ...
- Java工程师学习指南第4部分:Java并发编程指南
本文整理了微信公众号[Java技术江湖]发表和转载过的Java并发编程相关优质文章,想看到更多Java技术文章,就赶紧关注本公众号吧吧. [纯干货]Java 并发进阶常见面试题总结 [Java基本功] ...
- Java并发指南开篇:Java并发编程学习大纲
Java并发编程一直是Java程序员必须懂但又是很难懂的技术内容. 这里不仅仅是指使用简单的多线程编程,或者使用juc的某个类.当然这些都是并发编程的基本知识,除了使用这些工具以外,Java并发编程中 ...
- Java并发编程:synchronized
Java并发编程:synchronized 虽然多线程编程极大地提高了效率,但是也会带来一定的隐患.比如说两个线程同时往一个数据库表中插入不重复的数据,就可能会导致数据库中插入了相同的数据.今天我们就 ...
- Java并发编程:Synchronized及其实现原理
Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...
- Java并发编程:Synchronized底层优化(偏向锁、轻量级锁)
Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...
- 【转】Java并发编程:synchronized
一.什么时候会出现线程安全问题? 在单线程中不会出现线程安全问题,而在多线程编程中,有可能会出现同时访问同一个资源的情况,这种资源可以是各种类型的资源:一个变量.一个对象.一个文件.一个数据库表等,而 ...
随机推荐
- ubuntu下mysql远程连接
第一步: vim /etc/mysql/my.cnf找到bind-address = 127.0.0.1 注释掉这行,如:#bind-address = 127.0.0.1 ...
- set_error_handler
set_error_handler这个函数的作用是为了防止错误路径泄露 何为错误路径泄露呢? 我们写程序,难免会有问题,而PHP遇到错误时,就会给出出错脚本的位置.行数和原因 有很多人说,这并没有什么 ...
- hdu 5091 给定矩形覆盖尽量多点 扫描线+线段树
http://acm.hdu.edu.cn/showproblem.php?pid=5091 给你10000以内的敌舰的坐标(即分别为x,y),要求用W*H的矩形去围住一个区域,使得这个区域内的敌舰最 ...
- hdu 5060 五种情况求圆柱体与球体交
http://acm.hdu.edu.cn/showproblem.php?pid=5060 官方题解http://bestcoder.hdu.edu.cn/给复杂了 实际上用圆柱体与球体体积差的积分 ...
- shell工具-sort
sort sort命令是在Linux里非常有用,它将文件进行排序,并将排序结果标准输出 基本语法 sort [选项] [参数] 选项说明 选项 说明 -n 依照数值大小排序 -r 以相反的顺序排序 - ...
- Python自动化开发 -进程、线程和协程(二)
本节内容 一.线程进程介绍 二. 线程 1.线程基本使用 (Threading) 2.线程锁(Lock.RLock) 3.信号量(Semaphore) 4.事件(event) 5.条件(Conditi ...
- EBS获取并发程序Trace File
http://blog.itpub.net/16832682/viewspace-1249765/ 最近因为项目上出现了PL/SQL性能的问题,因此需要对已经开发好的并发程序进行调优的工作.调优有个很 ...
- Winform下的Combox根据值来选中项
其实很简单,因为Combox的Item是一个K/V的object,那么就可以把它的items转换成IEnumerable<DictionaryEntry>类型的(System.Collec ...
- Python3.5 学习二十二
回顾: 发送请求时:发送请求头和请求数据 request.META和request.request.body 响应请求时:响应头和响应返回数据 response.HEADER和response.bod ...
- cad.net之ACAD移植到GCAD的自动加载问题
将acad.pgp,lsp,fas,vlx,名称增加一份gcad.pgp,lsp,fas,vlx.涉及系统加载用. Lisp的拖拉加载在gcad无法通过lastprompt获取命令历史栏最后一行(含路 ...