石家庄的网站建设公司,个人网站备案名称填写的注意事项,wordpress新淘客,手机网址2021年免费不封文章目录 一、创建和启动线程#xff08;1#xff09;概述#xff08;2#xff09;方式1#xff1a;继承Thread类1、使用步骤2、举例2.1 案例12.2 案例22.3 案例3 3、两个问题3.1 问题13.2 问题2 4、代码及总结 二、练习#xff08;1#xff09;方式一#xff08;21概述2方式1继承Thread类1、使用步骤2、举例2.1 案例12.2 案例22.3 案例3 3、两个问题3.1 问题13.2 问题2 4、代码及总结 二、练习1方式一2方式二 一、创建和启动线程
1概述
Java语言的JVM允许程序运行多个线程使用java.lang.Thread类代表线程所有的线程对象都必须是Thread类或其子类的实例。 创建一个线程可以理解为创建这个类的一个对象一个对象对应一个线程Thread类的特性 每个线程都是通过某个特定Thread对象的run()方法来完成操作的因此把run()方法体称为线程执行体。通过该Thread对象的start()方法来启动这个线程而非直接调用run()要想实现多线程必须在主线程中创建新的线程对象。 2方式1继承Thread类
1、使用步骤
Java通过继承Thread类来创建并启动多线程的步骤如下
定义Thread类的子类并重写该类的run()方法该run()方法的方法体就代表了线程需要完成的任务创建Thread子类的实例即创建了线程对象调用线程对象的start()方法来启动该线程
如下 举例代码如下
package com.atguigu.thread;
//自定义线程类
public class MyThread extends Thread {//定义指定线程名称的构造方法public MyThread(String name) {//调用父类的String参数的构造方法指定线程的名称super(name);}/*** 重写run方法完成该线程执行的逻辑*/Overridepublic 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) {//创建自定义线程对象1MyThread mt1 new MyThread(子线程1);//开启子线程1mt1.start();//创建自定义线程对象2MyThread mt2 new MyThread(子线程2);//开启子线程2mt2.start();//在主方法中执行for循环for (int i 0; i 10; i) {System.out.println(main线程i);}}
}注意 如果自己手动调用run()方法那么就只是普通方法没有启动多线程模式。run()方法由JVM调用什么时候调用执行的过程控制都有操作系统的CPU调度决定。想要启动多线程必须调用start方法。一个线程对象只能调用一次start()方法启动如果重复调用了则将抛出以上的异常“IllegalThreadStateException”。 2、举例
2.1 案例1
描述创建一个分线程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() ---将此线程要执行的操作声明在此方法体中Overridepublic void run() {for (int i 1; i 100; i) {if(i%20){System.out.println(i);}}}
}3 创建当前Thread的子类的对象。
public class EvenNumberTest {public static void main(String[] args) {//3.创建当前Thread的子类的对象PrintNumber t1new PrintNumber();}
}4通过对象调用start(): 1.启动线程 2.调用当前线程的run()。
public class EvenNumberTest {public static void main(String[] args) {//3.创建当前Thread的子类的对象PrintNumber t1new PrintNumber();//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()t1.start();}
}//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{//2.重写Thread类的run() ---将此线程要执行的操作声明在此方法体中Overridepublic void run() {for (int i 1; i 100; i) {if(i%20){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 t1new PrintNumber();//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()t1.start();}
}//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{//2.重写Thread类的run() ---将此线程要执行的操作声明在此方法体中Overridepublic void run() {for (int i 1; i 100; i) {if(i%20){System.out.println(i);}}}
}输出结果部分 2.2 案例2
刚才并没有感觉到线程的存在啊现在修改一下代码
package yuyi01.thread;public class EvenNumberTest {public static void main(String[] args) {//3.创建当前Thread的子类的对象PrintNumber t1new PrintNumber();//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()t1.start();//main()方法所在的线程执行的操作for (int i 1; i 100; i) {if(i%20){System.out.println(imain()做的事情);}}}
}//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{//2.重写Thread类的run() ---将此线程要执行的操作声明在此方法体中Overridepublic void run() {for (int i 1; i 100; i) {if(i%20){System.out.println(i);}}}
}此时有两个线程 再次执行代码部分 此时它们没有交互如果数字足够多会有交叉的数据出现就是交替执行。
这说明两个线程都在执行前面的执行一下后面的执行一下。谁先执行都有可能。这里就体现出了两个不同的线程。
2.3 案例3
其实这里还有一种方式可以看出来是两个线程在交替执行需要用到一个方法后面再说这里先用一下。
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 t1new PrintNumber();//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()t1.start();//main()方法所在的线程执行的操作for (int i 1; i 10000; i) {if(i%20){System.out.println(Thread.currentThread().getName() : i**********);}}}
}//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{//2.重写Thread类的run() ---将此线程要执行的操作声明在此方法体中Overridepublic void run() {for (int i 1; i 10000; i) {if(i%20){System.out.println(Thread.currentThread().getName() : i);}}}
}输出结果部分 可以看到现在的执行结果前面都有自己线程的名字。
t1线程也有自己的线程名字默认叫Thread-0。因为此时new的是当前类PrintNumber的对象默认的是调用super()是父类Thread里面的构造器如下 所以当我们不给线程起名字的时候默认第一个线程就是Thread-0第二个线程就是Thread-1。
注意下面 3、两个问题
3.1 问题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()的调用。
3.2 问题2
☕问题2
比如现在启动了一个线程调用一下start()打印了1000以内的偶数。代码如下
package yuyi01.thread;public class EvenNumberTest {public static void main(String[] args) {//3.创建当前Thread的子类的对象PrintNumber t1new PrintNumber();//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()t1.start();//main()方法所在的线程执行的操作for (int i 1; i 1000; i) {if(i%20){System.out.println(Thread.currentThread().getName() : i**********);}}}
}//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{//2.重写Thread类的run() ---将此线程要执行的操作声明在此方法体中Overridepublic void run() {for (int i 1; i 1000; i) {if(i%20){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 t1new PrintNumber();//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()t1.start();/** 问题2再提供一个分线程用于100以内偶数的遍历** 注意不能让已经start()的线程再次执行start()操作否则报IllegalThreadStateException非法线程状态的异常。* *///t1.start();PrintNumber t2new PrintNumber();t2.start();//main()方法所在的线程执行的操作for (int i 1; i 1000; i) {if(i%20){System.out.println(Thread.currentThread().getName() : i**********);}}}
}//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{//2.重写Thread类的run() ---将此线程要执行的操作声明在此方法体中Overridepublic void run() {for (int i 1; i 1000; i) {if(i%20){System.out.println(Thread.currentThread().getName() : i);}}}
}输出结果部分 输出默认的线程名也很好理解造的第二个对象就是Thread-1。
从上面的输出结果可以看到三个线程交替执行。
4、代码及总结
线程的创建方式一继承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 t1new PrintNumber();//4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()t1.start();/*问题1能否使用t1.run()来替换t1.start()的调用实现分线程的创建和调用 不能*///t1.run();/** 问题2再提供一个分线程用于100以内偶数的遍历** 注意不能让已经start()的线程再次执行start()操作否则报IllegalThreadStateException非法线程状态的异常。* *///t1.start();PrintNumber t2new PrintNumber();t2.start();//main()方法所在的线程执行的操作for (int i 1; i 1000; i) {if(i%20){System.out.println(Thread.currentThread().getName() : i**********);}}}
}//1.创建一个继承于Thread类的子类
class PrintNumber extends Thread{//2.重写Thread类的run() ---将此线程要执行的操作声明在此方法体中Overridepublic void run() {for (int i 1; i 1000; i) {if(i%20){System.out.println(Thread.currentThread().getName() : i);}}}
}输出结果部分 二、练习
题目描述
练习创建两个分线程其中一个线程遍历100以内的偶数另一个线程遍历100以内的奇数。
1方式一
分析
【回顾】
线程的创建方式一继承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 t1new EvenNumberPrint();OddNumberPrint t2new OddNumberPrint();t1.start();t2.start();}
}//打印偶数
class EvenNumberPrint extends Thread {Overridepublic void run() {for (int i 1; i 100; i) {if (i % 2 0) {System.out.println(Thread.currentThread().getName() i);}}}
}//打印奇数
class OddNumberPrint extends Thread {Overridepublic void run() {for (int i 1; i 100; i) {if (i % 2 ! 0) {System.out.println(Thread.currentThread().getName() --i);}}}
}输出结果部分 2方式二
分析
上面的方式一是一种标准写法之前还讲过匿名子类的方式需要提供Thread子类的对象。
所以直接new一个Thread()因为是匿名子类后面直接加一对大括号这样对象就造好了如下
new Thread(){}这时可以声明为t1然后通过t1去调start()如下
Thread t1new 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();}
}输出结果部分 可以看到方法一与方法二的效果一样。
若是以后有一个临时需求造一个线程做个事情可能都不会按照方式一那么正规地写都是采用匿名的方式做一下。