JUC之Atomic系列12大类实例讲解和原理分解
一、java内存模型
提到同步、锁,就必须提到Java的内存模型,为了提高程序的执行效率,java也吸收了传统应用程序的多级缓存体系。
在共享内存的多处理器体系架构中,每个处理器都拥有自己的缓存,并且定期地与主内存进行协调。在不同的处理器架构中提供了不同级别的缓存一致性(Cache Coherence),其中一部分只提供最小的保证,即允许不同的处理器在任意时刻从同一个存储位置上看到不同的值。操作系统、编译器以及运行时(有时甚至包括应用程序)需要弥合这种在硬件能力与线程安全之间的差异。
要想确保每个处理器都能在任意时刻知道其他处理器正在进行的工作,将需要非常大的开销。在大多数时间里,这种信息是不必要的。因此处理器会适当放宽存储一致性保证,以换取性能的提升。在架构定义的内存模型中将告诉应用程序可以从内存系统中获得怎样的保证,此外还定义了一些特殊的指令(称为内存栅栏),当需要共享数据时,这些指令就能实现额外的存储协调保证。为了使java开发人员无须关心不同架构内存模型之间的差异,Java还提供了自己的内存模型,并且JVM通过在适当的位置上插入内存栅栏来屏蔽在JVM与底层之平台内存模型之间的差异。
经过上面的讲解和上图,我们知道线程在运行时候有一块内存专用区域,Java程序会将变量同步到线程所在的内存。这时候会操作工作内存中的变量,而线程中的变量何时同步回到内存是不可预期的。但是java内存模型规定,通过关键词”synchronized“、”volatile“可以让java保证某些约束。
- “volatile” - 保证读写的都是主内存变量。
- “synchronized” - 保证在块开始时,都同步主内存值到工作内存,而快结束时,将工作内存同步会主内存。
重排序
public class PossibleReordering {
static int x = 0,y=0;
static int a=0,b=0;
public static void main(String[] args) throws InterruptedException {
Thread one = new Thread(new Runnable() { @Override
public void run() {
a = 1;
x = b;
}
}); Thread two = new Thread(new Runnable() { @Override
public void run() {
b = 2;
y = a;
}
});
one.start();two.start();
one.join();two.join();
System.out.println("x:" + x+",y:"+y);
}
}
重排序。如上图,执行结果,一般人可能认为是1,1;真正的执行结果可能每次都不一样。拜JMM重排序所赐,JMM使得不同线程的操作顺序是不同的,从而导致在缺乏同步的情况下,要推断操作的执行结果将变得更加复杂。各种使操作延迟或看似乱序执行的不同原因,都可以归为重排序。内存级的重排序会使程序的行为变得不可预测。如果没有同步,要推断出程序的执行顺序是非常困难的,而要确保在程序中正确的使用同步却是非常容易的。同步将限制编译器和硬件运行时对内存操作重排序的方式。
锁synchronized
锁实现了对临界资源的互斥访问,被synchronized修饰的代码只有一条线程可以通过,是严格的排它锁、互斥锁。没有获得对应锁对象监视器(monitor)的线程会进入等待队列,任何线程必须获得monitor的所有权才可以进入同步块,退出同步快或者遇到异常都要释放所有权,JVM规范通过两个内存屏障(memory barrier)命令来实现排它逻辑。内存屏障可以理解成顺序执行的一组CPU指令,完全无视指令重排序。
什么是锁
public class TestStatic {
public syncronized static void write(boolean flag) {
xxxxx
}
public synchronized static void read() {
xxxxx
}
}
线程1访问TestStatic.write()方法时,线程2能访问TestStatic.read()方法吗
线程1访问new TestStatic().write()方法时,线程2能访问new TestStatic().read()方法吗
线程1访问TestStatic.write()方法时,线程2能访问new TestStatic().read()方法吗
public class Test {
public syncronized void write(boolean flag) {
xxxxx
}
public synchronized void read() {
xxxxx
}
}
Test test = new Test();线程1访问test.write() 方法,线程2能否访问test.read()方法
Test a = new Test(); Test b = new Test();线程1访问a.write()访问,线程2能否访问b.read()方法
答案,java中每个对象都可以作为一个锁,而对象就决定了锁的粒度大小。
对于实例同步方法,锁是当前对象。
对于静态方法,锁是TestSTatic.class对象
对于同步代码块,锁是Synchronized括号里面配置的对象
TestStatic类,1问,作用范围全体class对象,线程1拿到,线程2就不能拿到
2问,3问同上
Test类,1问,不能,锁都是实例对象test,线程1拿到锁之后,线程2无法访问
2问,可以,线程1锁是实例a,线程2是实例b。
独占锁
如果你不敢确定该用什么锁,就用这个吧,在保证正确的前提下,后续在提高开发效率。
public class ServerStatus {
public final Set<String> users;
public final Set<String> quers;
public synchronized void addUser(String u ) {
users.add(u);
}
public synchronized void addQuery(String q ) {
quers.add(q);
}
public synchronized void removeUser(String u) {
users.remove(u);
}
public synchronized void removeQuery(String q) {
quers.remove(q);
}
}
分拆锁
如果在整个应用程序只有一个锁,而不是为每个对象分配一个独立的锁,那么所有同步代码块的执行就会变成串行化执行。由于很多线程都会竞争同一个全局锁,因此两个线程同时请求这个锁的概率将会剧增,从而导致更严重的竞争。所以如果将这些锁请求分到更多的锁上,就能有效降低锁竞争程度。由于等待而被阻塞的线程将更少,从而可伸缩性将提高。
上文中users、quers是两个相互独立的变量,可以将此分解为两个独立的锁,每个锁只保护一个变量,降低每个锁被请求的频率。
public class ServerStatus {
public final Set<String> users;
public final Set<String> quers;
public void addUser(String u ) {
synchronized(users) {
users.add(u);
}
}
public void addQuery(String q ) {
synchronized(quers) {
quers.add(q);
}
}
public void removeUser(String u) {
synchronized(users) {
users.remove(u);
}
}
public void removeQuery(String q) {
synchronized(quers) {
quers.remove(q);
}
}
}
分离锁
在某些情况下,可以将锁分解技术进一步扩展为对一组独立对象上的锁进行分解,这种情况称为锁分段。例如ConcurrencyHashMap是有一个包含16个锁的数组实现,每个锁保护所有散列桶的1/16,其中第N个散列桶由第(N mod 16)个锁来保护。假设所有关键字都时间均与分布,那么相当于把锁的请求减少到原来的1/16,可以支持多达16个的并发写入。
锁分段的劣势在于:与采用单个锁来实现独占访问相比,要获取多个锁来实现独占访问将更加困难并且开销更高,比如计算size、重hash。
分布式锁
zookeeper,判断临时节点是否存在,存在就说明已经有人争抢到锁;不存在就创建节点,表明拥有该锁。
记下,以后详细研究
volatile
volatile是比synchronized更轻量级的同步原语,volatile可以修饰实例变量、静态变量、以及数组变量(网上大牛说,维护的是引用,但是里面的对象。。。嘿嘿嘿)。被volatile修饰的变量,JVM规范规定,一个线程在修改完,另外的线程能读取最新的值。
但仅仅保证可见性,不保证原子性,所以volatile通常用来修饰boolean类型或者状态比较少的数据类型,而且不能用来更新依赖变量之前值的操作(例volatile++)。
volatile内部仅仅是对变量的操作多了一条cpu指令(lock#指令),它会强制写数据到缓存,如果缓存数据同时也在主存,会强制写数据更新到主存,并且使所有持有该主存数据地址的缓存统统失效,触发其他持有缓存数据的线程从主存获取最新数据,从而实现同步。
JUC之Atomic系列12大类实例讲解和原理分解的更多相关文章
- Java JUC之Atomic系列12大类实例讲解和原理分解
Java JUC之Atomic系列12大类实例讲解和原理分解 2013-02-21 0个评论 作者:xieyuooo 收藏 我要投稿 在java6以后我们不但接触到了Loc ...
- dagger2系列之生成类实例
上一节的最后,我讲到一次注入生成类实例的生成步骤.先来回顾一下: 1 Module中存在创建方法,则看此创建方法有没有参数 如果有参数,这些参数也是由Component提供的,返回步骤1逐一生成参数 ...
- 通过模拟Mybatis动态代理生成Mapper代理类,讲解Mybatis核心原理
本文将通过模拟Mybatis动态代理生成Mapper代理类,讲解Mybatis原理 1.平常我们是如何使用Mapper的 先写一个简单的UserMapper,它包含一个全表查询的方法,代码如下 pub ...
- XAML实例教程系列 - XAML传递参数到值转换类实例 八
Kevin Fan分享开发经验,记录开发点滴 XAML实例教程系列 - XAML传递参数到值转换类实例 2012-06-28 05:25 by jv9, 508 阅读, 0 评论, 收藏, 编辑 继上 ...
- java架构之路(多线程)原子操作,Atomic与Unsafe魔术类
这次不讲原理了,主要是一些应用方面的知识,和上几次的JUC并发编程的知识点更容易理解. 知识回顾: 上次主要说了Semaphore信号量的使用,就是一个票据的使用,我们举例了看3D电影拿3D眼镜的例子 ...
- 转:c++类实例在内存中的分配
转自:http://blog.csdn.net/alexwei2009/article/details/6157926 c++是一种面向对象的编程语言,它向下保持了对c的兼容,同时也允许程序员能够自由 ...
- Java 集合系列 12 TreeMap
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- ASP.NET MVC+EF框架+EasyUI实现权限管理系列(12)-实现用户异步登录和T4模板
原文:ASP.NET MVC+EF框架+EasyUI实现权限管理系列(12)-实现用户异步登录和T4模板 ASP.NET MVC+EF框架+EasyUI实现权限管系列 (开篇) (1):框架搭建 ...
- JDK中的Atomic包中的类及使用
引言 Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作.原子变量的底层使用了处理器提供的原子指令,但是不同的CPU ...
随机推荐
- 淘宝(taobao)HSF框架
一.背景 随着网站访问量增加,仅仅靠增加机器已不能满足系统的要求,于是需要对应用系统进行垂直拆分和水平拆分.在拆分之后,各个被拆分的模块如何通信?如何保证 性能?如何保证各个应用都以同样的方式交互?这 ...
- 随机森林分类(Random Forest Classification)
其实,之前就接触过随机森林,但仅仅是用来做分类和回归.最近,因为要实现一个idea,想到用随机森林做ensemble learning才具体的来看其理论知识.随机森林主要是用到决策树的理论,也就是用决 ...
- PHP中的抽象类与接口
抽象类 php5支持抽象类和抽象方法.类前加 abstract, 此类就成为抽象类,无法被实例化,此类天生就是用来被继承的,给子类提供了一个类的模板; 类方法前加 abstract,是抽象方法,抽象方 ...
- 什么是hibernate?
一.什么是hibernate框架?1.通过数据库保存java运行时产生的对象和恢复对象,其实就是实现java对象与关系数据库记录的映射关系称为ORM(Object Relation Mapping), ...
- 使用apt-fast 来加速你的Ubuntu 的apt
使用apt-fast 来加速你的Ubuntu 的apt sudo add-apt-repository ppa:apt-fast/stable sudo apt-get update sudo apt ...
- UML建模之活动图介绍(Activity Diagram)
一.活动图的组成元素 Activity Diagram Element 1.活动状态图(Activity) 2.动作状态(Actions) 3.动作状态约束(Action Constraints) 4 ...
- 如何在React中使用CSS3动画
一.需求 1.在页面添加item时要有渐变效果 2.单击item可删除,带渐变效果 二.代码 1.通过Reacat插件ReactCSSTransitionGroup实现 <!DOCTYPE ht ...
- linux 开机自启动软件(包含xampp方法)
linux设置apache和mysql: linux开启启动的程序一般放在/etc/rc.d/init.d/里面,/etc/init.d/是其软连接. mysql设为linux服务 cp /usr/l ...
- kail新手安装
Kali Linux下载链接:http://mirrors.ustc.edu.cn/kali-images/kali-2.0/ (ps:建议用国内镜像源下载,速度有保证) Kali Linux安装之后 ...
- python判断文件目录是否存在
import os os.path.isfile('test.txt') # 如果不存在就返回False os.path.exists(directory) # 如果目录不存在就返回False o ...