一、进程与线程
1、进程。每个运行中的程序就是一个进程,一般而言,进程有三个特征:
(1)、独立性。可以拥有自己独立的资源,拥有自己私有的地址空间。
(2)、动态性。主要是与程序的区别,程序是一个静态的指令集合,进程是一个动态的指令集合,即在进程中有个时间的概念。进程具有自己的生命周期和各种不同的状态,而程序不具备。
(3)、并发性。指各个进程可以在单独处理器上并发执行。
2、线程。一个进程内部可能包含多个顺序执行流,每个顺序执行流就是一个线程。
并发和并行性:
并发性(concurrency):同一时刻只有一条指令执行,但多条指令被快速轮换执行。
并行性(parallel):同一时刻有多条指令在多个处理器上同时执行。
多线程的优势:
(1)、易于通信。与分隔的进程相比,同一进程中线程之间的隔离程度较弱,所以更易于实现通信。
(2)、占内存资源少。同一进程多个线程之间共享进程的虚拟空间(共享的数据具体包括:进程代码段、进程的公有数据等),而不同进程必须分配各自的内存空间,所以线程所占内存资源更少。
二、线程的创建与启动
1、继承Thread 类创建;
2、实现Runnable 接口创建对象,再以此对象作为Thread 对象的目标(target);
3、实现Future 接口创建对象(Java5 开始),再以此对象作为Thread 对象的目标(target)。与实现Runnable 接口不同的是,Future 接口构造参数必须传一个Callable 接口对象,而Callable 接口的call()方法才是线程真正执行的方法,相当于run()方法,当它可以有返回值。另外,Java 已经提供了一个Future 接口的实现类FutureTask 类。
第3种方式示例:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TestFuture {
//Callable泛型接口定义真正执行的任务,泛型为返回值
public static Callable<Integer> task = null;
//Future 接口包装Callable 接口,并能获取Callable 接口call 方法的返回值,泛型为Callable 的泛型
public static FutureTask<Integer> ft = null;
public static void main(String[] args) {
task = new Callable<Integer>(){
@Override
public Integer call() throws Exception {
return new Integer(20);
}};
ft = new FutureTask<Integer>(task);
//开启新线程,以Future 接口对象作为target
new Thread(ft,"我的线程").start();
try {
boolean complete = false;
while(!complete){
if(ft.isDone()){
System.out.println("线程返回的值:"+ft.get().intValue());
complete = true;
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
说明:
Future<T> 接口提供了三个方法:T get()获取包装的Callable 接口的call 方法的返回值、boolean cancel(...)取消该Future 关联的Callable 任务、boolean isCancelled()是否在Callable 任务正常完成前被取消的、boolean isDone()是否完成已完成。
三种方式特点比较:
1、实现Runnable 或Future 接口的方式:(1)、还可继承其它类;(2)、多个线程可共享一个target,适合多个线程处理同一份资源的情况;(3)、访问当前线程只能使用Thread.currentThread()方法。
2、实现Thread 类方式:(1)、编程更简单,但不能再继承其它类;(2)、访问当前线程只需使用this 即可。
三、线程的生命周期
1、新建状态:刚new 出来时的状态;
2、就绪状态:调用start()方法后的状态,此时还未真正进入运行状态;
3、运行状态:线程获得了CPU资源,执行run()方法的状态;
4、阻塞状态:
(1)、调用了sleep()方法主动放弃CPU资源;
(2)、该线程调用了一个阻塞式IO方法,在该方法返回前,该线程被阻塞;
(3)、该线程试图获得同步监视器(即试图执行与synchronize相关的语句块或方法),但同步监视器被其他线程持有;
(4)、线程在等待某个通知(notify);
(5)、调用了suspend()方法将线程挂起(最好别用,易导致死锁。要解除挂起,需调用resume()方法)。
5、死亡状态:
(1)、run()或call()方法执行完毕,善终;
(2)、线程抛出一个未捕获的异常(Exception)或错误(Error);
(3)、直接调用线程的stop()方法结束该线程(不推荐,易导致死锁)。
注意事项:
1、线程的start()方法只能调用一次,线程死亡后不能再start();
2、当主线程结束时,其他线程不受任何影响,不会随之结束;
3、线程的run()或call()方法不能手动调用,否则它将不作为线程执行体。
四、线程控制
1、join线程:
Thread 提供了一个线程等待另一个线程完成的方法——join()方法。某线程调用此方法后,则该线程所在的线程才会继续执行,示例:
class TestThread extends Thread {
public static void main(String args[]) throws InterruptedException{
Thread t = new TestThread();
for (int i = 0; i < 10; i++) {
if(i==3){
t.start();
t.join();
}
System.out.println("主线程"+i);
}
}
public void run(){
for(int i=0;i<10;i++){
System.out.println("被join的线程:"+i);
}
}
}
结果:
主线程0
主线程1
主线程2
被join的线程:0
被join的线程:1
被join的线程:2
被join的线程:3
被join的线程:4
被join的线程:5
被join的线程:6
被join的线程:7
被join的线程:8
被join的线程:9
主线程3
主线程4
主线程5
主线程6
主线程7
主线程8
主线程9
2、后台线程
后台线程(Daemon Thread)又称守护线程或精灵线程,特征:(1)、所有前台线程都死亡后,后台线程会自动死亡,并不会执行完再死亡。JVM垃圾回收线程是典型的守护线程。
设置线程为守护线程:调用Thread 对象的 setDaemon(true)方法设置线程为守护线程(必须在start()方法前调用)。
判断线程是否为守护线程:isDaemon()。
3、线程睡眠
Thread 类的静态方法:static void sleep(long millis),线程睡眠millis 毫秒,并处于阻塞状态,不会竞争CPU资源。
4、线程让步
Thread 类的静态方法:static void yield(),线程被暂停,让系统的调度器重新调度一次,与sleep方法区别是,该线程完全可能又获得执行权(只有此一个线程时,概率就是百分百)。
5、sleep与 yield 方法区别
(1)、优先级问题。调用sleep()后,其它线程竞争cpu资源时不考虑线程优先级;但yield()方法只给优先级相同或更高的线程执行机会;
(2)、sleep()会进入阻塞状态,而yield()不会;
(3)、sleep()方法声明了抛出 InterruptedException 异常,但 yield()方法没有声明抛出任何异常;
(4)、sleep()方法比yield()方法有更好的可移植性(暂时不理解),通常不建议使用yield()方法。
6、改变线程优先级
优先级高的线程会获得更多的执行机会!优先级范围为1-10.
设置线程优先级:Thread 对象的setPriority(int newPriority);
获取线性优先级:Thread 对象的getPriority();
3个静态常量:
Thread.MAX_PRIORITY:10;
Thread.MIN_PRIORITY:1;
Thread.NORM_PRIORITY:5.
线程的各套控制方法整理:
sleep()、yield()可为一类:重新调度;
suspend() 与 resume()为一套:中途暂停与重新启用;
wait() 与notify() 或notifyAll() 为一套:等待与唤醒等待;
join() 方法为一类:执行完此线程后再继续执行该线程所在的线程。