JAVA编程思想笔记 : 并发
程序中的所有事物,在任意时刻都只能执行一个步骤.
并发的多面性
并发解决的问题大体上可以分为"速度"和"设计可管理性"两种
并发通常是提高运行在单处理器上的程序的性能.
实现并发最直接的方式是在操作系统级别使用进程.
困难: 协调不同线程驱动的任务之间的对内存/IO 资源的使用. 以使得这些资源不会同时被多个任务访问.
改进代码设计
Java 的线程机制是抢占式,表示调度机制会周期性的中断线程,将上下文切换到另一个线程,从而为每个线程都提供时间片,使得每个程序都会分配到数量合理的时间去驱动它的任务.
基本的线程机制
将程序划分为多个分离的/独立运行的任务.通过使用多线程机制,这些独立任务中的每一个都将由执行线程来驱动.
一个线程就是在进程中的一个单一的顺序控制流,因此,单个进程可以拥有多个并发执行的任务.
底层机制是切分 CPU 时间.
定义任务
线程可以驱动任务,因此你需要一种描述任务的方式,可以由 Runnable 接口来提供.
Thead 类
将 Runnable 对象转换为工作任务的传统方式就是把它交给一个 Thread 构造器.
使用 Executor
管理 Thread 对象.
单个 Executor 被用来创建和管理系统中的所有任务
shutdown () 方法的调用可以防止新的任务提交给Executor
从任务中产生返回值
Runnable 是执行工作的独立任务,但是它并不返回任何值.
任务完成之时能够返回值: 实现 Callable 接口.
Callabel 是一种具有参数类型的泛型,它的类型参数表示的事从方法 call()中返回值.
并且必须使用 ExecutorService.submit()方法调用它.
submit()方法会产生 Future 对象,他用 Callable 返回结果的特定类型进行了参数化.
用 isDone() 方法,查看任务是否完成.
package com.step21;
import java.util.ArrayList;
import java.util.concurrent.*;
class TaskWithResult implements Callable<String>{
private int id ;
public TaskWithResult(int id){
this.id = id ;
}
@Override
public String call() throws Exception {
return "result of TaskWithResult : "+id + " , currentThread : " + Thread.currentThread().getName();
}
}
public class CallableDemo {
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newCachedThreadPool();
ArrayList<Future<String>> list = new ArrayList<>();
for (int i = 0 ; i<10 ; i++) {
list.add(exec.submit(new TaskWithResult(i))) ;
}
for (Future<String> fs : list) {
System.out.println(fs.get());
}
exec.shutdown();
}
}
output:
result of TaskWithResult : 0 , currentThread : pool-1-thread-1
result of TaskWithResult : 1 , currentThread : pool-1-thread-2
result of TaskWithResult : 2 , currentThread : pool-1-thread-3
result of TaskWithResult : 3 , currentThread : pool-1-thread-3
result of TaskWithResult : 4 , currentThread : pool-1-thread-1
result of TaskWithResult : 5 , currentThread : pool-1-thread-3
result of TaskWithResult : 6 , currentThread : pool-1-thread-3
result of TaskWithResult : 7 , currentThread : pool-1-thread-1
result of TaskWithResult : 8 , currentThread : pool-1-thread-1
result of TaskWithResult : 9 , currentThread : pool-1-thread-3
休眠
sleep() 使任务终止执行指定的时间.
优先级
线程的优先级是将该线程的重要性传递给了调度器.
尽管 CPU 处理现有线程集的顺序是不确定的,但是调度器将倾向于让优先权最高的线程先执行.
然而,并不意味着优先权较低的线程将得不到执行.
优先级较低的线程仅仅是执行的频率较低.
在绝大多数的时间里,所有线程有应该以默认的优先级运行,试图操纵线程优先级通常是一种错误.
让步 yield()
后台线程
程序运行的时候在后台提供一种通用服务的线程,并且这种线程并不属于程序中不可或缺的部分.因此,当所有的非后台线程结束时.程序也就终止了,同时会杀死进程中的所有后台线程. 反过来说,只要有任何非后台线程还在运行,程序就不会终止.
必须在线程启动前调用 setDaemon()方法,才能把它设置为后台线程.
编码的变体
继承 Thread
加入一个线程
join() : 等待一段时间,直到第二个线程结束才继续执行.
package com.step21;
class Sleeper extends Thread{
private int duration ;
public Sleeper(String name , int sleepTime){
super(name);
this.duration = sleepTime;
start();
}
public void run (){
try {
sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "has awakened ... ");
}
}
class Joiner extends Thread{
private Sleeper sleeper ;
public Joiner(String name , Sleeper sleeper){
super(name);
this.sleeper = sleeper;
start();
}
public void run (){
try {
sleeper.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "join completed ... ");
}
}
public class Joining {
public static void main(String[] args) {
Sleeper sleepy = new Sleeper("Sleepy",1500) ;
Sleeper grumpy = new Sleeper("Sleepy",1500);
Joiner dopey = new Joiner("Dopey",sleepy) ;
Joiner doc = new Joiner("Doc",sleepy) ;
grumpy.interrupt();
}
}
Connected to the target VM, address: '127.0.0.1:55371', transport: 'socket'
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.step21.Sleeper.run(Joining.java:15)
Sleepyhas awakened ...
Sleepyhas awakened ...
Dopeyjoin completed ...
Docjoin completed ...
Disconnected from the target VM, address: '127.0.0.1:55371', transport: 'socket'
创建有响应的用户界面.
线程组
线程组持有一个线程集合.
"继续错误的代价由别人承担,承认错误的代价由自己承担"
捕获异常
由于线程的本质特性,使得你不能捕获从线程中逃逸的异常.
package com.step21;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
class ExceptionThread2 implements Runnable {
public void run(){
Thread t = Thread.currentThread();
System.out.println(" run () by " + t);
System.out.println(" eh = "+ t.getUncaughtExceptionHandler());
throw new RuntimeException();
}
}
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler{
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("caught : " + e );
}
}
class HandlerThreadFactory implements ThreadFactory{
public Thread newThread(Runnable r){
System.out.println(this+ " create new Thread .. ");
Thread t = new Thread(r) ;
System.out.println(" crreate " + t);
t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler() );
System.out.println(" eh = " + t.getUncaughtExceptionHandler() );
return t ;
}
}
public class CaptureUncaughtException {
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool(new HandlerThreadFactory());
exec.execute(new ExceptionThread2());
}
}
输出:
Connected to the target VM, address: '127.0.0.1:55756', transport: 'socket'
[email protected] create new Thread ..
crreate Thread[Thread-0,5,main]
eh = [email protected]
run () by Thread[Thread-0,5,main]
eh = [email protected]
[email protected] create new Thread ..
crreate Thread[Thread-1,5,main]
eh = [email protected]
caught : java.lang.RuntimeException
Disconnected from the target VM, address: '127.0.0.1:55756', transport: 'socket'
这个处理器只有在不存在线程专有的未捕获异常处理器的情况下才会被调用.
系统会检查线程专有版本, 如果没有发现,则检查线程组是否有其专有的 UNcaughtException()方法
如果也没有,在调用 defaultUncaughtExceptionHandler.
共享受限资源
不正确的访问资源
方案: 序列化访问共享资源 - 给定时刻只允许一个任务访问共享资源. - 互斥量
synchronized void f(){ /*......*/}
synchronized void g(){ /*......*/}
所有对象都自动含有单一的锁.当在对象上调用其任意 synchronized 方法的时候,次对象都被加锁.这时该对象上的其他 synchronized 方法只有等到前一个方法调用完毕并释放了锁之后才能被调用.
在使用并发时,将域设置为 private 是非常重要的,否则 synchronized 关键字就不能防止其他任务直接访问域,这样就会产生冲突.
读写线程必须用相同的监视器锁同步
每个访问临界共享资源的方法都必须被同步,否则他们就不能正确的工作.
使用显式的 Lock 对象
Lock 对象必须被显式地创建.锁定和释放.
Lock() 必须放置在 finally 子句中带有 unlock 的 try-finally 的语句中.
ReentrantLock允许你尝试着获取但最终未获得锁,如果其他人已经获取了锁,那么你就可以决定离开去做其他的事情了.而不是等待直至这个锁被释放.
//: concurrency/AttemptLocking.java
package concurrency; /* Added by Eclipse.py */
// Locks in the concurrent library allow you
// to give up on trying to acquire a lock.
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
public class AttemptLocking {
private ReentrantLock lock = new ReentrantLock();
public void untimed() {
boolean captured = lock.tryLock();
try {
System.out.println("tryLock(): " + captured);
} finally {
if(captured)
lock.unlock();
}
}
public void timed() {
boolean captured = false;
try {
captured = lock.tryLock(2, TimeUnit.SECONDS);
} catch(InterruptedException e) {
throw new RuntimeException(e);
}
try {
System.out.println("tryLock(2, TimeUnit.SECONDS): " + captured);
} finally {
if(captured)
lock.unlock();
}
}
public static void main(String[] args) {
final AttemptLocking al = new AttemptLocking();
al.untimed(); // True -- lock is available
al.timed(); // True -- lock is available
// Now create a separate task to grab the lock:
new Thread() {
{ setDaemon(true); }
public void run() {
al.lock.lock();
System.out.println("acquired");
}
}.start();
Thread.yield(); // Give the 2nd task a chance
al.untimed(); // False -- lock grabbed by task
al.timed(); // False -- lock grabbed by task
}
}
/* Output:
tryLock(): true
tryLock(2, TimeUnit.SECONDS): true
acquired
tryLock(): false
tryLock(2, TimeUnit.SECONDS): false
*///:~
原子性与易变性
原子操作时不能被线程调度机制中断的操作.
尚未完成,节后整理.........
680