Java基础---多线程宝典

多线程

文章目录

  • 多线程
    • 核心概念
      • `Process`与`Thread`
    • 线程三种创建方式
      • 继承`Thread`类
          • 使用多线程实现下载网络图片
      • 实现`Runnable`接口
          • 多线程同时操作同一个对象
          • 模拟龟兔赛跑(线程执行速度)
      • 实现`Callable`接口
    • 静态代理
    • `Lanmda`表达式
      • 简介
      • 作用:
      • 函数式接口`Functional Interface`
      • 定义
      • 示例:
        • Lambda表达式简化:
      • 总结
    • 线程状态
      • 创建
        • 总结
      • 就绪
        • 线程礼让
      • 运行
      • 阻塞
        • 线程休眠`Sleep`
        • `Join`合并线程
      • 死亡
      • 线程状态观测
      • 线程优先级
      • 守护线(daemon)程
    • 线程同步
    • 死锁
      • 产生死锁的四个必要条件:
    • Lock(锁)
      • `synchronized `与`Lock`的对比
    • 线程并发协作模型:生产者消费者模式
      • 解决方法
        • 管程法
        • 信号灯法
    • 线程池
      • 背景:
      • 思路:
      • 好处:
      • 使用
      • 示例

核心概念

ProcessThread

  • 一个进程对应一个应用程序,程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。也就是说你本地的项目代码在不运行它的时候它们就是一堆静态的文件,不运行它将不会起任何作用!

  • 进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位

  • 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义

    • 线程是CPU调度和执行的的单位。
  • 真正的多线程是指有多个CPU,即多核。如果是单核服务器,那么它所属的缩线程应用一定是模拟出来的,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以对于人类来说就有同时执行的错觉。

  • 多进程的作用不是提高执行速度,而是提高CPU的使用率

  • 进程和进程之间的内存是独立的

  • 线程就是独立的执行路径;

  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;

  • main()称之为主线程,为系统的入口,用于执行整个程序;

  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。

  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;线程会带来额外的开销,如cpu调度时间,并发控制开销。

  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

  • 线程是一个进程中的执行场景。一个进程可以启动多个线程

  • 多线程作用不是为了提高执行速度,而是提高应用程序的使用率

  • 线程和线程共享“堆内存和方法区内存”,栈内存是独立的,一个线程一个栈

  • 由于多线程在来回切换,所以给现实世界中的人类一种错觉:感觉多个线程在同时并发执行。

线程三种创建方式

继承Thread

  1. 继承Thread
  2. 重写Thread类的run()方法
  3. 创建线程对象,调用start()方法启动线程(如果是调用run()方法将使用单线程)

线程不一定是立即执行的,什么时候执行由CPU来调度安排

/**
* 1.继承用于操作线程的Thread类:extends Thread
*/
public class TestThread extends Thread {
   
    /**
     * 2.重写Tread的run()方法
     */
    @Override
    public void run() {
   
        // run方法线程体
        for (int i = 0; i < 18; i++) {
   
            System.out.println("你在吃屎!");
        }
    }

    /**
     * main线程----主线程
     */
    public static void main(String[] args) {
   
        // 3.创建一个线程对象
        TestThread testThread = new TestThread();

        // 4.调用start()方法启动线程,使线程同时执行(多线程),此时多个线程会随机的交替执行,这个随机是由CPU决定的
        testThread.start();

        // 单线程
        //testThread.run();单线程只会一条线走,先执行什么、再执行什么

        for (int i = 0; i < 999; i++) {
   
            System.out.println("你在蹦迪");
        }
    }
}
使用多线程实现下载网络图片
// 要导入commons.io这个包
import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

/**
 * @Description: 实现多线程同步下载图片(顺序有点乱,标注的顺序是思维也是程序行走正常所需的步骤)
 * @Author Djn
 * @Date 2020/11/13
 * @Since V1.0 add-->
 * @Version V1.0
 **/
// 1.继承Thread线程类
public class ThreadDownloaderPng extends Thread {
   
    /**
    * 4.声明对象的属性:地址与名称,以及本类的构造器
    */
    // 图片存储地址
    private String url;
    // 图片存储名称
    private String name;

    // 构造器
    public ThreadDownloaderPng(String url, String name) {
   
        this.url = url;
        this.name = name;
    }

	// 5.重写Thread的run()方法	即创建要测试的多线程 线程体
    @Override
    public void run() {
   
        PngDownloader pngDownloader = new PngDownloader();
        pngDownloader.downloader(url, name);
        System.out.println("下载了文件名为" + name);
    }

    // 6.创建主线程
    public static void main(String[] args) {
   
        // 7.使用上面的构造器创建对象	这里我依次下载了apaqi、docker以及Maven的logo
        ThreadDownloaderPng t1 = new ThreadDownloaderPng("http://commons.apache.org/images/commons-logo.png", "阿帕奇的logo.jpg");
        ThreadDownloaderPng t2 = new ThreadDownloaderPng("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605265343111&di=bb9215e6666b513c91a0a9e4284b5adf&imgtype=0&src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20190608%2F94ffe7706df04db4ab2fc2de10697a68.jpeg", "Docker.jpg");
        ThreadDownloaderPng t3 = new ThreadDownloaderPng("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1605265313962&di=e8f6395d62c50c92d57502a0ebbe6f02&imgtype=0&src=http%3A%2F%2Fpic2.zhimg.com%2Fv2-94a00bfec307801902f81d81140a1c8a_1200x500.jpg", "Maven.jpg");

        // 8.启动线程  虽然写的启动顺序是1、2、3  但实际执行的时候这三条线程的顺序是随机的
        t1.start();
        t2.start();
        t3.start();
    }
}


// 2.写一个下载器
class PngDownloader {
   
	// 3.下载方法	因为下载资源要定位资源的地址,所以url必不可少,name为下载下来后将这个文件命名成什么
    public void downloader(String url, String name) {
   
        try {
   
            // URL要抛出IO异常,可以try catch也可以直接抛出去
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
   
            // 这里自定义输出了一下异常信息,方便定位问题
            System.out.println("发生了IO异常,PngDownloader方法出现了问题");
        }
    }
}

实现Runnable接口

  • 启动线程方式:传入目标线程对象+Thread(对象).start()

    // 1.实现Runnable接口
    public class TestRunnable implements Runnable {
         
        // 2.重写run方法作为线程体
        @Override
        public void run() {
         
            for (int i = 0; i < 20; i++) {
         
                System.out.println("++++++Runnable线程体!+++++++");
            }
        }
    
        // 3.主线程
        public static void main(String[] args) {
         
            // 4.创建runnable接口的实现类对象
            TestRunnable testRunnable = new TestRunnable();
            // 5.代理:创建一个线程对象用于调用start()方法开启线程
            // 将实现runnable接口的对象放入Thread里
            new Thread(testRunnable).start();
    
            for (int i = 0; i < 30; i++) {
         
                System.out.println("==========主线程============");
            }
        }
    }
    
多线程同时操作同一个对象
// 实现多个线程操作同一资源,不过此时线程是不安全的,数据会紊乱(同一个资源被多个线程拿到)
// 1.实现Runnable接口
public class TestThread2 implements Runnable {
   

    private int ticketNums = 15;

    // 2.重写run方法 线程体
    @Override
    public void run() {
   
        // 循环减ticketNums
        while (true) {
   
            // ticketNums小于1时停止循环
            if (ticketNums <= 0) {
   
                break;
            }
            // 因为CPU处理速度太快,模拟让线程延时一下
            try {
   
                Thread.sleep(200);
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
            // Thread.currentThread()获取这个线程,.getName()获取线程名字
            System.out.println(Thread.currentThread().getName() + "--->拿到了第" + ticketNums-- + "票");
        }
    }
    // 主线程
    public static void main(String[] args) {
   
        TestThread2 t = new TestThread2();

        // 开启多线程
        new Thread(t, "a").start();
        new Thread(t, "b").start();
        new Thread(t, "c").start();
        new Thread(t, "d").start();
    }
}
模拟龟兔赛跑(线程执行速度)
  • 准备

    1. 准备一个有固定距离的赛道
    2. 判断比赛是否结束
    3. 打印输出胜利者
    4. 龟兔赛跑开始
    5. 让兔子睡觉来实现乌龟跑赢兔子
    6. 乌龟
        // 胜利者
        private static String winner;
    
        @Override
        public void run() {
         
            for (int i = 0; i <= 100; i++) {
         
                // 模拟兔子休息       每20步休息50毫秒
                if (Thread.currentThread().getName().equals("兔子") && i % 20 == 0) {
         
                    try {
         
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
         
                        e.printStackTrace();
                    }
                }
                // 判断比赛是否结束
                boolean flag = gameOver(i);
                // 如果比赛结束了,就停止程序
                if (flag) {
         
                    break;
        

你可能感兴趣的:(JAVA,多线程,java)