定义
在计算机中,进程代表了内存中正在进行的引用程序,计算机中的资源(cpu 内存 磁盘 网络等),会按照需求分配给每个进程,从而这个进程对应的应用程序就可以使用这些资源。进程是在系统中运行一个应用程序的基本单位。
线程是进程中的一个代码执行单元,负责当前进程中代码程序的执行,一个进程中有一个或多个线程。当一个进程中启动了多个线程去分别执行代码的时候,这个程序就是多线程程序。
1、程序的并发执行是指,在一个时间段内,两个或多个线程,使用一个CPU进行交替执行
2、程序的并行执行是指,在同一时刻,两个或多个线程各自使用一个CPU同时进行运行
1、线程要使用一个CPU时,CPU会分配给这个线程一小段时间(毫秒级别),这个一小段时间叫做时间片,在这段时间内该线程有CPU的使用权。
2、一个时间片结束,而线程还未运行完,那这个时候该线程就要停止运行,交出CPU的使用权,然后等待下一个CPU时间片的分配
3、宏观上一小段时间内两个线程看似是在同时运行代码,其实在微观上看这两个线程是在使用同一个CPU,他们是交替执行的,只是这个交替执行的时间片很小,交替速度快,给人一种好像两个线程同时在运行的感觉。
定义
当俩个或多个线程使用一个CPU来运行代码的时候,在操作系统的内核中,就会有相应的算法来控制线程获取CPU时间片的方式,从而使得这些线程可以按照某种顺序来使用cpu运行代码,这种情况被称为线程调度。
常见调度方式
JVM中的线程使用的是抢占式调度
具体表现看下面代码
public class Demo01 {
public static void main(String[] args) {
Thread t1 = new Thread("线程1") {
@Override
public void run() {
for(int i = 0; i<100; i++) {
System.out.println("hello");
}
}
};
Thread t2 = new Thread("线程2") {
@Override
public void run() {
for(int i = 0; i<100; i++) {
System.out.println("java");
}
}
};
//启动线程
t1.start();
t2.start();
}
}
程t1中循环输出100次hello,线程t2中循环输出100次world,启动t1和t2后,它们就开始公平的竞争(默认情况下优先级相等),抢占CPU的时间片(使用权),抢到之后就可以使用cpu运行代码了,所以可以看到最后的运行结果,hello和world是交替运行的,但是每次的运行结果都会有所不同。
使用java命令来运行一个类的时候,首先会启动JVM(进程),JVM会在创建一个名字叫做main的线程,来执行类中的程序入口(main方法)
如下:
public class Test {
public static void main(String[] args) {
//获取执行当前方法的线程对象
Thread currentThread = Thread.currentThread();
System.out.println("执行当前方法的线程名字为:"+currentThread.getName());
}
}
//运行结果:执行当前方法的线程名字为:main
- 使用java命令运行Test类,会先启动JVM
- 应用类加载器通过CLASSPATH环境变量配置的路径,找到Test.class文件,并加载到方法区。注意,这里会同时生产一个Class类型对象,来代表这个Test类型,并且会优先处理类中的静态代码(静态属性、静态方法、静态代码块)
- JVM创建并启动一个名字叫做main的线程
- main线程将Test中的main方法加载到栈区中
- 在栈里面,main线程就可以一行行的执行方法中的代码了
- 如果在执行代码中,遇到了方法调用,那么线程会继续把被调用的方法,加载到栈中(压栈操作),然后执行栈顶这个最新添加进来的方法,栈顶方法执行完,就释放(出栈操作),然后在进行执行当前最新的栈顶方法(之前我们画过栈里面的方法调用图,例如在异常的学习过程中)
- 代码执行过程输出执行结果
- 当前是单线程程序,main线程结束了,JVM就停止了,如果是多线程程序,那么JVM要等所有线程都结束了才会停止
所以在main方法中的代码其实都是有名字叫做main的线程去执行的
Thread.currentThread();可以写在任意方法中,返回的是执行这个方法的线程对象
定义
java.lang.Thread是java中的线程类,所有线程对象必须是Thread类获其子类的实例。每个线程的作用,就是完成我们给它指定的任务,实际上就是执行一段我们指定的代码。我们只需要在Thread类的子类中重写run方法,把执行的代码写入到run方法中即可,这就是线程的执行任务
创建并启动线程的步骤
1、定义Thread类的子类(可以是匿名内部类),重写Thread类中的run方法,run方法中的代码就是线程的执行任务
2、创建Thread子类对象,这个对象代表了要独立运行的新线程
3、调用线程对象的start方法来启动线程
例如:
public class Test {
public static void main(String[] args) {
//2.创建线程类对象
Thread t = new MyThread();
//3.调用start方法启动线程
t.start();
}
}
//1.子类继承父类Thread,并重写run方法(指定线程的执行任务)
class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("hello world");
try {
//可以让当前执行代码的线程睡眠1000毫秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
或者使用匿名内部类的形式
public class Test{
public static void main(String[] args) {
Thread t = new MyThread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("hello world");
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t.start();
}
}
给一个线程对象指定要执行的任务,除了继承Thread类后重写run方法之外,还可以利于Runnable接口来完成线程任务的指定。
(1)java.lang.Runnable接口中只有一个抽象方法run()
punlic interface Runnable{
public abstract void run();
}
(2)Thread类其实也是Runnable接口的实现类
代码大致结构如下
public class Thread implements Runnable {
/* What will be run. */
private Runnable target;
public Thread() {
//...
}
public Thread(Runnable target) {
this.target = target;
//..
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
可以看出子类重写的Thread中的run方法也是来自于Runnable接口的
(3)使用Runnable接口的匿名内部类,来指定线程的执行任务
public class Test{
public static void main(String[] args) {
//Runnable接口的实现类中,重写了run方法,指定线程的执行任务
Runnable run = new Runnable() {
@Override '
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("hello world");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
//创建线程对象,将run对象传进
Thread t = new Thread(run);
t.start();
}
}
}
(4)实现Runnable接口比继承Thread类所具有的优势:
String name = Thread.currentThread().getName();
Thread.currentThread()方法获取当前线程对象,
getName()方法获取当前线程对象的名字
这里说的当前线程值的是当前方法中的线程
默认情况下,主线程中创建出的线程,它们都会有一个默认的名字
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
“Thread-” + nextThreadNum() 就是在拼接出这个线程默认的名字,Thread-0 Thread-1 Thread-2等等
例如:
public class Test {
public static void main(String[] args) {
String name = Thread.currentThread().getName();
System.out.println("执行当前main方法的线程是:"+name);
Runnable run = new Runnable() {
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("执行当前run方法的线程是:"+name);
}
};
Thread t = new Thread(run);
t.start();
}
}
//运行结果为:
//执行当前main方法的线程是:main
//执行当前run方法的线程是:Thread-0
可以在创建线程对象的时候给它设置一个指定的名字
//方式1:创建对象时给线程命名
Thread t = new Thread("t线程");
//或者
Thread t = new Thread(new Runnable(){
public void run(){
//执行任务
}
},"t线程");
//方式2:通过对象调用setName方法给线程命名
Thread t = new Thread();
t.setName("t线程");
前台线程
又叫执行线程,用户线程。专门用来执行用户编写的代码,如果当前还有前台线程未执行完,则JVM虚拟机就不会停止运行。
执行程序入口的主线程(main线程)就是一个前台线程,还有在主线程中创建并启动的新线程默认情况下就是一个前台线程。
后台线程
又叫守护线程,精灵线程。专门用来给前台线程服务的,给前台线程提供一个良好的运行环境,JVM是否停止运行并不惯性后台线程的运行情况和状态。如垃圾回收器就是一个后台线程。
将线程设置为后台线程
setDaemon(true)方法
public class Test {
public static void main(String[] args) {
Thread t = new Thread("t线程"){
@Override
public void run() {
String name = Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
System.out.println(name+": hello "+i);
}
}
};
//在启动线程之前,可以将其设置为后台线程,否则默认是前台线程
t.setDaemon(true);
t.start();
}
}
表示线程优先级的源代码
public class Thread implements Runnable {
private int priority;
//线程的优先级使用int类型数字表示,最大是10,最小是1,默认的优先级是5。
/** * The minimum priority that a thread can have. */
public final static int MIN_PRIORITY = 1;
/** * The default priority that is assigned to a thread. */
public final static int NORM_PRIORITY = 5;
/** * The maximum priority that a thread can have. */
public final static int MAX_PRIORITY = 10;
public final int getPriority() {
return priority;
}
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException(); //优先级小于1或者大于10会报参数不合法的异常
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority(); //比较优先级
}
setPriority0(priority = newPriority);
}
}
//设置线程优先级的方法,是一个native方法,并不是java语言实现的
private native void setPriority0(int newPriority);
}
特点:
当俩个线程争夺CPU时间片的时候:
Java中使用java.lang.ThreadGroup类来表示线程组,它可以对一批线程进行管理,对线程组进行操作,同时也会对线程组里面的这一批线程操作。
public class ThreadGroup{
public ThreadGroup(String name){
//..
}
public ThreadGroup(ThreadGroup parent, String name){
//..
}
}
创建线程组的时候,需要指定该线程组的名字。也可以指定其父线程组,如果没有指定,那么这个新创建的线程组的父线程组就是当前线程组。只有在创建线程对象的时候,才能指定其所在的线程组,线程运行中途不能改变它所属的线程组
样例一:
public class Test {
public static void main(String[] args) {
//获取当前线程对象
Thread currentThread = Thread.currentThread();
//获取当前所属的线程组
ThreadGroup currentThreadGroup = currentThread.getThreadGroup();
System.out.println(currentThreadGroup);
}
}
结果:
java.lang.ThreadGroup[name=main,maxpri=10]
样例二:
public class Test {
public static void main(String[] args) {
ThreadGroup group = new ThreadGroup("我的线程组");
//指定线程所属的线程组
Thread t = new Thread(group,"t线程");
ThreadGroup threadGroup = t.getThreadGroup();
System.out.println(threadGroup);
}
}
java.lang.ThreadGroup[name=我的线程组,maxpri=10]
样例三:
import java.util.Arrays;
public class Test {
public static void main(String[] args) {
ThreadGroup group = new ThreadGroup("我的线程组");
Runnable run = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
Thread t1 = new Thread(group,run,"t1线程");
Thread t2 = new Thread(group,run,"t2线程");
Thread t3 = new Thread(group,run,"t3线程");
t1.start();
t2.start();
t3.start();
//返回当前线程组中还没有“死亡”的线程个数
System.out.println("线程组中还在存活的线程个数为:" + group.activeCount());
//准备好数组,保存线程组中还存活的线程
Thread[] arr = new Thread[group.activeCount()];
//将存活的线程集中存放到指定数组中,并返回本次存放到数组的存活线程个数
System.out.println("arr数组中存放的线程个数是:" + group.enumerate(arr));
//输出数组中的内容
System.out.println("数组中的内容是:" + Arrays.toString(arr));
}
}
输出结果
线程组中还在存活的线程个数为:3
arr数组中存放的线程个数是:3
数组中的内容是:[Thread[t1线程,5,我的线程组], Thread[t2线程,5,我的线程组], Thread[t3线程,5,我的线程组]]
编程状态 | 名称 | 描述 |
---|---|---|
NEW | 新建 | 线程刚被创建, |
RUNNABLE | 可运行 | start方法调用结束,线程由NEW变成RUNNABLE,线程存活着,并尝试抢占CPU资源,或者已经抢占到CPU资源正在运行,这俩种情况的状态都显示为RUNNABLE |
BLOCKED | 锁阻塞 | 线程A和线程B都要执行方法test,而且方法test被加了锁,线程A先拿到了锁去执行test方法,线程B这时候需要等待线程A把锁释放。这时候线程B就是处理BLOCKED |
WAITING | 无限期等待 | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
TIMED_WAITING | 有限期等待 | 和WAITING状态类似,但是有一个时间期限,时间到了,自己也会主动醒来 |
TERMINATED | 终止死亡 | run方法执行结束的线程处于这种状态。 |
BLOCKED,WAITING,TIMED_WAITING 这三种都属于线程阻塞,只是触发的条件不同,以及从阻塞状态中恢复过来的条件也不同而已
getState()方法获取线程状态
注意