多线程之前学过,很久没用忘得差不多了,重新学习一下,然后再看《Java并发编程的艺术》。
一、进程与线程
计算机同时运行多个程序,每个运行中的程序就是一个进程。每一个进程内部又有多个线程。
进程有如下三个特征:
(1)独立性:它可以有自己独立的资源,有自己的私有地址空间;
(2)动态性:进程是一个在系统中活动的指令集合,具有自己的生命周期和不同的状态;
(3)并发性:多个进程可以在单个处理器上并发执行。
并发性与并行性:
(1)并发指在同一时刻只有一条指令执行,处理器快速切换多个进程指令;
(2)并行性指在同一时刻,有多条指令在多个处理器上同时执行。
现代操作系统都支持多进程的并发,常用方式有:共用式多任务操作策略和抢占式多任务操作策略。
二、创建线程的方式
1、继承Thread类
1)继承Thread类,并重新写run()方法;
2)创建上述类的实例,即创建线程对象;
3)调用start()方法启动线程。
public class FirstThread extends Thread {
private int i=0;
public void run() {
for(;i<100;++i) {
System.out.println(getName()+" "+i);
}
}
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();
}
}
}
}
Thread.currentThread()是Thread类的静态方法,返回当前正在执行的线程对象; getName()是Thread类的实例方法,返回调用该方法的线程的名字。
2、实现Runnable接口
1)定义Runnable接口的实现类,重写run()方法
2)创建Runnable实现类的实例,并作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。
可以在创建Thread类对象的时候为该对象取一个名字:new Thread(secondThread,"线程2");
public class SecondThread implements Runnable {
private int i = 0;
@Override
public void run() {
for (; i < 100; ++i) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 20) {
SecondThread st1 = new SecondThread();
new Thread(st1,"new-1").start();
new Thread(st1,"new-2").start();
}
}
}
}
Runnable接口是函数式接口,可以使用Lambda表达式创建Runnable对象。
上面代码中,多个线程共享了一个target。
3、使用Callable和Future创建线程
Callable像是Runnable的增强版:
call()方法可以有返回值;
call()方法可以声明,抛出异常。
Java5提供了Future接口来代表Callable接口中call()方法的返回值。它有FutureTask实现类,该类实现了Future接口和Runnable接口,可以作为Thread类的Target。
该方法创建线程的步骤:
1)创建Callable接口的实现类,并实现call()方法,call方法为线程执行体,可以有返回值。再创建Callable的实例。
2)使用FutureTask类来包装Callable对象。
3)使用FutureTask对象作为Thread对象的target创建并启动线程。
4)可以使用FutureTask类的get()方法来获得子线程执行结束后的返回值。
public class ThirdThread {
public static void main(String[] args) {
ThirdThread rt = new ThirdThread();
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()-> {
int i=0;
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"的循环变量i的值:"+i);
}
return i;
});
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+"的循环变量i的值:"+i);
if(i==20) {
new Thread(task,"有返回值的线程").start();
}
}
try {
System.out.println("子线程的返回值:"+task.get());
}catch(Exception e) {
e.printStackTrace();
}
}
}
三、线程的生命周期
在线程的生命周期中,要经过:新建、就绪、运行、阻塞、死亡5种状态。
当使用new关键字创建了一个线程后,该线程就处于新建状态。(此时还没有表现出来线程的动态特性);当调用start()方法后,线程处于就绪状态,JVM会为其创建方法调用栈和程序计数器;当就绪状态的线程获得了CPU资源,开始执行run()方法,此时处于运行状态;当发生如下情况时,程序处于阻塞状态:
1线程调用sleep()方法主动放弃处理器资源;
2该线程视图获得一个同步监视器,但是该监视器被其他线程持有;
3线程在等待通知notify
4调用了线程的suspend()方法将线程挂起。
而当sleep了指定时间;获得了同步监视器;其他线程发出了通知;挂起的线程被调用了resume()的方法,时可以解除阻塞,让线程重写处于就绪状态。
当:1run或call方法执行完成;2线程抛出异常或error;3直接调用stop()方法结束该线程(容易死锁),该线程死亡。