一、进程和线程的概念
1.进程和线程概念
每个运行的程序就是一个进程。程序进行时,内部可能包含多个顺序执行流,每个顺序执行流就是一个线程。
进程间有独立的内存资源,而线程是共享父进程的全部资源。线程间是抢占式的运行,即并发性。
2. 多线程是并发性而不是并行性的
并发:在时间片间相互切换。
并行:同时运行。
所以,多线程的意义在于最大限度的使用CPU。
二、线程的创建与启动
1.实现方法一:继承Thread类创建线程
第一步:继承Tread类
第二步:重写run()方法。
第三步:在主线程里面开启子线程
核心代码:new SonThread().start();
public class ThreadDemo {
// 主线程
public static void main(String[] args) {
// 第三步:在主线程中调用start()启动
SonThread sonThread = new SonThread();
sonThread.start();
//sonThread.run(); // 错误的用法
System.out.println("after thread");
}
// 子线程类
// 第一步:继承Thread类
public static class SonThread extends Thread {
// 第二步:重写run方法
@Override
public void run() {
// things need todo
for (int i = 0; i < 10; i++) {
System.out.println("something need long time");
}
}
}
}
2.实现方法二:实现Runnable接口
第一步:定义一个类,实现Runnable接口。
第二步:在类里面实现run()方法。
第三步: 在主线程里创建子线程对象。
第四步:创建新的Thread对象,将类对象传入Thread构造方法。
第五步:新的对象启动start()方法。
其实和第一种方式一一样,都是都是new Thread().start()。
核心代码:
new Thread(new Runnable() {
@Override
public void run() {
// do something
}
}).start();
3.两种方式的比较
使用Thread类的缺点:由于Java的点继承,该类会无法再继承其他类。(不过多数情况下也不需要再继承其他类。)
Runnable的优势:可以让多个线程共享同一个Runnable对象,很适合多个线程来处理同一份资源的情况,即同一个runnable对象作为参数传入多个Thread的构造方法
其实两种方式的本质都是new Thread().start()启动线程,不过一个是直接重写run()方法,一个是用runnable对象从构造方法传入。
一般推荐用Runnable方法。
三、线程常用的基本方法
getName():获得当前线程的名称。
static Tread.currentThread():获得当前执行的线程对象。
start():让线程进入就绪状态。
boolean isAlive():判断线程是否处于活动状态。
void setName(String name); 设置线程的名称
四、线程的生命周期
新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)、死亡(Dead)
1.新建(New)
仅仅由Java虚拟机分配了内存,并初始化其成员变量的值。这个时候线程对象还没有表现出线程的动态特征,线程体也不会被执行。
2.就绪(Runnable)
线程的run方法体并不会立刻执行,只是告诉程序该线程可以运行了。至于何时运行,则取决于JVM的线程调度器的调度。
3.运行(Running)
处于就绪状态的线程获得了CPU,则该线程会开始执行run()方法内的线程执行体,此时线程进入了运行状态。线程调度取决于底层平台所采用的策略。
4.阻塞(Blocked)
a.何为阻塞
当一个运行状态下线程除了被强制中断以外,还有可能被阻塞,从而进入到阻塞状态,进入阻塞状态后,其他线程就可以获得执行的机会。被阻塞的线程在合适的时候会解除阻塞,从而再次进入就绪状态(不是运行状态),等待线程调度器再次调度它。
b.引起阻塞的情况
线程调用sleep()方法主动放弃所占用的处理器资源。
线程调用一个阻塞式的IO方法,在该方法返回之前,线程被阻塞。
线程视图获得一个同步监视器,但该同步监视器正被其他线程所持有。即等待其他线程完毕。
线程在等待某个通知。
调用了线程suspend()方法将该线程挂起,但是这个方法容易引起死锁,所以不推荐使用,已过时。
c.线程的结束阻塞
调用sleep()方法的线程经过了指定的时间
线程调用的阻塞式IO方法已经返回
线程成功地获得了视图取得的同步监视器
线程正在等待某个通知时,其他线程发出了一个通知
处于挂起状态的线程被调用了resume()恢复方法(不推荐使用suspend()方法将该线程挂起)
5.死亡(Dead)
线程结束后,就会进入死亡状态。
6.注意
1)可以通过isAlive()方法来判断当前线程的状态,
如果线程处于就绪、运行、阻塞状态,该方法返回true;
如果处于新建、死亡状态,该方法返回false;
2)只能对新建状态的线程调用start()方法,只能调用一次。不是调用run()方法。
五、线程的控制
1.join方法
当某个线程A调用了其他线程B的join()方法时,那么线程A将被阻塞,直到线程B执行完毕后,线程A才重新进入就绪状态。
if (i = 100) {
otherThread.join(); // 开始执行otherThread线程,直到执行完毕,才会继续执行自己。
}
2.后台线程
在启动一条线程前,可以通过setDaemon(true);方法,设置该线程为后台线程,又称为守护线程,JVM的垃圾回收线程就是典型的后台线程。
如果前台线程全部死亡,则后台线程也会自动死亡,后台线程不再继续执行。
public static void main(String[] args) {
AThread s = new Thread();
s.setDaemon(true); // 将该线程设置为后台线程
s.setName(“彦祖的线程”); // main()死掉后,s就自行了结
s.start();
}
通常开启的线程默认都是前台线程,后台线程开启的子线程默认是后台线程。
setDaemon()方法必须在start()之前调用。
3.sleep()方法
该方法可以使当前正在执行的线程暂定一段时间,进入阻塞状态。sleep()方法有一个参数,用来设置线程暂停多少毫秒,经过指定时间后,线程重新进入就绪状态
处于sleep()休眠中的线程不会被执行,即便当前没有任何线程在执行,该线程也不会执行。
4.yield方法(放弃)
(少用)
让当前正在执行的线程暂停,但是不会阻塞该线程,而是直接将该线程转入到就绪状态。
但可能刚yield完,调度器又调用了该线程。
当某个线程调用了yield()方法暂停后,只有优先级大于等于该线程的,并且处于就绪状态的线程才会获得执行的机会。
5.线程的优先级
每个线程执行时都具有一定的优先级,优先级高的线程可能会比优先级低的线程获得更多的执行机会。
Thread类提供了setPriority(int newPriority);方法来设置线程的优先级,范围是1-10,数字越大优先级越高。
另外Thread还提供了三个常量来表示优先级,推荐用这种方式设置,因为有的操作系统优先级最高可能不是10,而是7,或是其他:
MAX_PRIORITY:其值是10。
NORM_PRIORITY:其值是5。
MIN_PRIORITY:其值是1。
每个线程的优先级默认都和创建它的父线程优先级相同。
其实线程优先级一般也不太靠谱,因为不能确实的保证最先执行。