JAVA并发编程(二)内置锁和对象共享

《JAVA并发编程实战》读书笔记

多线程的优势

1、充分发挥多处理器能力
2、建模的简单性
3、异步事件的简化处理
4、响应更灵敏的用户界面

多线程的风险

1、线程安全性问题
2、活跃性问题
3、性能问题

线程的安全性

当多个线程访问同一个可变状态变量时,如果没有合适的同步处理程序会出现错误。解决方法通常有三个
1、不在线程之间共享变量
2、将共享变量修改为不可变的变量
3、在访问变量时使用同步
无状态的对象一定是线程安全的。要保持状态的一致性,就要在单个原子操作中更新所有相关的变量状态。

内置锁

Java提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)。内置锁的最小颗粒度是对象,修饰方法上的锁也是作用于对象。有时候叫它方法锁,但是实际上是锁住的对象。
内置锁又被称为监视锁,线程在进入同步代码块之前会自动获得锁,推出时才释放锁,获得内置锁的唯一路径就是进入这个锁保护的同步代码块或者同步方法。
最多只有一个线程可持有这种锁,所以它是线程安全的。当某个线程请求一个其他线程持有的锁时,发出请求的线程就会阻塞。但是如果某个线程视图获得一个已经由自己持有的锁,这是可以得。也就是说锁可以重入。重入意味着获取锁的操作粒度是“线程”而不是“调用”。
重入的一种实现方法是,为每个锁关联一个获取计数值和一个所有者线程。当计数为0时,这个锁没有任何持有者。当线程请求一个没有被持有的锁时,JVM记录下锁的持有者,计数值加1,如果同一个线程再次获取这个锁,计数值递增,而线程退出同步代码块时,计数器相应递减。当计数为0时,这个锁被释放。
如果一个变量会被多个线程访问、修改,那么就需要对这个变量进行锁保护。

活跃性和性能

同步方法每次只允许一个线程执行,这样同步方法的效率反而降低了。大量的请求排队处理就会导致不良并发。解决方法是尽可能缩小同步代码作用范围,当然,只能说是优化。要判断同步代码块的合理大小,需要在各种设计需求之间进行权衡。包括安全性、简单性和性能。
当执行时间较长的计算或者可能无法快速完成的操作时,一定不要持有锁。

对象的共享

在没有同步的情况下,编译器、处理器以及运行时等都可能对操作的执行顺序进行一些意想不到的调整。
当一个线程读取一个值,可能读取到的是失效数据。失效值能保证最低安全性。这个值可能是之前的值,而不是一个随机值。但是对非原子的64位操作会有更严重的问题,JVM允许将64位操作分解为两个32位操作,可能会读取到某个值得高32位和另一个值得低32位。
为了保证所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。
JAVA提供了稍微弱一些的同步机制,volatile变量。它能确保变量的更新操作能通知到其他线程。volatile变量上的操作不会与其他操作一起重排序,volatile变量不会被缓存在寄存器或者其他处理器不可见的地方,读取volatile变量总会返回最新值。
加锁机制既可以确保可见性,又可以确保原子性。而volatile变量只能确保可见性。
当且仅当满足以下条件时,才能使用volatile变量:
1、对变量的写入操作不依赖当前值,或者确保只有一个线程更新变量值。
2、该变量不会与其他变量一起纳入不变性条件中。
3、在访问变量时不需要加锁。
如果仅在单个线程内访问数据,就不需要同步。这种技术被称为线程封闭。volatile变量是一种特殊的线程封闭,相当于将修改操作封闭在一个线程中,并且volatile变量的可见性确保了其他线程看到的是最新值。
ThreadLocal类可以很好的实现线程封闭。

不可变对象一定是线程安全的。
除非某个域需要改变,否则应将其声明为final域。


欢迎扫描二维码,关注公众号
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值