一、引入线程
在任务管理器界面可以看到很多进程,可以利用CPU多核心这一点来更有效的执行这些进程,即在处理过程中将CPU的多个时间片分配给每个进程,这样的 "多进程编程"就可以起到并发编程的效果,因为进程可以被调度到
虽然多进程编程可以解决进程多的问题,但也带来了一些麻烦
比如:一个服务器要给多个客户端提供服务,每当有一个客户端连上服务器,服务器就要创建一个进程给其提供服务,当客户端断开时,服务器就要销毁这个进程,那么问题就是如果有客户端频繁的连接、断开,服务器就要频繁的创建和销毁进程,这样的频繁操作会使服务器响应变慢
所以为了解决上述进程太"重量"的问题,就引入了线程(创建和销毁的开销较小)
二、线程(thread)
2.1 再谈PCB
线程可以理解为进程的一部分,一个进程可以包含一个线程或多个线程,上篇文章讲过PCB用来描述进程,实际上是一个PCB描述一个线程,多个PCB联合在一起描述了一个进程
针对PCB的那几个属性:
每一个线程都是独立在CPU上调度执行的,所以又称线程是系统调度的基本单位
2.2 为什么线程创建和销毁的开销比进程小?
在创建进程时,会涉及到资源分配和释放,而一个进程中的若干个线程是共享资源的,在创建线程时,此时进程已经创建好了,资源也分配好了,那么创建线程就省去了资源分配和释放的步骤,后续再创建线程就不必再申请资源
同一个进程包含N个线程,只有在创建第一个线程时(也是创建进程时)会进行资源申请操作,后续再创建线程时就不会在进行申请资源的操作了
三、多线程代码
Java提供了一个 Thread类 用来表示线程,在该类中有一个run方法,方法内容就是该线程要执行的任务,所以可以通过写一个类继承Thread并重写run方法,这样就可以自己设定线程要完成的任务
class MyThread extends Thread {@Overridepublic void run() {System.out.println("hello thread");}
}public class Demo1 {public static void main(String[] args) {Thread t = new MyThread();}
}
run方法只是描述了线程要完成的任务,并没有创建线程,此时就需要调用start方法,通过start方法调用操作系统的提供的"创建线程"api,在内核中创建对应的pcb,在系统调度到这个线程时,就会执行run方法中的逻辑
(run方法不是被start调用的,而是在start创建出来的线程,在线程里被系统调用的)
class MyThread extends Thread {@Overridepublic void run() {System.out.println("hello thread");}
}public class Demo1 {public static void main(String[] args) {Thread t = new MyThread();t.start(); //打印出 hello thread}
}
多个线程之间的执行顺序是无序的
在上述代码中,有两个线程:1.t 线程,2. main方法所在的线程(主线程),给出下面一段代码
class MyThread extends Thread {public void run() {while (true) {try {Thread.sleep(1000); //sleep是让该线程主动处于阻塞状态1s,即代码在此处等待1s,不继续往下执行,由于该方法会抛出异常,所以用try-catch结构进行处理} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("hello thread");}}
}
public class Demo {public static void main(String[] args) throws InterruptedException {Thread t = new MyThread();t.start();while (true) {Thread.sleep(1000);System.out.println("hello main");}}
}
该代码的执行结果为:
main线程和t线程并发执行,main线程和t线程的打印都在执行,且每次循环谁先执行的顺序不一定,所以称多个线程之间的执行顺序是无序的
3.1 5种创建线程的方法
1.上述的创建Thread子类并重写run方法就是其中一种
2.通过实现Runnable接口创建线程
Runnable接口:
在该接口中只有run方法,run方法用来描述线程要做的事情
class MyRunnable implements Runnable {public void run() {while (true) {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("hello thread");}}
}
public class Demo1 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(new MyRunnable());t.start();while (true) {Thread.sleep(1000);System.out.println("hello main");}}
}
上述的Thread t = new Thread(new MyRunnable());这样写可以解耦合,Thread没有记录任务内容,它只负责执行,任务内容由Runnable记录,这样就将任务和执行分开了,未来改用其他方式执行这些任务的改动成本较低
3.针对1的变形,使用匿名内部类,继承Thread并重写run方法
public class Test {public static void main(String[] args) {Thread t = new Thread() {@Overridepublic void run() {while (true) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};t.start();}
}
使用匿名内部类的具体步骤如下:
1)创建一个Thread的子类,该类的名字匿名
2)创建子类的同时又创建这个子类的实例
3)最后重写run方法
4.针对Runnable的匿名内部类
Thread t = new Thread(new Runnable() {@Overridepublic void run() {...}
});
此处的匿名内部类只针对Runnable,和Thread没有关系,只是把Runnable的实例作为参数传入到Thread的构造方法中
5.使用lambda表达式
Thread t2 = new Thread(() -> {//在这里直接写线程所要执行的任务,也就是run方法中的内容
});
总结:上述5种写法本质上就是2点:1.把线程要执行的任务内容表示出来,2.通过Thread的start方法来创建/启动系统中的线程
🙉本篇文章到此结束,之后会继续探究多线程的内容