new Thread的弊端及Java四种线程池的使用:https://www.cnblogs.com/williamjie/p/9389383.html
http://ifeve.com/java-7-concurrency-cookbook/并发编程的官方网址
并发与多线程的联系与区别:
并发与多线程之间的关系就是目的与手段之间的关系。
并发(Concurrent)的反面是串行,串行好比多个车辆行驶在一股车道上,它们只能“鱼贯而行”。而并发好比多个车辆行驶在多股车道上,它们可以“并驾齐驱”。并发的极致就是并行(Parallel)。多线程就是将原本可能是串行的计算“改为”并发(并行)的一种手段、途径或者模型。
因此,有时我们也称多线程编程为并发编程。当然,目的与手段之间常常是一对多的关系。并发编程还有其他的实现途径,例如函数式(Functional programming)编程。多线程编程往往是其他并发编程模型的基础,所以多线程编程的重要性不言而喻。
多线程并不一定是并发,如果是并发执行,那么肯定是多个线程在一块执行,当然也未必,多个进程也是并发执行。
学习笔记:
1)创建线程
创建一个Thread类的对象不会创建新的执行线程。同样,调用实现Runnable接口的 run()方法也不会创建一个新的执行线程。只有调用start()方法才能创建一个新的执行线程。
注:线程的构造函数:
public Thread(){init(null, null, "Thread-" + nextThreadNum(), 0);};
public Thread(Runnable target){init(null, target, "Thread-" + nextThreadNum(), 0);};
public Thread(String name){init(group, target, "Thread-" + nextThreadNum(), 0);};
public Thread(Runnable target, String name){init(null, null, name, 0);};
public Thread(ThreadGroup group, Runnable target){init(group, null, name, 0);};
public Thread(ThreadGroup group, String name){init(null, target, name, 0);};
public Thread(ThreadGroup group, Runnable target, String name){init(group, target, name, 0);};
public Thread(ThreadGroup group, Runnable target, String name, long stackSize){init(group, target, name, stackSize);};
2)获取和设置线程信息
Thread.currentThread().getName()获取当前线程名称
Thread.currentThread().getPriority()获取当前线程等级
Thread.currentThread().getState()获取当前线程状态
Thread.currentThread().setName(arg0)设置当前线程名称
Thread.currentThread().setPriority(arg0)设置当前线程等级
3)中断线程
interrupt()
4)操作线程的中断机制
http://ifeve.com/thread-management-5/
InterruptedException异常
如果线程实现的是由复杂的算法分成的一些方法,或者它的方法有递归调用,那么我们可以用更好的机制来控制线程中断。为了这个Java提供了InterruptedException异常。当你检测到程序的中断并在run()方法内捕获,你可以抛这个异常。
5)线程的睡眠与恢复
Thread的sleep和TimeUnit列举元素的sleep() 方法
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.printf("The FileClock has been interrupted");
}
在上面例子中当 Thread 是睡眠和中断的时候,那方法会立刻抛出InterruptedException异常并不会一直等到睡眠时间过去
6)守护线程的创建和运行
thread.setDaemon(true)
只能在start() 方法之前可以调用 setDaemon() 方法。一旦线程运行了,就不能修改守护状态。
可以使用 isDaemon() 方法来检查线程是否是守护线程(方法返回 true) 或者是使用者线程 (方法返回 false)
7)在线程里处理不受控制的异常
http://ifeve.com/thread-management-9/
Java里有2种异常:
- 检查异常(Checked exceptions): 这些异常必须强制捕获它们或在一个方法里的throws子句中。 例如, IOException 或者ClassNotFoundException。
- 未检查异常(Unchecked exceptions): 这些异常不用强制捕获它们。例如, NumberFormatException。
在一个线程 对象的 run() 方法里抛出一个检查异常,我们必须捕获并处理他们。因为 run() 方法不接受 throws 子句。当一个非检查异常被抛出,默认的行为是在控制台写下stack trace并退出程序。
8)线程组
http://ifeve.com/thread-management-11/
Java并发 API里有个有趣的方法是把线程分组。这个方法允许我们按线程组作为一个单位来处理。例如,你有一些线程做着同样的任务,你想控制他们,无论多少线程还在运行,他们的状态会被一个call 中断。
Java 提供 ThreadGroup 类来组织线程
9)处理线程组内不受控制异常
http://ifeve.com/thread-management-12/
1. 首先, 创建一个类叫MyThreadGroup来扩展 ThreadGroup 类 。我们必须声明一个拥有一个参数的构造方法,因为ThreadGroup类有一个没有参数的构造方法。
public class MyThreadGroup extends ThreadGroup {
public MyThreadGroup(String name) {
super(name);
}
2. 覆盖 uncaughtException() 方法。ThreadGroup 类的其中一个线程抛出异常时,就会调用此方法 。在这里,这个方法会把异常和抛出它的线程的信息写入操控台并中断ThreadGroup类的其余线程。
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.printf("The thread %s has thrown an Exception\n",t.getId());
e.printStackTrace(System.out);
System.out.printf("Terminating the rest of the Threads\n");
interrupt();
}
10)同步方法( synchronized)
http://ifeve.com/basic-thread-synchroinzation-2/
public synchronized void addAmount(double amount) {
double tmp=balance;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
tmp+=amount;
balance=tmp;
}
确保这个方法不会被多个线程同时操作,一个线程操作时其他线程会处于等待状态
11)在同步的类里安排独立属性
synchronized
http://ifeve.com/basic-thread-synchronization-3/
给代码块加synchronized
12)使用Lock同步代码块
http://ifeve.com/basic-thread-synchronization-5/
-
它允许以一种更灵活的方式来构建synchronized块。使用synchronized关键字,你必须以结构化方式得到释放synchronized代码块的控制权。Lock接口允许你获得更复杂的结构来实现你的临界区。
-
Lock 接口比synchronized关键字提供更多额外的功能。新功能之一是实现的tryLock()方法。这种方法试图获取锁的控制权并且如果它不能获取该锁,是因为其他线程在使用这个锁,它将返回这个锁。使用synchronized关键字,当线程A试图执行synchronized代码块,如果线程B正在执行它,那么线程A将阻塞直到线程B执行完synchronized代码块。使用锁,你可以执行tryLock()方法,这个方法返回一个 Boolean值表示,是否有其他线程正在运行这个锁所保护的代码。
-
当有多个读者和一个写者时,Lock接口允许读写操作分离。
-
Lock接口比synchronized关键字提供更好的性能。
Lock queueLock=new ReentrantLock();
queueLock.lock(); queueLock.unlock();
queueLock.wait();
queueLock.notify();
queueLock.notifyAll();
13)使用读/写锁同步数据访问
http://ifeve.com/basic-thread-synchronization-6/
锁所提供的最重要的改进之一就是ReadWriteLock接口和唯一 一个实现它的ReentrantReadWriteLock类。这个类提供两把锁,一把用于读操作和一把用于写操作。同时可以有多个线程执行读操作,但只有一个线程可以执行写操作。当一个线程正在执行一个写操作,不可能有任何线程执行读操作。
ReadWriteLock lock=new
ReentrantReadWriteLock()
lock.readLock().lock(); lock.readLock().unlock();
lock.writeLock().lock(); lock.writeLock().unlock();
14)修改Lock的公平性
http://ifeve.com/basic-thread-synchronization-7/
在ReentrantLock类和 ReentrantReadWriteLock类的构造器中,允许一个名为fair的boolean类型参数,它允许你来控制这些类的行为。默认值为 false,这将启用非公平模式。在这个模式中,当有多个线程正在等待一把锁(ReentrantLock或者 ReentrantReadWriteLock),这个锁必须选择它们中间的一个来获得进入临界区,选择任意一个是没有任何标准的。true值将开启公平 模式。在这个模式中,当有多个线程正在等待一把锁(ReentrantLock或者ReentrantReadWriteLock),这个锁必须选择它们 中间的一个来获得进入临界区,它将选择等待时间最长的线程。考虑到之前解释的行为只是使用lock()和unlock()方法。由于tryLock()方 法并不会使线程进入睡眠,即使Lock接口正在被使用,这个公平属性并不会影响它的功能。
Lock queueLock=
new
ReentrantLock(
true
);
15)
Condition类的
http://ifeve.com/basic-thread-synchronization-8/
Condition接口提供一种机制,阻塞一个线程和唤醒一个被阻塞的线程
所 有Condition对象都与锁有关,并且使用声明在Lock接口中的newCondition()方法来创建。使用condition做任何操作之前, 你必须获取与这个condition相关的锁的控制。所以,condition的操作一定是在以调用Lock对象的lock()方法为开头,以调用相同 Lock对象的unlock()方法为结尾的代码块中。
await():当一个线程在一个condition上调用await()方法时,它将自动释放锁的控制,所以其他线程可以获取这个锁的控制并开始执行相同操作,或者由同个锁保护的其他临界区。
signal(),signallAll():当一个线程在一个condition上调用signal()或signallAll()方法,一个或者全部在这个condition上等待的线程将被唤醒。
注:
Condition接口提供不同版本的await()方法,如下:
- await(long time, TimeUnit unit):这个线程将会一直睡眠直到:
(1)它被中断
(2)其他线程在这个condition上调用singal()或signalAll()方法
(3)指定的时间已经过了
(4)TimeUnit类是一个枚举类型如下的常量:
DAYS,HOURS, MICROSECONDS, MILLISECONDS, MINUTES, NANOSECONDS,SECONDS
- awaitUninterruptibly():这个线程将不会被中断,一直睡眠直到其他线程调用signal()或signalAll()方法
- awaitUntil(Date date):这个线程将会一直睡眠直到:
(1)它被中断
(2)其他线程在这个condition上调用singal()或signalAll()方法
(3)指定的日期已经到了
你可以在一个读/写锁中的ReadLock和WriteLock上使用conditions。
16)使用Semaphore控制并发访问一个资源
http://ifeve.com/thread-synchronization-utilities-2/
Semaphore semaphore=
new
Semaphore(
1
);
使用Semaphore的三个步骤:
1. 首先, 你要调用acquire()方法获得semaphore。
2. 然后, 对共享资源做出必要的操作。
3. 最后, 调用release()方法来释放semaphore。
注:Semaphore类有另2个版本的 acquire() 方法:
- acquireUninterruptibly():acquire()方法是当semaphore的内部计数器的值为0时,阻塞线程直到semaphore被释放。在阻塞期间,线程可能会被中断,然后此方法抛出InterruptedException异常。而此版本的acquire方法会忽略线程的中断而且不会抛出任何异常。
- tryAcquire():此方法会尝试获取semaphore。如果成功,返回true。如果不成功,返回false值,并不会被阻塞和等待semaphore的释放。接下来是你的任务用返回的值执行正确的行动。
17)使用Semaphore控制并发访问多个资源
Semaphore semaphore=new
Semaphore(
3
);
Semaphore对象创建的构造方法是使用3作为参数的。前3个调用acquire() 方法的线程会获得临界区的访问权,其余的都会被阻塞 。当一个线程结束临界区的访问并解放semaphore时,另外的线程才可能获得访问权。
18)等待多个并发事件完成
http://ifeve.com/thread-synchronization-utilities-4/
CountDownLatch
//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行
public void await() throws InterruptedException { };
//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };
//将count值减1
public void countDown() { };
Java并发API提供这样的类,它允许1个或者多个线程一直等待,直到一组操作执行完成。 这个类就是CountDownLatch类。它初始一个整数值,此值是线程将要等待的操作数。当某个线程为了想要执行这些操作而等待时, 它要使用 await()方法。此方法让线程进入休眠直到操作完成。 当某个操作结束,它使用countDown() 方法来减少CountDownLatch类的内部计数器。当计数器到达0时,这个类会唤醒全部使用await() 方法休眠的线程们。
19)创建一个线程执行者
http://ifeve.com/thread-executors-2/
ThreadPoolExecutor executor=(ThreadPoolExecutor)Executors.newCachedThreadPool();
ThreadPoolExecutor executor=(ThreadPoolExecutor)Executors.newSingleThreadScheduledExecutor();
ThreadPoolExecutor executor=(ThreadPoolExecutor)Executors.newScheduleThreadPool();
ThreadPoolExecutor executor=(ThreadPoolExecutor)Executors.newSingleThreadExecutor();
ThreadPoolExecutor executor=(ThreadPoolExecutor)Executors.newFixedThreadPool()
使用Executor framework的第一步就是创建一个ThreadPoolExecutor类的对象。你可以使用这个类提供的4个构造器或Executors工厂类来 创建ThreadPoolExecutor。一旦有执行者,你就可以提交Runnable或Callable对象给执行者来执行。
20)创建一个大小固定的线程执行者
Executors类提供一个方法来创建大小固定的线程执行者。这个执行者有最大线程数。 如果你提交超过这个最大线程数的任务,这个执行者将不会创建额外的线程,并且剩下的任务将会阻塞,直到执行者有空闲线程。这种行为,保证执行者不会引发应用程序性能不佳的问题。
ThreadPoolExecutor executor=(ThreadPoolExecutor)Executors.newFixedThreadPool(5
);
21)
运行多个任务并处理第一个结果
executor.invokeAny(taskList)
invokeAll(Collection<? extends Callable<T>> tasks, long timeout,TimeUnit unit):此方法执行所有任务,当它们全部完成且未超时,返回它们的执行结果。TimeUnit类是个枚举类,有如下常量:DAYS,HOURS,MICROSECONDS, MILLISECONDS, MINUTES,,NANOSECONDS 和SECONDS。
如果我们有两个任务,可以返回true值或抛出异常。有以下4种情况:
- 两个任务都返回ture。invokeAny()方法的结果是第一个完成任务的名称。
- 第一个任务返回true,第二个任务抛出异常。invokeAny()方法的结果是第一个任务的名称。
- 第一个任务抛出异常,第二个任务返回true。invokeAny()方法的结果是第二个任务的名称。
- 两个任务都抛出异常。在本例中,invokeAny()方法抛出一个ExecutionException异常
22)执行者延迟运行一个任务
ScheduledThreadPoolExecutor executor=(ScheduledThreadPoolExecutor)Executors.newScheduledThreadPool(
1
);
executor.schedule(task,i+
1
, TimeUnit.SECONDS);
你必须使用schedule()方法,让执行者在一段时间后执行任务。这个方法接收3个参数,如下:
- 你想要执行的任务
- 你想要让任务在执行前等待多长时间
- 时间单位,指定为TimeUnit类的常数
在本例中,每个任务等待的秒数(TimeUnit.SECONDS)等于它在任务数组中的位置再加1。
23)执行者周期性地运行一个任务
当你想要使用执行者框架执行一个周期性任务,你需要ScheduledExecutorService对象。Java建议使用 Executors类创建执行者,Executors类是一个执行者对象工厂。在本例中,你应该使用newScheduledThreadPool()方法,创建一个 ScheduledExecutorService对象。这个方法接收池的线程数作为参数。正如在本例中你只有一个任务,你传入了值1作为参数。
一旦你有执行者需要执行一个周期性任务,你提交任务给该执行者。你已经使用了scheduledAtFixedRate()方法。此方法接收4个参数:你想要周期性执行的任务、第一次执行任务的延迟时间、两次执行之间的间隔期间、第2、3个参数的时间单位。它是TimeUnit类的常 量,TimeUnit类是个枚举类,有如下常量:DAYS,HOURS,MICROSECONDS, MILLISECONDS, MINUTES,,NANOSECONDS 和SECONDS。
很重要的一点需要考虑的是两次执行之间的(间隔)期间,是这两个执行开始之间的一段时间。如果你有一个花5秒执行的周期性任务,而你给一段3秒时间,同一时刻,你将会有两个任务在执行。
24)执行者取消一个任务
Future<String> result=executor.submit(task);
result.cancel(
true
);
当你想要取消你已提交给执行者的任务,使用Future接口的cancel()方法。根据cancel()方法参数和任务的状态不同,这个方法的行为将不同:
- 如果这个任务已经完成或之前的已被取消或由于其他原因不能被取消,那么这个方法将会返回false并且这个任务不会被取消。
- 如果这个任务正在等待执行者获取执行它的线程,那么这个任务将被取消而且不会开始它的执行。如果这个任务已经正在运行,则视方法的参数情况而定。 cancel()方法接收一个Boolean值参数。如果参数为true并且任务正在运行,那么这个任务将被取消。如果参数为false并且任务正在运行,那么这个任务将不会被取消。
25)执行者控制一个任务完成
http://ifeve.com/thread-executors-10/
FutureTask类提供一个done()方法,允许你在执行者执行任务完成后执行一些代码。你可以用来做一些后处理操作,生成一个报告,通过e-mail发送结果,或释放一些资源。当执行的任务由FutureTask来控制完成,FutureTask会内部调用这个方法。这个方法在任务的结果设置和它的状态变成isDone状态之后被调用,不管任务是否已经被取消或正常完成。
默认情况下,这个方法是空的。你可以重写FutureTask类实现这个方法来改变这种行为。在这个指南中,你将学习如何重写这个方法,在任务完成之后执行代码。