Java中的多线程

**1.读书笔记:Android开发进阶,从小工到专家,ThinkinJava
2.参考blog
3.http://www.cnblogs.com/dolphin0520/p/3932921.html 多线程系列
4.http://mp.weixin.qq.com/s/DbpGfRwBQHjImoDZKBCqiQ 常见多线程的问题
5.http://www.cnblogs.com/xingele0917/p/4317577.html 一套系列的文章
**

一、Thread中的方法

1.join
在一个线程中调用另一个线程的join方法,表示让另一个线程的任务加入进来,当前线程让出cpu(即当前的线程停止工作)
例如:在Thread2中持有了Thread1对象,调用thrad1的join方法,表示在Thread2执行时,thread1要加入进来。这时会执行thread1的任务,thread2会等待thread1执行完再执行。

class Thread1 extends Thread  
{  
    public Thread1(String threadName)  
    {     
        super(threadName);  
    }  
      
    public void run()  
    {  
        System.out.println(getName() + "is running");  
        try  
        {  
            sleep(2000);  
        }   
        catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        }  
    }  
}  
class Thread2 extends Thread  
{  
    private Thread1 thread1;  
      
    public Thread2(String threadName, Thread1 thread1)  
    {  
        super(threadName);  
        this.thread1 = thread1;       
    }  
      
    public void run()  
    {  
        System.out.println(getName() +  "is running");  
        try  
        {  
            thread1.start();  
            thread1.join();  
        }   
        catch (InterruptedException e)  
        {  
            e.printStackTrace();  
        }  
        System.out.println("thread2 is over");    
    }  
}  

2.yeild让出cpu
让出cpu让其他的theard执行

Thread.yeild();

3.线程优先级
设定线程的优先级,cpu会考虑优先让哪一个优先级高的线程先执行,但不是必然的

setPriority();

**4.捕获线程的异常
因为线程的特性,使用try...catch不能捕获从线程中抛出的异常,只能捕获线程中任务的异常
例如:下面的try--catch是没有意义的。

try{
     new Thread(new Runnable(){
         run(){
           。。。。。
          }
     })
  }catch(RuntimeExeception e){
       //
  }

需要给该线程的runnable添加uncaughtExeception(),这样所有没有捕获的异常,都能通过该类去处理。

Thread t=new Thread(new Runnable(
    run(){
       //任务
    }
  ));
t.setUncaughtExeceptionHandler(new MyUncaughtExeception());

class MyUncaughtExeceptionHandler implements Thread.UncaughtExceptionHandler{
    public void uncaughtException(Thread t,Throwable e){
          Log.d("aa",e.toString());
   }
}

一、FutureTask

1.FutrueTask是一种更好的线程方法,它提供了:是否已经取消,是否已经完成,获取结果的方法。它同样实现了Runnable方法,所以它同样可以交给线程池来管理。它实现了Runnable,Future接口,同时包装了Callable.

//线程池管理
private final ExecutorService mExecutors =Executors.newFixedThreadPool(1); 
Future mFuture=mExecutors.submit(new Callable(){   
 @Override    
public Integer call() throws Exception { 
       return 1;   
 }
}
));
mFuture.isCancelled();//是否已经取消
mFuture.isDone();//是否已经完成
mFuture.get();//拿到结果

给线程池提交FutrueTask对象

FutureTask mTask=new FutureTask(new Callable{
           public Integer call(){
            return 1;
         }
  }
);
mExecutor.submit(mTask);

二、线程池:

1.负责管理线程任务。当我们需要频繁的创建多个线程进行耗时操作的时候,每次新建new Thread()和销毁Thread在性能上来说很差,线程缺乏统一的管理,可能无线的创建线程,相互抢占资源。所以引入线程池,它的好处有:

1.重用存在的线程,减少对象的创建
2.可有效的控制最大的并发线程数,提高资源的使用率
3.提供定时,定期执行,单线程,并发数控制等功能。
4.通过ThreadFactory定制统一的线程。

2.线程池负内部创建了线程,我们将任务交给线程池,线程池再将指定的任务交给某一个线程去执行。
3.JDK提供了一个创建线程池的工厂方法---Executors来简化创建的过程。
**
多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。但如果对多线程应用不当,会增加对单个任务的处理时间。可以举一个简单的例子:假设在一台服务器完成一项任务的时间为T **

T1 创建线程的时间
T2 在线程中执行任务的时间,包括线程间同步所需时间
T3 线程销毁的时间

显然T = T1+T2+T3。注意这是一个极度简化的假设。
可以看出T1,T3是多线程本身的带来的开销(在Java中,通过映射pThead,并进一步通过SystemCall实现native线程),我们渴望减少T1,T3所用的时间,从而减少T的时间。但一些线程的使用者并没有注意到这一点,所以在程序中频繁的创建或销毁线程,这导致T1和T3在T中占有相当比例。显然这是突出了线程的弱点(T1,T3),而不是优点(并发性)。
线程池技术正是关注如何缩短或调整T1,T3时间的技术,从而提高服务器程序性能的。

1.通过对线程进行缓存,减少了创建销毁的时间损失
2.通过控制线程数量阀值,减少了当线程过少时带来的CPU闲置(比如说
长时间卡在I/O上了)与线程过多时对JVM的内存与线程切换时系统调用的压力

文/BlackSwift(作者)
原文链接:http://www.jianshu.com/p/6637369d02e7

一、启动指定数量的线程池:-----newFixedThreadPool

ThreadPoolExecutor 线程池实际的实现。
1.corePoolSize:线程池中所保存的核心线程数。
2.maximumPoolSize:线程池允许创建的最大线程数。
当新的任务来临时,如果当前线程池中线程数 小于corePoolSize数,则创建新的线程。如果当前线程池中线程数量大于corePoolSize小于maximumPoolSize,则当阻塞队列满时,才创建新的线程,如果maximumPoolSize=corePoolSize。创建了固定大小的线程池。如果设置成Integer.MAX_VALUE,则代表创建了一个无界限的线程池。
3.keepAliveTime 当前线程池中的线程大于核心线程时,终止多余的空闲线程的时间
4.Unit keepAliveTime的执行时间单位。
5.workQueue:任务队列,如果当前线程池达到核心线程数corePoolSize,且当所有线程都处于活跃的状态时,将新的任务放到次队列中。
6.threadFactory:让用户可以定制线程的创建过程。
7.Handler:拒绝策略 当线程池和workQueue都满时,对新的任务采取的处理策略。

二、定时执行任务的线程池:ScheduledThreadPoolExecutor

ScheduledExecutorService executorService=Executors.newScheduledThreadPool(3);

executorServcie.scheduleAtFixedRate(new Runnable(),延时时间,执行周期,TimeUnit.SECONDS)//最后一个参数为 时间单位。

三、Java提供了线程池的工厂方法,用来管理线程池--------Executors。

1.newFixedThreadPool(线程数):创建固定的线程数。内部return了ThreadPoolExecutor,将corePoolSize和maximumPoolSize传入相同的数值。

2.newCachedThreadPool().当有新的任务来临后,立马开启新的任务来执行。

3.newScheduledThreadPool同样对ScheduledThreadPoolExecutor进行了封装。

ScheduledExecutorService mExecutor=Executors.newScheduledThreadPool(线程数)
mExecutor.scheduleAtFixedRate(new Runable(){},延时的时间,执行周期,时间单位);

四.ThreadFactory定制线程的创建

ThreadFactory可以指定创建什么样的线程
****1.在okhttp中床ijande每一个线程都是前台线程。****


Java中的多线程_第1张图片
xian'cheng
Java中的多线程_第2张图片
Paste_Image.png

****2.创建能够捕获异常的线程****

class HandleExceptionThreadFactory implements ThreadFactory{
  public Thread  newThread(Runnable r){
     Thread t=new Thread(r);
     t.setUncaughtExceptionHandler(new MyUncaughtException());
    return t;
  }
}
ExcutorService excutor=Excutors.newCachedThreadPool(new HandleExceptionThreadFactory());
excutor.execute(new Thread())

注意:Java提供默认的创建线程池的方法在性能上有很多损耗,甚至会oom,推荐自己创建线程池。

四、同步锁

为防止多个线程操作一个对象,java提供了synchronized关键字来控制共享资源。
使用synchronized关键词的场景

如果一个变量有可能被多个线程访问(进行读写操作),那么访问该参数的方法应该被synchronized关键词修饰。同时把参数设置成private,否则synchronized关键词不能防止其他的任务访问该参数

同时将要访问该资源的 操作封装成一个方法,并用synchronized关键词修饰。当某一个线程调用被synchronized方法修饰的方法去修改某一个变量时,首先检查锁是否可用。这样的解决思路不是站在被调用者的角度。而是站在调用者的角度去解决共享资源竞争的问题------当多个调用者通过一个方法去争抢资源时,谁先拿到方法的使用权,谁才可以去修改资源。下个调用者只能等待上一个人释放锁。

public class Foo {    
 private int x = 100; //要修改的元素
 public int getX() {
     return x;    
 }  
 public (synchronized) int fix(int y) {       
  x = x - y;      
  return x;  
  }
}

public class MyRunnable implements Runnable {  
private Foo foo = new Foo();   
  public static void main(String[] args) {    
  MyRunnable r = new MyRunnable();       
  Thread ta = new Thread(r, "Thread-A");      
  Thread tb = new Thread(r, "Thread-B"
);         
     ta.start();         
     tb.start();   
  }  
 public void run() {     
  for(int i = 0; i < 3; i++) {   
         this.fix(30);           
  try {               
       Thread.sleep(1);        } 
  catch (InterruptedException e) {               
       e.printStackTrace();         
  }          
        System.out.println(Thread.currentThread().getName() +
       " : 当前foo对象的x值   = " + foo.getX());         }   
  } 
 public int fix(int y) {     
      return foo.fix(y); 
    }
 }

上述代码有两个线程在不断的执行foo的fix(y),有可能造成1线程已经修改了x,而2线程再去调用时,还是没有修改过得x。
解决上面问题的办法时,加入同步锁,及每次只能有一个线程操作该方法。

1.Java中每个对象都有一个内置锁,可以使用synchronized同步那些
修改变量的代码,使用synchronized关键字同步方法或代码。
2.如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象的)类中的任何一个同步方法。
(一个线程访问被锁住的方法时会获得它的锁,其他的线程必须等待它完成)
3.线程睡眠时,它所持的任何锁都不会释放
4. 尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时, 
其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
5.当一个线程访问object的一个synchronized(this)同步代码块时,
 另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
6.每一个类有一个锁,该类的实例同样有自己的锁

当synchronized作用于方法时,实际上锁的也是对象,锁定的对象是该函数所在的类的对象。当作用于class对象,则是锁定这个class类

两种对象锁
1.synchornized void testMethod(){
   
   }
2.void  testMethod(So so){
/**锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明
确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:例如  使用byte节省开销
private byte[] lock = new byte[0];  代码块改为synchornized(lock)**/
   synchornized (so){
  
   }
}
两种类锁
1.void testClass(){
   synchornized (testClass.class){

   }
 }
2.同步类
public synchronized static void methodAAA()   
// 同步的static 函数     {         //….     }
public void methodBBB()     {
       synchronized(Foo.class)   
    } 

打断线程的操作

1.通过Thread.interrupt和线程池的shutdownNow.

1.使用上述无法中断正在尝试获取线程锁以及试图执行IO操作的线程。
2.解决上述问题的方式:通过关闭该任务在其底层上的资源。System.in.close()

五、使用ReentrantLock和Condiition(显示锁和条件)

通常在使用synchronized不能解决问题,以及要在一定时间内持有锁时,才需要使用该方法对程序加锁。
一定要在finally中释放锁,否则会出现死锁
1.lock 获取锁
2.trylock 尝试获取锁
3.tryLock(1000,TimeUnit.Second)在指定时间内获取锁
4.unlock 释放锁
5.newCondition 获得条件

Lock lock=new ReentrantLock();

Condition emptyCondition=lock.newCondition();
Condition fullCondition=lock.newCondition();
 public void putData(){
  lock.lock();
   try{
       //任务
        while(count==10){
           //当满足该条件时  让该线程挂起
           fullCondition.await();
       }
        emptyCondition.signalAll();唤醒所有被数据为空条件挂起的线程
   }finally{
      lock.unluck();
   }
}


 public void getData(){
  lock.lock();
   try{
       //任务
        while(count==0){
           //当数据为空时,满足该条件
           emptyCondition.await();
       }
        fullCondition.signalAll();唤醒所有被数据已满条件挂起的线程
   }finally{
      lock.unluck();
   }
}

六、原子性可视性Volatile

事物要么完整的执行完毕,要么不执行的情况称之为原子性。volatile修饰的参数会被写入到主存中,而读取的操作也存放在主要存中,因此可以保证了参数的可见性。

如果一个参数可能被多个方法同时修改时,或者这些方法有一个是写入的操作,那么这个参数就应该被volatile修饰。

1.注意:原子性可以用于除long和double之外所有的基本类型上
2.可视性:一个参数如果被volatile修饰,一旦它被修改,会立刻写入到主存中,那么所有读取的操作都能发现这个修改

同步代码块

1.当我们不需要对整个方法进行同步控制,只需要对方法中的某一个部分进行同步时。可以使用同步代码块的方法。同样使用synchronized关键词。锁住某一个对象,该对象用来控制代码的同步。

Object object=new Object();
synchronized(object){

}

2.通常最合理的做法是使用synchronized(this),使用正在调用这个方法的对象锁。

七、访问一个对象中的两个加锁任务。

通常情况下,如果访问了一个被加锁的对象,其他该类中的加锁方法都要等待改该方法释放锁。但是如果要访问的方法获取的锁不是同一个,那么可以同时访问两个不同锁的方法。因为这两个同步快是相互独立的。

class Test{
  Object object=new Object();
  int i=0;  
   public synchronized void t1(){
    i++;
   }
   public void t2(){
      synchronized(object){
          print(“jhj”);
      }
   }
}
八、ThreadLocal
http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83]%E8%A7%A3%E5%AF%86ThreadLocal/   参考资料

解决多个线程访问同一个变量的问题,提供线程内部的局部变量(即使使用ThreadLocal创建的是一个全局变量),在本线程内随时随地可取,隔离其他线程。
在线程内使用ThreadLocal对象获取一个值时,ThreadLocal会为该线程提供一个局部变量。因为ThreadLocal内部维护了一个Map,这个Map的键是线程对象。针对每一个线程提供了一个值。修改的也只是Map集合中对应的线程键的值。
对于参考资料的解析:
1.创建了一个全局的ThreadLocal兑现value,相当于创建了一个Integer类型的全局变量。
2.循环开启5个线程,每个线程都对value做累加的操作,查看结果。
总结
结果并没有因为其他线程对value做了累加的操作,而影响的当前线程的value值。

九、中断阻塞的线程

使用yeild和sleep阻塞线程,不会释放锁。wait方法会释放锁。

1.使用Thread的interrupt方法。中断线程的操作
2.使用ExcutorServices的executor执行线程,那么可以用shtdownNow().方法停止所有线程。
3.使用ExcutorServices的submit执行线程,那么可以拿到一个Future对象。可以使用它的cancel方法终于当前的这个线程。

如果线程中执行的是io操作或者是synchronized操作。那么使用上述方法是无法中断的。

通过关闭底层资源。切断阻塞的任务

executor.shutdownNow();
input.close();
System.in.close();//关闭底层资源
十、错失信号问题

使用wait,和notify,notifyAll进行协作。可能会错失了信号
1.下面的情况是T1,T2分别是两个任务,分别在两个线程中,T1唤醒T2。但有一种可能是T2在执行到while循环中执行了T1,改变了条件:object在唤醒状态时调用了T1,而后执行了T2。导致object在后面一直处于等待状态。

T1:
boolean someCondition=false;
synchronized(object){
  //改变条件   someCondition
  object.notify();
}
T2:
while(someCondition){
   //执行T1. 
    synchronized(object){
     object.wait();
  }
}

2.这是因为T2采用的是对象锁,锁住部分代码块,但T2锁住的是条件内部的代码,而没有将条件一并锁住。所以可能在T2执行的时候,T1获得对象锁插入进来。解决方法是锁住T2的整个代码块,这样当T2执行时,先获得锁, 同样需要Object锁的T1必须等待T2释放锁才能执行

T1:
boolean someCondition=false;
synchronized(object){
  //改变条件   someCondition
  object.notify();
}
T2:
 synchronized(object){
   while(someCondition){
   //执行T1. 
     object.wait();
  }
}

十一、阻塞队列。

阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。

使用阻塞队列可以避免显示的使用同步方法(synchronized和lock等),因为所有的同步工作都由队列和系统的设计隐式的管理了。

十二、同步构建
1.CountDownLatch

当我们需要执行的任务需要等待另一个任务执行完毕才能执行时,可以使用这个CountDownLatch对象实现:提供CountDownLatch一个计数,使用await方法让当前任务等待,使用countDown减小计数,直到countdownlatch计数为0时,恢复当前任务

注意 await方法会阻塞后续代码的执行,一定要确保其他任务已经开始执行再调用await。它强调的是一个线程需要等待其他线程任务完成后再进行。

例子:主线程等待其他两个线程完成以后继续工作--->执行打印

/**
*  work1 和 work2 是两个一样的线程。代表任务的进行。完成一个任务  countdown会通过调用countdown减少计数。
**/
public class Work1 implements Runnable{

    CountDownLatch countDownLatch;
    public Work1(CountDownLatch countDownLatch){
        this.countDownLatch=countDownLatch;
    }
    @Override
    public void run() {
        dowork();
    }
    public void dowork(){
        System.out.println("work 1 执行完毕");
        countDownLatch.countDown();
    }
}
//当计数减少到0 的时候  await后面的代码会执行
  public static void main(String[] args) throws Exception{
        CountDownLatch countDownLatch=new CountDownLatch(2);
        ExecutorService mExecutor=Executors.newCachedThreadPool();
        //countDownLatch.await();  不可以写在这里会阻塞后续代码的执行
        mExecutor.execute(new Work1(countDownLatch));
        mExecutor.execute(new Work2(countDownLatch));
        countDownLatch.await();
        mExecutor.shutdownNow();
        System.out.println("work1 和  work2 已经执行完毕");
    }

2.循环栅栏CyclicBarrier
blog介绍

一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。它强调的是一个线程等待其他线程加入时,一起执行。它是可以服用。

例如:指定CyclicBarrier条件为3,及有三个线程加入时,await后续的代码才会进行。


Java中的多线程_第3张图片
Paste_Image.png
Java中的多线程_第4张图片
Paste_Image.png

3.信号量Semaphore
Semaphore是一个计数信号量,它的内部维护了一个信号量集合,每当有任务需要执行时,需要查询集合中是否有“信号”,拿到信号才可以执行后续的任务。否则要等待前边的任务释放信号。

Java中的多线程_第5张图片
Paste_Image.png

4.定时任务
ScheduledThreadPoolExecutor是一个延时处理任务(执行线程的)线程池。使用schedule()延时执行,使用scheduleAtFixedRate()每隔一段时间重复执行。

5.ExChanger安全数据交换
用来在成对的任务中交换数据。比如线程1和线程2中的数据进行互换。 此时就可以用ExChanger.
1.ExChangerProducer负责生成1,2,3三个数值,没生成一个数据就和ExChangerConsumer换数据,当一个任务调用exchange方法时会阻塞,直到下一个任务调用exchange方法被调用完成交换时,任务继续。

Java中的多线程_第6张图片
Paste_Image.png

注意:每次ExChangerConsumer线程的run方法都会将data变成0

Java中的多线程_第7张图片
Paste_Image.png
Java中的多线程_第8张图片
Paste_Image.png

你可能感兴趣的:(Java中的多线程)