(一)、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赋予了变量可见——禁止编译器对成员变量进行优化,它修饰的成员变量在每次被线程访问时,都强迫从主存中重读该成员变量的值;而且,当成员变量发生变化时,强迫线程将变化值回写到共享主存,这样在任何时刻两个不同线程总是看到某一成员变量的同一个值,这就是保证了可见性。文摘:

Java规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量
的原始值对比。这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。而volatile关键字就是提示
VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。
使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。
由于使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。 注意:如果给一个变量加上volatile修饰符,就相当于:每一个线程中一旦这个值发生了变化就马上刷新回主存,使得各个线程取出的值相同。编译器不要对这个变量的读、写操作做优化。但是值得注意的是,除了对long和double的简单操作之外,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 对象

  1. package com.chu.test.current;
  2. import java.util.concurrent.Callable;
  3. import java.util.concurrent.ExecutionException;
  4. import java.util.concurrent.FutureTask;
  5. public class TestFutureTask {
  6. public static void main(String[] args) throws InterruptedException, ExecutionException {
  7. FutureTask<String>  ft  = new FutureTask<String>(new Callable<String>(){
  8. @Override
  9. public String call() throws Exception {
  10. return "aaaaa";
  11. }
  12. });
  13. new Thread(ft).start();
  14. while(!ft.isDone()){
  15. System.out.println("增在计算结果...");
  16. }
  17. System.out.println(ft.get());
  18. }
  19. }

CountDownLatch

一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 他的实现方法就是一个计数器,在初始化CountDownLatch的时候定下来计数器的数量。每次调用countDown方法,计数器的数量就会减1,在计数器为0之前await方法会一直等待。

  1. package com.chu.test.current;
  2. import java.util.concurrent.CountDownLatch;
  3. public class TestCountDownLatch {
  4. public static void main(String[] args) throws InterruptedException {
  5. final CountDownLatch cdl = new CountDownLatch(1);
  6. new Thread() {
  7. @Override
  8. public void run() {
  9. try {
  10. System.out.println("等待执行...");
  11. cdl.await();//在countDown执行之前会一直等待
  12. System.out.println("执行完成。");
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }.start();
  18. Thread.sleep(5000);
  19. cdl.countDown();
  20. }
  21. }

Semaphore

一个计数信号量。 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。

  1. package com.chu.test.current;
  2. import java.util.concurrent.ExecutorService;
  3. import java.util.concurrent.Executors;
  4. import java.util.concurrent.Semaphore;
  5. public class TestSemaphore {
  6. public static void main(String... args) {
  7. ExecutorService exec = Executors.newCachedThreadPool();
  8. final Semaphore semp = new Semaphore(3);
  9. for (int index = 0; index < 5; index++) {
  10. final int NO = index;
  11. Runnable run = new Runnable() {
  12. public void run() {
  13. try {
  14. // 获取许可
  15. semp.acquire();
  16. System.out.println("Accessing: " + NO);
  17. Thread.sleep(2000);
  18. // 访问完后,释放
  19. semp.release();
  20. System.out.println("-----------------" + semp.availablePermits());
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. };
  26. exec.execute(run);
  27. }
  28. }
  29. }

CyclickBarrier

CyclicBarrier就象它名字的意思一样,可看成是个障碍,所有的线程必须到齐后才能一起通过这个障碍。

  1. package com.chu.test.current;
  2. import java.util.concurrent.BrokenBarrierException;
  3. import java.util.concurrent.CyclicBarrier;
  4. public class TestCyclicBarrier {
  5. public static void main(String[] args) {
  6. CyclicBarrier cb = new CyclicBarrier(4);
  7. new Thread(new Work(cb,"A")).start();
  8. new Thread(new Work(cb,"B")).start();
  9. new Thread(new Work(cb,"C")).start();
  10. new Thread(new Work(cb,"D")).start();
  11. }
  12. }
  13. class Work implements Runnable{
  14. CyclicBarrier cb;
  15. String name;
  16. public Work(CyclicBarrier cb , String name){
  17. this.cb = cb;
  18. this.name = name;
  19. }
  20. @Override
  21. public void run() {
  22. System.out.println(name+"准备工作...");
  23. try {
  24. cb.await();
  25. } catch (InterruptedException e) {
  26. e.printStackTrace();
  27. } catch (BrokenBarrierException e) {
  28. e.printStackTrace();
  29. }
  30. System.out.println(name+"开始工作...");
  31. }
  32. }

Exchanger

用于交换两个线程的信息。

  1. package com.chu.test.current;
  2. import java.util.concurrent.Exchanger;
  3. public class TestExchanger {
  4. public static void main(String[] args) {
  5. Exchanger<String> ex = new Exchanger<String>();
  6. new Thread(new Change(ex,"A")).start();
  7. new Thread(new Change(ex,"B")).start();
  8. }
  9. }
  10. class Change implements Runnable{
  11. Exchanger<String> ex;
  12. String name;
  13. public Change(Exchanger<String> ex,String name){
  14. this.ex = ex;
  15. this.name = name;
  16. }
  17. @Override
  18. public void run() {
  19. System.out.println(Thread.currentThread().getName()+"来了");
  20. System.out.println(Thread.currentThread().getName()+"准备把【"+name+"】换出去");
  21. try {
  22. String new_name = ex.exchange(name);
  23. System.out.println(Thread.currentThread().getName()+"把【"+new_name+"】换回来");
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }

容器类

这里只简单的列举一下常见的同步容器类,他们的用法和非同步的容器类大同小异,这里就不举例说明他们的用法。可以度娘一下应有尽有。

同步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线程安全的更多相关文章

  1. Java线程并发:知识点

    Java线程并发:知识点   发布:一个对象是使它能够被当前范围之外的代码所引用: 常见形式:将对象的的引用存储到公共静态域:非私有方法中返回引用:发布内部类实例,包含引用.   逃逸:在对象尚未准备 ...

  2. Java线程的概念

    1.      计算机系统 使用高速缓存来作为内存与处理器之间的缓冲,将运算需要用到的数据复制到缓存中,让计算能快速进行:当运算结束后再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了. 缓 ...

  3. Java 线程池框架核心代码分析--转

    原文地址:http://www.codeceo.com/article/java-thread-pool-kernal.html 前言 多线程编程中,为每个任务分配一个线程是不现实的,线程创建的开销和 ...

  4. 细说进程五种状态的生老病死——双胞胎兄弟Java线程

    java线程的五种状态其实要真正高清,只需要明白计算机操作系统中进程的知识,原理都是相同的. 系统根据PCB结构中的状态值控制进程. 单CPU系统中,任一时刻处于执行状态的进程只有一个. 进程的五种状 ...

  5. 【转载】 Java线程面试题 Top 50

    Java线程面试题 Top 50 不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题.Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员 的欢迎.大多数待遇丰厚的J ...

  6. 第24章 java线程(3)-线程的生命周期

    java线程(3)-线程的生命周期 1.两种生命周期流转图 ** 生命周期:**一个事物冲从出生的那一刻开始到最终死亡中间的过程 在事物的漫长的生命周期过程中,总会经历不同的状态(婴儿状态/青少年状态 ...

  7. 第23章 java线程通信——生产者/消费者模型案例

    第23章 java线程通信--生产者/消费者模型案例 1.案例: package com.rocco; /** * 生产者消费者问题,涉及到几个类 * 第一,这个问题本身就是一个类,即主类 * 第二, ...

  8. 第22章 java线程(2)-线程同步

    java线程(2)-线程同步 本节主要是在前面吃苹果的基础上发现问题,然后提出三种解决方式 1.线程不安全问题 什么叫线程不安全呢 即当多线程并发访问同一个资源对象的时候,可能出现不安全的问题 对于前 ...

  9. 第21章 java线程(1)-线程初步

    java线程(1)-线程初步 1.并行和并发 并行和并发是即相似又有区别: 并行:指两个或者多个事件在同一时刻点发生. 并发:指两个或多个事件在同一时间段内发生 在操作系统中,并发性是指在一段事件内宏 ...

  10. [转]Java线程安全总结

    最近想将java基础的一些东西都整理整理,写下来,这是对知识的总结,也是一种乐趣.已经拟好了提纲,大概分为这几个主题: java线程安全,java垃圾收集,java并发包详细介绍,java profi ...

随机推荐

  1. Zygote进程【1】——Zygote的诞生

    在Android中存在着C和Java两个完全不同的世界,前者直接建立在Linux的基础上,后者直接建立在JVM的基础上.zygote的中文名字为"受精卵",这个名字很好的诠释了zy ...

  2. XAMPP中proftpd的简明配置方法

    XAMPP中proftpd的简明配置方法   用LAMPP的安装方法可以开一个默认的nobody用户,用lampp security就可以初始设置相应的默认用户密码.如果要有多用户,又怎样管理.目录怎 ...

  3. Swift中的Masonry第三方库——SnapKit

    在OC开发时我常用一个名叫Masonry的第三方Autolayout库,在转Swift后发现虽然Swift可以混编OC,但总感觉有些麻烦,在Github上发现了这个叫做SnapKit的第三方库,发现使 ...

  4. 【转】Sql Server参数化查询之where in和like实现详解

    转载至:http://www.cnblogs.com/lzrabbit/archive/2012/04/22/2465313.html 文章导读 拼SQL实现where in查询 使用CHARINDE ...

  5. android 中退出程序的两种方式

    转自:http://blog.sina.com.cn/s/blog_5da93c8f0100t76l.html 思考:如何安全的退出程序? finish是Activity的类,仅仅针对Activity ...

  6. Linux 网络编程详解十

    select int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *tim ...

  7. noi题库(noi.openjudge.cn) 1.9编程基础之顺序查找T01——T05

    T01 查找特定元素的值 描述 在一个序列(下标从1开始)中查找一个给定的值,输出第一次出现的位置. 输入 第一行包含一个正整数n,表示序列中元素个数.1 <= n <= 10000.第二 ...

  8. node基础01:简要介绍

    1.node vs php 优点 性能高(机制问题) 开发效率高(省了不少优化的事) 应用范围广(可以开发桌面系统,electron框架) 缺点 新,人少 中间件少 IDE不完善 2.node的劣势和 ...

  9. STM32 控制红外线收发

    买了一块STM32的板子,这次需要将IR的code移植到STM32上面,因为STM32成本比树莓派低得多,所以 一些简单的外设挂在STM32上就行了. 我买的板子的型号是STM32F103C8T6,价 ...

  10. Nodejs进阶:基于express+multer的文件上传

    关于作者 程序猿小卡,前腾讯IMWEB团队成员,阿里云栖社区专家博主.欢迎加入 Express前端交流群(197339705). 正在填坑:<Nodejs学习笔记> / <Expre ...