您的位置:首页 > 科技 > 能源 > [001-02-001]. 第07-02节:线程的创建与使用

[001-02-001]. 第07-02节:线程的创建与使用

2024/9/24 3:23:47 来源:https://blog.csdn.net/weixin_43783284/article/details/142181979  浏览:    关键词:[001-02-001]. 第07-02节:线程的创建与使用

我的后端学习大纲

我的Java学习大纲


1、方式1:继承Thread类:

1.1.实现步骤

  • 1.创建一个继承于Thred()类的子类
  • 2.重写Thread类的run()
  • 3.创Thread类的子类的对象
  • 4.通过这个对象去调用start()方法

在调用start方法时就做了两件事,分别是:

  • 启动当前线程
  • 调用当前线程的run()

1.2.代码实现:


//1. 创建一个继承于Thread类的子类
class MyThread extends Thread {//2.重写Thread类的run(),把这个线程需要干的事情写在run方法中就可以,现在以循环100以内的数为例来写@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + ":" + i);}}}
}
package com.atguigu.java;public class ThreadTest001 {public static void main(String[] args) {//3. 主线程创建Thread类的子类的对象MyThread t1 = new MyThread();//4.通过此对象调用start(),会执行如下两部操作://   启动当前线程 //   调用当前线程的run()t1.start();//问题一:我们不能通过直接调用run()的方式启动线程。//t1.run();这种方式相当于是对象调方法,和多线程无关系//问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException//t1.start();//我们需要重新创建一个线程的对象来实现MyThread t2 = new MyThread();t2.start();//如下操作仍然是在main线程中执行的。for (int i = 0; i < 100; i++) {if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************");}}}
}

1.3.分析代码中线程执行的情况:

在这里插入图片描述

1.4.案例:找出1到100以内的所有奇数和偶数

  • 1.原始方式实现:
package com.jianqun.day09;public class ThreadDemo {public static void main(String[] args) {//原始方式验证多线程:MyThread1 myThread1 = new MyThread1();MyThread2 myThread2 = new MyThread2();myThread1.start();myThread2.start();}
}class MyThread1 extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {if (i%2 == 0){System.out.println(Thread.currentThread().getName() + "::" + i);}}}
}class MyThread2 extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {if (i%2 != 0){System.out.println(Thread.currentThread().getName() + "::" + i);}}}
}
  • 2.利用Thread类的匿名子类的方式来实现:
package com.jianqun.day09;public class ThreadDemo {public static void main(String[] args) {//利用Thread类的匿名子类的方式来测试多线程//遍历奇数new Thread(){@Overridepublic void run() {for (int i = 0; i < 100; i++) {if (i%2 != 0){System.out.println(Thread.currentThread().getName() + "::" + i);}}}}.start();new Thread(){@Overridepublic void run() {for (int i = 0; i < 100; i++) {if (i%2 == 0){System.out.println(Thread.currentThread().getName() + "::" + i);}}}}.start();}
}

1.5.Thread中的常用方法:

a.常用方法介绍:

  • 1.start():启动当前线程;调用当前线程的run()
  • 2.run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
  • 3.currentThread():是个静态方法,返回执行当前代码的线程
  • 4.getName():获取当前线程的名字
  • 5.setName():设置当前线程的名字
  • 6.yield():释放当前cpu的执行权
  • 7.join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
  • 8.stop():已过时。当执行此方法时,强制结束当前线程
  • 9.sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
  • 10.isAlive():判断当前线程是否存活

b.编码测试方法:

class HelloThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i % 2 == 0){// 子类重写的方法不能比父类抛的异常还大,父类如果没有抛异常,那么子类必定不能抛异常了//try {//   sleep(1000);// } catch (InterruptedException e) {//    e.printStackTrace();//}System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);}// 如果i除以20取模为0的话,释放当前cpu的执行权//if(i % 20 == 0){//   yield();//}}}// 有参构造器方式给线程命名// 定义了有参构造器后,无参构造器就没有了,所以类的实例化的时候,要传一个参数,这个参数的含义就是线程的名称public HelloThread(String name){super(name);}
}
package com.atguigu.java;/*** @author: jianqun* @email: 1033586391@qq.com* @creat: 2022-04-08-22:35* @Description:*/public class ThreadTest002 {public static void main(String[] args) {// 未声明HelloThread 的有参构造的时候,如下方式更改线程的名称// HelloThread h1 = new HelloThread();// h1.setName("线程一");//利用有参构造器给线程名称命名HelloThread h1 = new HelloThread("Thread:1");//设置分线程的优先级h1.setPriority(Thread.MAX_PRIORITY);h1.start();//给主线程命名Thread.currentThread().setName("主线程");Thread.currentThread().setPriority(Thread.MIN_PRIORITY);for (int i = 0; i < 100; i++) {if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);}//  if(i == 20){//    try {// 			在主线程中调用了子线程的join()方法,// 			此时主线程就进入阻塞状态,直到子线程完全执行完以后,主线程才结束阻塞状态//       	h1.join();//    } catch (InterruptedException e) {//   	 e.printStackTrace();//  }//}}// System.out.println(h1.isAlive());}
}

1.6.线程的调度

a.调度策略:

  • 策略1:时间片:
    在这里插入图片描述
  • 策略2:抢占式,高优先级的线程抢占CPU

b.调度方法:

  • 同优先级线程组成的先进先出队列(先到先服务),使用时间片策略
  • 高优先级,使用优先调度的抢占式策略

c.线程优先级:

  • MAX_PRIORITY:10
  • MIN _PRIORITY:1
  • NORM_PRIORITY:5

d.涉及的方法

  • getPriority() :返回线程优先级的值
  • setPriority(int newPriority) :改变线程的优先级

e.说明

  • 线程创建时继承父线程的优先级
  • 低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

2、方式2:实现Runnable接口

2.1.线程创建的步骤:

  • 1.定义子类,实现Runnable接口
  • 2 子类中重写Runnable接口中的run方法
  • 3.创建实现类的对象secondTread :SecondTread secondTread = new SecondTread();
  • 4.将创建的实现类的这个对象secondTread作为参数传递到Thread类的含参构造器中,创建线程对象
  • 5.调用Thread类的start方法:开启线程,去调用Runnable子类接口的run方法
class SecondTread implements Runnable{// b `子类中重写Runnable接口中的run方法`。@Overridepublic void run() {for (int i = 0; i < 100; i++) {if(i % 2 == 0){System.out.println(Thread.currentThread().getName() + "::" + i);}}}
}
package com.atguigu.java;/*** @author: jianqun* @email: 1033586391@qq.com* @creat: 2022-04-09-9:29* @Description: 第二种创建方式*/
public class ThreadTest004 {public static void main(String[] args) {//c.创建实现类的对象SecondTread secondTread = new SecondTread();//d.将创建的实现类的这个对象secondTread作为参数传递到Thread类的含参构造器中,创建线程对象。Thread t1 = new Thread(secondTread);//e.调用Thread类的start方法t1.setName("线程1");t1.start();//再启动一个线程的方式:Thread t2 = new Thread(secondTread);t2.setName("线程2");t2.start();}
}

2.2.案例分析:卖票问题

a.编码实现卖票:

在这里插入图片描述
在这里插入图片描述

  • 上述代码中,卖票的三个窗口,每个窗口都卖100张票,共计300张票,但是其实就100张,卖超了; private int ticket = 100;这个会导致每个对象都有100张票,属性资源不共享

b.迭代1:改善代码:

  • 1.解决办法是添加static,定义:private static int ticket = 100;,对象们共享一个静态变量,一共就100张票
package com.atguigu.java;
class Bwindow extends Thread{//private int ticket = 100;//每个对象都有100张票,属性不共享private static int ticket = 100;//对象们共享一个静态变量,一共就100张票,但是存在线程安全问题,会导致卖票重复@Overridepublic void run() {while(true){if (ticket >0){System.out.println(getName() + ":卖票,票号是::" + ticket);ticket--;}else{break;}}}
}public class ThreadTest003 {public static void main(String[] args) {Bwindow bwindow1 = new Bwindow();Bwindow bwindow2 = new Bwindow();Bwindow bwindow3 = new Bwindow();bwindow1.setName("窗口1");bwindow2.setName("窗口2");bwindow3.setName("窗口3");bwindow1.start();bwindow2.start();bwindow3.start();}
}

上述改善方式存在线程安全问题,假设还有最后一张票,在if判断这里,如果线程1判断为true,但是还没有减票,此时线程B来了,然后减少1张票,此时剩余的实际票的数量为0,然后线程1再去减少票的数量的时候,就出现问题了!!,所以说存在线程安全问题,会导致卖票重复

c.迭代2:改善代码:

class Bwindow implements Runnable{private int ticket = 100;@Overridepublic void run() {while(true){if (ticket >0){System.out.println(getName() + ":卖票,票号是::" + ticket);ticket--;}else{break;}}}
}
package com.atguigu.java;/*** @author: jianqun* @email: 1033586391@qq.com* @creat: 2022-04-09-9:09* @Description:*/public class ThreadTest003 {public static void main(String[] args) {Bwindow bwindow1 = new Bwindow();// 创建线程:3个线程共用一个bwindow1对象,这样就共用了ticket 属性,就不用再加static了,但是还是会存在线程安全问题Thread t1 = new Thread(bwindow1);Thread t2 = new Thread(bwindow1);Thread t3 = new Thread(bwindow1);t1.start();t2.start();t3.start();}
}

上述改善方式仍然存在线程安全问题,假设还有最后一张票,在if判断这里,如果线程1判断为true,但是还没有减票,此时线程B来了,然后减少1张票,此时剩余的实际票的数量为0,然后线程1再去减少票的数量的时候,就出现问题了!!,所以说存在线程安全问题,会导致卖票重复

2.3.两种线程创建方式的对比:

在这里插入图片描述

说明:上面说到的线程安全问题在此笔记中进行了分析和解决


3、方式3:实现Callable接口

3.1.与使用Runnable相比, Callable功能更强大些

  • 1.相比run()方法,可以有返回值
  • 2.方法可以抛出异常
  • 3.支持泛型的返回值
  • 4.需要借助FutureTask类,比如获取返回结果

3.2.Future接口

  • 1.可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等
  • 2.FutrueTask是Futrue接口的唯一的实现类
  • 3.FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

3.3.实现过程:

  • 1.创建一个实现callable接口的实现类
  • 2.实现call方法,将此线程需要执行的操作声明在call()方法中
  • 3.创建Callable接口实现类的对象
  • 4.将实现Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
  • 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法

3.4.代码举例:

在这里插入图片描述

  • 如下代码中的get()方法的返回值是:FutureTask构造器参数Callable实现类重写的call方法的返回值
    在这里插入图片描述

3.5.Callable和Runnable对比:

在这里插入图片描述

3.6.泛型举例:

在这里插入图片描述
在这里插入图片描述


4、方式4:使用线程池

4.1.线程池背景:

  • 1.经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
  • 2.提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。

4.2.好处:

  • 1.提高响应速度(减少了创建新线程的时间)
  • 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 3.便于线程管理
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间后会终止

4.3.线程池相关API:

  • 1.JDK 5.0起提供了线程池相关API:ExecutorServiceExecutors
  • 2.ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
    • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
    • <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行Callable
    • void shutdown() :关闭连接池
  • 3.Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
    • Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
    • Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
    • Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
    • Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行

4.4.代码实现:

在这里插入图片描述
在这里插入图片描述


5、多线程创建中的继承方式和实现方式的联系与区别

5.1.区别

  • 继承Thread:线程代码存放Thread子类run方法中。
  • 实现Runnable:线程代码存在接口的子类的run方法

5.2.实现Runnable接口方式的好处:

  • Runnable接口 避免了单继承的局限性:
    在这里插入图片描述
  • Runnable方式可以实现多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源

5.3.联系:

  • Thread类也是实现了Runnable接口 -
  • 两种方式都需要重写run(),将线程要执行的逻辑在run()方法中声明

6、线程的启动

  • 1.Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread类来体现
    在这里插入图片描述

  • 2.Thread类的特性

    • 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常把run()方法的主体称为线程体
    • 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com