**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每一个线程都是前台线程。****
****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后续的代码才会进行。
3.信号量Semaphore
Semaphore是一个计数信号量,它的内部维护了一个信号量集合,每当有任务需要执行时,需要查询集合中是否有“信号”,拿到信号才可以执行后续的任务。否则要等待前边的任务释放信号。
4.定时任务
ScheduledThreadPoolExecutor是一个延时处理任务(执行线程的)线程池。使用schedule()延时执行,使用scheduleAtFixedRate()每隔一段时间重复执行。
5.ExChanger安全数据交换
用来在成对的任务中交换数据。比如线程1和线程2中的数据进行互换。 此时就可以用ExChanger.
1.ExChangerProducer负责生成1,2,3三个数值,没生成一个数据就和ExChangerConsumer换数据,当一个任务调用exchange方法时会阻塞,直到下一个任务调用exchange方法被调用完成交换时,任务继续。
注意:每次ExChangerConsumer线程的run方法都会将data变成0