Java基础之多线程4月打卡day08
Java基础之多线程4月打卡day08
关于作者
-
作者介绍
🍓 博客主页:
🍓 简介:JAVA领域优质创作者🥇、一名在校大三学生🎓、在校期间参加各种省赛、国赛,斩获一系列荣誉🏆。
🍓 关注我:关注我学习资料、文档下载统统都有,每日定时更新文章,励志做一名JAVA资深程序猿👨💻。
1、多线程
要使用多线程必须有一个前提,有一个线程的执行主类。从多线程开始,Java正式进入到应用部分,而对于多线程的开发,从JavaEE上表现的并不是特别多,但是在Android开发之中使用较多,并且需要提醒的是,鄙视面试的过程之中,多线程所问道的问题是最多的。
1.1 多线程的基本概念
如果要想解释多线程,那么首先应该从单线程开始讲起,最早的DOS系统有一个最大的特征:一旦电脑出现了病毒,电脑会立刻死机,因为传统DOS系统属于单进程的处理方式,即:在同一个时间段上只能有一个程序执行,后来到了windows时代,电脑即使(非致命)存在了病毒,那么也可以正常使用,只是满了一些而已,因为windows属于多进程的处理操作,但是这个时候的资源依然只有一块,所以在同一时间段上会有多个程序共同执行,而在一个时间点上只能有一个程序在执行,多线程实在一个进程基础之上的进一步划分,因为进程的启动所消耗的时间是非常长的,所以在进程之上的进一步划分就变得非常重要,而且性能也会有所提高。
所有的线程一定要依附于进程才能够存在,那么进程一旦消失了,线程也一定会消失,但反过来不一定,而Java是为数不多的支持多线程的开发语言之一。
1.2 多线程的实现
在Java之中,如果要想实现多线程的程序,就必须依靠一个线程的主体类(叫好比主类的概念一样,表示的是一个线程的主类),但是这个线程的主体类在定义的时候也需要有一些特殊的要求,这个类可以继承Thread类或实现Runnable接口来完成定义。
1.3 继承Thread类实现多线程
Java.lang.Thread是一个线程操作的核心类负责线程操作的类,任何类只需要继承了Thread类就可以成为一个线程的主类,但是既然是主类必须有它的使用方法,而线程启动的主方法是需要覆写Thread类中的run()方法才可以(相当于线程的主方法)。
package com.day12.demo;
class MyThread extends Thread{
private String title;
public MyThread(String title){
this.title = title;
}
public void run(){
for (int i = 0; i < 5; i++) {
System.out.println(this.title + ",i = " + i);
}
}
}
public class ThreadDemo {
@SuppressWarnings("unused")
public static void main(String[] args) {
MyThread thread1 = new MyThread("线程1");
MyThread thread2 = new MyThread("线程2");
MyThread thread3 = new MyThread("线程3");
thread1.run();
thread2.run();
thread3.run();
}
}
我们只是做了一个顺序打印操作,而和多线程没有关系。多线程的执行和进程是相似的,而是多个程序交替执行,而不是说各自执行各自的。正确启动多线程的方式应该是调用Thread类中的start()方法。
-
启动多线程只有一个方法:public void start(); 调用此方法会调用run()
正确启动多线程
package com.day12.demo;
class MyThread extends Thread{
private String title;
public MyThread(String title){
this.title = title;
}
public void run(){
for (int i = 0; i < 5; i++) {
System.out.println(this.title + ",i = " + i);
}
}
}
public class ThreadDemo {
@SuppressWarnings("unused")
public static void main(String[] args) {
MyThread thread1 = new MyThread("线程1");
MyThread thread2 = new MyThread("线程2");
MyThread thread3 = new MyThread("线程3");
thread1.start();
thread2.start();
thread3.start();
}
}
此时再次执行代码发现所有所有的线程对象变为交替执行。所以得出结论:要想启动线程必须依靠Thread类的start()方法执行,线程启动之后会默认调用了run()方法。
问题:为什么线程启动的时候必须调用start()而不是直接调用run()?
如果想要了解必须打开Java源代码进行浏览(在JDK按照目录下)
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
//这个异常的产生只有在你重复启动线程的时候才会发生
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
//只声明未实现的方法,同时使用native关键字定义,native调用本机的原生系统函数
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
多线程的实现一定需要操作系统的支持,那么异常的start0()方法实际上就和抽象方法很类似没有方法体,而这个方法体交给JVM去实现,即:在windows下的JVM可能使用A方法实现了start0(),而在Linux下的JVM可能使用了B方法实现了start0(),凡是在调用的时候并不会去关心集体是何方式实现了start0()方法,只会关心最终的操作结果,交给JVM去匹配了不同的操作系统。
所以多线程操作之中,使用start()方法启动多线程的操作是需要进行操作系统函数调用的。
1.4 Runnable接口实现多线程
Thread类的核心功能就是进行线程的启动,但是如果一个类直接继承Threa类就会造成单继承的局限,在Java中又提供了有另外一种实现Runable接口。
public interface Runnable{
public void run();
}
分享:如何区分新老接口?
在JDK之中个,由于其发展的时间较长,那么会出现一些新的接口和老的接口,这两者有一个最大的明显特征:所有最早提供的接口方法里面都不加上public,所有的新接口里面都有public。
通过Runnable接口实现多线程
package com.day12.demo;
class MyThread implements Runnable{
private String title;
public MyThread(String title){
this.title = title;
}
public void run(){
for (int i = 0; i < 5; i++) {
System.out.println(this.title + ",i = " + i);
}
}
}
这个时候和之前的继承Thread类区别不大,但是唯一的好处就是避免了单继承局限,不过现在问题也就来了,刚刚解释过,如果要想启动多线程依靠Thread类的start()方法完成,之前继承Thread()类的时候可以将此方法直接继承过来使用,但现在实现的是Runnable接口,没有这个方法可以继承了,为了解决这个问题,还是需要依靠Thread类完成,在Thread类中定义一个构造方法:public Thread(Runnable target),接收Runnable接口对象。
利用Thread类启动
public class ThreadDemo {
@SuppressWarnings("unused")
public static void main(String[] args) {
MyThread thread1 = new MyThread("线程1");
MyThread thread2 = new MyThread("线程2");
MyThread thread3 = new MyThread("线程3");
new Thread(thread1).start();
new Thread(thread2).start();
new Thread(thread3).start();
}
}
这个时候就实现了多线程的启动,而且没有了单继承局限。
现在Thread类Runnable接口都可以作为同一功能的方式来实现多线程,那么这两者如果从Java的十年开发而言,肯定使用Ruanable接口,因为可以有效的避免单继承的局限,那么除了这些之外,这两种方式是否还有其他联系呢?
为了解释这两种方式的联系,下面可以打开Thread类的定义:
Public class Thread Object implements Runnable
发现Thread类也是Runnable接口的子类,而如果真的是这样,那么之前程序的结构就变为了一下形式。所以说多线程非常类似于代理模式。
这个时候表现出来的代码模式非常类似于代理设计模式,但是它不是严格意义上的代理设计模式,因为从严格意义上来讲代理设计模式之中,代理主体所能够使用的方法依然是接口中定义的run()方法,而此处代理主题调用的是start()方法,所以只能够说形式上类似于代理设计模式,但本质上还是有差别的。
但是除了以上的联系之外,对于Runnable和Thread类还有一个不太好区分的区别:使用Runnable接口可以更加方便的表示出数据共享的概念。
买票程序
package com.day12.demo;
class MyTicket extends Thread{
private int ticket = 10;
public void run(){
for (int i = 0; i < 20; i++) {
if(ticket > 0)
System.out.println("买票 =" + this.ticket--);
}
}
}
public class TicketDemo {
public static void main(String[] args) {
new MyTicket().start();
new MyTicket().start();
new MyTicket().start();
}
}
运行后发现,数据没有共享。
package com.day12.demo;
class MyTicket extends Thread{
private int ticket = 10;
public void run(){
for (int i = 0; i < 20; i++) {
if(ticket > 0)
System.out.println("买票 =" + this.ticket--);
}
}
}
public class TicketDemo {
public static void main(String[] args) {
MyTicket mt = new MyTicket();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
}
}
经过改进之后,发现数据进行共享,但是对于逻辑是解释是不好理解的,MyTicket类继承了Thread类,自己拥有了start()方法但是不执行自己的start()方法,而是通过匿名方法共用MyTicket()实例化对象mt调用匿名方法的start()方法。
再次经过改进之后
package com.day12.demo;
class MyTicket implements Runnable{
private int ticket = 10;
public void run(){
for (int i = 0; i < 20; i++) {
if(ticket > 0)
System.out.println("买票 =" + this.ticket--);
}
}
}
public class TicketDemo {
public static void main(String[] args) {
MyTicket mt = new MyTicket();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
}
}
通过MyTicket类实现Runnable接口来进行改进,原因是因为Runnable接口里面只有一个自己的run()方法,而此处的start()的方法,是通过匿名类进行调用start()方法来实现线程的启动。
面试题:请解释多线程的两种实现方式区别?分别编写程序验证两种实现。
多线程的两种实现方式都需要一个线程的主类,而这个类可以实现Runnable接口或继承Thread,不管使用何种方式都必须在子类之中覆写run()方法,此方法为线程的主方法;
Thread类是Runnable接口的子类,而且使用Runnable接口可以避免单继承局限,以及更加方便的实现数据共享的概念。
Runnable 接口:
class MyThread implements Runnable{
private int ticket=5;
public void run(){
for(int x=0;x<50;x++)
if(this.ticket>0){
System.out.println(this.ticket--);
}
}
}
MyThread mt = new MyThread();
new Thread(mt).start();
Thread 类:
class MyThread extends Thread{
private int ticket=5;
public void run(){
for(int x=0;x<50;x++)
if(this.ticket>0){
System.out.println(this.ticket--);
}
}
}
MyThread mt = new MyThread();
mt.start();
# 后语
厂长写博客目的初衷很简单,希望大家在学习的过程中少走弯路,多学一些东西,对自己有帮助的留下你的赞赞👍或者关注➕都是对我最大的支持,你的关注和点赞给厂长每天更文的动力。
对文章其中一部分不理解,都可以评论区回复我,我们来一起讨论,共同学习,一起进步!
- 点赞
- 收藏
- 关注作者
评论(0)