最近作笔试题,遇到了很多关于线程安全的题目,好比:java
synchronized和volatile的区别是什么?编程
StringBuilder和StringBuffer的区别是什么?安全
HashMap和HashTable的区别是什么?等等......多线程
这些问题的答案涉及到的,就是关于线程安全问题。首先先要对线程安全有个概念,怎样才叫线程安全。并发
线程安全指的是多个线程并发执行的时候,当一个线程访问该类的某个数据的时候,经过加锁的机制,保护数据,直至当前线程读取完,释放锁后,其余线程才能继续使用,咱们认为这样是线程安全的。jvm
有线程安全,天然就有线程不安全,线程不安全指的是,多个线程并发执行的时候,数据没有获得保护,可能会出现多个线程修改或使用某个数据,致使所获得的数据不正确,也就是脏数据。性能
了解完基本概念后,接下来要引用某大神从Java内存模型和线程同步机制方面来描述线程的安全性。ui
不一样的平台,内存模型是不同的,可是jvm的内存模型规范是统一的。其实java的多线程并发问题最终都会反映在java的内存模型上,所谓线程安全无 非是要控制多个线程对某个资源的有序访问或修改。总结java的内存模型,要解决两个主要的问题:可见性和有序性。 atom
可见性: 多个线程之间是不能互相传递数据通讯的,它们之间的沟通只能经过共享变量来进行。Java内存模型(JMM)规定了jvm有主内存,主内存是多个线程共享 的。当new一个对象的时候,也是被分配在主内存中,每一个线程都有本身的工做内存,工做内存存储了主存的某些对象的副本,固然线程的工做内存大小是有限制 的。当线程操做某个对象时,执行顺序以下:spa
(1) 从主存复制变量到当前工做内存 (read and load)
(2) 执行代码,改变共享变量值 (use and assign)
(3) 用工做内存数据刷新主存相关内容 (store and write)
当一个共享变量在多个线程的工做内存中都有副本时,若是一个线程修改了这个共享 变量,那么其余线程应该可以看到这个被修改后的值,这就是多线程的可见性问题。
有序性:线程在引用变量时不能直接从主内存中引用,若是线程工做内存中没有该变量,则会从主内存中拷贝一个副本到工做内存中,完成后线程会引用该副本。当同一线程再度引用该字段时,有可能从新从主存中获取变量副本(read-load-use),也有可能直接引用原来的副本 (use),也就是说 read,load,use顺序能够由JVM实现系统决定。
线程不能直接为主存中字段赋值,它会将值指定给工做内存中的变量副本(assign),完成后这个变量副本会同步到主存储区(store- write),至于什么时候同步过去,根据JVM实现系统决定.有该字段,则会从主内存中将该字段赋值到工做内存中,这个过程为read-load,完成后线 程会引用该变量副本。
举个例子1,现有一变量x=10,a线程执行x=x+1操做,b线程执行x=x-1操做,俩线程同时运行的时候,x的值是不肯定的,有可能为9,也有可能为11,这就是多线程并发执行的顺序是不可预见致使的,因此要使线程安全,要保证a线程和b线程的有序执行,且执行的操做必须为原子操做。
原子操做:在多线程访问共享资源时,可以确保全部其余的进程(线程)都不在同一时间内访问相同的资源。原子操做(atomic operation)是不须要synchronized,这是Java多线程编程的老生常谈了。所谓原子操做是指不会被线程调度机制打断的操做;这种操做一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另外一个线程)。
那么如何确保线程执行的有序性和可见性呢?
synchronized关键字能够解决有序性和可见性的问题,它保证了多个线程之间是互斥的,当一段代码会修改共享变量,这一段代码成为互斥区或 临界区,为了保证共享变量的正确性,synchronized标示了临界区。
经常使用用法:
Java代码
synchronized(lock) { 临界区代码 } //例如: public synchronized void method() {} public static synchronized void method() {}
不管synchronized关键字是加在方法仍是对象中,都是取对象看成锁,理论上每一个对象均可以是一个锁。
对于public synchronized void method()这种状况,锁就是这个方法所在的对象。同理,若是方法是public static synchronized void method(),那么锁就是这个方法所在的class。
synchronized关键字有两种锁对象,一种是对象加锁,另外一种是对类加锁,对类加锁,则类锁对类里的全部对象都起做用,而对象锁只是针对该类的一个指定的对象加锁,这个类的其它对象仍然可使用已经对前一个对象加锁的synchronized方法。
例如:车站只剩一张票,A业务员和B业务员同时出票,执行public synchronized void sell()方法,那么结果就会出现,剩余票数为(-1)的状况,这就是对象锁致使的问题,其余对象仍然可使用这个方法;
当把sell方法加上static后,锁的对象就是这个类,对于A业务员和B业务员来讲,都要按顺序来操做业务,这样就不会出现剩余票数为负数的轻卡UN个了。
当一个对象是锁的时候,应该要被多个线程共享才是有意义的。这也得出一个结论:非线程安全!=不安全。
好比ArrayList就是线程不安全的,可是并不是说多线程状况下就不用ArrayList,若是你的每个线程都new了一个ArrayList对象,也就是对象是非共享的,线程之间不存在资源竞争,那么多线程执行的时候也是没有安全问题的。
每一个锁对象都有两个队列,一个是就绪队列,一个是阻塞队列,就绪队列存储了将要得到锁的线程,阻塞队列存储了被阻塞的线程,当一个线程被唤醒 后,才会进入到就绪队列,等待cpu的调度。
一个线程执行临界区代码过程以下:
1 得到同步锁
2 清空工做内存
3 从主存拷贝变量副本到工做内存
4 对这些变量计算
5 将变量从工做内存写回到主存
6 释放锁
可见,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。
生产者/消费者模式就是典型的同步锁问题,多线程执行过程除了操做是互斥的之外,每每也会出现线程之间互相协做的状况。
比方说,A机器人负责造车子,造好了车子,就放在仓库里,仓库每次只能放一辆车。
(1)起初,A机器人被运行,执行造车子make方法,造好了车子放在仓库后,没有立刻关机(释放锁),而是唤醒了(notify)准备上班的(阻塞队列)B机器人,本身再下班(进入阻塞队列)
(2)B机器人得到同步锁,上班,开始把造好的车子运出仓库拿去出售,车子运出去后,它也没有立刻溜了,而是唤醒了刚下班的A机器人继续造车子(赤裸裸的剥削,我也要卖车子)。
(3)A机器人发现仓库的车子被运走了,接到任务就只能继续开始造车子,造好车子把B机器人叫回来。
(4)B机器人卖车子,叫醒A机器人造车子。
。。。。。。
能够看出,在同步锁的做用下,造车子,卖车子,造车子,卖车子能够有序的进行,很愉快。
接下来要说的是另一个关键字:volatile
volatile一样也是Java同步的一种方法,只不过和synchronized锁相比,稍弱了点,它只能保证多线程执行的可见性,可不能保证有序性。
它的原理是不须要从主内存中复制一份副本到工做内存,而是直接对主内存的数据进行修改,这样,其余线程都能立马看到数据的修改。所以,volatile的使用范围要臂synchronized小,经常使用于直接赋值的状况,诸如例子1的状况就是不适用的。
最后须要注意的是,synchronized锁虽好,可是不能多用,会影响执行效率,阻塞队列的线程也会不断地尝试获取锁,消耗性能。更多关于synchronized和volatile的用法这里就不展开来讲了,能够baidu一下详细的用法。
本文参考了http://www.iteye.com/topic/806990,比较容易理解,感谢大神。