Java Thread Safety
java线程安全的两个主要点:java的内存模型,java的线程同步机制。参考:http://www.iteye.com/topic/806990
浅谈java内存模型
java的内存模型的两个主要问题:可见性和有序性。我们都知道计算机有高速缓存的存在,处理器并不是每次处理数据都是取内存的。
多个线程之间是不能互相传递数据通信的,它们之间的沟通只能通过共享变量来进行。Java内存模型规定了jvm有主内存,主内存是多个线程共享 的。当new一个对象的时候,也是被分配在主内存中,每个线程都有自己的工作内存,工作内存存储了主存的某些对象的副本,当然线程的工作内存大小是有限制 的。当线程操作某个对象时,执行顺序如下:
(1) 从主存复制变量到当前工作内存 (read and load)
(2) 执行代码,改变共享变量值 (use and assign)
(3) 用工作内存数据刷新主存相关内容 (store and write)
当一个共享变量在多个线程的工作内存中都有副本时,如果一个线程修改了这个共享变量,那么其他线程应该能够看到这个被修改后的值,这就是多线程的可见性问题。
什么是有序性呢?线程在引用变量时不能直接从主内存中引用,如果线程工作内存中没有该变量,则会从主内存中拷贝一个副本到工作内存中,这个过程为read-load,完 成后线程会引用该副本。当同一线程再度引用该字段时,有可能重新从主存中获取变量副本(read-load-use),也有可能直接引用原来的副本 (use),也就是说 read,load,use顺序可以由JVM实现系统决定。
线程不能直接为主存中中字段赋值,它会将值指定给工作内存中的变量副本(assign),完成后这个变量副本会同步到主存储区(store- write),至于何时同步过去,根据JVM实现系统决定.有该字段,则会从主内存中将该字段赋值到工作内存中,这个过程为read-load,完成后线程会引用该变量副本。线程有可能只对工作内存中的副本进行赋值,只到最后一次赋值后才同步到主存储区,所以assign,store,weite顺序可以由JVM实现系统决 定。
synchronized关键字 :
保证了多个线程对于同步块是互斥的,解决java多线程的执行有序性和内存可见性。当一段代码会修改共享变量,这一段代码成为互斥区或临界区,为了保证共享变量的正确性,synchronized标示了临界区。
理论上,每个对象都可以做为锁,但一个对象做为锁时,应该被多个线程共享,这样才显得有意义,在并发环境下,一个没有共享的对象作为锁是没有意义的。
每个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要获得锁的线程,阻塞队列存储了被阻塞的线程,当一个被线程被唤醒 (notify)后,才会进入到就绪队列,等待cpu的调度。
一个线程执行临界区代码过程如下:
- 获得同步锁
- 清空工作内存
- 从主存拷贝变量副本到工作内存
- 对这些变量计算
- 将变量从工作内存写回到主存
- 释放锁
volatile是java提供的一种同步手段,因为volatile只能保证多线程的内存可见性,不能保证多线程的执行有序性。任何被volatile修饰的变量,都不拷贝副本到工作内存,任何修改都及时写在主存。因此对于Valatile修饰的变量的修改,所有线程马上就能看到,但是volatile不能保证对变量的修改是有序的。Volatile修饰的成员变量在每次被线程访问时,都强迫从主内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到主内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。
要使volatile变量提供理想的线程安全,必须同时满足下面两个条件:
- 对变量的写操作不依赖于当前值。
- 该变量没有包含在具有其他变量的不变式中
volatile只保证了可见性,所以Volatile适合直接赋值的场景。一个变量被多个线程共享,线程直接给这个变量赋值。
如何保证线程安全:
- 保证线程安全的三种方法
- 不要跨线程访问共享变量
- 使共享变量是final类型的
- 将共享变量的操作加上同步
- 无状态或只读对象永远是线程安全的。
- 当一个线程请求获得它自己占有的锁时(同一把锁的嵌套使用),我们称该锁为可重入锁。
- 必要时使用ThreadLocal变量确保线程封闭性,封闭对象的例子在实际使用过程中,例如jdbc的connection机制。
- 单一不可变对象往往是线程安全的(复杂不可变对象需要保证其内部成员变量也是不可变的),将所有的域都声明为final。
- 将数据封装在对象内部,并保证对数据的访问是原子的。建议采用volatile javabean模型或者构造同步的getter,setter。
- 在并发编程中,需要容器支持的时候,优先考虑使用jdk并发容器。其并发容器的迭代器,以及全范围的size(),isEmpty()都表现出弱一致性。他们只能标示容器当时的一个数据状态。无法完整响应容器之后的变化和修改。
- 使用有界队列,在队列充满或为空时,阻塞所有的读与写操作。BlockQueue下的实现有LinkedBlockingQueue与ArrayBlockingQueue,前者为链表,可变操作频繁优先考虑,后者为数组,读取操作频繁优先考虑。PriorityBlockingQueue是一个按优先级顺序排列的阻塞队列,它可以对所有置入的元素进行排序(实现Comparator接口)
浙公网安备 33010602011771号