多线程在较低的层次上拓展了多任务的概念:一个程序同时执行多个任务。多进程与多线程本质区别在于每个进程拥有自己的一整套变量,而线程则共享数据。
一、线程
1. 多线程
Thread(Runnable target)
void start()//创建新线程应该调用该方法
void run()
2. 中断线程
中断线程并不意味着终止线程
Runnable r = () -> {
try{
while(! Thread.currentThread().isInterrupted() && more word to do){
do more word
}
}catch(InterruptedException e){
//thread was interrupted furing sleep or wait
}
finally{
cleanup,if required
}
}
java.lang.Thread1.0
void interrupt//向线程发送中断请求,状态被置为true,如果该线程被一个sleep调用阻塞,则抛出InterruptedException异常
static boolean interrupted()//测试当前线程是否被中断,如果是,则会将当前中断状态重置为false
boolean interrupted()//与静态版本不同,该调用不会重置中断状态
static Thread currentThread()//返回代表当前执行线程的Thread对象
3. 线程状态
线程有6种状态:
- New 新创建 使用new操作符创建一个新线程,
new Thread(r)
- Runnable 可运行
- Blocked 被阻塞
- Waiting 等待
- Timed waiting 计时等待
- Terminated 被终止
3.1 New
New 新创建 使用new操作符创建一个新线程,new Thread(r)
3.2 Runnable
一旦调用start方法,线程处于runnable状态。一个可运行的线程可能正在运行也可能没有运行。一个正在运行中的线程仍然处于可运行的状态
3.3 被阻塞线程和等待线程
当线程处于被阻塞或等待状态时,它暂时不活动。
- 当一个线程试图获取一个内部的对象锁,而该锁被其他线程所持有,则该线程进入阻塞状态。
- 当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。Object.wait,Thread.join。被阻塞状态与等待状态是有很大不同的。
- 有几个方法有一个超时参数。调用它们导致线程进入计时等待状态。这一状态将一直保持到超时期满或者接收到适当的通知。Thread.sleep,Thread.join,Lock.tryLock以及Condition.await的计时版本。
3.4 被终止的进程
有两个原因被终止:
- 因为run方法正常退出而自然死亡
- 因为一个没有捕获的异常终止了run方法而意外死亡
java.lang.Thread 1.0
void join()
void join(long millis)
Thread.State getState() 5.0//得到线程的状态
二、线程属性
1 线程优先级
在Java程序设计语言中,每个线程有一个优先级。默认情况下,一个线程继承它的父线程的优先级。当虚拟机依赖于宿主机平台的线程实现机制时,Java线程的优先级被映射到宿主机平台的优先级上,也许更多也许更少。因此不要将程序构建为功能的正确性依赖于优先级。
2 守护线程
t.setDaemon(true)
为其他线程提供服务。
3 未捕获异常处理器
线程的run方法不能抛出任何受查异常,但是非受查异常会导致线程终止。不需要catch自句来处理可以被传播的异常,相反就在线程死亡之前,异常被传递到一个用于未捕获异常的处理器。该处理器必须属于一个实现Thread.UncaughtExceptionHandler接口的类。该接口只有一个方法void uncaughtException(Thread t, Throwable e)
setUncaughtExceptionHandler方法为任何线程安装一个处理器。。。
三、同步
根据各线程访问数据的次序,可能会产生讹误的对象。这样一个情况通常被称为竞争条件。
1. 锁对象
ReentrantLock类
下面的代码结构保证任何时刻只有一个线程进入临界区。
myLock.lock();//a ReentrantLock object
try{//不能使用带资源的try语句
critical section
}finally{
myLock.unlock();
}
2. 条件对象
public class Bank {
private final double[] accounts;
private Lock bankLock;
private Condition sufficientFunds;
public Bank(int n, double initialBalance){
accounts = new double[n];
Arrays.fill(accounts, initialBalance);
bankLock = new ReentrantLock();
sufficientFunds = bankLock.newCondition();
}
public void transfer(int from, int to, double amount) throws InterruptedException{
bankLock.lock();
try{
while(accounts[from] < amount)
sufficientFunds.await();
System.out.println(Thread.currentThread());
accounts[from] -= amount;
System.out.printf(" %10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
sufficientFunds.signalAll();
}finally {
bankLock.unlock();
}
}
}
java.util.concurrent.locks.Lock 5.0
Condition newCondition();//返回一个条件对象
void await()// 将该线程放到条件的等待集中
void signalAll()//解除该条件的等待集中的所有线程的阻塞状态
void signal()//从该条件的等待集中随机地选择一个线程,解除其阻塞状态。
3. synchronized
锁分为对象锁和类锁
可重入性:线程级别
锁和条件的关键之处
- 锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码
- 锁可以管理试图进入被保护代码段的线程
- 锁可以拥有一个或多个相关的条件对象
- 每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程
如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。
缺陷- 锁释放条件少、无法设置超时时间、不能中断一个正在试图获得锁的线程
- 不够灵活,仅有单一对象
- 无法知道是否成功获取一把锁
如何选择Lock和synchronized - 尽量使用并发包而不是锁
- 可以使用synchronized那就优先使用synchronized
class Bank{
private double[] accounts;
public synchronized void transfer(int from, int to, int amount) throws InterruptedException{
while(accounts[from] < amount)
wait(); //
...
notifyAll();
}
public synchronized double getTotalBalance(){...}
}
内部锁和条件存在局限:
- 不能中断一个正在试图获得锁的线程
- 试图获得锁时不能设定超时
- 每个锁仅有单一的条件,可能是不够的
java.lang.Object
void notifyAll()
void notify()//随机
void wait()
void wait(long millis)
void wait(long millis, int nanos)
4. 同步阻塞
private Object lock = new Object;
synchronized(lock){ // lock仅仅用于加锁
account[from] -= amount;
accounts[to] += amount;
}
5. 监视器概念
监视器特性:
- 监视器是只包含私有域的类
- 每个监视器类的对象有一个相关的锁
- 使用该锁对所有的方法进行加锁
- 该锁可以有任意多个相关条件
然而java对象不同于监视器,不要求必须是private,不要求必须是synchronized,内部锁对客户是可用的
6. Volatile域
为实例域的同步访问提供了一种免锁机制。如果声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。
private volatile boolean done;
7. 读写锁
java.util.concurrent.locks.ReentrantReadWriteLock 5.0
Lock readLock();
Lock writeLock();
四、阻塞队列
对于许多线程问题,可以通过使用一个或多个队列以优雅且安全地方式将其形式化。
java.util.concurrent.ArrayBlockingQueue<E> 5.0
ArrayBlockingQueue(int capacity)
ArrayBlockingQueue(int capacity, boolean fair)//构造阻塞队列
java.util.concurrent.LinkedBlockingQuque<E> 6
java.util.concurrent.LinkedBlockingDeque<E> 6
LinkedBlockingQueue()
LinkedBlockingDeque
LinkedBlockingQueue(int capacity, boolean fair)//构造阻塞队列
LinkedBlockingDeque(int capacity, boolean fair)//构造阻塞队列
put,take,offer,poll
五、线程安全的集合
1、高效的映射、集和队列
java.util.concurrent包提供了映射、有序集和队列的高效实现:ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、ConcurrentLiskedQueue。
并发的散列映射表,可高效地支持大量的读者和一定数量的写者。
2、映射条目的原子更新
六、Callable与Future
Callable与Runnable类似,但是由返回值
public interface Callable<V>
{
V call() throws Exception;
}
FutureTask包装器
Callable<Integer> myComputation = ...;
FutureTask<Integer> task = new FutureTask<Integer<(myComputation);
Thread t = new Thread(task);
t.start();
...
integer result = task.get();
七、执行器
构建一个新的线程是有代价的,因为涉及与操作系统的交互。如果程序中创建了大量的生命期很短的线程,应该使用线程池。使用线程池能减少并发线程的数目。
执行器类有许多静态工程方法用来构建线程池。
1.执行者工厂方法
方法 | 描述 |
---|---|
newCacheedThreadPool | 必要时创建新线程;空闲线程会被保留60秒 |
newFixedThreadPool | 该池包含固定数量的线程;空闲线程会一直被保留 |
newSingleThreadExecutor | 只有一个线程的“池”,该线程顺序执行每一个提交的任务(类似于Swing事件分配线程) |
newScheduledThreadPool | 用于预定执行而构建的固定线程池,替代java.util.Timer |
newSingleThreadScheduledExecutor | 用于预定执行而构建的单线程“池” |
2.使用连接池
- 调用Executors类中静态的方法newCachedThreadPool或…
- 调用submit提交Runnable或Callable对象
- 如果想要取消一个任务,或如果提交Callable对象,那就要保存好返回的Future对象
- 当不再提交任何任务时,调用shutdown
3.预定执行
4.控制任务组
invokeAny方法提交所有对象到一个Callable对象的集合中,并返回某个已经完成了的任务的结果。无法知道返回的究竟是哪个任务的结果,也许是最先完成的那个也许是最后。
List<Callable<T>> tasks = ...;
List<Future<T>> results = executor.invokeAll(tasks);
for(Future<T> result : results)
precessFurthre(result.get());
5.Fork-Join框架
ForkJoinPool pool = new ForkJoinPool()
pool.invoke(counter);
...
invokeAll(first, secod);
并发这一块较为繁琐,现在只是入个门