文章目录
一、线程和进程的概念
1.概念
- 进程指的一段正在运行的独立的应用程序,进程是程序执行过程中资源分配和管理的基本单位。进程有独立的空间。相对于线程安全性更高。
- 一个程序运行中可以执行多个任务,任务称之为线程。线程是组成进程的基本单元,每一个线程完成特定的功能。线程是cpu执行的最小单位。 线程存在共享的空间。
2.多线程和多进程的区别:
- 每个进程拥有自己独有的数据,线程共享数据,线程之间的通信相比于进程之间的通信更有效更容易
- 线程相比于进程创建/销毁开销更小。
- 进程是资源分配的最小单位,线程是cpu调度的最小单位
- 多进程程序更加健壮,多线程程序只要有一个线程挂掉,对其共享资源的其他线程也会产生影响。
- 如果追求速度,或者频繁创建和销毁,选择线程。因为进程创建和销毁开销很大(需要不停的分配资源)。如果追求系统更加稳定,选择进程。线程是轻量级的进程。
二、线程的创建
1.继承Thread 类
需要重写Thread类的Run方法,然后调用start()方法启动线程,启动线程后执行的是run方法。
- 创建线程的步骤:
- 创建一个类继承Thread类,重写run()方法,将所要完成的任务代码写进run()方法中;
- 创建Thread类的子类的对象;
- 调用该对象的start()方法,该start()方法表示先开启线程,然后调用run()方法。
public class TestThread extends Thread{
@Override
public void run() {
for(int i=1; i<=3; i++) {
System.out.println(this.getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.Runnable 实现
需要重写run方法,然后将这个实现类当做参数传给Thread类
- 创建线程的步骤:
- 创建一个类并实现Runnable接口;
- 重写run()方法,将所要完成的任务代码写进run()方法中;
- 创建实现Runnable接口的类的对象,将该对象当做Thread类的构造方法中的参数传进去;
- 使用Thread类的构造方法创建一个对象,并调用start()方法即可运行该线程。
public class TestRunnable implements Runnable{
@Override
public void run() {
int times = 5;
for(int i=0; i<times; i++) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(100);
}
catch(InterruptedException ie){
ie.printStackTrace();
}
}
}
}
3.Callable 实现
- 创建线程步骤:
- 创建Callable接口的实现类,重写call方法
- 创建Callable实现类的实例,使用FutureTask包装该实例
- .将FutureTask实例作为参数创建线程对象
- 启动该线程
- 调用FutureTask的get方法获取子线程的执行结果
public interface Callable<V>{
V call() throws Exception;
}
接口存在Executor框架中类,相比于Runnable更加强大:
- Callable可以在任务执行结束之后提供一个返回值
- call方法可以抛出异常
- 运行Callable任务可以拿到一个Future对象,Future提供get方法
4.匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}).start();
三、线程的生命周期及常用方法解析
(一) 线程基本状态
1.new(新建状态)
当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
2.Runnable(就绪状态)
当调用线程对象的start()
方法,线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了x.start()此线程立即就会执行。
3.Running(运行状态)
4.Blocked(阻塞状态)
处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。
5.Waiting(等待状态)
等待一个监视器锁进入同步代码块或者同步方法,代码块/方法某一时刻只能够有一个线程执行,其他线程只能等待。
6.Timed_Waiting(睡眠状态)
用sleep()
方法可使线程进入睡眠方式。一个睡眠着的线程在指定的时间过去可进入就绪状态。
7.Terminated(终止状态)
是一个线程的最终状态,线程进入到Terminated状态,意味着该线程的生命周期结束了,下面这些情况都会使得线程进入Terminated状态
- 线程执行正常结束
- 线程运行出错意外结束
- JVM crash
(二)常用方法
start() 启动线程
启动一个线程,将线程添加一个线程组中,线程状态会从New状态转换到Runnable状态,然后获取Cpu之后进入Running状态执行run
run() 子线程执行体
yield() 线程让步
让步于相同优先级或更高优先级的线程来执行。yield表示提醒cpu调度器我愿意放弃当前的cpu资源,(属于启发式方法),如果cpu资源不紧张的情况下,会忽略这种提示
- yield和sleep的区别:
- jdk1.5之前,yield调用了sleep
- sleep使得当前线程暂停执行,不会占用cpu资源,yield只是对于cpu调度器的一个提示
- sleep会导致线程短暂的阻塞,yield会使得线程Runnable -> Runnable
- sleep会捕获到中断异常,yield不会捕获到中断异常
sleep() 线程睡眠
sleep是一个静态方法,其中存在两个重载函数
public static native void sleep(long millis)
public static void sleep(long millis, int nanos)
使得线程进入睡眠状态,暂停执行,sleep不会放弃monitorlock的所有权。jdk1.5之后,jdk引入一个枚举TimeUnit,其对sleep方法做了封装,直接使用从而时间单位
换算的步骤,比如线程睡眠 3h 27min 8sec 88 msec
join() 线程同步
让线程进行串行处理。在线程B中join某个线程A,会使得B线程进入等待,直到线程A结束生命周期,或者达到给定的时间,在这期间线程B会处于等待状态