Java多线程核心技术2 - 对象和变量的并发访问
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Java多线程核心技术2 - 对象和变量的并发访问,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含4848字,纯文字阅读大概需要7分钟。
内容图文
![Java多线程核心技术2 - 对象和变量的并发访问](/upload/InfoBanner/zyjiaocheng/609/75284fe21ac9425292d3befa1767a719.jpg)
Java多线程核心技术2 - 对象和变量的并发访问
来自阅读Java多线程编程核心技术的读书笔记,按照自己思路写了一些整理
历史内容:Java多线程核心技术1 - 多线程技能
2. 对象和变量的并发访问
2.1 为什么产生线程安全问题
非线程安全是发生在多个线程对同一个对象中的实例变量的访问。若多个线程对同一个对象的实例变量访问,可能产生脏读的后果,即读到的数据是被修改过的。但是,不是所有变量和对象访问都有问题
- 方法内的变量(私有变量)不存在线程安全问题
- 实例变量(对象中变量)存在线程安全问题
2.2 synchronized 同步方法
- 同步方法写法:
synchronized public void methodB() { ... }
- 相当于对拥有此方法的对象本身加锁,锁是这个对象(称为object)
- A线程先持有object对象的锁,B线程若想持有则需要等待
synchronized 可重入、继承可重入
-
synchronized
关键字具有可重入功能:即使用synchronized
后,当一个线程得到对象锁,其再次请求对象锁一定可以获得 -
即在一个
synchronized
方法/块内部调用本类其他synchronized
方法/块,永远可以得到锁- 若不可锁重入,则引发死锁
-
可重入锁对父子类继承有效:
-
当父子类存在继承关系时,子类完全可以通过可重入锁调用父类的同步方法
// 父类 public class Main { public int i = 10; synchronized public void operateIMainMethod() { i--; System.out.println("main print i="+i); Thread.sleep(100); } } // 子类 public class Sub extends Main { synchronized public void operateISubMethod() { i--; System.out.println("sub print i="+i); Thread.sleep(100); this.operateIMainMethod(); // 可以重入 } }
运行结果:
-
异常自动释放
- 当一个线程出现异常时,锁被自动释放
同步不具有继承性
- 父类某方法
synchronized
,若子类Override这个方法且不加synchronized
,则不会具有同步性。必须显式在子类方法中添加才行!
2.3 synchronized 同步语句块
-
synchronized
声明同步方法的弊端:- 调用同步方法执行一个长时间的任务,整个执行方法的过程中都将占用锁
- 使用
synchronized
语句块,减少同步块体积
-
使用格式
try { synchronized (this) { // ... } catch (InterruptedException e) { e.printStackTrace(); } }
对象监视器
- 之前使用
synchronized(this)
作为同步代码块,则使用this
作为对象监视器 - Java同样还支持对“任意对象”作为监视器实现同步功能;同样的,同一时间内,只有一个线程可以进入
synchronized(x) { ... }
包裹的临界区中 - 3个结论
- 当多个线程同时执行
synchronized(x) {}
同步代码块时呈同步效果 - 当其他线程执行
x
对象synchronized
同步方法呈同步效果 - 当其他线程执行
x
**对象方法**里面synchronized (this)
代码块时也就呈现同步效果
- 当多个线程同时执行
静态同步 synchronized 方法 & synchronized(class) 方法块
-
当
synchronzied
用在static静态方法上时,加锁对象是对应的class
对象public class Service { // 对类对象加锁,不会影响到其他锁 synchronized public static void printA() { try { // ... } catch (InterruptedException e) { e.printStackTrace(); } } }
String 常量池特性与synchronized
-
要注意的是,Java中String使用常量池,相同值的String引用指向的可能是常量池中相同的对象
// 同步方法 public static void print(String stringParam) { try { // 对stringParam - synchronzied // 在不同的线程中调用 print("aa") // 可能被锁到线程池中的同一个对象 synchronized (stringParam) { while (true) { // ... } } } catch (InterruptedException e) { e.printStackTrace(); } }
2.4 volatile 关键字
-
volatile
关键字作用:保证变量可见性;禁止指令重排序-
保证此变量对所有的线程的可见性,这里的“可见性”,如本文开头所述,当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。但普通变量做不到这点,普通变量的值在线程间传递均需要通过主内存(详见:Java内存模型)来完成。
-
禁止指令重排序优化。有volatile修饰的变量,赋值后多执行了一个“load addl $0x0, (%esp)”操作,这个操作相当于一个内存屏障(指令重排序时不能把后面的指令重排序到内存屏障之前的位置),只有一个CPU访问内存时,并不需要内存屏障;(什么是指令重排序:是指CPU采用了允许将多条指令不按程序规定的顺序分开发送给各相应电路单元处理)。
-
保证变量可见性
-
在
-server
环境中,私有堆栈中的值和公共堆栈的值不同步(为了提升线程运行效率,读取将预先读取工作内存中的值) -
启动
volatile
关键字后,将强制从公共内存中读取变量的值
非原子性特征
-
volatile
关键字增加了实例变量在多个线程之间的可见性,但是不支持原子性 -
如果在方法/代码块中声明了
synchronzied
,则不再需要volatile
volatile
的主要使用场合是:多个线程**共享读取**变量,希望获取到最新的值
-
下图展示变量在内存中的工作过程:
- 可以得出结论:
- read和load阶段:主存复制变量到当前工作内存
- use和assign阶段:执行代码、改变共享变量值
- store和write阶段:用工作内存的数据刷新主存对应变量值
- 对于Volatile修饰的变量,JVM虚拟机只保证从主内存加载到线程工作内存的值是最新的
- 可以得出结论:
volatile vs synchronized
-
比较
synchronized
与volatile
关键字- 关键字
volatile
是线程同步的轻量级实现,只能修饰变量;synchronized
可以修饰方法和代码块; - 多线程访问
volatile
不会发生阻塞;但synchronzied
会阻塞 volatile
保证数据的可见性、不保证数据的原子性
synchronized
既保证原子性、又保证可见性,因为会在私有内存与公共内存之间做数据同步
- 关键字
-
synchronized
代码块有volatile
同步的功能
内容总结
以上是互联网集市为您收集整理的Java多线程核心技术2 - 对象和变量的并发访问全部内容,希望文章能够帮你解决Java多线程核心技术2 - 对象和变量的并发访问所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。