您的位置:首页 > 编程语言 > Java开发

Java多线程笔记

2015-07-12 16:00 351 查看
1、使用new Thread(runnableObj)方式,而非继承自Thread。

对于耗时的任务,应放到线程中执行

调用new Thread(runnable).start()方法启动线程,将会在线程中调用对应的runnalbe.run方法

2、中断线程的方式:调用interrupt方式,会置位线程中断状态。检查这个中断位可判断线程是否被中断:Thread.currentThread().isInterrupted().

currentThread可用于获取当前线程。

sleep方法会清除中断状态并抛出InterruptedException。

interrupt()会向线程发中断请求,并将中断状态置为true。

interrupted()方法会检测是否线程被中断了,如果是会清除中断状态。

线程状态:New,Runnable可运行(调用start后的状态,正在执行的线程状态以及等待执行的都是可运行),Blocked被阻塞,Waitting,Timed waiting计时等待,Terminated。

被阻塞的情况:1、等待锁被释放;2、当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态;方法带有超时参数,超时时会进入计时等待状态。如Thread.sleep,Object.wait(),Thread.join(),Lock.tryLock(),Condition.await()

3、join()等待终止指定的线程,join(long mills)等待指定线程死亡或经过指定的毫秒数;

Thread.State getState()获取线程的状态

4、线程继承自父线程的优先级。用setProperty调整优先级,取值0~10,值越大优先级越高。

static void yield()此方法导致当前执行线程处于让步状态,有同优先级线程存在时,会优先执行。

5、this.setDaemon(true)使线程转换为守护线程,此方法必须在线程启动前调用。守护线程的用途是为其他线程提供服务。当只剩下守护线程时,虚拟机退出。守护线程应该永远不要去访问固定资源,如文件、数据库,因为它在任何时候都可能在一个执行过程中发生中断。

6、线程的run不能抛出被检测的异常。未被检测的异常会导致线程终止,线程死亡前异常会被传递到一个用于未捕获异常的处理器。该处理器实现了Thread.UncaughtExcetptionHandler接口。没安装默认处理器的话,默认处理器为空。不为独立的线程安装处理器,其处理器就是ThreadGroup对象

不要在Catch语句中处理可被传递的异常。

javap -c -v Bank 用于对Bank.class进行反编译

7、采用synchronized保证代码块的并发访问。

ReentrantLock是一个锁对象,可用于控制并发。在一个公共访问代码中声明一个锁对象。在try之前获得锁,finally里释放锁。而非将锁对象作为线程的成员。

锁是可重入的,线程可以重复获取已持有的锁。锁保持一个持有计数,在临界区内调用其他方法时,计数加1,所调用的方法退出时,锁减1.

留心临界区的异常处理,不要因异常而导致跳出临界区。

8、条件对象用于线程等待某个条件满足时执行操作。单纯的锁无法满足这种场景。

一个锁可以关联多个条件变量Condition。bankLock.newCondition()创建锁关联的条件变量。在临界区内,while(condition no statisfy)condition.await();在循环体中调用是必须的。 通过调用条件变量中的await方法会进入该条件的等待集,阻塞,等待另一个线程调用同一条件上的signalAll方法(会解除所有等待这一条件的线程的阻塞,使得被解除阻塞的线程可以在当前线程退出阻塞之后,通过竞争实现对象的访问)。signal()方法则是随机解除一个阻塞的线程,这个方式需保证被解除阻塞的线程可以执行,否则会造成死锁。

每个条件对象管理那些已经进入了被保护的代码段,但还不能运行的线程。

每个对象内都有一把锁,要调用synchronized声明的对象成员方法,必须事先获得内部的对象锁。

内部的对象锁只有一个关联的条件对象,wait会将线程添加到等待集中,notify/notifyAll会解除等待线程的阻塞状态。只能在synchronized内部使用wait和notify方法实现条件对象等待某个条件实现的功能。模式与await和signal相似。

一般不要用synchronized和Condition,优先用synchronized。

线程有本地缓存和寄存器,可以暂存内存中的值。如果使用锁来执行并发访问共享变量,编译器被要求在必要的时候刷新本地缓存(更新共享变量在本地的值)来保持锁的效应。

volatile为同步访问提供了免锁机制,但是不保证原子性。如果共享变量除了赋值外,不完成其他操作,可以将其声明为volatile。

atomic对象可以原子方式对变量做加减。

9、public static final ThreadLocal<SimpleDateFormat> dataFormat= 
new ThreadLocal<SimpleDateFormat>(){
protect SimpleDateFormat initialValue(){
return new SimpleDateFormat("yyyy-MM-dd");
}
}

10、锁超时: if(myLock.tryLock(100,TimeUnit.MILLISECONDS)){...} 超时时抛出InterruptedException,可用于打破死锁。

myCondition.await(100,TimeUnit.MILLISECONDS)

11、读线程多写线程少用ReentrantReadWriteLock。

private rwLock =  new ReentrantReadWriteLock();

rLock=rwLock.readLock();该所被所有读线程共用,会排斥写操作在读之前用rLock.lock();结束后用rLock.unlock();

wLock=rwLock.writeLock();排斥其他的读操作和写操作n。在写之前用wLock.lock();结束后用wLock.unlock();

12、阻塞队列BlockingQueue(这个有几种继承类:LinkedBlockingQueue, LinkedBlockingDeque, ArrayBlockingQueue)就是一个生产者消费者模型中的用于存放共享数据的队列。该队列有考虑生产者消费者间的并发操作,处理生产者消费者速度的负载平衡。用offer添加一个元素、poll移出并返回队头、peek返回队头元素(这三种操作可指定操作的超时时间)。

PriorityBlockingQueue优先队列。

13、线程安全集合:阻塞队列、ConcurrentHashMap, ConcurrentSkipListMap, ConcurrentSkipListSet(这两个是线程安全的有序集,需事先Comparable接口), ConcurrentLinkedQueue. 这些集合的size()内部需要通过遍历来确定集合大小,这与一般的集合不同。

这些集合返回的是弱一致性的迭代器,不一定能返回被修改后的值,但不会将同一个值返回两次,不会抛出ConcurrentModificationException。

ConcurrentHashMap<E>(int initialCapacity, float loadFactor, int concurrentLevel);中参数是指定集合的初始容量,默认为16.concurrentLevel是并发写者线程的估计数目,表明可供多达16个写进程同时写,多了会阻塞剩下的线程。

ConcurrentHashMap.putIfAbsent(k,v)是在该键不存在时插入kv对,否则返回键k对应的值。

写数组的拷贝:

CopyOnWriteArrayList和CopyOnWriteArraySet。写线程对底层数据做修改。如果读线程数大于写线程数,则这类集合很适合。读写可不用同步就可保持一致性。

任何集合类通过同步包装器可变为线程安全的,通过给集合加入锁来实现的,这不推荐使用,因为还是需要在访问的代码中加入synchronized(synchashMap){...},推荐使用ConcurrentXXX集合:

List<E> syncArrayList = Collections.synchronizedList(new ArrayList<E>());

HashMap<K,V> syncHashMap = Collections.synchronizedMap(new HashMap<K,V>());

对于经常被修改的数组列表,采用synchronized(arrayList){}会比CopyOnWriteArrayList性能更好。

14、回调Callable和Future。 Runnalbe是无返回值无参数的异步方法,Callable和Future有返回值。Callable<E>{ E call();}只有一个方法返回值类型为E的call().

Future保存了异步计算结果,其get(long timeout,TimeUnit)可指定超时时间,超时时中断。如果Future还在执行,则isDone()返回false。cancle()方法可取消该运算。

FutureTask包装器,可将Callable转化为Future何Runnable。

Callable<Integer> myCompution=..;

FutureTask<Integer> task= new FutureTask<Integer>(myCompution);//构造一个既是Future又是Runnable的对象。

Thread t= new Thread(task);

t.start();

...

Integer result=task.get();//对get调用会阻塞直到有可获得的结果或者超时为止。

15、执行器Executor

如果程序中有大量生命期很短的线程,最好使用线程池。线程池中包含了许多准备运行的空闲线程。将Runnable交给线程池,就会有一个线程调用Runnable.run()。run退出时线程又回到线程池等待下一次运行。

使用线程池可减少并发线程数。

Executor中有许多静态方法可用于构建线程池:newCachedThreadPool空闲线程被保存60s;newFixedThreadPool固定线程数且空闲线程一直保存,如果任务数大于线程数,未得到执行的任务会放到队列中等待空闲线程可用;newSingleThreadPool只有一个线程,用于顺序执行。这些方法返回实现了ExecutorService接口的ThreadPoolExecutor对象。

可用ExecutorService接口中的Future<?> submit(Runnable,T result);Future<?>submit(Callable<T> Task)方法提交Runnable和Callable对象给ExecutorService。

线程池用完后调用shutdown()关闭线程池。

步骤:

a、Executors.newFixedThreadPool() 

b、submit提交Runnable或Callable对象;

c、获取Future对象result,并调用result.get();

d、关闭线程池shutdown()

15、预定执行(定时任务)

ScheduledExecutorService接口中有为预定执行或重复执行的任务设计的方法。

ScheduledExecutorService ss=Executors.newScheduledThreadPool();

schedule(Callable/Runnable,initDelayTime,TimeUnit);

scheduleAtFixedRate(Callable/Runnable,initDelayTime,long period,TimeUnit);//每隔period执行一次

16、控制任务组

Executor可用于控制一组相关任务。

invokeAny:多个任务执行,只要有一个执行完成就可以结束了。

invokeAll:多个任务执行,所有任务都执行完成才可以结束。使用方式如下:

List<Callable<T>> tasks=...; 

List<Future<T>> results=executor.invokeAll(tasks);

for(Future<T>res:results){//顺序遍历在第一个任务耗时很长的情况下会花很多时间进行等待,推荐用ExecutorCompletionService按结果产生顺序进行保存,更有效
processFuturer(result.get());

}

17、fork-join框架(大任务划分为小任务,并行执行后merge)

RecursiveTask用于有计算结果返回,RecursiveAction用于无计算结果返回。二者的compute用于生成并调用子任务并合并结果。

class Counter extends RecursiveTask<Integer>{//分治算法
protect Integer compute(){
if(to-from<THRESHOLD){直接执行(单任务)}
else{
int mid=(low+high)>>1;
Counter first=new Counter(from,mid);//拆分子任务
Counter second=new Counter(mid+1,high);
invokeAll(first,second);//调度所有子任务
return first.join()+second.join();//合并结果
}
}

}

main(){
ForkJoinPool pool=new ForkJoinPool();
pool.invoke(counter);
System.out.println(counter.join());

}

18、同步器

提供了实现线程间相互合作的类:

CyclicBarrier:等待一定数目的线程都到达一个公共屏障,在执行一个处理屏障的动作。

CyclicBarrier barrier=new CyclicBarrier(numThreads,barrierAction);//当numThreads个线程都到达屏障时执行barrierAction

Runnable{
public void run(){
doSomething...
barrier.await();//等待直到屏障打开,可设置超时时间,超时时抛异常,会导致其他等待await的线程都抛BrokenBarrierException异常。
...
}

}

CountDownLatch:允许等待直到计数器为0。用于当一个或多个线程需等到指定数目事件发生时。这是一次性的,只可用一次。

Exchanger:允许两个线程在要交换对象准备好时交换对象。两个线程在同一个数据结构的两个不同实例上,一个向实例添加数据,另一个用于清除数据

Semaphore:允许线程等待直到被允许执行为止。用于限制访问资源的线程数。

信号量管理了许多permits,用于限制通过的线程数量。信号量仅维护一个计数。其他线程通过调用release()释放许可permits。

SynchronousQueue:允许一个线程把对象交给另一个线程。在没显式同步时,两个线程将一个对象从一个线程传到另一个线程。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: