Volatile域
结构: public boolean isDone(){return done;}
public void setDone(){done = true;}
private volatile boolean done;
volatile关键字为实例域的同步访问提供了一种免锁机制,如果声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。
volatile变量不能提供原子性 例如:
public void flipdone() { done = !done ;} //not atomic 不能确保改变域中的值
在这样一种非常简单的情况下,可以使用java.util.concurrent.atomic中提供的包装器类,用于原子的整数,浮点数,数组等。该类有get和set方法,确保是原子的。该实现使用有效的机器指令,在不使用锁的情况下确保原子性。
锁测试与超时
tryLock试图申请一个锁,在成功获得锁后返回true,否则立即返回false,而且线程可以立即离开做其他事情。
调用tryLock时可使用超时参数。lock方法不能被中断,如果出现死锁,lock方法将无法终止。然而如果调用带超时参数的tryLock,那么如果线程在等待期间被中断,将抛出InterruptedException,将允许打破死锁。
lockInterruptlibly相当于一个超时设为无限的tryLock方法
在等待一个条件await时,也可以设置超时
读/写锁ReentrantReadWriteLock
结构:private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock ();
private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();
readLock:得到一个可以被多个读操作共用的读锁,但会排斥所有写操作
writeLock:得到一个写锁,排斥所有其他所有读操作和写操作。
阻塞队列
对于许多线程问题,可以通过使用一个或多个队列以优雅的且安全的方式将其形式化。生产者线程向队列插入元素,消费者线程则取出它们。使用队列,可以安全地从一个线程向另一个线程传递数据。在协调多个线程之间的合作时,阻塞队列是一个有用的工具。
阻塞队列的方法分为3类:
1.put和take:当试图向满的队列中体添加,或从空的队列中移出元素时,导致线程阻塞。用于将队列当做线程的管理工具来使用。
2.add,remove和element,当试图向满的队列中体添加,或从空的队列中移出元素时抛出异常。
3.offer,poll和peek,当试图向满的队列中体添加,或从空的队列中移出元素时如果不能完成任务,只是给出一个错误提示(返回null),而不会抛出异常。
阻塞队列的几个变种:
1.LinkedBlockingQueue : 没有上边界
2.ArrayBlockingQueue :构造时需要指定容量
3.priorityBlockingQueue:是一个带优先级的队列,而不是先进先出队列
4.DelayQueue:包含实现Delayed接口的对象
线程安全的集合
阻塞队列也是线程安全的集合。
其它集合:
1.ConcurrentHashMap : 有相应的方法用于原子性的关联插入以及关联删除 (散列映像表)
2.ConcurrentSkipListSet : 有序映像表
3.ConcurrentLinkedQueue : 无边界非阻塞队列
4.ConcurrentSkipListMap: 有相应的方法用于原子性的关联插入以及关联删除
CopyOnWriteArrayList 和copyOnWriteArraySet 是线程安全的集合。其中所有的修改线程对底层数组进行复制。如果在集合上进行迭代的线程超过修改的线程数,这样的安排是很有用的。当构建一个迭代器的时候,它包含一个对当前数组的引用,如果数组后来被修改了,迭代器仍然引用旧数组。
Vector和HashTable是线程安全的,但后被弃用,取而代之的是ArrayList和HashMap,它们不是线程安全的。任何集合类通过使用同步包装器变成线程安全的。
List synchArrayList = Collections.synchronizedList(new ArrayList());
Map SynchHashMap = Collections.synchronizedMap(new HashMap());
结果集合的方法使用锁加以保护,提供了线程的安全访问。
如果在另一个线程可能进行修改时,要对集合进行迭代,仍需要使用同步阻塞,因为如果迭代过程中,别的线程修改该集合,迭代器会失效,抛出ConcurrentModificationException。最好使用java.utile.concurrent中定义的集合,不使用同步包装器,有一个例外经常被修改的数组列表,在那种情况下,同步的arrayList可以胜过copyOnWriteArrayList.
执行器
构建一个新的线程是有一定代价的,因为涉及与操作系统的交互。如果程序中创建了大量的生命周期很短的线程,应该使用线程池。一个线程池中包含许多准备运行的空闲线程,将Runnable对象交给线程池,就会有一个线程调用run方法。当run方法退出时,线程不会死亡,而是在池中准备为下一个请求提供服务。
执行器(Executor)类有许多静态工厂方法用来构建线程池:
1.newCachedThreadPool :对于每个任务,如果有空闲线程可用,立即让它执行任务,如果没有则创建一个新线程。
2.newFixedThreadPool :构建一个具有固定大小的线程池,如果提交的任务数多于空闲的线程数,那么把得不到服务的任务放置到队列中。
3.newSingleThreadExecutor:是一个退化了的大小为一个线程池,由一个线程执行提交的任务,一个接着一个。
4.3个方法的返回值都是实现了ExecutorService接口的ThreadPoolExecutor类的对象。
5.传递Runnable或者Callable对象的方法:
i.Future submit(Runnable task)
ii.Future submit(Runnable task,T result)
iii.Future submit(Callable task)
6.调用shutdown方法启动线程池的关闭序列,被关闭的执行器不再接受新的任务,当所有的任务都完成之后,线程池中的线程死亡。shutdownNow取消尚未开始的所有任务并试图中断正在运行的线程
预订执行(ScheduledExecrtorService)接口具有为预订执行或重复执行任务而设计的方法,它是一种允许使用线程池机制的java.utile.timer的泛化。
1.newScheduledthreadPool
2.newSingleThreadScheduledExecutor
3.二者可以预订Runnable或Callable在初始的延迟之后之运行一次,也可以预订一