Java多线程与android多线程

线程和进程的区别

线程和进程的本质:由CPU进行调度的并发式执行任务,多个任务被快速轮换执行,使得宏观上具有多个线程或者进程同时执行的效果。

进程:在操作系统来说,一个运行的程序或者说一个动态的指令集合通常对应一个进程Process,它是系统进行资源分配和调度的一个独立单位,也是拥有系统资源的基本单位。进程是系统中独立存在的实体,它可以拥有自己独立的资源,拥有自己私有的地址空间,进程之间不能直接访问其他进程的地址空间。

线程:线程是CPU调度的基本单位,也就是说在一个进程中可以有多个并发程序执行流,线程拓展了进程的概念,使得任务的执行得到更加的细分,所以Thread有时候也被称为Lightweight Process。线程是进程的执行单元,但是线程不是分配系统资源的单位,它们共享所在进程的资源,包括共享内存,公有数据,全局变量,进程文件描述符,进程处理器,进程代码段,进程用户ID等等。

线程独立拥有自己的线程ID,堆栈,程序计数器,局部变量,寄存器组值,优先级,信号屏蔽码,错误返回码等等,线程是独立运行的,其执行是抢占式的。线程共享进程资源,线程之间的通信要进程之间的通信来得容易得多。此外,线程的创建和销毁的开销也远远小于进程的系统开销。

一、start()与run()

1) start:

用start方法来启动线程,真正实现了多线程运行。通过调用Thread类的start()方法来启动一个线程,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。

2) run:

run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,这样就没有达到写线程的目的。总结:调用start方法方可启动线程,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void.。

二、线程池

java.util.concurrent包主要介绍java中线程以及线程池的使用。在初始状态创建并维护一定数量的空闲线程,当有需要执行的任务,就交付给线程中的一个线程,任务执行结束后,该线程也不会死亡,而是回到线程池中重新变为空闲状态。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

为什么要用线程池:

1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

三、死锁

当线程需要同时持有多个锁时,有可能产生synchronized方式死锁。

规避死锁:

1、只在必要的最短时间内持有锁,考虑使用同步语句块代替整个同步方法;

2、尽量编写不在同一时刻需要持有多个锁的代码,如果不可避免,则确保线程持有第二个锁的时间尽量短暂;

3、创建和使用一个大锁来代替若干小锁,并把这个锁用于互斥,而不是用作单个对象的对象级别锁;

4个条件:

互斥条件:资源是独占的且排他使用,进程互斥使用资源,即任意时刻一个资源只能给一个进程使用,其他进程若申请一个资源,而该资源被另一进程占有时,则申请者等待直到资源被占有者释放。

不可剥夺条件:进程所获得的资源在未使用完毕之前,不被其他进程强行剥夺,而只能由获得该资源的进程资源释放。

请求和保持条件:进程每次申请它所需要的一部分资源,在申请新的资源的同时,继续占用已分配到的资源。

循环等待条件:在发生死锁时必然存在一个进程等待队列{P1,P2,…,Pn},其中P1等待P2占有的资源,P2等待P3占有的资源,…,Pn等待P1占有的资源,形成一个进程等待环路,环路中每一个进程所占有的资源同时被另一个申请,也就是前一个进程占有后一个进程所深情地资源。

四、ThreadLocal<T>

ThreadLocal<T>为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本.从线程的角度看,目标变量就象是线程的本地变量.ThreadLocal自身不会保存这些特定的数据资源,而是由每个线程自己来管理。每个Thread对象都有一个ThreadLocal.ThreadLocalMap类型的名为threadLocals的实例变量,它保存了ThreadLocal设置给这个线程的数据。当通过ThreadLocal的set(data)方法来设置数据的时候,ThreadLocal会首先获取当前线程的引用,然后通过该引用获取当前线程持有的threadLocals,最后,以当前ThreadLocal作为key,将要设置的数据设置到当前线程。

ThreadLocal<T>接口支持泛型,只有4个方法:

void set(Object value)设置当前线程的线程局部变量的值。

public Object get()该方法返回当前线程所对应的线程局部变量。

public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用。当线程结束后,对应该线程的局部变量将自动被垃圾回收。

protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

五、android多线程

为什么子线程中不允许对UI进行操作呢

因为Android的UI控件并不是线程安全,多线程的并发访问会带来UI控件的不可预期的状态,且考虑到加锁机制会带来性能上的问题,因此Android在设计初期就禁止子线程处理UI。UI操作时ViewRootImpl会对操作者所在的线程进行checkThread,如果非主线程,会抛出CalledFromWrongThreadException。

Android除了java原生的Thread/Runnable等线程形态,还有哪些包装过了的有特点的线程形式?

AsyncTask: AsyncTask 封装了线程池和Handler,主要是为了方便开发者不去写自己的后台线程和定义Handler,而方便更新UI界面。

IntentService: IntentService从名字来看,可以知道它是一个服务,其内部采用HandlerThread执行任务,执行完毕后自动退出。其特点是它是Service,比起其他线程来说具有更高的优先级,不容易被系统杀死,而能够保证任务的执行。

HandlerThread: HandlerThread是一个具有消息循环loop的线程,也就是一开始就准备好了loop的线程,在其内部可以直接使用Handler。

HandlerThread

  • HandlerThread 是Thread的子类,它的特点是可以使用Handler的Thread。它的run方法体如下所示,外界可以通过handler来通知HandlerThread来处理某些事情,从这可以看出Handler的用处不仅仅局限于处理UI相关的问题。

  1. public void run() {
  2. mTid = Process.myTid();
  3. Looper.prepare();
  4. synchronized(this) {
  5. mLooper = Looper.myLooper();
  6. notifyAll();
  7. }
  8. Process.setThreadPriority(mPriority);
  9. onLooperPrepared();
  10. Looper.loop();
  11. mTid =-1;
  12. }
  • IntentService
  • IntentService封装了HandlerThread和Handler,但是它继承了Service,所以导致它的优先级比单纯线程要高,所以IntentService适合执行一些高优先级的后台任务。

ThreadPoolExecutor


  1. public ThreadPoolExecutor(int corePoolSize,
  2. int maximumPoolSize,
  3. long keepAliveTime,
  4. TimeUnit unit,
  5. BlockingQueue<Runnable> workQueue,
  6. ThreadFactory threadFactory,
  7. RejectedExecutionHandler handler) {
  8. this.corePoolSize = corePoolSize;
  9. this.maximumPoolSize = maximumPoolSize;
  10. this.workQueue = workQueue;
  11. this.keepAliveTime = unit.toNanos(keepAliveTime);
  12. this.threadFactory = threadFactory;
  13. this.handler = handler;
  14. }
  • corePoolSize: 线程池的核心线程数,默认情况下, 核心线程会在线程池中一直存活, 即使处于闲置状态. 但如果将allowCoreThreadTimeOut设置为true的话, 那么核心线程也会有超时机制, 在keepAliveTime设置的时间过后, 核心线程也会被终止.
  • maximumPoolSize: 最大的线程数, 包括核心线程, 也包括非核心线程, 在线程数达到这个值后,新来的任务将会被阻塞.
  • keepAliveTime: 超时的时间, 闲置的非核心线程超过这个时长,讲会被销毁回收, 当allowCoreThreadTimeOut为true时,这个值也作用于核心线程.
  • unit:超时时间的时间单位.
  • workQueue:线程池的任务队列, 通过execute方法提交的runnable对象会存储在这个队列中.
  • threadFactory: 线程工厂, 为线程池提供创建新线程的功能.
  • handler: 任务无法执行时,回调handler的rejectedExecution方法来通知调用者.
  1. 如果线程池中线程的数目少于corePoolSize,就算线程池中有其他的没事做的核心线程,线程池还是会重新创建一个核心线程;直到核心线程数目到达corePoolSize(常驻线程就位)
  2. 如果线程池中线程的数目大于或者等于corePoolSize,但是工作队列workQueue没有满,那么新的任务会放在队列workQueue中,按照FIFO的原则依次等待执行;(当有核心线程处理完任务空闲出来后,会检查这个工作队列然后取出任务默默执行去)
  3. 如果线程池中线程数目大于等于corePoolSize,并且工作队列workQueue满了,但是总线程数目小于maximumPoolSize,那么直接创建一个线程处理被添加的任务。
  4. 如果工作队列满了,并且线程池中线程的数目到达了最大数目maximumPoolSize,那么就会用最后一个构造参数handler处理;**默认的处理方式是直接丢掉任务,然后抛出一个异常。

Java多线程与android多线程

相关推荐