目录
1、介绍
2、线程安全
3、共享对象
1、介绍
- 线程的优点
- 恰当使用线程,可以提升复杂程序的性能,降低开发和维护成本
- 可以把一部分复杂代码转为直接、简洁易懂的代码
- 更有效地利用空闲处理器资源,提高吞吐量
- 用户界面有更好的响应性
- 线程的缺点
- 安全性(synchronized解决)
- 活跃度:死锁、饥饿、活锁
- 性能,上下文切换
2、线程安全
- 编写线程安全的代码,本质上就是对状态(state)的的访问,而且通常是共享的、可变的状态
- 线程不安全:如果多个线程访问了同一个变量,你的程序就存在线程不安全的可能性。在没有正确同步的情况下,就会有线程安全隐患。
- 有3种方法修复它:
- 不要跨线程共享变量
- 使状态变量为不可变的
- 在任何访问状态变量的时候使用同步
- 有3种方法修复它:
- 一开始就将一个类设计成线程安全的,比后期修复他更容易
- 使用封装、不可变性、明确的不变约束
- 线程不安全:如果多个线程访问了同一个变量,你的程序就存在线程不安全的可能性。在没有正确同步的情况下,就会有线程安全隐患。
- 线程安全性
- 当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用方代码不必作其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的
- 简单来说:如果一个类在被多个线程访问时,可以持续进行正确的行为,它就是线程安全的
- 无状态对象永远是线程安全的
- 当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用方代码不必作其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的
- 原子性:单独的、不可分割的
- 竞争条件:检查再运行
- 锁
- 内部锁 synchronized
- 一个synchronized块包含两部分
- 锁对象的引用
- 这个锁保护的代码块
- synchronized方法的锁,就是该方法所在的对象本身(静态synchronized方法从class对象上获取锁)
- 互斥(同一时间至多有一个线程访问)、可重入
- 一个synchronized块包含两部分
- 内部锁 synchronized
- 使用锁来保护状态
- 操作共享状态的复合操作必须是原子的,复合操作要在完整的运行期间占有锁
- 用锁来协调访问变量时,每次访问变量需要用同一把锁
- 活跃度与性能
- 缩小synchronized块的范围,可以提高性能,但决不能因此而舍弃安全性
- 耗时的计算或操作,在操作期间不要占有锁
3、共享对象
- 可见性
- 重排序
- 书中解释的不清楚,额外重点看一下
- 多线程中共享的、可变的long和double是不安全的,即使不在意过期数据
- Java允许将64位的操作拆分成两个32位,如果读和写发生在不同的线程,这个long变量又没有被volatile修饰,这种情况下读取这个long变量,那么就可能读到一个值的低32位和另外一个值的高32位
- 锁和可见性
- 内置锁可以个用来确保一个线程以某种可预见的方式看到另一个线程的影响。当线程A执行一个同步块时,线程B也随后进入了被同一个锁监视的同步块中,这时可以保证,在锁释放之前对A可见的变量的值,B获得锁之后同样是可见的。
- 锁不仅仅是关于同步与互斥的,也是关于内存可见的。为了保证所有线程都能够看到共享的、可变变量的最新值,读取和写入线程必须使用公共的锁进行同步。
- volatile
- Java提供的一种同步的弱形式,确保对一个变量的更新以可预见的方式通知给其他线程
- 当一个变量被volatile修饰的时候,编译器和运行时会监视这个变量,对这个变量的操作不会与其他内存操作一起被重排序。volatile变量不会缓存在寄存器或者缓存在对其他处理器隐藏的地方,所以想要读取一个volatile变量的值的时候,总是返回由某一线程读取的最新值
- 访问volatile变量的操作并不会加锁,也不会引起执行线程的阻塞
- 使用volatile变量来控制状态可见性的代码,比使用锁的代码更脆弱,更难理解
- 正确使用的方式:用于确保他们所用的对象的状态的可见性、用于标识重要的生命周期事件的发生
- 加锁可以保证原子性和可见性,volatile只保证可见性
- 只有满足了下面所有的标准后,你才能使用 volatile 变量:
- 写入变量时并不依赖变量的当前值;或者能够确保只有单一的线程修改变量的值
- 变量不需要与其他的状态变量共同参与不变约束;
- 而且,访问变量时,没有其他的原因需要加锁。
- Java提供的一种同步的弱形式,确保对一个变量的更新以可预见的方式通知给其他线程
- 重排序
- 发布(publishing)和逸出(escape)
- 发布(publishing)一个对象的意思是使它能够被当前范围之外的代码所使用
- 一个对象在尚未准备好时就将它发布,这种情况称作逸出(escape)
- 线程封闭:不共享数据就可以避免同步。线程封闭是实现线程安全最简单的方法之一
- 栈限制:对象被限制在保存本地变量的执行线程中
- threadLocal:一种维护线程限制的更加规范的方式
- 它允许你将每个线程与持有数值的对象关联在一起。threadLocal提供了get与set 访问器,为每个使用它的线程维护一份单独的拷贝。所以 get总是返回由当前执行线程通过 set 设置的最新值。
- ThreadLocal变量通常用于防止在基于可变的单体(Singleton)或全局变量的设计中,出现(不正确的)共享。比如说,一个单线程化的应用程序可能会维护一个全局的数据库连接,这个connection在启动时就已经被初始化了。这样就可以避免为每个方法都传递一个 connection。因为JDBC 规范并未要求 connection 自身一定是线程安全的,因此,如果没有额外的协调时,使用全局变量的多线程应用程序同样不是线程安
全的。通过利用 ThreadLocal存储JDBC连接,每个线程都会拥有属于自己的 connection。
- 不可变性
- 创建后状态不能被修改的对象叫做不可变对象。不可变对象永远是线程安全的
- 但是不可变性不等于把所有域都声明为final类型。final依然可变,只是引用不可变而已,内部的值依然是可以变的