在开发android中一个考验程序员技术的重点就是并发编程。并发编程的核心就在于多线程编程。并发编程包含了java以及android两部分,重点在于java部分,因为android使用了java的很多并发编程类,但android有着自己的并发编程类,这是java程序员所不能使用的。
对于并发编程,关键在于线程类,然后以线程管理类,线程工具类等为辅。其中线程类包含了java的线程类Thread,Runnable,Callable以及android的Handler,AsncTask等类。线程管理类以TreadPoolExecutor类为核心,以其父类接口Executor,ExecutorService为参考(这些父类平时基本不会用到,除非需要自行设计线程管理类,线程池管理类时,可以使用,不然基本上就是查看他们的源码作为参考),以及一些类似ScheduledTreadPoolExecutor类等特殊功能Executor类的子类作为备用管理类。而工具类基本上就是Executors,PipedReader,PipedWriter,Semaphore,BlockingQueue,DelayQueue,Exechanger,ReadWriteLock等。(java SE5的java.util.concurrent包大量设计用来解决并发问题的新类,这些类就包括了Executors,PipedReader,PipedWriter,Semaphore,BlockingQueue,DelayQueue,Exechanger,ReadWriteLock等。)
一览表:
线程:Thread,Runnable,Callable,Handler,AsncTask;
线程管理:ThreadPoolExecutor,ScheduledThreadPoolExecutor等以ThreadPoolExecutor为核心的Executor子类;
工具类:Executors,PipedReader,PipedWriter,Semaphore,BlockingQueue,DelayQueue,Exechanger,ReadWriteLock等。
简要的对这些类进行说明:
(java 5.0新加入四个协调线程间进程的同步装置:Semaphore,CountDownLatch,Exchanger,CyclicBarrier)
ThreadPoolExecutor类是Executor接口的子接口类ExecutorService的抽象类AbstractExecutorService的子类。同时由于Executor的子类众多,所以这里有一个方便记忆的规律,Executor接口与我们实际使用最后使用的类的中间层一般是ExecutorService,或者XXExecutorService。同时注意,我们最后使用的一般是XXThreadExecutor。简单点记忆就是Executor----》ExecutorService---》ThreadPoolExecutor,其他类链命名一般遵循这种规律,只是在这三个类上面增加功能命名。
Callable类功能跟Runable类似,关键是Callable的方法会返回值,而Runnable不会,通常可以使用ExecutorService.sumit(Callable)方法来操作Callable,也可以跟Runnable一样使用。这里ExecutorService.sumit(Callable)方法将会返回一个Future类对象,通过这个对象,我们可以查看返回的数据是否返回,以及返回数值情况,我们可以调用Future.isDone(),查看数值是否返回,Future.get()异步得到数值。
Executors类是线程管理类的工具类,一般用于操作线程管理类的生成等。如Exetutors.newCachedThreadPool()方法;这里注意,Executors有很多很好用的方法,这些方法的返回对象都是ExecutorService类型的,但是,由于多态的原因实际上这些返回的对象是拥有新特性的,这些方法包括:Executors.newCachedThreadPool(),Executors.newFixedThreadPool(),Executors.newSingleThreadPool(),Executors.newScheduledThreadPool();这里这些方法的命名上其实是有规律的,都是以newXXThreadPool()进行命名,XX为功能。这里返回的之所以最后以ThreadPool结尾,不单单是因为返回的是ExecutorService,而更重要的是这是线程池。
FixedThreadPool指的是线程数量一定的线程池。使用了有限的线程集来执行所提交的任务,有了它就可以一次性预先执行代价高昂的线程分配,因为也就可以限制线程的数量了。
CachedThreadPool指的是缓冲线程池。它将为每个任务创建一个线程。尽管使用的是CachedThreadPool,但是也应该在产生线程的代码中使用FixedThreadPool。CachedThreadPool在程序执行的过程中通常会创建与所需数量相同的线程,然后在他回收旧线程时停止。
SingleThreadPool就是数量为宜的FixedThreadPool。
PipedReader,PipedWriter是IO类,在并发编程里会涉及管道传输,这时候对于线程间管道传输就会用到他们。他们其实就是任务间管道IO。任务间使用管道进行输入输出。
这里PipedReader,PipedWriter需要配合使用,从他们的构造器就可以看出来,这里常见的使用方法就是继承他们,同时实现Runnable接口,这样就可以通过ExecutorService.execute(Runnable)使用他们了,这样线程之间就形成了一个管道了。
同时注意extends一个类,然后implements Runnable形成一个类是多线程里面重要的线程交互手段,以为extends之后的类可以包含我们所需的对象,这样我们就可以操作我们需要的对象了。
Semaphore计数信号量。简历一个对象池的概念去理解他。正常的锁(来自concurrent.locks或内建的synchronized锁)在任何时刻都只允许一个任务访问一项资源,而计数信号量允许n个任务同时访问这个资源,你还可以将信号量看作是在向外分发使用资源的“许可证”,尽管实际上没有使用任何许可正对象。
Semaphore的关键方法有acquire(),release(),通过acquire()获得许可,若没有则等待,通过release()释放许可。实际上Semaphore就是用来控制访问个数,当访问个数控制为一个时,就可以实现同步功能,互斥锁功能。(重入锁ReentrantLock实际上可以实现类似功能,但是更为复杂。)
CyclicBarrier同步辅助类,主要方法是await(long timeout, TimeUnit unit),这个方法会返回一个线程数量,这个方法没被调用一次,构造器new CyclicBarrier(int, Runnable),传入的计数值就会减一,并且阻塞线程。当计数值变为0时,阻塞解除,在上面阻塞的线程开始执行。在调用这个方法又会从计数值-1开始,这就是Cyclic的原因。同时注意,CyclicBarrier释放时,构造器传入的Runnable就会执行。虽然CountDownLatch可以实现与CyclicBarrier相同的功能,但是他们又有区别,一个主线程等待一组工作线程执行完毕在执行它是CountDownLatch的主要场合。CyclicBarrier用于一组或几组线程,比如一组线程需要在一个时间点上达成一致,比如同时开始一个工作。其实这种去别从方法里面可以看出,但是总的来说他们两个基本上功能很像。
ReentrantLock(未完成)
DelayQueue队列,这里他跟BlockingQueue类似,他的队列里面对象时Delayed。他是一个无界BlockingQueue,用于放置实现了Delayed接口对象,其中对象到期之后才能从队列中拿走。
Exechanger用于两个任务间交换对象,是两个任务间交换对象的栅栏。Exchanger只能用在两个线程之间,当一个线程调用exchange(V)之后他将被阻塞,直到另一个线程也调用exchange(V)方法,才可以往下执行。然后以线程安全的方式执行下去,之后两个线程继续执行。
ReadWriteLock对向数据结构相对不频繁的写入,但是有多个线程要经常读取这个数据结构的这类情况进行了优化。在同一线程中,在获得读锁之后不能直接调用写锁的lock()方法,否则会造成死锁。这里ReadWriteLock.readLock(),ReadWriteLock.writeLock()方法会返回一个Lock对象,这个对象是ReentrantLock的父类,这个类有两个重要的方法,lock(),unlock(),通过调用lock()可以锁住,unlock()解开。注意,没有写锁的情况下,读锁是无阻塞的。(Lock接口使用它可以控制竞争资源的安全访问,当时这种锁不区分读写,是普通的锁。锁的意思是,当某一个访问想要读写时,如果调用了lock()方法,那么其他资源就不能读或者写,或者说阻塞了。而ReadWriteLock在没有写锁的情况下,读是无阻塞的。)
CountDownLatch递减锁存器,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,他允许一个或多个线程一直等待。这里他有两个最重要的方法countDown(),await(long timeout,TimeUnit unit)。new CountDownLatch(int)实例化一个计数值,countDown()方法每一次调用将会将它减一,await()方法被调用的线程将会被阻塞,直到countDown()将计数值减为0.
这里还涉及到一个Process类,他其实是进程类,用于操控进程,一般用不上,但是他Process.killProcess(int pid)方法用于杀死进程,使用Process.myPid()得到进程ID号pid。
这里还涉及到了一些并发编程会涉及到的编程问题,我们称之为病态行为,分别有:饿死,竞争,死锁,活锁。
对线程管理类,我个人总结的是:需要对线程进行的管理有:1.线程的生成 2.线程的重用 3.线程的生命周期管理 3.线程的销毁
同时注意,之所以之所以线程需要管理,是因为线程的性能,线程的增多会影响性能。
这里简要的介绍了一下涉及到的一些类,下面进入并发编程。
其实对于并发编程,我们应该建立一种观念:1.多线程需要管理 2.线程是有生命周期的 3.多线程的核心就在于对对象操作的同步异步,线程间的数据交互,或者说线程交互。
这里,一个线程的生命周期包含了:1.就绪(Runnable) 2.运行(Running) 3.阻塞(Blocking) 4.死亡(Dead)。
线程常用的方法有Thread.start(),Thread.interrupt(),Thread.isInterrupt(),Thread.interrupted(),Thread.yield(),Thread.join(),Thread.sleep()同时还涉及到Object.wait(),Object.notify(),Object.notifyAll()。同时注意Thread.stop(),Thread.destroy(),Thread.suspend()等方法已经已经一般不再使用了,或者说弃用了,因为安全性的问题,也就是说除非必要这里个方法最好不要用了。
而这些方法中Thread.sleep()方法不会释放对象锁,Object.wait()引起线程释放对象。
注意线程中一旦遇到Object.wait()则线程终止执行,释放对象锁,需要在另外一个线程中Object.notify(),Object.notifyAll()之后并且刚好轮到该线程执行才可以执行,在调用Object.notify(),Object.notifyAll()之后如果没有轮到线程执行,那么线程仅仅就在就绪状态而已。同时醉意Object.notify(),Object.notifyAll(),Object.wait()必须在synchronized同步快块里面执行。
Thread.yield()方法的意思是让步的意思,当调用这个方法之后,线程将会让给优先级一样的线程,如果没有相同优先级的,那么这个方法不起作用。我们可以通过Thread.setPriority()设置优先级。
Thread.interrupt()这个方法应该注意,他并不会真的就中断线程,它的作用其实并不是这样的,他只是设置的一个中断状态,其实当线程中调用了Thread.sleep(),Thread.join(),Object.wait(),Object.notify,Object.notifyAll(),而外部线程调用这个线程的Thread.interrupt()方法时,Object.wait(),Object.notify,Object.notifyAll(),会抛出InterruptedException。而Thread.interrupted()方法则是返回一个上一次是否中断的判断,这个我们可以使用Thread.isInterrupt()判断,然后会将Thread的中断设置置空。
这里注意线程的重要板块在于线程的关闭,而线程的Thread.stop()方法不安全的话那么用哪一个呢?我们有两种做法:1.设置一个变量当检查到变量发生变化时中断,一般这个变量时boolean的。 2.使用Thread.interrupt()它是安全的,也是建议的。
java中的线程机制看起来非常复杂并且难以正确使用。另外,他好像还有点达不到预期效果的味道------尽管多个任务可以并行工作,但是你必须花费很大的气力去实现防止这些任务彼此不干扰的技术。
有一种可替换的方式被称为活动对象或行动者(Future在这里派上用场)
活动对象或行动者,之所以称这些对象是“活动的”,是因为每个对象都维护着它自己的工作器线程和消息队列,并且所有对这种对象的请求都将进入队列排队,任何时刻都只能运行其中的一个。因此,有了活动对象,我们就可以串行化消息而不是方法,这意味着不再需要防备一个任务在其循环的中间被中断这种问题了。
为了能够在不经意间就可以防止线程之间的耦合,任何传递给活动对象方法调用的参数都必须是只读的其他活动对象,或者是不连接对象,即没有连接任何其他任务的对象。有了活动对象:
1. 每个对象都可以拥有自己的工作器线程。
2.每个对象都将维护对它自己的域的全部控制权。
3.所有活动对象之间的通信都将以在这些对象之间的消息形式发生。
4.活动对象之间的所有消息都要排队。(未完成)
Executors,PipedReader,PipedWriter,Semaphore,BlockingQueue,DelayQueue,Exechanger,ReadWriteLock,countDownLatch,DelayTaskHandler(未完成)
活动对象(行动者)(未完成)
(由于本人最近比较忙没来得及整理写完,所以可以加群185637563,我尽量解答)
(未完成)