一、线程简介

1.线程、进程和多线程

线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

2.普通方法调用和多线程

在这里插入图片描述

3.并行和并发

  • 并行:多个处理器或多核处理器同时处理多个多个任务。
  • 并发:多个任务在同一个CPU核上,按细分的时间片轮流交替执行,从逻辑上来看那些任务是同时执行的。
  • 并发 = 两个队列和一台咖啡机
  • 并行 = 两个队列和两台咖啡机

很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,,这就是并行。
如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉,这就是并发。

4.核心概念

  • 线程就是独立的执行路径
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程
  • main()称之为主线程,为系统的入口,用于执行整个程序
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为的干预的。
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
  • 线程会带来额外的开销,如cpu调度时间,并发控制开销。
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致

5.守护线程

守护线程是运行在后台的一种特殊进程。他独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。在Java中垃圾回收线程就是特殊的守护线程。

二、线程创建

1.继承Thread类

1.实现方法

  • 自定义线程类继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程

2.代码实现

public class MyThread extends Thread{
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println(i+"看电视");
        }
    }

    public static void main(String[] args) {
        //main主线程
        //创建一个自己的线程对象
        MyThread myThread = new MyThread();
        //开启线程
        myThread.start();
        
        //另一件事
        for (int i = 0; i < 1000; i++) {
            System.out.println(i+"学习");
        }
    }
}


总结:线程开启并不一定是立即执行,而是由CPU进行调度执行。

3.start和run()方法的区别

start()是开启线程,只能开启一次,也就是这个start方法只能用一下。
run()是调用线程方法体的方法,可以多次调用,顺序执行。

4.多线程同时下载图片例子

public class MyThread2 extends Thread{
    private String url; //网络图片地址
    private String name;//保存的文件名
    //有参构造方法
    public MyThread2(String url,String name){
        this.url = url;
        this.name = name;
    }
    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下载了"+name);
    }

    public static void main(String[] args) {
        MyThread2 t1 = new MyThread2("https://ts1.cn.mm.bing.net/th/id/R-C.1e670af528d4aac7d858aeab6436d2d6?rik=OBWRV%2biBaDwRcw&riu=http%3a%2f%2fwww.beihaiting.com%2fuploads%2fallimg%2f150307%2f10723-15030G20Ab07.jpg&ehk=b2AOFe0YPBd6APAOy63TcBUlRDkVbxTsfyxoUT9GyCw%3d&risl=&pid=ImgRaw&r=0", "1.jpg");
        MyThread2 t2 = new MyThread2("https://ts1.cn.mm.bing.net/th/id/R-C.1e670af528d4aac7d858aeab6436d2d6?rik=OBWRV%2biBaDwRcw&riu=http%3a%2f%2fwww.beihaiting.com%2fuploads%2fallimg%2f150307%2f10723-15030G20Ab07.jpg&ehk=b2AOFe0YPBd6APAOy63TcBUlRDkVbxTsfyxoUT9GyCw%3d&risl=&pid=ImgRaw&r=0", "2.jpg");
        MyThread2 t3 = new MyThread2("https://ts1.cn.mm.bing.net/th/id/R-C.1e670af528d4aac7d858aeab6436d2d6?rik=OBWRV%2biBaDwRcw&riu=http%3a%2f%2fwww.beihaiting.com%2fuploads%2fallimg%2f150307%2f10723-15030G20Ab07.jpg&ehk=b2AOFe0YPBd6APAOy63TcBUlRDkVbxTsfyxoUT9GyCw%3d&risl=&pid=ImgRaw&r=0", "3.jpg");

        t1.start();
        t2.start();
        t3.start();
    }
}


//下载器
class WebDownloader{
    //下载方法
    public void downloader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

2.实现Runnable接口

1.实现方法

  • 定义MyThread类实现Runnable接口
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
public class MyThread3 implements Runnable{
    @Override
    public void run() {
        //run方法线程体
        for (int i = 0; i < 20; i++) {
            System.out.println(i+"看电视");
        }
    }

    public static void main(String[] args) {
        //main主线程
        //创建一个自己的线程对象
        MyThread myThread = new MyThread();
        //开启线程   这样可以开启多个相同内容的线程
        new Thread(myThread).start();
        new Thread(myThread).start();

        //另一件事
        for (int i = 0; i < 1000; i++) {
            System.out.println(i+"学习");
        }
    }
}

在这里插入图片描述

3.Tread和Runnable比较

继承Thread类

  • 子类继承Thread类具备多线程能力
  • 启动线程:子类对象. start()
  • 不建议使用:避免OOP单继承局限性

实现Runnable接口

  • 实现接口Runnable具有多线程能力
  • 启动线程:Thread对象(传入线程对象).start()
  • 推荐使用:避免单继承局限性,灵活方便,方便同一个线程对象被多个线程使用

4.Runnable实现多线程,多线程同时操作统一资源的时候问题

/**
 * @ClassName: MyThread4
 * @Author: hdat
 * @Create: 2022/10/18 0018 16:17 Created with IntelliJ IDEA
 * @Description: 火车票抢票     多线程操作同一个资源,线程不安全了
 **/
public class MyThread4 implements Runnable{
    //票数
    private int ticketNums = 100;

    @Override
    public void run() {
        while (true){
            ticketNums--;
            if (ticketNums <= 0) {
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName()+"买到"+ticketNums+"号票");
        }
    }

    public static void main(String[] args) {
        MyThread4 myThread4 = new MyThread4();

        new Thread(myThread4,"阿土").start();
        new Thread(myThread4,"阿微").start();
        new Thread(myThread4,"小芳").start();
    }
}

多线程并发遇到的问题

3.实现Callable接口

  • 实现Callable接口,需要返回值类型
  • 重写call方法,需要抛出异常
  • 创建目标对象
  • 创建执行服务:ExecutorService ser = Executors.newFixedThreadPool(1);
  • 提交执行:Future result1 = ser.submit(t1);
  • 获取结果:boolean r1 = result1.get()
  • 关闭服务:ser.shutdownNow();

使用实现callable接口方法实现多线程下载

public class MyCallable implements Callable<Boolean> {
    private String url; //网络图片地址
    private String name;//保存的文件名
    //有参构造方法
    public MyCallable(String url,String name){
        this.url = url;
        this.name = name;
    }
    @Override
    public Boolean call() {
        WebDownloader2 webDownloader = new WebDownloader2();
        webDownloader.downloader(url,name);
        System.out.println("下载了"+name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable t1 = new MyCallable("https://ts1.cn.mm.bing.net/th/id/R-C.1e670af528d4aac7d858aeab6436d2d6?rik=OBWRV%2biBaDwRcw&riu=http%3a%2f%2fwww.beihaiting.com%2fuploads%2fallimg%2f150307%2f10723-15030G20Ab07.jpg&ehk=b2AOFe0YPBd6APAOy63TcBUlRDkVbxTsfyxoUT9GyCw%3d&risl=&pid=ImgRaw&r=0", "1.jpg");
        MyCallable t2 = new MyCallable("https://ts1.cn.mm.bing.net/th/id/R-C.1e670af528d4aac7d858aeab6436d2d6?rik=OBWRV%2biBaDwRcw&riu=http%3a%2f%2fwww.beihaiting.com%2fuploads%2fallimg%2f150307%2f10723-15030G20Ab07.jpg&ehk=b2AOFe0YPBd6APAOy63TcBUlRDkVbxTsfyxoUT9GyCw%3d&risl=&pid=ImgRaw&r=0", "2.jpg");
        MyCallable t3 = new MyCallable("https://ts1.cn.mm.bing.net/th/id/R-C.1e670af528d4aac7d858aeab6436d2d6?rik=OBWRV%2biBaDwRcw&riu=http%3a%2f%2fwww.beihaiting.com%2fuploads%2fallimg%2f150307%2f10723-15030G20Ab07.jpg&ehk=b2AOFe0YPBd6APAOy63TcBUlRDkVbxTsfyxoUT9GyCw%3d&risl=&pid=ImgRaw&r=0", "3.jpg");

        //创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(1);
        //提交执行
        Future<Boolean> result1 = ser.submit(t1);
        Future<Boolean> result2 = ser.submit(t2);
        Future<Boolean> result3 = ser.submit(t3);
        //获取结果
        boolean r1 = result1.get();
        boolean r2 = result2.get();
        boolean r3 = result3.get();
        //关闭服务
        ser.shutdownNow();

    }
}


//下载器
class WebDownloader2{
    //下载方法
    public void downloader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

4.线程底部实现原理–静态代理

未完待续…(✪ω✪)

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐