进程与线程
进程:即运行中的程序,一个进程有着 产生、存在、消亡 的完整过程。
线程:是进程的一个分支,一个进程可以拥有多个线程。当然一个线程也可以产生多个子线程分支
例如:打开一个QQ,就是创建了一个进程,一个QQ里面有多个聊天界面,每个聊天界面就可以理解为一个线程。
一、线程的基本使用
1、创建线程
创建线程有两种常用方式:
1.继承Thread类
2.实现Runnable接口
(1)继承Thread类,并重写run方法
class A extends Thread{
@Override
public void run() {
//写需要执行的业务代码
}
}
使用时创建a对象,调用start方法(它的内部会自行调用run方法)启动线程
public class Text {
public static void main(String[] args) {
A a = new A();
// a.run();//这只是调用了run方法,并没有实现线程的创建
a.start();
}
}
(2)实现Runnable接口,并重写run方法
class A implements Runnable{
@Override
public void run() {
//写需要执行的业务代码
}
}
使用时创建A对象,不能直接调用start方法启动线程,需要先创建一个Thread对象,把a对象放入Thread对象中,让其进行代理,再使用新建的Thread对象调用start方法启动线程
public class Text {
public static void main(String[] args) {
A a = new A();
//创建thread对象并把a对象放进去
Thread thread = new Thread(a);
thread.start();
}
}
说明:
因为Java时单继承机制的,有时候一个类可能已经继承了别的父类,导致其不能通过 继承Thread 的方式来创建线程,此时,就只能通过第二种方式, 实现Runnable接口 来创建线程了。
继承Thread 与 实现Runnable接口的区别:
- 从Java的设计来看, 继承Thread 与 实现Runnable接口来创建线程没有本质上的区别,因为看JDK文档会发现Thread类本身就是实现了Runnable接口的
- 实现Runnable接口的方式更加适合多个线程共享一个资源的情况,且避免了单继承的限制,所以推荐使用实现Runnable接口的方式创建线程
实现Runnable接口的方式更加适合多个线程共享一个资源,示例代码:
public class Thread01 {
public static void main(String[] args) {//主线程
T1 t1 = new T1();
T2 t2 = new T2();
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();//启动第1个子线程
thread2.start();//启动第2个子线程
//...
//threadn.start();//启动第n个子线程
System.out.println("主线程结束。");
}
}
2、线程终止
1、当线程执行完它自己的所有语句时,自动退出,结束线程
2、可以通过使用变量来控制run方法退出的方式停止线程,即通知方式
public class Thread02 {
public static void main(String[] args) throws InterruptedException {
T t1 = new T();
Thread thread = new Thread(t1);
thread.start();
//如果希望main线程去控制 thread 线程的终止
//则需要修改 key 的值来终止线程
//让主线程休眠 10 秒后就通知 thread线程 退出
Thread.sleep(10 * 1000);
t1.setKey(false);//此语句执行完,子线程 thread 被终止
}
}
class T implements Runnable {
//设置一个控制变量key
private boolean key = true;
@Override
public void run() {
while (key) {//key值一直为true,则线程永远不会结束
try {
Thread.sleep(50);// 让当前线程休眠50ms
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setKey(boolean key) {
this.key = key;
}
}
二、线程的常用方法
1、setName:设置线程的名称,使之与参数name相同
2、getName:返回该线程的名称
3、start:开始执行线程(在Java虚拟机底层调用该线程的start0方法)
4、run:调用该线程的run方法
5、setPriority:更改线程的优先级
6、getPriority:获取线程的优先级
7、sleep:让该线程休眠指定秒数(单位:毫秒,1秒=1000毫秒)
8、interrupt:中断线程(不是终止),常用于中断线程的休眠
线程的优先级,有以下几种:
9、yield:线程的礼让,即让出cpu给其它线程执行,但是礼让时间不确定,不一定礼让成功。可以理解为给出了一个礼让的动作,礼让是否成功得看具体情况
10、join:线程的插队。线程一旦插队成功,就一定是插队的线程线执行完所有任务,再执行被插队的线程未执行完的任务
三、用户线程与守护线程
1、用户线程:也叫工作线程,当线程 执行完毕 或 被通知结束(通知方式)时结束
2、守护线程:一般是为工作线程服务的,当所有的用户线程结束后,守护线程自动结束。如:家长监督孩子(用户线程)做作业,作业做完了,家长也就离开了
3、常见的守护线程:垃圾回收机制
用户线程与守护线程,代码案例:
public class ThreadMethodExercise {
public static void main(String[] args) throws InterruptedException {//孩子线程
Thread parent = new Thread(new Parents());//家长线程
//如果我们希望当孩子线程结束后,家长线程自动结束
//只需将家长线程设为守护线程即可
parent.setDaemon(true);
parent.start();
for( int i = 1; i <= 10; i++) {//孩子线程,10本作业
System.out.println("小孩在疯狂的写了 " + i + " 本作业");
Thread.sleep(100);//1本作业时长
if(i == 10){
System.out.println("小孩作业写完了,家长离开");//做完后,家长线程就随之结束了
}
}
}
}
class Parents implements Runnable {
private int times = 0;//监督时长
@Override
public void run() {
while (true) {
try {
Thread.sleep(200);//休眠200毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("家长监督小孩了 " + (++times * 200) + "毫秒");
}
}
}
运行结果:
小孩在疯狂的写了 1 本作业
小孩在疯狂的写了 2 本作业
家长监督小孩了 200毫秒
小孩在疯狂的写了 3 本作业
小孩在疯狂的写了 4 本作业
家长监督小孩了 400毫秒
小孩在疯狂的写了 5 本作业
小孩在疯狂的写了 6 本作业
家长监督小孩了 600毫秒
小孩在疯狂的写了 7 本作业
小孩在疯狂的写了 8 本作业
家长监督小孩了 800毫秒
小孩在疯狂的写了 9 本作业
小孩在疯狂的写了 10 本作业
家长监督小孩了 1000毫秒
小孩作业写完了,家长离开
四、线程的7种状态
线程可以处于以下状态之一:
1、NEW
尚未启动的线程处于此状态。
2、RUNNABLE
在Java虚拟机中执行的线程处于此状态。
3、BLOCKED
被阻塞等待监视器锁定的线程处于此状态。
4、WAITING
正在等待另一个线程执行特定动作的线程处于此状态。
5、 TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
6、TERMINATED
已退出的线程处于此状态。
RUNNABLE状态又可细分为Ready状态和Running状态,故认为有7种状态。
一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。
想知道线程当前状态,可通过调用getState
方法来返回线程的当前状态值