一多线程基本概念
独占:通过阻止多个并发行为间的有害干扰来维护状态的一致性通常使用异步方法sychronized
状态依赖:触发阻止延迟恢复某些行为是由一些对象是否处在这些行为可能成功或者已经
成功的状态决定的主要通过监视器(monitor)实现 objectwait objectnotify objectnotifyAll
客户Client: Client Object 期待某个动作的执行
服务Serverce :包括执行这个动作的代码
主体客户和服务都可以是一个对象这个对象就是一个主体而又他调用的对象通常就是辅助对象(help)
Synchronized: 防止并法导致数据不一致可以修饰一个方法或者代码块
Object 或子类的每一个实例都会在进入这个synchronized方法前加锁并在离开这个方法时自动释放锁
使用synchronized关键字来定义方法就会锁定类中所有使用synchronzied关键字定义的静态方法或非静态方法
不同的对象实例的synchronized方法是不相干扰的也就是说其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法
对于修饰静态的方法它可以对类的所有对象实例起作用
对代码块的加锁也是只是需要指明对那个对象加锁在静态代码块中使用类class来指明
在非静态代码块中多数是用this
synchronized关键字是不能继承的也就是说基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){}而是变成了f(){}继承类需要你显式的指定它的某个方法为synchronized方法
规则
)永远只是在更新对象的成员变量是加锁
)永远只是在访问有可能被更新对象的成员变量时加锁
)永远不要在调用其它对象的方法时加锁
二 ncurent 包介绍
执行器(Executor) 用来管理Thread 对象下面介绍三种Executor
() newCachedThreadPool
ExecutorService exec = ExecutorsnewCachedThreadPool();//创建一个可根据需要创建新线程的线程池
for(int = ; i<; i++){
execexecute(new LiftOff());//LiftOff implements Runnable接口的类
execshutdown()
}
() newFixedThreadPool
ExecutorService exec = ExecutorsnewFixedThreadPool();//创建一个可重用固定线程数的线程池以共享的无界队列方式来运行这些线程
for(int = ; i<; i++){
execexecute(new LiftOff());//LiftOff implements Runnable接口的类
execshutdown()
}
() newSingleThreadPool
SingleThreadPool 就好象是线程数量为的FixedThreadPool如果向SingleThreadPool提交多个任务那么这些任务都将排列每个任务都会在下一个任务开始之前运行结束
ExecutorService exec = ExecutorsnewSingleThreadPool();//创建一个使用单个 worker 线程的 Executor以无界队列方式来运行该线程
for(int = ; i<; i++){
execexecute(new LiftOff());//LiftOff implements Runnable接口的类
execshutdown()
}
从任务中产生返回值
Runnable是执行工作的独立任务但是不返回任何值如果实现Callable 接口则Callable接口方法call()可以返回一个值
package cdlchen;
/**
* @ Class CallableDemojava
* @ Description
* @ Company OpenData
* @ Author yinlei
* @ Version
* @ Date Mar
*/
import javautilArrayList;
import ncurrentCallable;
import ncurrentExecutorService;
import ncurrentExecutors;
import ncurrentFuture;
public class CallableDemo{
public static void main(String[] args){
ExecutorService exec = ExecutorsnewCachedThreadPool();
ArrayList<Future<String>> results = new ArrayList<Future<String>>();
for(int i = ; i < ; i++){
resultsadd(execsubmit(new TaskWithResult(i)));//这里使用submit 方法会产生Future 对象
}
for(Future<String> fs:results){
try{
if(fsisDone()){//检查Future是否已经完成或者不用isDone()来检查那么get()将会阻塞直到结果准备就绪
Systemoutprintln(fsget());//通过get()得到call()方法返回的值
}
} catch(Exception ex){
exprintStackTrace();
} finally{
execshutdown();
}
}
}
}
class TaskWithResult implements Callable<String>{
private int id;
public TaskWithResult(int id){
thisid = id;
}
public String call(){
return result of TaskWithResult + id;
}
}
//结果
result of TaskWithResult
result of TaskWithResult
result of TaskWithResult
result of TaskWithResult
result of TaskWithResult
result of TaskWithResult
result of TaskWithResult
result of TaskWithResult
result of TaskWithResult
result of TaskWithResult
sleep() 和wait()方法的区别
sleep是Thread类的静态方法sleep的作用是让线程休眠制定的时间在时间到达时恢复也就是说sleep将在接到时间到达事件事恢复线程执行其控制范围是由当前线程块
wait是Object的方法也就是说可以对任意一个对象调用wait方法调用wait方法将会将调用者的线程挂起直到其他线程调用同一个对象的notify方法才会重新激活调用者
sleep()是让某个线程暂停运行一段时间其控制范围是由当前线程决定也就是说在线程里面决定好比如说我要做的事情是 点火>烧水>煮面而当我点完火之后我不立即烧水我要休息一段时间再烧对于运行的主动权是由我的流程来控制
而wait()首先这是由某个确定的对象来调用的将这个对象理解成一个传话的人当这个人在某个线程里面说暂停!也是 thisOBJwait()这里的暂停是阻塞还是点火>烧水>煮饭thisOBJ就好比一个监督我的人站在我旁边本来该线 程应该执行后执行再执行
而在处被那个对象喊暂停那么我就会一直等在这里而不执行但正个流程并没有结束我一直想去煮饭但还没被允许 直到那个对象在某个地方说通知暂停的线程启动!也就是thisOBJnotify()的时候那么我就可以煮饭了这个被暂停的线程就会从暂停处 继续执行
本质的区别是一个线程的运行状态控制一个是线程之间的通讯的问题
tjoin();方法
package cdlchen;
/**
* @ Class Joiningjava
* @ Description
* @ Company OpenData
* @ Author yinlei
* @ Version
* @ Date Mar
*/
class Sleeper extends Thread{
private int duration;
public Sleeper(String name int sleepTime){
super(name);
duration = sleepTime;
start();
}
public void run(){
try{
sleep(duration);
}catch(InterruptedException ex){
Systemoutprintln(thisgetName()+ was interrupted+isInterrupted():+thisisInterrupted());
}
Systemoutprintln(thisgetName()+ has awakened);
}
}
class Joiner extends Thread{
private Sleeper sleeper;
public Joiner(String name Sleeper sleeper){
super(name);
thissleeper = sleeper;
start();
}
public void run(){
try{
sleeperjoin();//线程Sleeper在线程Joiner上执行join()则Joiner 线程将被挂起直到sleeper 线程结束(isAlive()==false)才恢复
} catch(InterruptedException ex){
Systemoutprint(Interrupted);
}
Systemoutprintln(thisgetName()+ join completed);
}
}
public class Joining {
public static void main(String []args){
Sleeper sleepy = new Sleeper(Sleepy);
Sleeper grumpy = new Sleeper(Grumpy);
Joiner dopey = new Joiner(Dopeysleepy);
Joiner doc = new Joiner(Docgrumpy);
grumpyinterrupt();//grumpy 中断doc 将继续
}
}
out put:
Grumpy was interruptedisInterrupted():false//当线程在该线程上调用interrupt()时将给该线程设定一个标志然而异常在捕获时会清理这个标志所以在catch 代码块里调用interrupt()总是false
Grumpy has awakened
Doc join completed
Sleepy has awakened
Dopey join completed
显示加锁ncurrentlocks
public synchronized int next(){
try{
return
}catch(){
}
}
private Lock lock = new ReentrantLock();
public int next(){
try{
locklock();
return
} catch(){
lockunlock();
}
使用synchronized关键字和显示Lock都可以解决资源同步的问题并且synchronized 关键字比显示加锁代码量少更少但是当synchronized关键字不
能尝试获取锁一段时间然后放弃它
线程本地化javalangThreadLocal
它的功用非常简单就是为每一个使用该变量的线程都提供一个变量值的副本是每一个线程都可以独立地改变自己的副本而不会和其它线程的副本沖突从线程的角度看就好像每一个线程都完全拥有该变量
通常通过get()set()方法来访问该对象的内容get()方法返回与其线程相关联的对象副本而set()方法将参数值插入到为其线程存储的对象里
package cdlchen;
import javautilRandom;
import ncurrentExecutorService;
import ncurrentExecutors;
import ncurrentTimeUnit;
class Accessor implements Runnable{
private final int id;
public Accessor(int idn){id = idn;}
public void run(){
while(!ThreadcurrentThread()isInterrupted()){
ThreadLocalVariableHolderincrement();
Systemoutprintln(this);
Threadyield();
}
}
public String toString(){
return #+id+:+ThreadLocalVariableHolderget();
}
}
public class ThreadLocalVariableHolder{
private static ThreadLocal<Integer> value=
new ThreadLocal<Integer>(){
private Random rand = new Random();
protected synchronized Integer initialValue(){
return randnextInt();
}
};
public static void increment(){
valueset(valueget() + );
}
public static int get(){return valueget();}
public static void main(String []args) throws Exception{
ExecutorService exec = ExecutorsnewCachedThreadPool();
for(int i = ; i < ; i++){
execexecute(new Accessor(i));
}
TimeUnitSECONDSsleep();
execshutdownNow();
}
}
//output:
#:
#:
#:
#:
#:
#:
#:
#:
#:
线程中捕获异常
任务的run()方法通常总会有某种形式的循环使得任务一直运行下去直到不再需要所以要设定跳出循环的条件
通常它被写成无限循环的形式这就意味着除非有个条件使用它终止否则它将永远运行下去
由于线程的本质特性所以我们不能捕获到从线程中逃逸的异常比如:
package chenlly;
public class ThreadException implements Runnable {
public void run() {
throw new RuntimeException(Im throw Exception);
}
public static void main(String []args){
try{
Thread t = new Thread(new ThreadException());
tstart();
} catch(Exception ex){
Systemoutprintln(Exception has been handled!);
exprintStackTrace();
}
}
}
/* output
Exception in thread Thread javalangRuntimeException: Im throw Exception
at chenllyThreadExceptionrun(ThreadExceptionjava:)
at javalangThreadrun(Unknown Source)
也就是说tyr catch 无法捕获到从run 方法里逃逸的异常
ThreadUncaughtExceptionHandler这个接口就是当 Thread 因未捕获的异常而突然终止时调用处理程序的接口
它允许你在每个Thread对象上都附着一个异常处理器
package chenlly;
public class ThreadException implements Runnable {
public void run() {
throw new RuntimeException(Im throw Exception );
}
public static void main(String[] args) {
Thread t = new Thread(new ThreadException());
tsetUncaughtExceptionHandler(new MyUncaughtException());//添加一个异常处理器
tstart();
}
}
//异常处理器
class MyUncaughtException implements ThreadUncaughtExceptionHandler {
public void uncaughtException(Thread t Throwable e) {
Systemoutprintln(t + : + egetMessage());
}
}
/* output
Thread[Threadmain] : Im throw Exception
线程之间的协助
多个任务可以通过使用锁或者synchronized(互斥)的方法来同步两个线程的行为从而使的一个任务不会干涉另外一个任务的资源
那么如何使得多个任务可以在一起彼此协作去解决某个问题使用相同的互斥基础互斥互斥能够确保只有一个任务可以响应某个信号
eg:一个是将蜡涂到Car上一个是抛光它抛光任务在涂蜡完成之前是不能操作的而涂蜡任务在涂另外一层蜡之前必须等待抛光结束
car 对象通过wait和notifyAll来挂起和重新启动这些任务
package chenlly;
import ncurrentExecutorService;
import ncurrentExecutors;
import ncurrentTimeUnit;
class Car{
private boolean waxOn = true;//默认为抛光状态同步信号
//打蜡完成设置状态并且唤醒打蜡
public synchronized void waxed(){
waxOn = false;
notifyAll();
}
//抛光完成设置状态并且唤醒打蜡
public synchronized void buffing(){
waxOn = true;
notifyAll();
}
//是否抛光结束
public synchronized void waitForWaxed() throws InterruptedException{
while(!waxOn){
wait();
}
}
//是否打蜡结束
public synchronized void waitForBuffing() throws InterruptedException{
while(waxOn){
wait();
}
}
}
//打蜡
class WaxOn implements Runnable{
private Car car;
public WaxOn(Car car){
thiscar = car;
}
public void run() {
try{
while(!Threadinterrupted()){
carwaitForWaxed();
Systemoutprintln(Wax On!);
TimeUnitSECONDSsleep();
carwaxed();
}
} catch (Exception ex){
exprintStackTrace();
}
}
}
//抛光
class Buffing implements Runnable{
private Car car;
public Buffing(Car car){
thiscar = car;
}
public void run() {
try{
while(!Threadinterrupted()){
carwaitForBuffing();
Systemoutprintln(Buffing On!);
TimeUnitSECONDSsleep();
carbuffing();
}
} catch (Exception ex){
exprintStackTrace();
}
}
}
public class WaxOMatic {
public static void main(String []args) {
try{
Car car = new Car();
ExecutorService exec = ExecutorsnewCachedThreadPool();
execexecute(new WaxOn(car));
execexecute(new Buffing(car));
TimeUnitSECONDSsleep();
execshutdownNow();
} catch (InterruptedException ex){
exprintStackTrace();
}
}
}
/* output
Wax On!
Buffing On!
Wax On!
Buffing On!
Wax On!
Buffing On!
打蜡和抛光相继进行控制权在这两者之间相互传递时这两个步骤过程在不断地重复秒后中断这两个任务
线程死锁问题
死锁某个任务在等待另外一个任务而后者又在等待别的任务这样一直下去任务之间连续的循环就是死锁问题
典型的哲学家就餐就是死锁问题
死锁的四个必要条件
() 互斥条件:资源不能被共享只能由一个进程使用
() 请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源
() 非剥夺条件:经分配的资源不能从相应的进程中被强制地剥夺
() 循环等待条件(Circular wait):系统中若干进程组成环路该环路中每个进程都在等待相邻进程正占用的资源
处理死锁的策略
通过破除死锁四个必要条件之一来防止死锁产生
哲学家就餐问题:该问题涉及到五个哲学家他们交替地进行思考和进餐
他们分别坐在位于一个圆形餐桌周围的五把椅子上圆桌中央是一碗米饭餐桌上共有五根筷子
分别摆放在每两个相邻座位的中间
当哲学家思考时他不与其他人交谈当哲学家饑饿时他将拿起和他相邻的两根筷子进行进餐
但他很可能仅拿到一根此时旁边的另一根正在他邻居的手中
只有他同时拿到两根筷子时他才能开始进餐完成进餐后
他将两根筷子分别放回原位然后再次开始思考