因为最近的项目遇到大量并发编程的问题,抽出时间把并发编程的基础整理一下,毕竟万丈高楼平地起。更深层次的并发编程会在以后的博客中介绍。
public class TaskDemo impement Runnable{
public void run(){
while(condition){
//doSomething
}
}
}
Thread t=new Thread(new TaskDemo());
t.start();
对shutdown()方法的调用可以防止新的任务被提交给这个Executor对象,当前线程将继续运行在shutdown()被调用之前所提交的的所有任务。这个程序将在Executor中的所有任务完成之后尽快推出。
这里写代码片ExecutorService exec=Executors.newCachedThreadPool();
for(int i=0;i<5;i++){
exec.execute(new TaskDemo());
}
exec.shutdown();
在介绍下面几个Executors的方法前,先介绍一下几个类,接口,方法之间的关系
<>Executor <-- <>ExecutorService <-- ThreadPoolEcecutor
<-- 返回 Executors(class) 静态方法:1,newCacheThreadPool();2,newFixedThreadPool(number);3,newSingleThreadExecutor();
Runnable是执行工作的独立任务,但是它不返回任何值。如果你希望任务在完成时能够返回一个值,那么可以实现Callable接口而不是Runnable接口。在Java SE5中引入的Callable是一种具有类型参数的泛型,它的类型参数表示的是从方法call()(而不是run())中返回的值,并且必须使用ExecutorService.submit()方法调用它。
class TaskWithResult implements Callable<String>{
public String call(){
return "xxx";
}
}
ArrayList> results=new ArrayList>();
for(int i=0;i<10;i++){
results.add(exec.submit(new TaskWithResult()));
}
线程的优先级将该线程的重要性传递给了调度器。尽管CPU处理现有线程集的顺序是不确定的,但是调度器将倾向于让优先权最高的线程先执行。然而,这并不意味着优先权较低的线程将得不到执行。优先权较低的线程仅仅是执行的频率较低。JDK有10个优先级,一般只使用Thread.MAX_PRIORITY,Thread.NORM_PRIORITY,Thread.MIN__PRIORITY。
getPriority(...);//读取现有线程的优先级
setPriority();//修改现有线程的优先级
Thread.currentThread().setPriority(...);
所谓后台(Daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非后台线程结束时,程序也就终止了,同时会杀死进程中的所有后台线程。反过来说,只要有任何非后台线程还在运行,程序就不会终止(例如执行main()的就是一个非后台线程)。
public SimpleDaemons implement Runnable{
public void run(){
//dosomething();
}
public static void main(String[] args) throws Exception{
Thread daemon=new Thread(new SimpleDaemons());
daemon.setDaemon(true);
daemon.start();
}
}
public class DaemonThreadFactory implement ThreadFactory{
public Thread newThread(Runnable r){
//dosomething;//设置线程的属性操作
}
}
ExeccutorService exec=Executors.newCacheThreadPool(new DaemonThreadFactory());
exec.execute(new DaemonFromFactory);//DaemonFromFactory类是一个实现了Runnable接口的类,在DaemonFromFactory类中覆写run方法
一个线程可以在其他线程之上调用join()方法,其效果是等待一段时间直到第二个线程才能继续执行。如果某个线程在另一个线程t上调用t.join(),此线程将被挂起,直到目标线程t结束才恢复。
线程A | 线程B
B.join() |
A线程挂起,B运行完 |
A才能继续运行 |
由于线程的本质特征,使得你不能捕获从线程中逃逸的异常。一旦异常逃出任务的run()方法,它就会向外传播到控制台,除非你采取特殊的步骤捕获这种错误的异常。Java SE5中,可以用Executor来解决这个问题。
//此类中设定对应线程中抛出异常的处理
class MyUnCaughtExceptionHandler implements Thread.UnCaughtExceptionHandler {
public void uncaughtException(Thread t,Throwable e){
...
}
}
//该线程工厂类中设定线程异常的监听器
class Handler implements ThreadFactory{
public Thread newThread(Runnable r){
Thread t=new Thread(r);
t.setUnCaughtExceptionHandler(new MyUnCaughtExceptionHandler());
}
}
//使用定制化的线程工厂类创建线程池,并开启线程
ExcutorService exec =Executors.newCachedThreadPool(new HandlerThreadFactory());
exec.execute(new ExceptionThread());
Lock对象必须被显式的创建、锁定和释放。因此,它与内建的锁形式相比,代码缺乏优雅性。但是,对于解决某些类型的问题来说,它更加灵活。
//Lock使用例子
private Lock lock=new ReentrantLock();
lock.lock();
try{
...
}finally{
lock.unlock();
}
大体上,当你使用synchronized关键字时,需要写的代码量更少,并且用户错误出现的可能性也会降低,因此通常只有在解决特殊问题时,才使用显示的Lock对象。例如,用synchronized关键字不能尝试获得锁并且最终获取锁失败,或者尝试着获取锁一段时间,然后放弃它。
//尝试获取锁
boolean captured=false;
captured=lock.tryLock();
try{
...
}finally{
lock.unLock();
}
//尝试获取锁一段时间
try{
captured=lock.tryLock(2,TimeUnit.SECONDS);
}catch(InterruptedException e){
throw new RuntimeException(e);
}
try{
...
}finally{
lock.unLock();
}
Java内存模型规定:
Java对原子性、可见性,以及有序性提供的保证:
原子性:在Java中,对基本数据类型的变量的读取操作是原子性操作,即这些操作是不可中断的,要么执行,要么不执行。
//只有第一个语句是原子性操作
x=10;
y=x;
x++;
x=x+1;
深入剖析volatile关键字
使用volatile必须具备的两个条件:
//volatile修饰状态标记量
volatile boolean init=false;
//==线程1=====================
context=loadContext();
inited=true;
//===========================
//==线程2====================
while(!inited){
sleep();
}
doSomethingWithConfig(context);
有时,你只是希望防止多个线程同时访问方法内部的部分代码而不是防止访问整个方法。通过这种方式分离出来的代码段被称为临界区,它也可以使用synchronized关键字建立。这里,synchronized被用来指定某个对象,此对象的锁被用来对花括号内的代码进行同步控制.这也被称为同步控制块;在进入此代码段前,必须得到syncObject对象的锁。如果其他线程已经得到这个锁,那么就得等到锁被释放以后,才能进入临界区。
synchronized(syncObject){
...
}
防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享。线程本地存储是一种自动化机制,可以为使用相同变量的每个不同线程都创建不同的存储。
private static ThreadLocal connThreadLocal = new ThreadLocal();
新建(new):当线程被新建时,它只会短暂地处于这种状态。此时它已经分配了必须的系统资源,并执行了初始化。此刻线程已经有资格获得CPU时间了,之后调度器将把这个线程转变为可运行状态。
就绪(Runnable):在这种状态下,只要调度器把时间片分配给线程,线程就可以运行。也就是说,在任意时刻,线程可以运行也可以不运行。只要调度器能分配时间片给线程,它就可以运行;这不同于死亡和阻塞状态。
Thread.interrupt()方法不能中断一个正在运行的线程,这一方法实际完成的是,在线程受到阻塞时抛出一个中断信号,这样就可以退出阻塞状态。更确切的说,如果线程被object.wait(),Thread.join(),Thread.sleep()三种方法之一阻塞,那么,它将收到一个中断异常(InterruptedException),从而提早地终结被阻塞的状态。
中断线程最推荐的方式是使用共享变量(shared variable)发出信号,告诉线程必须停止正在运行的任务。
shutDownNow()方法:执行该方法线程池的状态立刻变成STOP状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,当然,它会返回那些未执行的任务。它会试图终止线程的方法是通过调用Thread.interrupt()方法来实现,但这种方法有限。例如,线程中没有sleep,wait,condition,定时锁等应用,interrupt方法是无法中断当前线程的。
interrupt、interrupted、isInterrupt的区别
public static interrupted(){
return currentThread.isInterrupted(true);
}
public native boolean isInterrupted(){
return currentThread.isInterrupted(false);
}
永远在while循环里而不是if语句下使用wait。这样,循环会在线程睡眠前后检查wait条件,并在条件实际未改变的情况下使用wait。这样,循环会在线程睡眠前后检查wait条件,并在条件实际未改变的情况下处理唤醒通知。
while(condition){
object.wait();
}
doSomething();
//注意wait()方法写在while和if中的区别
if(){
object.wait();
}
doSomething();
使用显式的Lock和Condition对象。Condition:await();signalAll(); 每个对Lock()的调用都必须紧跟一个try-finally语句,用来保证在所有情况下都可以释放锁。
private Lock lock=new ReentrantLock();
private Condition condition=lock.newCondition();
public coid method1() throws InterruptedException{
lock.lock();
try{
while(...)
condition.wait();
}finally{
lock.unLock();
}
}
public coid method2() throws InterruptedException{
lock.lock();
try{
while(...)
condition.signalAll();
}finally{
lock.unLock();
}
}
如果试图向一个已经满了的阻塞队列中添加一个元素或者是从一个空的阻塞队列中移除一个元素,将导致线程阻塞。在多线程合作时,阻塞队列是很有用的工具。
Queue: offer()
peek()
element()
poll()
remove()
BlockingQueue: put()添加一个元素,如果队列满,则阻塞
take()移除并返回队列头部的元素,如果队列为空,则阻塞
BlockingQueue接口的实现类:
通过输入/输出线程间进行通信很有用。提供线程功能的类库以“管道”的形式对线程间的输入输出提供了支持。它们在Java输入/输出类库中的对应物就是PipedWriter类(允许任务向管道写)和PipedWriter类(允许不同任务从同一管道中读取)。
class Sender implements Runnable{
private PipedWriter out=new PipedWriter();
public PipedWriter getPipedWriter(){return out;}
public void run(){
while(true){
...
out.write(数据);
}
}
}
class Reveiver implements Runnable{
private PipedReader in;
public Receiver(Sender sender){
in=new PipedReader(sender.getPipedWriter());
}
public void run(){
while(true){
...
in.read();
}
}
}
public class PipedDemo{
public static void main(String[] args){
Sender sender=new Sender();
Receiver receiver=new Receiver(sender);
//开启两个线程
...
}
}