java.lang.Thread
类代表线程,所有的线程对象都必须是Thread类或其子类的实例。 (创建一个线程可以理解为创建这个类的一个对象,一个对象对应一个线程)线程执行体
。Java通过继承Thread类来创建并启动多线程的步骤如下:
如下:
举例代码如下:
package com.atguigu.thread;
//自定义线程类
public class MyThread extends Thread {
//定义指定线程名称的构造方法
public MyThread(String name) {
//调用父类的String参数的构造方法,指定线程的名称
super(name);
}
/**
* 重写run方法,完成该线程执行的逻辑
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在执行!"+i);
}
}
}
测试类:
package com.atguigu.thread;
public class TestMyThread {
public static void main(String[] args) {
//创建自定义线程对象1
MyThread mt1 = new MyThread("子线程1");
//开启子线程1
mt1.start();
//创建自定义线程对象2
MyThread mt2 = new MyThread("子线程2");
//开启子线程2
mt2.start();
//在主方法中执行for循环
for (int i = 0; i < 10; i++) {
System.out.println("main线程!"+i);
}
}
}
注意:
- 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
- run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
- 想要启动多线程,必须调用start方法。
- 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“
IllegalThreadStateException
”。
🌋描述:创建一个分线程1,用于遍历100以内的偶数。
🚗分析
<1> 创建一个继承于Thread类的子类。
public class EvenNumberTest {
}
//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{
}
<2> 重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中。
public class EvenNumberTest {
}
//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{
//2.重写Thread类的run() --->将此线程要执行的操作,声明在此方法体中
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if(i%2==0){
System.out.println(i);
}
}
}
}
<3> 创建当前Thread的子类的对象。
public class EvenNumberTest {
public static void main(String[] args) {
//3.创建当前Thread的子类的对象
PrintNumber t1=new PrintNumber();
}
}
<4>通过对象调用start()
: 1.启动线程 2.调用当前线程的run()
。
public class EvenNumberTest {
public static void main(String[] args) {
//3.创建当前Thread的子类的对象
PrintNumber t1=new PrintNumber();
//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()
t1.start();
}
}
//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{
//2.重写Thread类的run() --->将此线程要执行的操作,声明在此方法体中
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if(i%2==0){
System.out.println(i);
}
}
}
}
当前PrintNumber
类里面没有重写start方法,这就意味着调用的是父类Thread
里面的start方法。
这个start()
方法有什么作用呢?
所以start方法有两个作用:1.启动线程 2.调用当前线程的
run()
方法。
在刚才的代码中,t1
调用start()
方法,这里的start()
方法是父类中的。
此时调用了当前线程的run()
方法,这个方法在Thread
类里面定义的,并且在子类PrintNumber
里面被重写了,所以父类中的方法被覆盖了,此时调用的就是子类的run()方法。
整体来看,调用t1.start()
,子类中的run()
方法就被执行了。
🌱整体代码
package yuyi01.thread;
/**
* ClassName: EvenNumberTest
* Package: yuyi01.thread
* Description:
* 创建一个分线程1,用于遍历100以内的偶数
* @Author 雨翼轻尘
* @Create 2024/1/19 0019 11:57
*/
public class EvenNumberTest {
public static void main(String[] args) {
//3.创建当前Thread的子类的对象
PrintNumber t1=new PrintNumber();
//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()
t1.start();
}
}
//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{
//2.重写Thread类的run() --->将此线程要执行的操作,声明在此方法体中
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if(i%2==0){
System.out.println(i);
}
}
}
}
🍺输出结果(部分)
刚才并没有感觉到线程的存在啊,现在修改一下代码:
package yuyi01.thread;
public class EvenNumberTest {
public static void main(String[] args) {
//3.创建当前Thread的子类的对象
PrintNumber t1=new PrintNumber();
//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()
t1.start();
//main()方法所在的线程执行的操作:
for (int i = 1; i <= 100; i++) {
if(i%2==0){
System.out.println(i+"main()做的事情");
}
}
}
}
//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{
//2.重写Thread类的run() --->将此线程要执行的操作,声明在此方法体中
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if(i%2==0){
System.out.println(i);
}
}
}
}
此时有两个线程:
再次执行代码:(部分)
此时它们没有交互,如果数字足够多,会有交叉的数据出现,就是交替执行。
这说明两个线程都在执行,前面的执行一下,后面的执行一下。谁先执行都有可能。这里就体现出了两个不同的线程。
其实这里还有一种方式可以看出来是两个线程在交替执行,需要用到一个方法,后面再说,这里先用一下。
Thread
有个方法叫做currentThread()
,获取当前执行的线程;然后它又有一个getName()
方法,即获取线程的名字。
即:
Thread.currentThread().getName()
🌱代码
package yuyi01.thread;
/**
* ClassName: EvenNumberTest
* Package: yuyi01.thread
* Description:
* 创建一个分线程1,用于遍历100以内的偶数
* @Author 雨翼轻尘
* @Create 2024/1/19 0019 11:57
*/
public class EvenNumberTest {
public static void main(String[] args) {
//3.创建当前Thread的子类的对象
PrintNumber t1=new PrintNumber();
//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()
t1.start();
//main()方法所在的线程执行的操作:
for (int i = 1; i <= 10000; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName() +":" +i+"**********");
}
}
}
}
//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{
//2.重写Thread类的run() --->将此线程要执行的操作,声明在此方法体中
@Override
public void run() {
for (int i = 1; i <= 10000; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName() +":" +i);
}
}
}
}
🍺输出结果(部分)
可以看到,现在的执行结果,前面都有自己线程的名字。
t1线程也有自己的线程名字,默认叫Thread-0
。因为此时new的是当前类PrintNumber的对象,默认的是调用super()
,是父类Thread里面的构造器,如下:
所以,当我们不给线程起名字的时候,默认第一个线程就是Thread-0
,第二个线程就是Thread-1
。
注意下面:
?问题1
启动线程,包括调用上面的run方法都用的是start()
。
那么能否使用t1.run()
来替换t1.start()
的调用,实现分线程的创建和调用?
🍰分析
比如现在这样写:
运行看一下:
可以看到不仅没有交替执行,而且还都是main,没有Thread-0了。它认为run()
方法是主线程main做的。
此时就是主线程造了一个t1对象,然后就调用了run()
这个普通的方法,执行完之后再执行循环输出。只有一条线程了。这就不是多线程问题了。
再举个例子,看看下面的代码是不是多线程:
public class SingleThread {
public void method1(String str) {
System.out.println(str);
}
public void method2(String str) {
method1(str);
}
public static void main(String[] args) { //main线程
SingleThread s = new SingleThread();
s.method2("hello!");
}
}
首先main方法进去,造了一个当前类的一个对象s,这个对象s调用method2(),method2()里面调用method1()。这里是单线程。
判断单线程还是多线程,就看能不能拿一条线穿起来。
比如此时就可以拿一条线穿起来,只有一条执行路径,那就是一个线程,即单线程
。如下:
之前的那个例子,也是类似,就是一个线程。如下:(用鼠标手画的有点不堪入目哈哈)
而start()
不一样,一个作用是启动线程,然后是调用run方法。
若是仅仅调用run方法的话,线程并没有启动,相当于还是只有主线程。
所以,不能使用t1.run()
来替换t1.start()
的调用。
?问题2
比如现在启动了一个线程,调用一下start()
,打印了1000以内的偶数。代码如下:
package yuyi01.thread;
public class EvenNumberTest {
public static void main(String[] args) {
//3.创建当前Thread的子类的对象
PrintNumber t1=new PrintNumber();
//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()
t1.start();
//main()方法所在的线程执行的操作:
for (int i = 1; i <= 1000; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName() +":" +i+"**********");
}
}
}
}
//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{
//2.重写Thread类的run() --->将此线程要执行的操作,声明在此方法体中
@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName() +":" +i);
}
}
}
}
现在需要再创建一个分线程,同样要去遍历一下1000以内的偶数。
那么此时要怎样去做呢?
🍰分析
直接用t1再次调用start()
方法可以吗?如下:
运行结果:
可以看到,出现了IllegalThreadStateException
的异常。
当我们首次调用start()
方法的时候,threadStatus
的值是0 ,就没有抛异常。当我们再次调用start()
之后,threadStatus
状态就不是0了,便会抛异常。
所以,线程不能start()
多次。
不能让已经start()的线程,再次执行start()操作,否则报
IllegalThreadStateException
非法线程状态的异常。
🎲解决方案
既然上面的方法不行,那么究竟应该怎么做呢?
需要重新创建一个对象,类PrintNumber
不需要重新创建了,因为要做的事情一样,都是遍历1000以内的偶数。
所以现在只需要再建立一个对象,然后用这个新的对象去调用start()
方法即可。
如下:
🌱整体代码
package yuyi01.thread;
public class EvenNumberTest {
public static void main(String[] args) {
//3.创建当前Thread的子类的对象
PrintNumber t1=new PrintNumber();
//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()
t1.start();
/*
* 问题2:再提供一个分线程,用于100以内偶数的遍历
*
* 注意:不能让已经start()的线程,再次执行start()操作,否则报IllegalThreadStateException非法线程状态的异常。
* */
//t1.start();
PrintNumber t2=new PrintNumber();
t2.start();
//main()方法所在的线程执行的操作:
for (int i = 1; i <= 1000; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName() +":" +i+"**********");
}
}
}
}
//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{
//2.重写Thread类的run() --->将此线程要执行的操作,声明在此方法体中
@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName() +":" +i);
}
}
}
}
🍺输出结果(部分)
输出默认的线程名也很好理解,造的第二个对象就是Thread-1
。
从上面的输出结果可以看到,三个线程交替执行。
线程的创建方式一:继承Thread类
步骤:
① 创建一个继承于Thread类的子类
② 重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中
③ 创建当前Thread的子类的对象
④ 通过对象调用start()
: 1.启动线程 2.调用当前线程的run()
方法
start()是父类Thread的方法。
🌱整体代码总结
package yuyi01.thread;
/**
* ClassName: EvenNumberTest
* Package: yuyi01.thread
* Description:
* 创建一个分线程1,用于遍历100以内的偶数
* @Author 雨翼轻尘
* @Create 2024/1/19 0019 11:57
*/
public class EvenNumberTest {
public static void main(String[] args) {
//3.创建当前Thread的子类的对象
PrintNumber t1=new PrintNumber();
//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()
t1.start();
/*
问题1:能否使用t1.run()来替换t1.start()的调用,实现分线程的创建和调用? 不能
*/
//t1.run();
/*
* 问题2:再提供一个分线程,用于100以内偶数的遍历
*
* 注意:不能让已经start()的线程,再次执行start()操作,否则报IllegalThreadStateException非法线程状态的异常。
* */
//t1.start();
PrintNumber t2=new PrintNumber();
t2.start();
//main()方法所在的线程执行的操作:
for (int i = 1; i <= 1000; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName() +":" +i+"**********");
}
}
}
}
//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{
//2.重写Thread类的run() --->将此线程要执行的操作,声明在此方法体中
@Override
public void run() {
for (int i = 1; i <= 1000; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName() +":" +i);
}
}
}
}
🍺输出结果(部分)
🌋题目描述
练习:创建两个分线程,其中一个线程遍历100以内的偶数,另一个线程遍历100以内的奇数。
🍰分析
【回顾】
线程的创建方式一:继承Thread类
步骤:
① 创建一个继承于Thread类的子类
② 重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中
③ 创建当前Thread的子类的对象
④ 通过对象调用start()
: 1.启动线程 2.调用当前线程的run()
方法
【分析】
现在是两个线程,做的事情不一样。run方法里面执行的方法体就不一样了。
两个分线程,这里就不要使用主线程了。
既然两个线程指定的事情不一样,那就写两个run,这就意味着要声明两个类了(要是两个线程做的事情一样,那就只用写一个类,就像上面的案例)。
🌱代码(方式一)
package yuyi01.thread.exer1;
/**
* ClassName: PrintNumberTest
* Package: yuyi01.thread.exer1
* Description:
*
* @Author 雨翼轻尘
* @Create 2024/1/19 0019 22:38
*/
public class PrintNumberTest {
public static void main(String[] args) {
//方式一
EvenNumberPrint t1=new EvenNumberPrint();
OddNumberPrint t2=new OddNumberPrint();
t1.start();
t2.start();
}
}
//打印偶数
class EvenNumberPrint extends Thread {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":"+i);
}
}
}
}
//打印奇数
class OddNumberPrint extends Thread {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + "-->"+i);
}
}
}
}
🍺输出结果(部分)
🍰分析
上面的方式一是一种标准写法,之前还讲过匿名子类的方式,需要提供Thread
子类的对象。
所以直接new一个Thread()
,因为是匿名子类,后面直接加一对大括号,这样对象就造好了,如下:
new Thread(){}
这时可以声明为t1,然后通过t1去调start()
,如下:
Thread t1=new Thread(){};
t1.start();
当然,我们可以不声明它为t1,直接在后面.start()
,如下:
new Thread(){}.start();
在大括号里面,可以将run()
方法重写一下。如下:
//方式二
new Thread(){
//打印偶数
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":"+i);
}
}
}
}.start();
new Thread(){
//打印奇数
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + "-->"+i);
}
}
}
}.start();
方法二可以叫做,创建Thread类的匿名子类的匿名对象,造完对象之后还顺便将start()方法给调用了。
🌱代码(方式二)
package yuyi01.thread.exer1;
/**
* ClassName: PrintNumberTest
* Package: yuyi01.thread.exer1
* Description:
*
* @Author 雨翼轻尘
* @Create 2024/1/19 0019 22:38
*/
public class PrintNumberTest {
public static void main(String[] args) {
//方式二
new Thread(){
//打印偶数
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":"+i);
}
}
}
}.start();
new Thread(){
//打印奇数
public void run() {
for (int i = 1; i <= 100; i++) {
if (i % 2 != 0) {
System.out.println(Thread.currentThread().getName() + "-->"+i);
}
}
}
}.start();
}
}
🍺输出结果(部分)
可以看到,方法一与方法二的效果一样。
若是以后,有一个临时需求,造一个线程做个事情,可能都不会按照方式一那么正规地写,都是采用匿名的方式做一下。