1.认识线程(Thread)
1.1概念
进程是系统分配资源的最小单位,线程时系统调度的(cpu执行指令)的最小单位。一个进程内的线程之间是可以共享资源的。每个进程至少有一个线程的存在,这个线程就被称为主线程(指的是C语言的main函数,而非java main函数)。
1.2接触观察进程和线程
线程可以通过JDK提供的java监控工具或命令来观察。
运行方式:双击,或者在cmd中输入命令
运行测试程序1,并观察线程,main发生阻塞
public class first {
//运行一个进程,main就是主线程
public static void main(String[] args) throws InterruptedException {
//第一段代码:观察main阻塞
Thread.sleep(9999999L);
}
}
结果:
java进程的运行过程:
本质上是调用java包名.类名来启动java程序,类名作为参数传递==>java进程运行。
java命令代码执行:
最先启动系统main方法,一般是C语言的main入口函数。----这个是系统级别的main线程,是系统级别的主线程。
执行过程图示:
运行测试程序2,观察线程。子线程阻塞
//第二段代码:观察子线程阻塞
public class first {
//运行一个进程,main就是主线程
public static void main(String[] args) throws InterruptedException {
//匿名内部类,相当于一个子线程
new Thread(new Runnable() {
@Override
public void run() {
try{
//在这行代码中发生阻塞,程序就在这里停止住了
Thread.sleep(9999999L);
}catch (InterruptedException e){
e.printStackTrace();
}
}
},"first").start();
}
}
结果:
运行测试程序3,观察线程。main线程和子线程都阻塞
//第三段代码:观察main和子线程同时阻塞
public class first {
//运行一个进程,main就是主线程
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {
@Override
public void run() {
try{
//在这行代码中发生阻塞,程序就在这里停止住了
Thread.sleep(9999999L);
}catch (InterruptedException e){
e.printStackTrace();
}
}
},"first").start();
Thread.sleep(9999999L);
}
}
结果:
所以可以看到:java main线程是可以没有的,前面说的每个进程都至少有一个主线程,是指系统级别的主线程也就是C语言的main函数。
1.3多线程的执行方式
面试:run()和start()的区别
- Thread一个线程(创建状态)。
- start()启动线程,申请系统调度并运行(就绪态)。
- run()系统调度线程时,线程处于运行态时,会执行run()里面的代码(运行态)。
- 有就绪态什么时候转换为运行态,是由系统调度自身决定的。
例:main线程直接电调用run()方法
分析:这里没有start()方法启动线程,这里的t.run()只是main线程的对象方法调用,没有启动线程,所以在监控器里自然只有main线程阻塞。例:main线程和子线程同时运行,观察执行顺序
结果:
分析:(面试时,要保证说服严谨)
运行过程:new Thread创建一个子线程,当执行到start()时,主线程会申请系统调度执行子线程,但此时的主线程仍处于自身的运行状态,所以会继续向下执行自身的打印代码行。当主线程的时间片用完,系统电镀轮转到子线程时,子线程处于运行态,执行run()方法里的打印代码。
运行结果:从概率上说,main字符串先打印的概率比较大。如果start()调用以后,main的时间片刚好用完,此时系统调度刚好切换到子线程,并且子线程很快调度并执行,三者都满足此时才先打印出“first”。所以在概率上说,“main线程”会先被打印出来。
1.4多线程的使用场景(优劣势)
1.什么时候使用多线程?
(1)同时执行多个任务,当每个任务量比较大的时候,可以使用多线程提高效率。
(2)当前线程执行阻塞式代码时,需要同时执行其他代码,可以使用多线程达到不阻塞执行其他代码的目的。
2.多线程的效率考虑两个因素:这两个因素都与系统资源有关
(1)单个线程的任务量——任务量越小,相对创建线程及申请系统调度的时间性能来说,就不划算。
(2)运行的线程数量——系统资源,包括cpu核数,cpu频率,内存大小等都相关。在当前系统可用资源的条件下,多线程数量有一定的阈值。
- 如果单个任务量比较多,使用多线程可以提高效率,用增加线程数来提高效率,但当达到阈值后,效率就开始下降了。
1.5多线程的执行顺序分析
例1:
例2:
结果:随机打印出来这5个线程的当前名字
2.线程创建
2.1线程创建的方法
方法1:继承Thread类
该方法的好处是this代表当前线程,不需要通过Thread.currentThread()来获取当前线程的引用。
//创建线程的方法1:继承一个Thread类
//好处:this就是当前线程,不需要通过Thread.currentThread()来获取当前线程的引用
public class MyThread extends Thread{
@Override
public void run() {
System.out.println(this.getName());
}
public static void main(String[] args){
new MyThread().start();//线程开始运行
}
}
方法2.实现Runnable接口
通过实现Runnable接口,并且调用Thread的构造方法时,将Runnable对象作为参数传入用来创建线程对象。该方法的好处是:可以规避类的单继承的限制,但是需要通过Thread.currentThread()来获取当前线程的引用。
//创建线程的方式2
//创建线程的方式一定是Thread,Runnable只是一个参数接口
//好处:避免类的单继承限制,需要通过Thread.currentThread()来获取当前线程的引用
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args){
//使用Runnable对象创建线程对象,Runnable只是任务描述
Thread t = new Thread(new MyRunnable());
t.start();//线程启动
}
}
方法3:使用匿名内部类创建Thread的子类对象
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("first");
}
},"first").start();
2.2Thread类以及常用方法
Thread类是JVM用来管理线程的一个类,每个线程都有唯一的一个Thread对象与之关联。
Thread对象就是用来描述一个线程执行的流的,JVM将这些Thread对象组织起来,用于线程调度,线程管理。
当前线程:静态方法被调用的代码行所在线程为当前线程。
Thread的常见构造方法:
Thread的几个常见属性:
Thread的几种静态方法:
Thread的几种实例方法:
各属性方法操作:
public class ThreadDome {
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "我还活着");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "我即将死去");
});
System.out.println(Thread.currentThread().getName() + "名称:" +t.getName());
System.out.println(Thread.currentThread().getName() + "状态:" + t.getState());
}
}
join()方法操作:
体会在多线程中使用join()方法:
//同时运行多线程,等待所有线程执行完毕,再执行main后面的方法
public class MultiThreadFinishThenDoMain {
public static void main(String[] args) throws InterruptedException {
//目前的代码:是mian先打印,然后0-19随机同时打印
//预期结果:先随机同时打印0-19,全部打印完之后,再打印main
//不能在循环内部加join,如果在循环里面加,就达不到同时执行20个子线程的目的
// 先在外面获取这20个线程的引用对象,然后再调用阻塞
//通过数组或者集合的方法,来获取这20个线程的引用对象,再取出来再主线程中join阻塞,就能达到目的了
Thread[] threads = new Thread[20];
for (int i = 0;i < 20;i++){
final int j = i;
Thread t = new Thread(()->{
System.out.println(j);
});
t.start();
threads[i] = t;
}
for (int i = 0;i < 20;i++){
//子线程同时运行的,这里的join只是对主线程有阻塞影响
threads[i].join();
}
System.out.println("main");
}
}
中断一个线程
根据代码来观察上三种中断方法的区别:
代码1:
代码2:
代码3:
代码4:
中断的三种方法总结(常考):