3.1 JAVA多线程使用

89 阅读3分钟

目录

  • 线程简介
  • 线程实现(重点)
  • 线程状态
  • 线程同步(重点)
  • 线程通信
  • 高级主题

一、线程简介

1. 多线程

  • 普通线程和多线程的执行对比

image.png

2. 程序、进程和线程

  • Process:进程是执行程序的一次执行过程,是一个动态概念,是系统资源分配的单位
  • Thread:一个进程中包含多个线程(主线程、GC线程),一个进程至少有一个线程,线程是CPU调度和执行的单位

二、线程创建

1. 线程创建的三种方式

image.png

2. 继承Thread类

2.1 多线程基本使用

  • 自定义线程类继承Thread类
  • 重写run()方法,编写执行体
  • 创建线程对象,调用start()方法启动线程
 public class MyThread extends Thread {
 ​
     public static final int THREAD_LOOP = 20;
 ​
     /**
      * 实现多线程的第一种方式,继承Thread类,重写run方法
      */
     @Override
     public void run() {
         for (int i = 0; i < THREAD_LOOP; i++) {
             System.out.println("我在看代码--Thread: " + i);
         }
     }
 ​
     /**
      * 主方法,创建线程类对象,并调用start方法开始执行
      * @param args
      */
     public static void main(String[] args) {
         MyThread myThread = new MyThread();
         // 注意这里是调用start方法,如果调用run则是执行完线程才会执行Main
         myThread.start();
 ​
         for (int i = 0; i < THREAD_LOOP; i++) {
             System.out.println("我在写代码--Main: " + i);
         }
     }
 }
 我在写代码--Main: 0
 我在看代码--Thread: 0
 我在看代码--Thread: 1
 我在看代码--Thread: 2
 我在看代码--Thread: 3
 我在看代码--Thread: 4
 我在看代码--Thread: 5
 我在看代码--Thread: 6
 我在看代码--Thread: 7
 我在看代码--Thread: 8
 我在看代码--Thread: 9
 我在写代码--Main: 1
 我在写代码--Main: 2
 我在看代码--Thread: 10
 我在写代码--Main: 3
 我在写代码--Main: 4
 我在写代码--Main: 5
 我在写代码--Main: 6
 我在写代码--Main: 7
 我在写代码--Main: 8
 我在写代码--Main: 9
 我在看代码--Thread: 11
 我在写代码--Main: 10
 我在看代码--Thread: 12
 我在写代码--Main: 11
 我在看代码--Thread: 13
 我在看代码--Thread: 14
 我在写代码--Main: 12
 我在看代码--Thread: 15
 我在写代码--Main: 13
 我在看代码--Thread: 16
 我在写代码--Main: 14
 我在写代码--Main: 15
 我在写代码--Main: 16
 我在写代码--Main: 17
 我在写代码--Main: 18
 我在看代码--Thread: 17
 我在写代码--Main: 19
 我在看代码--Thread: 18
 我在看代码--Thread: 19
 ​
 Process finished with exit code 0

2.2 多线程示例--下载图片

需要引入commons-io包:

 <!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
 <dependency>
     <groupId>commons-io</groupId>
     <artifactId>commons-io</artifactId>
     <version>2.11.0</version>
 </dependency>

源码:

 import org.apache.commons.io.FileUtils;
 ​
 import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 ​
 /**
  * @ClassName DownloadPictureDemo
  * @Description 使用多线程下载图片
  * @Author wangwk-a
  * @Date 2022/1/1 18:28
  * @Version 1.0
  */
 public class DownloadPictureDemo extends Thread {
     public static final String IAMGE_URL_1 = "http://5b0988e595225.cdn.sohucs.com/images/20180621/4b8604d3a7394b33a7462aae6790ff55.jpeg";
     public static final String IAMGE_URL_2 = "http://5b0988e595225.cdn.sohucs.com/images/20180621/51b5e16007cb4739a4980ec6e0a50611.jpeg";
     public static final String IAMGE_URL_3 = "http://5b0988e595225.cdn.sohucs.com/images/20180621/aeae68a9856744d9a6cddb180be41bcb.jpeg";
 ​
     /**
      * 网络图片地址
      */
     private String url;
     /**
      * 保存的文件名
      */
     private String fileName;
 ​
     public DownloadPictureDemo(String url, String fileName) {
         this.url = url;
         this.fileName = fileName;
     }
 ​
     /**
      * 下载图片的执行体
      */
     @Override
     public void run() {
         WebDownloader webDownloader = new WebDownloader();
         webDownloader.downloader(url, fileName);
         System.out.println("下载了文件名为:" + fileName);
     }
 ​
     public static void main(String[] args) {
         DownloadPictureDemo th1 = new DownloadPictureDemo(IAMGE_URL_1, "1.jpg");
         DownloadPictureDemo th2 = new DownloadPictureDemo(IAMGE_URL_2, "2.jpg");
         DownloadPictureDemo th3 = new DownloadPictureDemo(IAMGE_URL_3, "3.jpg");
 ​
         // 启动线程
         th1.start();
         th2.start();
         th3.start();
     }
 }
 ​
 class WebDownloader {
     /**
      * 下载方法
      */
     public void downloader(String url, String fileName) {
         try {
             FileUtils.copyURLToFile(new URL(url), new File(fileName));
         } catch (IOException e) {
             e.printStackTrace();
             System.out.println("IO异常,downloader方法出现问题");
         }
     }
 }
 下载了文件名为:3.jpg
 下载了文件名为:2.jpg
 下载了文件名为:1.jpg
 ​
 Process finished with exit code 0

3. 实现Runnable接口

  • 定义线程类实现Runnable接口
  • 实现run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程(静态代理)

优点:可以避免单继承的局限性,灵活方便,方便同一个对象被多个线程使用

3.1 基本使用

 /**
  * @ClassName MyThreadImpl
  * @Description 通过实现Runnable接口来创建线程
  * @Author wangwk-a
  * @Date 2022/1/1 18:48
  * @Version 1.0
  */
 public class MyThreadImpl implements Runnable{
     public static final int THREAD_LOOP = 20;
 ​
     @Override
     public void run() {
         for (int i = 0; i < THREAD_LOOP; i++) {
             System.out.println("我在看代码--Thread: " + i);
         }
     }
 ​
     public static void main(String[] args) {
         // 执行线程,需要丢入runnable接口实现类,调用start
         MyThreadImpl myThread = new MyThreadImpl();
         // new Thread是代理,具体实现是MyThreadImpl的run
         new Thread(myThread).start();
 ​
         for (int i = 0; i < THREAD_LOOP; i++) {
             System.out.println("我在写代码--Main: " + i);
         }
     }
 }
 我在写代码--Main: 0
 我在写代码--Main: 1
 我在写代码--Main: 2
 我在看代码--Thread: 0
 我在看代码--Thread: 1
 我在写代码--Main: 3
 我在写代码--Main: 4
 我在看代码--Thread: 2
 我在写代码--Main: 5
 我在看代码--Thread: 3
 我在写代码--Main: 6
 我在写代码--Main: 7
 我在写代码--Main: 8
 我在看代码--Thread: 4
 我在写代码--Main: 9
 我在看代码--Thread: 5
 我在写代码--Main: 10
 我在看代码--Thread: 6
 我在看代码--Thread: 7
 我在写代码--Main: 11
 我在写代码--Main: 12
 我在写代码--Main: 13
 我在写代码--Main: 14
 我在写代码--Main: 15
 我在写代码--Main: 16
 我在写代码--Main: 17
 我在写代码--Main: 18
 我在写代码--Main: 19
 我在看代码--Thread: 8
 我在看代码--Thread: 9
 我在看代码--Thread: 10
 我在看代码--Thread: 11
 我在看代码--Thread: 12
 我在看代码--Thread: 13
 我在看代码--Thread: 14
 我在看代码--Thread: 15
 我在看代码--Thread: 16
 我在看代码--Thread: 17
 我在看代码--Thread: 18
 我在看代码--Thread: 19
 ​
 Process finished with exit code 0

3.2 一个对象被多个线程调用示例

 /**
  * @ClassName ThreadMultiCall
  * @Description 测试多个线程操作同一个对象
  * @Author wangwk-a
  * @Date 2022/1/1 18:54
  * @Version 1.0
  */
 public class ThreadMultiCall implements Runnable{
     /**
      * 火车票数
      */
     private int ticketNums = 10;
 ​
     @Override
     public void run() {
         while (true) {
             if (ticketNums <= 0) {
                 break;
             }
             System.out.println(Thread.currentThread().getName() + "拿到了第" + ticketNums-- + "张票");
         }
     }
 ​
     public static void main(String[] args) {
         ThreadMultiCall th = new ThreadMultiCall();
         // 多个线程操作一个对象
         new Thread(th, "线程1").start();
         new Thread(th, "线程2").start();
         new Thread(th, "线程3").start();
     }
 }
 线程2拿到了第10张票
 线程2拿到了第7张票
 线程2拿到了第6张票
 线程2拿到了第5张票
 线程2拿到了第4张票
 线程2拿到了第3张票
 线程2拿到了第2张票
 线程1拿到了第9张票
 线程3拿到了第8张票
 线程2拿到了第1张票

多个线程操作同一个资源的时候,是线程不安全的,可能会出现多个线程获取相同的资源情况,这个在后续的线程并发中解决

4. 实现Callable接口(扩充)

  • 实现Callable接口,需要返回值的类型
  • 重写callf方法,需要抛出异常
  • 创建目标对象
  • 创建执行服务ExecutorServive(线程池)
  • 提交执行submit
  • 获取结果
  • 关闭服务
 import org.apache.commons.io.FileUtils;
 ​
 import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 import java.util.concurrent.*;
 ​
 /**
  * @ClassName ThreadImplCallableMethod
  * @Description TODO
  * @Author wangwk-a
  * @Date 2022/1/1 19:04
  * @Version 1.0
  */
 public class ThreadImplCallableMethod implements Callable {
     public static final String IAMGE_URL_1 = "http://5b0988e595225.cdn.sohucs.com/images/20180621/4b8604d3a7394b33a7462aae6790ff55.jpeg";
     public static final String IAMGE_URL_2 = "http://5b0988e595225.cdn.sohucs.com/images/20180621/51b5e16007cb4739a4980ec6e0a50611.jpeg";
     public static final String IAMGE_URL_3 = "http://5b0988e595225.cdn.sohucs.com/images/20180621/aeae68a9856744d9a6cddb180be41bcb.jpeg";
 ​
     /**
      * 网络图片地址
      */
     private String url;
     /**
      * 保存的文件名
      */
     private String fileName;
 ​
     public ThreadImplCallableMethod(String url, String fileName) {
         this.url = url;
         this.fileName = fileName;
     }
 ​
     @Override
     public Boolean call() throws Exception {
         WebDownloader webDownloader = new WebDownloader();
         webDownloader.downloader(url, fileName);
         System.out.println("下载了文件名为:" + fileName);
 ​
         return true;
     }
 ​
     public static void main(String[] args) throws ExecutionException, InterruptedException {
         ThreadImplCallableMethod th1 = new ThreadImplCallableMethod(IAMGE_URL_1, "1.jpg");
         ThreadImplCallableMethod th2 = new ThreadImplCallableMethod(IAMGE_URL_2, "2.jpg");
         ThreadImplCallableMethod th3 = new ThreadImplCallableMethod(IAMGE_URL_3, "3.jpg");
 ​
         /* TODO: 1.创建执行服务 */
         ExecutorService service = Executors.newFixedThreadPool(3);
 ​
         /* TODO: 2.提交执行 */
         Future<Boolean> submit1 = service.submit(th1);
         Future<Boolean> submit2 = service.submit(th2);
         Future<Boolean> submit3 = service.submit(th3);
 ​
         /* TODO: 3.获取结果 */
         Boolean r1 = submit1.get();
         Boolean r2 = submit1.get();
         Boolean r3 = submit1.get();
 ​
         /* TODO: 4.关闭服务 */
         service.shutdown();
     }
 }
 ​
 class WebDownloader {
     /**
      * 下载方法
      */
     public void downloader(String url, String fileName) {
         try {
             FileUtils.copyURLToFile(new URL(url), new File(fileName));
         } catch (IOException e) {
             e.printStackTrace();
             System.out.println("IO异常,downloader方法出现问题");
         }
     }
 }
 下载了文件名为:2.jpg
 下载了文件名为:3.jpg
 下载了文件名为:1.jpg
 ​
 Process finished with exit code 0