2016-1-1
ch16 多线程
-
线程概述
Java提供了非常优秀的多线程支持,创建,控制,同步,线程池
-
线程和进程
进程:独立性,动态性,并发性
线程:进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈,自己的程序计数器和自己的局部变量,但不拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一个进程中多个线程之间可以并发执行。
-
多线程的优势
较之多进程编程方便,并发性高,性能好
-
-
线程的创建和启动
-
继承Thread类创建线程类
使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量
class FirstThread extends Thread { public int i=0; public void run() { for(;i<100;++i) { System.out.println(getName()+":"+i); } } } public class ThreadTest { public static void main(String[] args) { for(int i=0;i<100;++i) { System.out.println(Thread.currentThread().getName()+":"+i); if(i==20) { new FirstThread().start(); new FirstThread().start(); } } } }
-
-
实现Runnable接口创建线程类
(1)定义Runnable接口的实现类,并重写该接口的run()方法,该run方法的方法体同样是该线程的线程执行体;
(2)创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象;
(3)调用线程对象的start方法来启动该线程。
class SecondThread implements Runnable { private int i=0; public void run() { for(;i<100;++i) { System.out.println(Thread.currentThread().getName()+":"+i); } } } public class ThreadTest { public static void main(String[] args) { for(int i=0;i<100;++i) { System.out.println(Thread.currentThread().getName()+":"+i); if(i==20) { SecondThread stSecondThread=new SecondThread(); new Thread(stSecondThread,"新线程1:").start(); // try { // Thread.sleep(1); // } catch (Exception e) { // // TODO: handle exception // } new Thread(stSecondThread,"新线程2:").start(); } } } }
-
使用Callable和Future创建线程
Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大:
-
call()方法可以有返回值;
-
call()方法可以声明抛出异常
-
Callable对象不能直接作为Thread的target
FutureTask实现了Future接口和Runnable接口,可以作为Thread类的target
创建并启动有返回值的线程的步骤如下:
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值;
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值;
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程;
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
-
-
创建线程的三种方式对比
-
采用实现Runnable和Callable接口的方式创建多线程
-
-
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类
-
多个线程可以共享同一个target对象
-
如果需要访问当前线程必须使用Thread.currentThread()方法
-
采用继承Thread类的方式
-
不能再继承其他父类
-
编写简单
-
线程的生命周期
-
新建和就绪状态
-
-
调用start()方法后线程由新建状态切换至就绪状态
-
只能对处于新建状态的线程调用start()方法
-
可以通过Thread.sleep(1)来让当前线程休眠1ms,则系统立刻会调度其他线程运行
-
运行和阻塞状态
-
两种调度策略:抢占式调度策略,协作式调度策略
-
被阻塞的线程阻塞解除后,必须重新等待线程调度器再次调度它
-
线程从阻塞状态只能进入就绪状态,无法直接进入运行状态,调用yield()方法可以让运行状态的线程转入就绪状态
-
线程死亡
-
三种方式结束线程:a.方法体执行完毕 b.抛出异常 c.调用stop()方法
-
isAlive()测试线程是否死亡或处在新建状态
-
死亡状态的线程不能调用start()重新进入就绪态
-
新建状态的线程也不能调用两次start()
-
控制线程
-
join线程
执行线程的join()方法可以让调用线程等待被调用进程执行完毕再继续执行,即调用进程会被阻塞
-
后台线程
守护线程,thread.setDaemon(true)
设置后台线程必须在启动该线程之前设置(改变线程的优先级无此要求)
-
线程睡眠:sleep
Thread类的静态方法,暂停程序执行,会阻塞线程
-
线程让步:yield
Thread类的静态方法,不会阻塞线程,只是让线程回到就绪态 -
当某个线程调用了yield方法暂停后,只有优先级与当前线程相同,或者优先级比当前线程更高的处于就绪态的线程才会获得执行的机会
-
sleep方法抛出InterruptedException异常,所以调用sleep方法时要么捕捉该异常,要么显示声明抛出该异常,yield方法则没有声明抛出任何异常
sleep方法比yield方法有更好的可移植性,通常不建议使用yield方法来控制并发线程的执行
-
改变进程优先级
-
每个线程默认的优先级都与创建它的父线程的优先级相同
-
Thread类提供了setPriority(范围1~10之间)和getPriority来设置和返回指定线程的优先级
-
更好的移植性:MIN_PRIORITY,NORMAL_PRIORITY,MAX_PRIORITY
-
-
-
线程同步
-
线程安全问题
-
同步代码块
-
synchronized(obj)
{}
-
obj就是同步监视器
-
临界区
-
-
同步方法
使用synchronized关键字来修饰某个方法,无须显示指定同步监视器,通过使用同步方法可以非常方便的实现线程安全的类
可变类的线程安全是以降低程序的运行效率作为代价的:
-
只对必要的方法进行同步
-
提供单线程和多线程两个版本的可变类
-
举例:单线程环境使用StringBuilder,多线程环境使用 StringBuffer
-
-
释放同步监视器的锁定
-
同步锁(Lock)
-
-
功能更强大
-
Lock,ReadWriteLock是Java5提供的两个根接口,并为Lock提供了ReentrantLock(可重入锁)实现类;为ReadWriteLock提供了ReentrantReadWriteLock实现类
-
private final ReentrantLock lock=new ReentrantLock();
-
死锁
当两个线程相互等待对方释放同步监视器时就会发生死锁,Java虚拟机没有监测,也没有采取措施来处理死锁情况