目录
一 线程
1.1线程的概念
• 进程:操作系统分配资源的基本单位,程序的一次执行过程就是进程,每一个进程都有独立的内存空间,点开一个程序从运行到关闭即代表了进程的创建,运行,和销毁。
• 线程:操作系统调度的基本单位,线程(轻量级进程)是包含在进程当中的,每一个进程包括多个线程(至少有一个线程),每一个线程都是独立执行流,一个进程里面的多个线程是并发执行的。
1.2 进程与线程
进程的创建,销毁,和切换需要频繁的申请,释放,切换内存空间,这对于系统来说是比较大的时间开销,所以引入了线程这一概念。
在操作系统中,线程属于比较抽象的内容,所以我采取图片和举例子的方式来解释线程,图解:
如果把一个进程比作一个工厂,在资金不足(对应系统资源不足)的情况下,你想同时生产多种配件(对应一个应用程序的多种功能,如QQ里面视频,通话等等),如果采取不断新建工厂(在内存中为一个程序的多个功能一一申请创建进程)的方式,既耗费资源(内存)又耗费时间(系统效率)。所以我们想到了另外一种方法,在工厂内共享生产设备(多个线程共享进程的内存、文件描述符表)来开设多个流水线完成不同配件的生产,由于流水线之间有很强的关联性(一个流水线停止运行,可能导致其他流水线不能运转),比如手机组装厂,当屏幕生产的流水线出现问题后,组装手机的流水线可能就会停止运作,从而导致整个工厂停止。
总结:
1.进程包含线程。
2.进程有自己的独立内存和文件描述表,一个进程的多个线程共享进程的内存和文件秒速表。
3.进程之间具有独立性,互不干扰,一个进程崩溃,并不会影响到其他进程的运行;线程是独立执行流,一个线程崩溃,可能影响其他的线程运行,从而会使整个进程崩溃。
1.3 线程调度
与多进程的运行方式相同,多线程也是采取并行或并发的方式来执行的。
• 并行:同一时刻,多个事件同时发生。
• 并发:同一时间段,多个事件发生(在计算机中,由于CPU运行速度非常快,多个事件看起来就像同时发生)。
所以通常用并发执行来笼统介绍线程或者进程的运行方式。
调度方式
线程的调度,取决于支持的是内核级线程还是用户级线程:
1.对于用户级线程,内核不知道线程的存在,就给了进程很大的自主权。内核只是调度进程,进程中的调度程序选择哪个线程来运行。
2.对于内核级线程,线程的调度就交给了系统完成。
二 线程的创建
2.1 Thread
在Java标准库中,使用了一个类**Thread**来表示线程。所有的线程对象都必须是 Thread 类或其⼦类的实例。
自定义线程类:
1.定义线程子类,并重写Thread类中的run方法,run⽅法的⽅法体就代表了线程需要完成的任务,所以run方法被称为线程执体。
class MyThread extends Thread{
@Override
public void run() {
while (true) {
System.out.println("哈喽沃尔德");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试类:
1.通过Thread类中的start方法来启动线程,start方法会自动调用run方法体。
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = new MyThread();
thread.start();
while (true){
System.out.println("0.0");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果如下:
为什么控制台会输出两行文字呢,在我们未接触线程之前,按照我们的设想,start启动后,会在run方法体死循环,不可能执行main方法里面的循环,然而控制台却出现了两行文字并且不断打印,这是因为main方法对应了一个线程,start方法又启动了一个新的线程,每一个线程都是独立执行流,此时两个线程并发(并发或者并行)执行。
图解:
2.2 多种创建方式
1.自定义类实现Runnable接口,并重写该接⼝的 run() ⽅法,该 run() ⽅法的⽅法体同样是该线程的线程执⾏体,通过Thread构造方法创建。
class MyTread implements Runnable{
@Override
public void run() {
while (true) {
System.out.println("哈喽沃尔德");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
MyTread myTread = new MyTread();
Thread thread = new Thread(myTread);
thread.start();
while (true) {
System.out.println("0.0");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.匿名内部类
使⽤线程的匿名内部类⽅式,可以⽅便的实现每个线程执⾏不同的线程任务操作。
public class ThreadDemo2 {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override
public void run(){
while (true) {
System.out.println("哈哈哈哈哈");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread.start();
while (true) {
System.out.println("嘿嘿嘿");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3.Lambda 表达式
public class ThreadDemo3 {
public static void main(String[] args) {
Thread thread = new Thread(()->{
while (true){
System.out.println("按时发斯蒂芬是的");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
while (true){
System.out.println("散货款的那颗");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.3 start()方法和run()方法区别
1.start用来启动线程,启动之后线程进入就绪态,一但运行,就会自动调用run方法,执行run方法里面的包含的内容(某个线程里的run方法即使没执行完毕,也不会影响其它线程的执行,每一个线程都是独立执行流,并发执行),此时run方法属于线程体,run方法执行结束,此时线程结束。
2.run方法如果不通过start自动调用,属于Thread类中的普通方法,在主线程中调用,得run方法的内容执行完毕(顺序执行)才能进行下一步。
总结:start方法启动线程会自动调用run方法,run方法(权限必须为public)只是Thread类中一个普通方法。两者返回值类型都是void。
三 线程基本用法
3.1 线程休眠
调用**sleep()**方法,参数:以毫秒为单位的时间差会抛出InterruptedException异常,需要处理使得线程由运行状态变为阻塞状态,当休眠时间到达时,才会重新变为就绪状态。即使此时系统中没有其他可执行的线程,处于sleep的线程也依然不会执行.
class MyThread extends Thread{
//重写run方法
@Override
public void run() {
for(int i=0;i<5;i++) {
System.out.println("休眠"+ i + "秒");
//线程休眠
//参数:以毫秒为单位
//需要捕获异常
try {
Thread.sleep(1000); //休眠1秒
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadClass {
public static void main(String[] args) {
//实例化一个线程
MyThread mt=new MyThread();
mt.start();
}
/****线程休眠****/
}
//输出形式:每隔1秒输出一个i值
3.2 线程中断
Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理(由程序员决定线程如何响应中断。被中断的线程拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择不停止。
中断机制是使用一个称为中断状态的内部标志来实现的。通过Thread.currentThread().isInterrupted()来查看当前标志位状态(默认为false),调用 Thread.interrupt() 设置此标志。
public class ThreadDemo {
public static void main(String[] args) {
Thread t = new Thread(() -> {
// currentThread 是获取到当前线程实例.
// 此处 currentThread 得到的对象就是 t
// isInterrupted 就是 t 对象里自带的一个标志位.
while (!Thread.currentThread().isInterrupted()) {
System.out.println("hello t");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
});
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 把 t 内部的标志位给设置成 true
t.interrupt();
}
}
按理来说,程序应该在三秒钟之后结束运行,而此时却抛出来了异常,并且继续往下执行。这是因为线程 sleep 期间能感受到中断标志,处于休眠中的线程被中断,线程是可以感受到中断信号的,并且会抛出一个InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false。此时while循环再次为真,再次循环。
解决方法:
在抛出异常后设置break中断,代码如下:
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
break;
}
3.3 程序等待
1.若在其他线程中执行某个线程join的,在该过程中,其他线程阻塞,待此线程执行完毕,再执行其他线程。
2.使用join会抛出InterruptedException异常,需要处理。
如以下代码:
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
System.out.println("0号线程");
});
Thread thread1 = new Thread(()->{
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1号线程");
});
thread.start();
thread1.start();
}
在1号线程中使用0号线程thread.join(),1号线程需要等待0号线程结束才能运行。
3.4 获取当前线程引用
通过Thread类中的方法currentThread()来获取当前线程引用。
public class ThreadDemo {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}