转Java线程安全
Posted wytiger
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了转Java线程安全相关的知识,希望对你有一定的参考价值。
(一)、java并发之原子性与可见性
原子性
原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。Java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。
可见性
可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就这这个操作同样存在线程安全问题。
他们之间关系
原子性是说一个操作是否可分割,可见性是说操作结果其他线程是否可见。这么看来他们其实没有什么关系。
volatile与synchronized关键字
(1)volatile
volatile赋予了变量可见——禁止编译器对成员变量进行优化,它修饰的成员变量在每次被线程访问时,都强迫从主存中重读该成员变量的值;而且,当成员变量发生变化时,强迫线程将变化值回写到共享主存,这样在任何时刻两个不同线程总是看到某一成员变量的同一个值,这就是保证了可见性。文摘:
参考链接: http://www.cnblogs.com/aigongsi/archive/2012/04/01/2429166.html
(2)synchronized
synchronized为一段操作或内存进行加锁,它具有互斥性。当线程要操作被synchronized修饰的内存或操作时,必须首先获得锁才能进行后续操作;但是在同一时刻只能有一个线程获得相同的一把锁(对象监视器),所以它只允许一个线程进行操作。
简单的理解方法:
synchronized(object) method();
这相当与为menthod()加了一把锁,这把锁就是object对象,当线程要访问method方法时,需要获取钥匙:object的对象监视器,如果该钥匙没人拿走(之前没有线程操作该方法或操作完成),则当前线程拿走钥匙(获取对象监视器),并操作方法;当操作完方法后,将“钥匙”放回原处!
如果“钥匙”不在原处,则该线程需要等待别人把钥匙放回来(等待即进入阻塞状态);如果多个线程要获取该钥匙,则它们需要进行“竞争”(一般是根据线程的优先级进行竞争)
(二)、java并发之线程封闭
线程封闭
实现好的并发是一件困难的事情,所以很多时候我们都想躲避并发。避免并发最简单的方法就是线程封闭。什么是线程封闭呢?
就是把对象封装到一个线程里,只有这一个线程能看到此对象。那么这个对象就算不是线程安全的也不会出现任何安全问题。实现线程封闭有哪些方法呢?
1:ad-hoc线程封闭
这是完全靠实现者控制的线程封闭,他的线程封闭完全靠实现者实现。也是最糟糕的一种线程封闭。所以我们直接把他忽略掉吧。
2:栈封闭
栈封闭是我们编程当中遇到的最多的线程封闭。什么是栈封闭呢?简单的说就是局部变量。多个线程访问一个方法,此方法中的
局部变量都会被拷贝一分儿到线程栈中。所以局部变量是不被多个线程所共享的,也就不会出现并发问题。所以能用局部变量就别用全局的变量,全局变量容易引起并发问题。
3:ThreadLocal封闭
使用ThreadLocal是实现线程封闭的最好方法,有兴趣的朋友可以研究一下ThreadLocal的源码,其实ThreadLocal内部维护了一个Map,Map的key是每个线程的名称,而Map的值就是我们要封闭的对象。每个线程中的对象都对应着Map中一个值,也就是ThreadLocal利用Map实现了对象的线程封闭。这里就不说ThreadLocal的使用方法了,度娘一下便知。
总之,当我们要用线程封闭来避免并发问题的时候,最好使用的就是 【栈封闭】 和 【ThreadLocal】。
(三)、java并发之工具类的使用
Java中提供了一些工具类和容器类 来帮助我们来更好的实现并发。这篇博文我们就来简单讨论一下java中的工具类和容器类。学会并且熟练使用这些工具类对java的并发有很大的帮助。
工具类
Future与Callable相关类
异步执行计算结果,在计算完成之前get方法会一直等待。一旦计算完成,就不能再重新开始或取消计算。可使用 FutureTask 包装 Callable 或 Runnable 对象
- package com.chu.test.current;
- import java.util.concurrent.Callable;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.FutureTask;
- public class TestFutureTask {
- public static void main(String[] args) throws InterruptedException, ExecutionException {
- FutureTask<String> ft = new FutureTask<String>(new Callable<String>(){
- @Override
- public String call() throws Exception {
- return "aaaaa";
- }
- });
- new Thread(ft).start();
- while(!ft.isDone()){
- System.out.println("增在计算结果...");
- }
- System.out.println(ft.get());
- }
- }
CountDownLatch
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 他的实现方法就是一个计数器,在初始化CountDownLatch的时候定下来计数器的数量。每次调用countDown方法,计数器的数量就会减1,在计数器为0之前await方法会一直等待。
- package com.chu.test.current;
- import java.util.concurrent.CountDownLatch;
- public class TestCountDownLatch {
- public static void main(String[] args) throws InterruptedException {
- final CountDownLatch cdl = new CountDownLatch(1);
- new Thread() {
- @Override
- public void run() {
- try {
- System.out.println("等待执行...");
- cdl.await();//在countDown执行之前会一直等待
- System.out.println("执行完成。");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }.start();
- Thread.sleep(5000);
- cdl.countDown();
- }
- }
Semaphore
一个计数信号量。 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。
- package com.chu.test.current;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.Semaphore;
- public class TestSemaphore {
- public static void main(String... args) {
- ExecutorService exec = Executors.newCachedThreadPool();
- final Semaphore semp = new Semaphore(3);
- for (int index = 0; index < 5; index++) {
- final int NO = index;
- Runnable run = new Runnable() {
- public void run() {
- try {
- // 获取许可
- semp.acquire();
- System.out.println("Accessing: " + NO);
- Thread.sleep(2000);
- // 访问完后,释放
- semp.release();
- System.out.println("-----------------" + semp.availablePermits());
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- };
- exec.execute(run);
- }
- }
- }
CyclickBarrier
CyclicBarrier就象它名字的意思一样,可看成是个障碍,所有的线程必须到齐后才能一起通过这个障碍。
- package com.chu.test.current;
- import java.util.concurrent.BrokenBarrierException;
- import java.util.concurrent.CyclicBarrier;
- public class TestCyclicBarrier {
- public static void main(String[] args) {
- CyclicBarrier cb = new CyclicBarrier(4);
- new Thread(new Work(cb,"A")).start();
- new Thread(new Work(cb,"B")).start();
- new Thread(new Work(cb,"C")).start();
- new Thread(new Work(cb,"D")).start();
- }
- }
- class Work implements Runnable{
- CyclicBarrier cb;
- String name;
- public Work(CyclicBarrier cb , String name){
- this.cb = cb;
- this.name = name;
- }
- @Override
- public void run() {
- System.out.println(name+"准备工作...");
- try {
- cb.await();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } catch (BrokenBarrierException e) {
- e.printStackTrace();
- }
- System.out.println(name+"开始工作...");
- }
- }
Exchanger
用于交换两个线程的信息。
- package com.chu.test.current;
- import java.util.concurrent.Exchanger;
- public class TestExchanger {
- public static void main(String[] args) {
- Exchanger<String> ex = new Exchanger<String>();
- new Thread(new Change(ex,"A")).start();
- new Thread(new Change(ex,"B")).start();
- }
- }
- class Change implements Runnable{
- Exchanger<String> ex;
- String name;
- public Change(Exchanger<String> ex,String name){
- this.ex = ex;
- this.name = name;
- }
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName()+"来了");
- System.out.println(Thread.currentThread().getName()+"准备把【"+name+"】换出去");
- try {
- String new_name = ex.exchange(name);
- System.out.println(Thread.currentThread().getName()+"把【"+new_name+"】换回来");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
容器类
这里只简单的列举一下常见的同步容器类,他们的用法和非同步的容器类大同小异,这里就不举例说明他们的用法。可以度娘一下应有尽有。
同步List
CopyOnWriteArrayList:是ArrayList线程安全的变体,其中引起此list改变的操作(add,remove)都是通过对底层数组进行一次新的复制来实现的。这一般需要很大的开销。但是当遍历操作大大超过可变操作时,这种方法比其他方法更有效。不会抛出ConcurrentModificationException。
同步Map
HashTable:比较古老的同步Map
ConcurrentHashMap:是HashMap的同步版本,是1.5新增的同步类,不会抛ConcurrentModificationException,并且key无序排列。
ConcurrentSkipListMap:映射可以根据键的自然顺序进行排序,也可以根据创建映射时所提供的Comparator
进行排序,具体取决于使用的构造方法。
同步Set
CopyOnWriteArraySet :内部使用CopyOnWriteArrayList 实现。
ConcurrentSkipListSet:内部使用ConcurrentSkipListMap实现。
同步队列
ArrayBlockingQueue :一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序
LinkedBlockingQueue :一个基于已链接节点的、任选范围的阻塞双端队列。
PriorityBlockingQueue :一个无界阻塞队列,它使用与类PriorityQueue
相同的顺序规则,并且提供了阻塞获取操作。虽然此队列逻辑上是无界的,但是资源被耗尽时试图执行 add 操作也将失败(导致OutOfMemoryError)。此类不允许使用 null 元素。依赖自然顺序的优先级队列也不允许插入不可比较的对象(这样做会导致抛出ClassCastException)。
ConcurrentLinkedQueue:一个基于链接节点的无界线程安全队列。此队列按照 FIFO(先进先出)原则对元素进行排序。非阻塞。
Collctions返回的同步容器
synchronizedList(List<T> list) 、synchronizedMap(Map<K,V> m) 、synchronizedSet(Set<T> s) 通过这些方法返回的容器,都是线程安全的容器,有一点需要注意的就是这些同步容器在迭代的时候,比如手工加同步,例
synchronized(list){...}否则会产生不可预料的结果。
以上是关于转Java线程安全的主要内容,如果未能解决你的问题,请参考以下文章