java多线程学习总结

 

在工作之余发现多线程对于自己来说比较重要,结合工作重新对多线程进行学习梳理。

一多线程的基本概念总结

1.进程:一个计算机程序的运行实例,包含了需要执行的指令;有自己的独立地址空间,包含程序内容和数据;不同进程的地址空间是互相隔离的;进程拥有各种资源和状态信息,包括打开的文件、子进程和信号处理。

2.线程:表示程序的执行流程,是CPU调度执行的基本单位;线程有自己的程序计数器、寄存器、堆栈和帧。同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源。

3.多线程:指的是这个程序(一个进程)运行时产生了不止一个线程

   并行与并发:

    并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。

    并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。

   并发与并行

    线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不   是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果,如不加事务的转账代码:

 1 package test;
 2 
 3 import dao.User;
 4 
 5 /**
 6  * Created by guoYunLong on 2017/5/30 0030.
 7  */
 8 public class news {
 9     void transferMoney(User from, User to, float amount){
10         to.setMoney(to.getBalance() + amount);
11         from.setMoney(from.getBalance() - amount);
12     }
13 }

   同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。

4.线程间的可见性:一个线程对进程中共享的数据的修改,是否对另一个线程可见

可见性问题:

 a、CPU采用时间片轮转等不同算法来对线程进行调度对于IdGenerator的getNext()方法,在多线程下不能保证返回值是不重复的:各个线程之间相互竞争CPU时间来获取运行机会,CPU切换可能发生在执行间隙。

 b、CPU缓存: 

目前CPU一般采用层次结构的多级缓存的架构,有的CPU提供了L1、L2和L3三级缓存。当CPU需要读取主存中某个位置的数据时,会一次检查各级缓存中是否存在对应的数据。如果有,直接从缓存中读取,这比从主存中读取速度快很多。当CPU需要写入时,数据先被写入缓存中,之后再某个时间点写回主存。所以某些时间点上,缓存中的数据与主存中的数据可能是不一致。

 c、指令顺序重排

出行性能考虑,编译器在编译时可能会对字节代码的指令顺序进行重新排列,以优化指令的执行顺序,在单线程中不会有问题,但在多线程可能产生与可见性相关的问题。

 

二基本线程类

基本线程类指的是Thread类,Runnable接口,Callable接口
Thread 类实现了Runnable接口,启动一个线程的方法:


a.继承Thread类:

 1 package test;
 2 
 3 import dao.User;
 4 
 5 /**
 6  * Created by guoYunLong on 2017/5/30 0030.
 7  */
 8 
 9    class Thread1 extends Thread{
10         private String name;
11         public Thread1(String name) {
12             this.name=name;
13         }
14         public void run() {
15             for (int i = 0; i < 5; i++) {
16                 System.out.println(name + "运行  :  " + i);
17                 try {
18                     sleep((int) Math.random() * 10);
19                 } catch (InterruptedException e) {
20                     e.printStackTrace();
21                 }
22             }
23 
24         }
25     
26         public static void main(String[] args) {
27             Thread1 mTh1=new Thread1("A");
28             Thread1 mTh2=new Thread1("B");
29             mTh1.start();
30             mTh2.start();
31 
32         }
33 
34 
35 }
说明:
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用MitiSay的两个对象的start方法,另外两个线程也启动了,这样,整个应用就在多线程下运行。
 
注意:start()方法的调用后并不是立即执行多线程代码,而是使得该线程变为可运行态(Runnable),什么时候运行是由操作系统决定的。
从程序运行的结果可以发现,多线程程序是乱序执行。因此,只有乱序执行的代码才有必要设计为多线程。
Thread.sleep()方法调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留出一定时间给其他线程执行的机会。
实际上所有的多线程代码执行顺序都是不确定的,每次执行的结果都是随机的。
但是start方法重复调用的话,会出现java.lang.IllegalThreadStateException异常。

 

b.实现Runnable接口:

 1 package test;
 2 
 3 
 4 /**
 5  * Created by guoYunLong on 2017/5/30 0030.
 6  */
 7 
 8     class Thread2 implements Runnable{
 9         private String name;
10 
11         public Thread2(String name) {
12             this.name=name;
13         }
14 
15         @Override
16         public void run() {
17             for (int i = 0; i < 5; i++) {
18                 System.out.println(name + "运行  :  " + i);
19                 try {
20                     Thread.sleep((int) Math.random() * 10);
21                 } catch (InterruptedException e) {
22                     e.printStackTrace();
23                 }
24             }
25 
26         }
27 
28     
29         public static void main(String[] args) {
30             new Thread(new Thread2("C")).start();
31             new Thread(new Thread2("D")).start();
32         }
33 
34     
35 }
说明:
Thread2类通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个约定。所有的多线程代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。
实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

实现Runnable接口比继承Thread类所具有的优势:

1):适合多个相同的程序代码的线程去处理同一个资源

2):可以避免java中的单继承的限制

3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

 

三线程状态转换

 

1、新建状态(New):新创建了一个线程对象。
2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3、运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
4、阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(一)、等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
(二)、同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。
(三)、其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5、死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
 
Java提供的线程同步方式

1、synchronized关键字

方法或代码块的互斥性来完成实际上的一个原子操作。(方法或代码块在被一个线程调用时,其他线程处于等待状态)

所有的Java对象都有一个与synchronzied关联的监视器对象(monitor),允许线程在该监视器对象上进行加锁和解锁操作。

a、静态方法:Java类对应的Class类的对象所关联的监视器对象。

b、实例方法:当前对象实例所关联的监视器对象。

c、代码块:代码块声明中的对象所关联的监视器对象。

注:当锁被释放,对共享变量的修改会写入主存;当活得锁,CPU缓存中的内容被置为无效。编译器在处理synchronized方法或代码块,不会把其中包含的代码移动到synchronized方法或代码块之外,从而避免了由于代码重排而造成的问题。

例:以下方法getNext()和getNextV2() 都获得了当前实例所关联的监视器对象

 1 package test;
 2 
 3 import dao.User;
 4 
 5 /**
 6  * Created by guoYunLong on 2017/5/30 0030.
 7  */
 8 
 9  class SynchronizedIdGenerator{
10     private int value = 0;
11     public synchronized int getNext(){
12         return value++;
13     }
14     public int getNextV2(){
15         synchronized(this){
16             return value++;
17         }
18     }
19 }  

2、Object类的wait、notify和notifyAll方法

生产者和消费者模式,判断缓冲区是否满来消费,缓冲区是否空来生产的逻辑。如果用while 和 volatile也可以做,不过本质上会让线程处于忙等待,占用CPU时间,对性能造成影响。

wait: 将当前线程放入,该对象的等待池中,线程A调用了B对象的wait()方法,线程A进入B对象的等待池,并且释放B的锁。(这里,线程A必须持有B的锁,所以调用的代码必须在synchronized修饰下,否则直接抛出java.lang.IllegalMonitorStateException异常)。

notify:将该对象中等待池中的线程,随机选取一个放入对象的锁池,当当前线程结束后释放掉锁, 锁池中的线程即可竞争对象的锁来获得执行机会。

notifyAll:将对象中等待池中的线程,全部放入锁池。

(notify锁唤醒的线程选择由虚拟机实现来决定,不能保证一个对象锁关联的等待集合中的线程按照所期望的顺序被唤醒,很可能一个线程被唤醒之后,发现他所要求的条件并没有满足,而重新进入等待池。因为当等待池中包含多个线程时,一般使用notifyAll方法,不过该方法会导致线程在没有必要的情况下被唤醒,之后又马上进入等待池,对性能有影响,不过能保证程序的正确性)

工作流程:

a、Consumer线程A 来 看产品,发现产品为空,调用产品对象的wait(),线程A进入产品对象的等待池并释放产品的锁。

b、Producer线程B获得产品的锁,执行产品的notifyAll(),Consumer线程A从产品的等待池进入锁池,Producer线程B生产产品,然后退出释放锁。

c、Consumer线程A获得产品锁,进入执行,发现有产品,消费产品,然后退出。

 1 package test;
 2 
 3 /**
 4  * Created by guoYunLong on 2017/5/30 0030.
 5  */
 6 public class new3 {
 7     public synchronized String pop(){
 8         // 唤醒对象等待池中的所有线程,可能唤醒的就是 生产者
 9         // (当生产者发现产品满,就会进入对象的等待池,这里代码省略,基本略同)
10         this.notifyAll();
11         //如果发现没产品,就释放锁,进入对象等待池  
12         while(index == -1){
13             this.wait();
14         }
15         //当生产者生产完后,消费者从this.wait()方法再开始执行,
16         // 第一次还会执行循环,万一产品还是为空,则再等待,所以这里必须用while循环,不能用if  
17         String good = buffer[index];
18         buffer[index] = null;
19         index--;
20         return good;// 消费完产品,退出。  
21     }
22 }

注:wait()方法有超时和不超时之分,超时的在经过一段时间,线程还在对象的等待池中,那么线程也会推出等待状态。

3、线程状态转换:

已经废弃的方法:stop、suspend、resume、destroy,这些方法在实现上时不安全的。

线程的状态:NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING(有超时的等待)、TERMINATED。

a、方法sleep()进入的阻塞状态,不会释放对象的锁(即大家一起睡,谁也别想执行代码),所以不要让sleep方法处在synchronized方法或代码块中,否则造成其他等待获取锁的线程长时间处于等待。

b、方法join()则是主线程等待子线程完成,再往下执行。

c、方法interrupt(),向被调用的对象线程发起中断请求。如线程A通过调用线程B的d的interrupt方法来发出中断请求,线程B来处理这个请求,当然也可以忽略,这不是必须的。Object类的wait()、Thread类的join()和sleep方法都会抛出受检异常java.lang.InterruptedException,通过interrupt方法中断该线程会导致线程离开等待状态。对于wait()调用来说,线程需要重新获取监视器对象上的锁之后才能抛出InterruptedException异常,并致以异常的处理逻辑。

可以通过Thread类的isInterrupted方法来判断是否有中断请求发生,通常可以利用这个方法来判断是否退出线程(类似上面的volatitle修饰符的例子);

Thread类还有个方法Interrupted(),该方法不但可以判断当前线程是否被中断,还会清楚线程内部的中断标记,如果返回true,即曾被请求中断,同时调用完后,清除中断标记。

如果一个线程在某个对象的等待池,那么notify和interrupt 都可以使该线程从等待池中被移除。如果同时发生,那么看实际发生顺序。如果是notify先,那照常唤醒,没影响。如果是interrupt先,并且虚拟机选择让该线程中断,那么即使nofity,也会忽略该线程,而唤醒等待池中的另一个线程。

e、yield(),尝试让出所占有的CPU资源,让其他线程获取运行机会,对操作系统上的调度器来说是一个信号,不一定立即切换线程。(在实际开发中,测试阶段频繁调用yeid方法使线程切换更频繁,从而让一些多线程相关的错误更容易暴露出来)。

 

高级同步对象(提高开发效率)

1、信号量。

 

信号量一般用来数量有限的资源,每类资源有一个对象的信号量,信号量的值表示资源的可用数量。

 

在使用资源时,需要从该信号量上获取许可,成功获取许可,资源的可用数-1;完成对资源的使用,释放许可,资源可用数+1; 当资源数为0时,需要获取资源的线程以阻塞的方式来等待资源,或过段时间之后再来检查资源是否可用。(上面的SimpleResourceManager类实际上时信号量的一个简单实现)

 

java.util.concurrent.Semaphore类,在创建Semaphore类的对象时指定资源的可用数

 

a、acquire(),以阻塞方式获取许可

 

b、tryAcquire(),以非阻塞方式获取许可

 

c、release(),释放许可。

 

d、accquireUninterruptibly(),accquire()方法获取许可以的过程可以被中断,如果不希望被中断,使用此方法。

 1 package test;
 2 
 3 import com.sun.org.apache.xml.internal.serialize.Printer;
 4 
 5 import java.util.Collection;
 6 import java.util.List;
 7 import java.util.concurrent.Semaphore;
 8 
 9 /**
10  * Created by guoYunLong on 2017/5/30 0030.
11  */
12 public class PrinterManager {
13     private final Semphore semaphore;
14     private final List<Printer> printers = new ArrayList<>():
15     public PrinterManager(Collection<? extends Printer> printers){
16         this.printers.addAll(printers);
17         //这里重载方法,第二个参数为true,以公平竞争模式,防止线程饥饿  
18         this.semaphore = new Semaphore(this.printers.size(),true);
19     }
20     public Printer acquirePrinter() throws InterruptedException{
21         semaphore.acquire();
22         return getAvailablePrinter();
23     }
24     public void releasePrinter(Printer printer){
25         putBackPrinter(pinter);
26         semaphore.release();
27     }
28     private synchronized Printer getAvailablePrinter(){
29         printer result = printers.get(0);
30         printers.remove(0);
31         return result;
32     }
33     private synchronized void putBackPrinter(Printer printer){
34         printers.add(printer);
35     }
36 }

2、倒数闸门

多线程协作时,一个线程等待另外的线程完成任务才能继续进行。

java.util.concurrent.CountDownLatch类,创建该类时,指定等待完成的任务数;当一个任务完成,调用countDonw(),任务数-1。等待任务完成的线程通过await(),进入阻塞状态,直到任务数量为0。CountDownLatch类为一次性,一旦任务数为0,再调用await()不再阻塞当前线程,直接返回。

 1 package test;
 2 
 3 import org.apache.commons.io.IOUtils;
 4 
 5 import java.io.IOException;
 6 import java.io.InputStream;
 7 import java.net.URL;
 8 import java.util.Collections;
 9 import java.util.Map;
10 import java.util.concurrent.CountDownLatch;
11 
12 /**
13  * Created by guoYunLong on 2017/5/30 0030.
14  */
15 public class PageSizeSorter {
16     // 并发性能远远优于HashTable的 Map实现,hashTable做任何操作都需要获得锁,同一时间只有有个线程能使用,而ConcurrentHashMap是分段加锁,不同线程访问不同的数据段,完全不受影响,忘记HashTable吧。  
17     private static final ConcurrentHashMap<String , Interger> sizeMap = new ConcurrentHashMap<>();
18     private static class GetSizeWorker implements Runnable{
19         private final String urlString;
20         public GetSizeWorker(String urlString , CountDownLatch signal){
21             this.urlString = urlStirng;
22             this.signal = signal;
23         }
24         public void run(){
25             try{
26                 InputStream is = new URL(urlString).openStream();
27                 int size = IOUtils.toByteArray(is).length;
28                 sizeMap.put(urlString, size);
29             }catch(IOException e){
30                 sizeMap.put(urlString, -1);
31             }finally{
32                 signal.countDown()://完成一个任务 , 任务数-1  
33             }
34         }
35     }
36     private void sort(){
37         List<Map.Entry<String, Integer> list = new ArrayList<sizeMap.entrySet());
38         Collections.slort(list, new Comparator<Map.Entry<String, Integer>>() {
39             public int compare(Entry<String, Integer> o1, Entry<Sting, Integer> o2) {
40                 return Integer.compare(o2.getValue(), o1.getValue());
41             }
42 
43             ;
44             System.out.println(Arrays.deepToString(list.toArray()));
45         }
46 
47     public void sortPageSize(Collection<String> urls) throws InterruptedException{
48         CountDownLatch sortSignal = new CountDownLatch(urls.size());
49         for(String url: urls){
50             new Thread(new GetSizeWorker(url, sortSignal)).start();
51         }
52         sortSignal.await()://主线程在这里等待,任务数归0,则继续执行  
53         sort();
54     }
55 }

3、循环屏障

循环屏障在作用上类似倒数闸门,不过他不像倒数闸门是一次性的,可以循环使用。另外,线程之间是互相平等的,彼此都需要等待对方完成,当一个线程完成自己的任务之后,等待其他线程完成。当所有线程都完成任务之后,所有线程才可以继续运行。

当线程之间需要再次进行互相等待时,可以复用同一个循环屏障。

类java.uti.concurrent.CyclicBarrier用来表示循环屏障,创建时指定使用该对象的线程数目,还可以指定一个Runnable接口的对象作为每次循环后执行的动作。(当最后一个线程完成任务之后,所有线程继续执行之前,被执行。如果线程之间需要更新一些共享的内部状态,可以利用这个Runnalbe接口的对象来处理)。

每个线程任务完成之后,通过调用await方法进行等待,当所有线程都调用await方法之后,处于等待状态的线程都可以继续执行。在所有线程中,只要有一个在等待中被中断,超时或是其他错误,整个循环屏障会失败,所有等待中的其他线程抛出java.uti.concurrent.BrokenBarrierException。

例:每个线程负责找一个数字区间的质数,当所有线程完成后,如果质数数目不够,继续扩大范围查找

 1 package test;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 import java.util.concurrent.CyclicBarrier;
 6 
 7 /**
 8  * Created by guoYunLong on 2017/5/30 0030.
 9  */
10 public class PrimeNumber {
11     private static final int TOTAL_COUTN = 5000;
12     private static final int RANGE_LENGTH= 200;
13     private static final int WORKER_NUMBER = 5;
14     private static volatitle boolean done = false;
15     private static int rangeCount = 0;
16     private static final List<Long> results = new ArrayList<Long>():
17     private static final CyclicBarrier barrier = new CyclicBarrier(WORKER_NUMBER, new Runnable(){
18         public void run(){
19             if(results.size() >= TOTAL_COUNT){
20                 done = true;
21             }
22         }
23     });
24     private static class PrimeFinder implements Runnable{
25         public void run(){
26             while(!done){// 整个过程在一个 while循环下,await()等待,下次循环开始,会再次判断 执行条件  
27                 int range = getNextRange();
28                 long start = rang * RANGE_LENGTH;
29                 long end = (range + 1) * RANGE_LENGTH;
30                 for(long i = start; i<end;i++){
31                     if(isPrime(i)){
32                         updateResult(i);
33                     }
34                 }
35                 try{
36                     barrier.await();
37                 }catch (InterruptedException | BokenBarrierException e){
38                     done =  true;
39                 }
40             }
41         }
42     }
43     private synchronized static void updateResult(long value){
44         results.add(value);
45     }
46     private synchronized static int getNextRange(){
47         return rangeCount++;
48     }
49     private static boolean isPrime(long number){
50         //找质数的代码  
51     }
52     public void calculate(){
53         for(int i=0;i<WORKER_NUMBER;i++){
54             new Thread(new PrimeFinder()).start();
55         }
56         while(!done){
57 
58         }
59         //计算完成  
60     }
61 
62 }

4、对象交换器
适合于两个线程需要进行数据交换的场景。(一个线程完成后,把结果交给另一个线程继续处理)

 

java.util.concurrent.Exchanger类,提供了这种对象交换能力,两个线程共享一个Exchanger类的对象,一个线程完成对数据的处理之后,调用Exchanger类的exchange()方法把处理之后的数据作为参数发送给另外一个线程。而exchange方法的返回结果是另外一个线程锁提供的相同类型的对象。如果另外一个线程未完成对数据的处理,那么exchange()会使当前线程进入等待状态,直到另外一个线程也调用了exchange方法来进行数据交换。

 1 package test;
 2 
 3 import java.util.concurrent.Exchanger;
 4 
 5 /**
 6  * Created by guoYunLong on 2017/5/30 0030.
 7  */
 8 public class new3 {
 9     private final Exchanger<StringBuilder> exchanger = new Exchanger<StringBuilder>();
10     private class Sender implements Runnable{
11         public void run(){
12             try{
13                 StringBuilder content = new StringBuilder("Hello");
14                 content = exchanger.exchange(content);
15             }catch(InterruptedException e){
16                 Thread.currentThread().interrupt();
17             }
18         }
19     }
20     private class Receiver implements Runnable{
21         public void run(){
22             try{
23                 StringBuilder content = new StringBuilder("World");
24                 content = exchanger.exchange(content);
25             }catch(InterruptedException e){
26                 Thread.currentThread().interrupt();
27             }
28         }
29     }
30     public void exchange(){
31         new Thread(new Sender()).start();
32         new Thread(new Receiver()).start();
33     }
34 }

 

六高级多线程控制类

1.ThreadLocal类

java.lang.ThreadLocal,线程局部变量,把一个共享变量变为一个线程的私有对象。不同线程访问一个ThreadLocal类的对象时,锁访问和修改的事每个线程变量各自独立的对象。通过ThreadLocal可以快速把一个非线程安全的对象转换成线程安全的对象。(同时也就不能达到数据传递的作用了)。

a、get()和set()分别用来获取和设置当前线程中包含的对象的值。

b、remove(),删除。

c、initialValue(),初始化值。如果没有通过set方法设置值,第一个调用get,会通过initValue来获取对象的初始值。

ThreadLoacl的一般用法,创建一个ThreadLocal的匿名子类并覆盖initalValue(),把ThreadLoacl的使用封装在另一个类中

 1 package test;
 2 
 3 
 4 /**
 5  * Created by guoYunLong on 2017/5/30 0030.
 6  */
 7 public  class new1 {
 8     private static final ThreadLocal<IdGenerator> idGenerator = new ThreadLocal<IdGenerator>() {
 9         protected IdGenerator initalValue() {
10             return new IdGenerator();//IdGenerator 是个初始int value =0,然后getNext(){  return value++}  
11         }
12     };
13 
14     public static int getNext() {
15         return idGenerator.get().getNext();
16     }
17 
18 
19     public static void main(String[] args) {
20         new Thread(new Thread2("C")).start();
21         new Thread(new Thread2("D")).start();
22     }
23 
24 }

ThreadLoal的另外一个作用是创建线程唯一的对象,在有些情况,一个对象在代码中各个部分都需要用到,传统做法是把这个对象作为参数在代码间传递,如果使用这个对I昂的代码都在同一个线程,可以封装在ThreadLocal中。如:在多线程中,生成随机数java.util.Random会带来竞争问题,java.util.concurrent.ThreadLocalRandom类提供多线程下的随机数声场,底层是ThreadLoacl。

2.原子类(AtomicInteger、AtomicBoolean……)

如果使用atomic wrapper class如atomicInteger,或者使用自己保证原子的操作,则等同于synchronized

1 //返回值为boolean
2     AtomicInteger.compareAndSet(int expect,int update)

AtomicReference
对于AtomicReference 来讲,也许对象会出现,属性丢失的情况,即oldObject == current,但是oldObject.getPropertyA != current.getPropertyA。
这时候,AtomicStampedReference就派上用场了。这也是一个很常用的思路,即加上版本号

3.Lock类 

lock: 在java.util.concurrent包内。共有三个实现:

1   ReentrantLock
2     ReentrantReadWriteLock.ReadLock
3     ReentrantReadWriteLock.WriteLock

主要目的是和synchronized一样, 两者都是为了解决同步问题,处理资源争端而产生的技术。功能类似但有一些区别。

区别如下:lock更灵活,可以自由定义多把锁的枷锁解锁顺序(synchronized要按照先加的后解顺序)提供多种加锁方案,lock 阻塞式, trylock 无阻塞式,

lockInterruptily 可打断式, 还有trylock的带超时时间版本。本质上和监视器锁(即synchronized是一样的)能力越大,责任越大,必须控制好加锁和解锁,

否则会导致灾难。和Condition类的结合。性能更高,对比如下图:

synchronized和Lock性能对比

 

七最后总结

总结:因为时间匆忙,纯属学习,自己花的时间不是很多,感觉来说可以这么考虑:多线程开发中应该优先使用高层API,如果无法满足,使用java.util.concurrent.atomic和java.util.concurrent.locks包提供的中层API,而synchronized和volatile,以及wait,notify和notifyAll等低层API 应该最后考虑。

 

 

posted @ 2017-05-30 20:10  guoyunlong  阅读(242)  评论(0)    收藏  举报