线程的概念
线程(Thread) ,也叫"轻量级进程",是为了实现并发编程而引入的.一个进程里可以有多个线程,像我们人体也由多个细胞组织构造,各个组织相互配合;
(关于线程,大家可以看看这里简单学习->线程的概念-CSDN博客来了解线程)
创建线程
如何使用java来创建线程
线程是操作系统里的概念,我们不能直接就操作线程,而是使用操作系统提供的api来操作线程;而 Java针对系统api进行了封装,这样我们就可以直接使用Java就可以操作线程 ;
Thread 类
在Java中,我们使用Thread 类就可以进一步操作线程,可以使用Thread类来创建线程 ;
Thread类里有两个重要的方法
- run()
- start()
run() 方法
我们创建一个线程时,是需要这个线程完成一些工作,那么在创建线程时,就告诉这个线程,要做什么工作,将任务"交给"线程;
run() 方法: 描述了线程要完成的任务;(当线程完成了它的任务,也就是线程的run方法执行完了,那么线程就会自动销毁)
例如:
(线程的run方法里是打印"hello world",那么这个线程的任务就是打印"hello world")
start() 方法
run()方法只是描述了线程要做什么,怎么创建线程? => 就是通过 start() 方法, 来把线程创建出来;
start()方法: 会真正的调用系统api , 创建出线程,然后让线程完成run()里面的任务;
(run()方法我们一般不会主动调用,我们应该调用start()方法,通过start()方法调用系统api创建出线程,让线程去调用run()方法,让线程去完成run()里的任务)
创建一个线程
我们来简单学习,如何通过Thread类来创建一个线程,并且让线程执行任务;
1.创建一个MyThread类, 继承Thread类,并且MyThread类重写Thread的run()方法 ;
(此时,重写的run方法里的就是线程要执行的操作)
2.通过MyThread构造出对象,然后调用start() 方法 ;(start是Thread类里原有的方法)
(通过start()方法来调用系统api,创建出线程)
3.执行结果输出: hello world
虽然我们没有手动调用run()方法,但是通过start()方法,创建出线程,线程就会自行调用run()方法,完成run()里面的工作;
代码
//创建MyThread继承Thread类
class MyThread extends Thread{
@Override
public void run() { //重写Thread的run方法
System.out.println("hello world"); //run方法里就是线程要完成的任务
}
}
public class Test3 {
public static void main(String[] args) {
//创建我们自己的MyThread对象,来操作线程 ;
MyThread myThread = new MyThread() ;
myThread.start(); //执行start()方法,调用系统api创建出线程 ;
}
}
线程和线程之间都是相互独立的,并发执行的,这里我们将稍微修改一下代码:
运行输出结果
可以看到一会输出main,一会输出MyThread;
因为要运行一个Java进程,里面就要有一个主线程,而这个主线程的入口方法就是main();
(可以试试写一个类,但是不写main方法,然后点击运行,idea会报错,因为没有main入口方法)
代码的基本执行顺序:
(最后两个线程都在while死循环里,由于并发执行,可以看到一会输出"main",一会输出"MyThread")
使用jconsole 来观察线程 ;
jconsole是JDK自带的工具,可以观察java进程里的多线程的情况 ;
使用jconsole
1.jconsole在我们安装的JDK的bin目录下 ;
2.执行我们的多线程代码,保证代码是在运行中;
//创建MyThread继承Thread类
class MyThread extends Thread{
@Override
public void run() {
while (true){
System.out.println("MyThread");
}
}
}
public class Test3 {
public static void main(String[] args) {
MyThread myThread = new MyThread() ;
myThread.start();
while (true){
System.out.println("main");
}
}
}
3. 双击运行桌面上的jconsole ;
如果这里的本地进程没有你的进程,那么返回idea查看自己的代码是否是正在运行,或者右击jconsole,使用管理员身份运行 ;
4.点击不安全连接
5.在这点击线程,我们就可以看到,目前正在运行的的线程的情况 ;
可以在下方可以看到各个线程和线程的信息
(左边是各个线程,右边是线程的信息)
通过jconsole就可以看到我们的确是用start()创建出了线程 ;(在运行java进程,除了我们创建的,还有很多其他线程)
了解怎么创建线程,就再来详细了解run 和 start 的区别
详细了解run() 和 start() 的区别
1.run()是线程的入口方法,描述了线程要做什么,不会创建出线程;
2.start()则是会调用系统api,在系统中创建出线程,然后在让线程去调用run();
3.run()方法是线程的入口方法,我们不直接调用run(),而是让start()间接去调用run() ;
举个例子
厂里的螺丝太多了,打不完,我要雇佣人来帮我"打螺丝"; 那么 "打螺丝"这个任务就是run(), "雇佣别人" 就是 start() ;
我会自己打螺丝吗? 当然不会. 我要雇佣别人, 来帮我打螺丝(所以我自己不会调用run()方法) , 那么我就雇佣别人(start) 过来, 然后别人 就会 完成 打螺丝(run) 这个任务, 当螺丝打完了(run方法执行完了), 那么雇佣过来的人就可以 领工资 解散了(run执行完了,线程销毁了);
代码例子:
1.调用start() 方法, 雇佣别人来打螺丝;
运行结果:
(运行结果两个线程并发执行,可以在jconsole里看到有两个线程)
如果说main主线程是"我自己" , 那么Thread-0线程就是 我雇佣 来的人 ;
2.不调用start()方法,直接调用run()方法, 不雇佣别人,自己打螺丝;(这样我就不用打螺丝,而是在监工, 螺丝是工人在打)
运行结果
(一直在输出打螺丝", jconsole里也只有main里一个主线程)
如果main线程是"我自己"的话,这不调用start(),而是直接调用run()方法,那么就只有 "我自己" 在打螺丝(不调用start()创建线程,那么就是普通的方法调用,调用了run方法,run里是个死循环,就一直输出"打螺丝",不会执行到下面的 "我是监工,不打螺丝");
代码:
class WorkerThread extends Thread {
@Override
public void run() {
while (true){
System.out.println("打螺丝");
}
}
}
public class Test1 {
public static void main(String[] args) {
WorkerThread worker = new WorkerThread();
worker.run(); // 直接调用run
//worker.start(); // 调用 start() , 雇佣别人来打螺丝
while (true){
System.out.println("我是监工,不打螺丝");
}
}
}
创建线程的常见方法
1. 继承Thread , 重写run方法
class MyThread extends Thread {
@Override
public void run() {
System.out.println("继承Thread , 重写run");
}
}
public class Test2 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
}
}
2. 实现 Runnable 接口, 重写run()方法
上面 的 继承Thread,重写的run()方法; 这个run任务, 只有MyThread对象创建的线程才能使用,如果我们要其他线程也 能够去执行这个run任务 => 利用 Runnable ;
Runnable 接口里只有一个 run()方法; 可以把Runnable看作一个单独的可执行的任务, 这个任务可以 给这个线程执行,也可以交给 别的线程 执行;(我这个Runnable任务,可以给这个Thread创建的线程来执行, 也可以 给别的 实体来执行);
通过向上转型,创建Runnable对象, 然后用Runnable对象,构造出Thread对象 ;
class MyRunnable implements Runnable{ // 创建MyRunnable类实现 Runnable接口
@Override
public void run() { //重写Runnable的 run 方法 ;
System.out.println("My Runnable");
}
}
public class Test2 {
public static void main(String[] args) {
//通过向上转型,创建出Runnable对象 ,
Runnable runnable = new MyRunnable();
// 然后用 Thread对象来接收 Runnable对象
Thread t1 = new Thread(runnable);
t1.start(); // 此时 t1 创建出来的 线程,会执行上面 MyRunnable里的 run任务 ;
// 不止是 t1 可以用, 其他 Thread对象也能执行这个Runnable任务;
Thread t2 = new Thread(runnable);
t2.start();
}
}
运行结果:
(这样将要执行的任务提取出来,就不只是一个对象能用,其他的也执行这个Runnable任务 )
使用 实现Runnable创建线程 和 继承Thread创建线程 的区别
1.解耦合: 降低 任务 和 Thread的耦合; (同一个螺丝, 我可以给路人甲打, 我也可以给路人乙打,就不会说非要这路人甲才能打这个螺丝)
2.Runnable是一个 单独的可执行的 任务, 这个任务可以交给Thread创建的线程来执行,也可以给其他实体来执行(这个Runnable任务,能给Thread创建的线程来执行,也可以给别的,后面我们会学习到)
3.创建线程主要有两步: 1.是确定要执行的任务是什么 2.是调用系统api创建出线程(我们用Runnable就可以把任务单独提取出来)
3. 继承Thread,重写run方法 (用匿名内部类)
前面1.的还要专门创建类出来,没有必要;
在创建Thread对象时,使用匿名内部类,继承Thread,重写run ;
public class Test2 {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override
public void run() {
System.out.println("匿名内部类,继承Thread,重写run");
}
};
t1.start();
}
}
这样就创建了一个 继承Thread 并且重写了 run方法 的匿名内部类, 然后用 Thread 来接收;就不用特意创建一个类出来 ;
4. 实现Runnable接口,重写run方法(使用匿名内部类)
上面2.实现Runnable接口,重写run方法,还要创建一个类出来,也有的没必要;
创建一个 实现 Runnable接口, 重写run方法的匿名内部类对象,然后用 Runnable 来接收
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("匿名内部类,实现Runnable,重写run");
}
};
Thread t1 = new Thread(runnable);
t1.start();
}
创建了一个 实现 Runnable接口 ,重写run方法的 匿名内部类对象 , 并用 Runnable 来接收,就不要特意创建一个类 ;
当然,也可以更便捷,将匿名内部类对象,直接用来构造 Thread对象 ;
public class Test2 {
public static void main(String[] args) {
Thread t2 = new Thread(new Runnable(){ // 创建出来的匿名内部类对象,直接传给Thread ;
@Override
public void run() {
System.out.println("匿名内部类,实现Runnable,重写run,");
}
});
t2.start();
}
}
构造Thread对象时可以传Runnable,所以传一个上面的匿名内部类对象也是可以的 ;
5.使用 Lambda 表达式 (最常用)
Runnable接口里只有 一个 run 方法 , 所以Runnable本来就是一个 函数式接口 ;
而且可以用 Runnable任务 ,来构造Thread 对象 ;
在创建 Thread对象时 ,使用 Lambda 表达式 ,代替上面 4.的匿名内部类对象, 作为参数来构造Thread 对象 ;
public class Test2 {
public static void main(String[] args) {
Thread t1 = new Thread(()->{ // Lambda代替 Runnable ;
System.out.println("Lambda表达式"); //run没有返回值,所以不需要return
});
t1.start();
}
}
使用Lambda表达式来创建出线程,是最常用的方式,方便,快捷;但上面的方法最后都不要忘记调用start() ;