目录
1. 线程的合并
- 语法:
线程对象名.join()
- 若代码为:
线程2.join()
表示:本线程先等待,让线程2先进行完后,再进行本线程 - 需要抛出异常
//线程合并join()
public class Test1 {
/**
* CountDownLatch:可以实现相同的效果
* @param args
*/
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + i);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
t1.start();
try {
t1.join();//t1线程插队,先进行t1线程,再进行主线程。
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "- main orver");
}
}
2. 线程的退出
2.1 stop
不推荐,线程退出方式粗暴,不管线程正在执行的任务,直接退出,可能丢失数据
//探究使用stop方法结束线程
import java.util.Scanner;
public class Test2 {
public static void main(String[] args) {
MyTask myTask = new MyTask();
Thread thread = new Thread(myTask);
thread.start();
Scanner scanner = new Scanner(System.in);
System.out.println("请输入(1/0)0表示退出");
int i = scanner.nextInt();
if (i == 0){
thread.stop(); //非优雅退出,现在不推荐使用stop()
}
System.out.println("main over");
}
}
class MyTask implements Runnable{
@Override
public void run() {
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
2.2 中断信号
-
interrupt
线程对象名.interrupt();
,本线程给线程对象名
发送中断信号(true)- 如果线程在阻塞状态,比如:
sleep(),join(),wait()
,这时接收到中断信号会抛出一个异常InterruptException
,同时中断信号清除(false)
- 如果线程在阻塞状态,比如:
-
static interrupted():得到中断信号(true),然后把中断信号设置成false
- 获取当前子线程
Thread.currentThread()
- 获取当前子线程
-
isInterrupted():得到中断信号,不会更改信号
3. 线程的调度和时间片
3.1 线程的时间片
由于 CPU 的计算频率非常高,每秒计算数十亿次,于是,可以将 CPU 的时间从毫秒的维度进行分段,每一小段叫做一个 CPU 时间片。不同的操作系统、不同的处理器,线程的 CPU 时间片长度都不同。
3.2 线程的调度
操作系统的线程一个时间片的时间长度为 20 毫秒(比如 Windows XP),在一个 2GHz 的 CPU 上,那么一个时间片可以进行计算的次数是: 20 亿/(1000/20) =4 千万次,也就是说,一个时间片内的计算量是非常巨大的。 目前操作系统中主流的线程调度方式大都是:基于 CPU 时间片方式进行线程调度。线程只有得到 CPU 时间片,才能执行指令,处于执行状态;没有得到时间片的线程,处于就绪状态,等待系统分配下一个 CPU 时间片。由于时间片非常短,在各个线程之间快速地切换,表现出来特征是很多个线程在“同时执行”或者“并发执行”。
3.3 线程的调度模型
线程的调度模型,目前主要分为两种调度模型:分时调度模型、抢占式调度模型。
- 分时调度模型
系统平均分配 CPU 的时间片,所有线程轮流占用 CPU。分时调度模型在时间片调度的分配上,所有线程人人平等。 下图就是一个分时调度的简单例子:三个线程,轮流得到 CPU 时间片;一个线程执行时,另外两个线程处于就绪状态
- 抢占式调度模型
系统按照线程优先级分配 CPU 时间片。优先级高的线程,优先分配 CPU 时间片;如果所有的就绪线程的优先级相同,那么会随机选择一个;优先级高的线程获取的 CPU 时间片相对多一些。 由于目前大部分操作系统都是使用抢占式调度模型进行线程调度。 Java 的线程管理和调度是委托给了操作系统完成的,与之相对应, Java 的线程调度也是使用抢占式调度模型
4. 线程状态
4.1 操作系统中
分别为:新建 就绪 运行 阻塞 死亡
4.2 Java中
-
NEW
- new Thread()创建线程,此时线程还未启动
-
RUNNABLE,
- 进入可执行:包含操作系统的就绪、运行两种状态
- 调用了线程的start()实例方法后,线程就处于该状态
- 调用线程的 start()方法,此线程进入就绪状态。
- 当前线程的执行时间片用完。
- 线程睡眠(sleep)操作结束。
- 对其他线程合入(join)操作结束。
- 等待用户输入结束。
- 线程争抢到对象锁(Object Monitor)。
- 当前线程调用了 yield 方法出让 CPU 执行权限。
-
BLOCKED
- 阻塞 分为I/O输出阻塞和线程等待获取锁
-
WAITING
- 等待(无限期等待)进入该状态有三种方式
- Object.wait() 方法,对应的唤醒方式为: Object.notify()
- Thread.join() 方法,对应的唤醒方式为:被合入的线程执行完毕。
- LockSupport.park() 方法,对应的唤醒方式为:
- 等待(无限期等待)进入该状态有三种方式
-
TIMED_WAITING,
- 限时等待 ,有明确的等待时间
- Thread.sleep(time) 方法,对应的唤醒方式为: sleep 睡眠时间结束
- Object.wait(time) 方 法 , 对 应 的 唤 醒 方 式 为 : 调 用 Object.notify()
- LockSupport.parkNanos(time)/parkUntil(time) 方法,对应的唤醒方式为:线程调用配套的 LockSupport.unpark(Thread)方法结束,或者线程停止(park)时限结束。
-
TERMINATED;
- 线程结束或线程发生异常
Java中的BLOCKED、WAITING、TIMED_WAITING等价于操作系统中的阻塞状态
进入者三个状态的线程都会让出CPU的使用权,另外,等待或阻塞状态被唤醒后,进入Ready状态,需要重新获取时间片才能接着运行。
5. 多线程自增案例
4个线程自增一个堆(共享的)里的对象的值
public class Plus {
private int amount;
public int getAmount() {
return amount;
}
public void selfAmount(){
amount++;
}
}
public class PlusTask implements Runnable{
private Plus plus;
public PlusTask() {}//无参构造函数
public PlusTask(Plus plus) {//构造函数
this.plus = plus;//此时plus为传入的plus
}
@Override
public void run() {
for (int i = 0; i < 100000000; i++) {
this.plus.selfAmount();
}
}
public Plus getPlus() {
return plus;
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException {
Plus plus = new Plus();
PlusTask plusTask = new PlusTask(plus);//将plus的地址值传入,
Thread t1 = new Thread(plusTask);//控制同一个PlusTask
Thread t2 = new Thread(plusTask);
Thread t3 = new Thread(plusTask);
Thread t4 = new Thread(plusTask);
t1.start();
t2.start();
t3.start();
t4.start();
/*
如果不加join()方法,那么main线程,
t1,t2,t3,t4线程将同时进行,
main线程不会等待子线程结束后才打印
*/
t1.join();
t2.join();
t3.join();
t4.join();
System.out.println("实际值" + plus.getAmount());
}
}