java 多线程 android线程池

来源:互联网 发布:office2016 for mac 编辑:程序博客网 时间:2024/05/16 18:14

多线程的创建

thread / Runnable

可以继承Thread类,或实现Runnable接口

     //继承Thread    MyThread myThread = new MyThread();    myThread.start();    //实现Runnable接口    MyRunnable myRunnable = new MyRunnable();    Thread thread = new Thread(myRunnable);    thread.start();

在调用start()方法后,只是变成就绪状态,而不是变成执行状态。线程的生命周期可以参考线程的生命周期和状态控制
Alt text

两种方式的区别
  1. java是单继承模式,如果使用继承Thread的方法,就不能再继承其他类了,如果使用实现Runnable接口的方式,就比较灵活。使用实现接口的方式能避免java但继承的局限性。
  2. 使用继承的方式,run方法内部的数据是独立的,而在实现接口的方式中,可以共用同一个runnable,不用的线程可以共享同一个数据。
start / run

start()只是让线程进入就绪状态,此时的线程需要等到线程获取到cpu的资源。
如果单纯的直接调用run(),只会想一个普通方法被调用一样,而不会具有多线程的特性,程序按顺序执行。

线程间通信

synchronized关键字

synchronized对象锁
为何要使用同步?

java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),
将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。

1.同步方法

即有synchronized关键字修饰的方法。
由于java的每个对象都有一个内置锁(一个对象实例对应一把锁),当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

代码如:
public synchronized void save(){}

2.同步代码块

即有synchronized关键字修饰的语句块。
被该关键字修饰的语句块会自动被加上内置锁,从而实现同步

代码如:

synchronized(this){}

注:**同步是一种高开销的操作,因此应该尽量减少同步的内容。
通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。**

synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问

对synchronized(this)的一些理解
1. 当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
2. 然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
3. 尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
4. 第三个例子同样适用其它同步代码块。也就是说,当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。
5. 以上规则对其它对象锁同样

synchronized来实现线程间通信

通过不同的线程去共享同一个对象实例,来进行锁的管理。

synchronized / volatile

线程有自己独立的内存,当一个变量在非主线程发生改变之后,有可能还并没有同步到其他线程,其他线程还没有对这个变量对应的内存做修改,而导致在其他线程获取到的值是一个没来得及同步的值。使用了volatile关键字后,可以让程序直接使用主内存的值,在其他线程修改了改变量之后,程序会通知所有线程更新该变量,确保使用的是同一个值。因此,volatile关键字的作用就是,让使用这个关键字的变量,在所有的线程中同步获得的数据。

volatile 与 synchronized的区别
volatile只能在主线程内存和其他线程内存之间同步一个变量值,而synchronized能在主线程和其他线程之间同步所有其间变量,并且能同步变量,方法,代码块,包括静态成员及方法。

synchronized / lock

lock方法要指定lock开启和关闭的位置,一般还会在finally中使用unlock() (乐观锁机制,假设没有冲突,就去完成某项操作,如果有冲突失败,就重试)

性能上 synchronized的操作是由jvm来操作的,是重量级的(悲观锁机制,线程获得独占cpu的机会,其他线程不得不阻塞等待线程释放锁),而lock是代码层的,所以前者性能消耗更多

sleep / wait

sleep会在指定的时间内阻塞线程的运行,不会改变线程持有锁的状态(如果该线程持有锁,不会释放掉),阻塞结束后,线程会进入就绪状态
wait会释放对象锁,同时需要调用notify来结束唤醒。

线程池

线程池的好处
  1. 降低资源消耗。通过重复利用已经创建的线程,来减少线程创建和销毁带来的性能消耗;
  2. 提高响应速度。当任务达到一定的数量的时候,任务不需要等待线程创建就立即执行;
  3. 提高线程的可管理性。因为线程是系统中比较稀缺的资源,尤其在移动设备上,如果无限制的创建线程,不仅会消耗系统资源,同时会降低系统的稳定性,使用线程池可以进行统一的分配。
ThreadPoolExecutor

ThreadPoolExecutor的构造方法

public ThreadPoolExecutor(    //核心线程数大小,当线程数 < corePoolSize ,会创建线程执行    int corePoolSize,    //maximumPoolSize 最大线程数, 如果此时线程池中的数量大于corePoolSize,    缓冲队列workQueue满,并且线程池中的数量小于maximumPoolSize,建新线程来处理被添加的任务。   int maximumPoolSize,   //保持存活时间,当线程数大于corePoolSize,空闲线程能保持的最大时间   。如果某线程空闲时间超过keepAliveTime,线程将被终止。   这样,线程池可以动态的调整池中的线程数。    long keepAliveTime,    //时间的单位    TimeUnit unit,    //保存任务的阻塞队列,有Array类型、Linked类型的队列 FIFO,如果此时线程池中的数量等于 corePoolSize,    但是缓冲队列 workQueue未满,那么任务被放入缓冲队列。    BlockingQueue<Runnable> workQueue,    //线程工厂    ThreadFactory threadFactory,    //用于饱和策略    RejectedExecutionHandler handler)
线程池的工作流程

当我们提交一个新的任务到线程池的时候:
1. 当池子大小小于corePoolSize就新建线程,并处理请求
2. 当池子大小等于corePoolSize,把请求放入workQueue中,池子里的空闲线程就去从workQueue中取任务并处理
3. 当workQueue放不下新入的任务时,新建线程入池,并处理请求,如果池子大小撑到了maximumPoolSize就用RejectedExecutionHandler来做拒绝处理(会由饱和策略执行该任务,如抛出异常,或忽略这个任务等)
4. 另外,当池子的线程数大于corePoolSize的时候,多余的线程会等待keepAliveTime长的时间,如果无请求可处理就自行销毁