Java多线程(2)-基础介绍
2017-01-14 21:27
309 查看
基础介绍
一.线程安全性
(1)概念
1.共享: 变量在多个线程中访问。
2.可变: 变量在其生命周期内可发生变化。
3.一个对象是否是线程安全的,取决于它是否被多个线程访问,同时满足于“共享”和“可变”这两个条件的变量,必须要添加同步机制以保证变量的线程安全性,而不同时满足的这两个条件的变量则是线程安全的。
4.原子性: 一个变量或一个复合过程在一个线程使用时,其他线程无法访问或者修改这个变量或者过程。
5.可见性: 一个变量在某一个线程中修改后,其他线程马上就能知道此变量已被修改。
正确的加锁既能保证锁中代码段的原子性,同时也能保证代码中对象的可见性。
6.为什么会有线程安全问题存在
例如 两个线程共同调用i++ , 会发生如下事件
可以看出i++并不是一个原子操作,它伴随这是个 读取->修改->写入 的操作序列,而线程二读取的时候,有可能线程一正在修改其值。这样就造成了顺序的混乱。产生线程安全性问题。
(7)重排序
因为可见性的原因主线程修改了ready的值,可能永远无法被Reader线程看到,所以循环可能一直进行下去。还有一种情况,输出number有可能为0,大家可能感到奇怪为什么已经给number赋值为42了,而显示会为0。这是因为在没有同步措施的情况下编译器和处理器以及运行时,都可能对程序的执行顺序进行意想不到的调整,并不像程序中所描述一样。这种现象叫做重排序。但是只要进行正确的同步这种情况就能被避免。
(2)以下几种情况可视为线程安全情况
1.不在线程之间共享该变量。
2.变量状态为final修饰的不可变的变量。
3.在线程间正确使用同步机制的变量。
4.方法中或者线程中包含的局部变量(变量不可被发布出去)。
(3)最低安全性保障
在没有同步措施的情况下,可能会读取一个失效值,但至少这个值是之前某个线程设置的。但是非volatile类型的long和double变量,JVM运行将64位的读写操作分解成两个32位的操作。如果没有同步保证,那么很可能会读取到某个线程设置的高32位,和另一个线程设置的低32位,这样连最低安全性都无法保障,所以不要抱有侥幸心理,赶紧用锁保护起来吧。
(4)Volatile变量描述
volatile变量不会造成线程阻塞,所以volatile是比synchronized更轻量级的一种同步机制。把一个变量声明为volatile后编译器以及运行时都会注意到这个变量是共享的。因此不会对这个变量操作进行重排序。其他线程对这个变量进行修改时会直接在主内存(其他线程可见)上修改,不会把它缓存到自己线程内存中。所以读取volatile类型的变量时都会读取到最新的修改值。可以说volatile能够保证变量的可见性。(典型应用:判断线程中循环退出)
注意:volatile无法保证原子性(因为它无法提供 读->写->改 的原子操作)它只能保证改后可见。
(5)设计同步类遵循的一些规则,帮助更好的设计程序
1.在设计多线程访问类时,设计线程安全的变量可以优先考虑前两种情况,能提高程序效率。加锁是最后考虑的。
2.需要同步的变量越少,越容易实现正确的同步,同时也能更加容易判断此变量在多线程中使用的情况,从而更好的优化设计类。
3.更好的封装,将多线程的复杂性都封装在类的内部,遵循依赖倒置原则。这样能更好的实现线程的安全性。后期的代码维护会相对容易。
4.对于已经正确运行且符合性能要求的多线程程序,要谨慎修改,因为并发错误往往是难以重现和调试的。
5.即使全部采用线程安全类来构建程序,依然无法保证程序是线程安全的。因为每个线程安全类只保证类内部线程安全。而外部调用的原子性依然无法保证。
6.为了保证活跃性和性能,尽量减少同步方法的使用,细化同步锁,不需要同步的代码尽量抽离同步块,不要将耗时操作放入同步块中。
一.线程安全性
(1)概念
1.共享: 变量在多个线程中访问。
2.可变: 变量在其生命周期内可发生变化。
3.一个对象是否是线程安全的,取决于它是否被多个线程访问,同时满足于“共享”和“可变”这两个条件的变量,必须要添加同步机制以保证变量的线程安全性,而不同时满足的这两个条件的变量则是线程安全的。
4.原子性: 一个变量或一个复合过程在一个线程使用时,其他线程无法访问或者修改这个变量或者过程。
5.可见性: 一个变量在某一个线程中修改后,其他线程马上就能知道此变量已被修改。
正确的加锁既能保证锁中代码段的原子性,同时也能保证代码中对象的可见性。
6.为什么会有线程安全问题存在
例如 两个线程共同调用i++ , 会发生如下事件
可以看出i++并不是一个原子操作,它伴随这是个 读取->修改->写入 的操作序列,而线程二读取的时候,有可能线程一正在修改其值。这样就造成了顺序的混乱。产生线程安全性问题。
(7)重排序
private static boolean ready = false; private static int number = 0; private static class ReaderThread extends Thread{ @Override public void run() { while(!ready) Thread.yield(); System.out.println(number); } } public static void main(String[] arge){ new ReaderThread().start(); number = 42; ready = true; }
因为可见性的原因主线程修改了ready的值,可能永远无法被Reader线程看到,所以循环可能一直进行下去。还有一种情况,输出number有可能为0,大家可能感到奇怪为什么已经给number赋值为42了,而显示会为0。这是因为在没有同步措施的情况下编译器和处理器以及运行时,都可能对程序的执行顺序进行意想不到的调整,并不像程序中所描述一样。这种现象叫做重排序。但是只要进行正确的同步这种情况就能被避免。
(2)以下几种情况可视为线程安全情况
1.不在线程之间共享该变量。
2.变量状态为final修饰的不可变的变量。
3.在线程间正确使用同步机制的变量。
4.方法中或者线程中包含的局部变量(变量不可被发布出去)。
(3)最低安全性保障
在没有同步措施的情况下,可能会读取一个失效值,但至少这个值是之前某个线程设置的。但是非volatile类型的long和double变量,JVM运行将64位的读写操作分解成两个32位的操作。如果没有同步保证,那么很可能会读取到某个线程设置的高32位,和另一个线程设置的低32位,这样连最低安全性都无法保障,所以不要抱有侥幸心理,赶紧用锁保护起来吧。
(4)Volatile变量描述
volatile变量不会造成线程阻塞,所以volatile是比synchronized更轻量级的一种同步机制。把一个变量声明为volatile后编译器以及运行时都会注意到这个变量是共享的。因此不会对这个变量操作进行重排序。其他线程对这个变量进行修改时会直接在主内存(其他线程可见)上修改,不会把它缓存到自己线程内存中。所以读取volatile类型的变量时都会读取到最新的修改值。可以说volatile能够保证变量的可见性。(典型应用:判断线程中循环退出)
注意:volatile无法保证原子性(因为它无法提供 读->写->改 的原子操作)它只能保证改后可见。
(5)设计同步类遵循的一些规则,帮助更好的设计程序
1.在设计多线程访问类时,设计线程安全的变量可以优先考虑前两种情况,能提高程序效率。加锁是最后考虑的。
2.需要同步的变量越少,越容易实现正确的同步,同时也能更加容易判断此变量在多线程中使用的情况,从而更好的优化设计类。
3.更好的封装,将多线程的复杂性都封装在类的内部,遵循依赖倒置原则。这样能更好的实现线程的安全性。后期的代码维护会相对容易。
4.对于已经正确运行且符合性能要求的多线程程序,要谨慎修改,因为并发错误往往是难以重现和调试的。
5.即使全部采用线程安全类来构建程序,依然无法保证程序是线程安全的。因为每个线程安全类只保证类内部线程安全。而外部调用的原子性依然无法保证。
6.为了保证活跃性和性能,尽量减少同步方法的使用,细化同步锁,不需要同步的代码尽量抽离同步块,不要将耗时操作放入同步块中。
相关文章推荐
- JAVA NIO入门讲解1
- java修改properties文件 丢失内容
- Spring MVC拦截器
- Spring标签提交List对象
- Spring MVC拦截器
- spring mvc详解
- Spring Boot应用连接数据库MySQL
- SpringBoot学习一
- JavaSE 学习参考:变量(2)
- 算法训练 大小写转换
- SpringMVC Junit单元测试
- 医疗保险管理系统设计 Java
- 构造函数--Java基础039
- javap使用
- 小博老师解析Java核心技术 ——JDBC普通增删改操作
- class 类 init 功能(类似java类的构造函数)
- 数组的0清空--Java基础038
- 封装--Java基础037
- Adapter模式
- 对象的内存图--Java基础036