【学习笔记】线程(一)之线程概念及创建

线程(一)之线程概念及创建

 

线程简介

多任务:

  • 边吃饭,边玩手机

  • 开车打电话

  • 现实中有很多这样的例子,看起来是多个任务在做,其实本质上我们的大脑在同一时间只做了一件事

多线程:

  • 执行main方法时,如果在main方法中调用了其他方法,就会出去main方法,去执行其他方法,然后在回来,多线程就实现了多条路同时执行

普通方法调用和多线程

image-20220731090541960

 

程序、进程和线程

  • 程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念

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

  • 通常一个进程中包含若干个线程,最少一个,线程是CPU调度和执行的单位

注意:很多多线程是模拟出来的,真正的多线程是有多个CPU,即多核,如服务器。如果是模拟出来的多线程,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以有同时执行的错觉。

 

核心概念:

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

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

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

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

  • 对同一份资源操作时,会存在资源抢夺问题,需要加入并发控制

  • 线程会带来额外的开销,如cpu调度时间,并发控制开销

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

 

线程创建

 

  1. 继承 Thread 类

    • 自定义线程继承Thread 类

    • 重写run() 方法

    • 创建线程对象,调用start() 方法创建线程

 

package com.thread;
​
public class Demo01 extends Thread{
    @Override
    public void run() {     //重写run方法
        for (int i = 0; i < 200; i++) {
            System.out.println("我是线程----"+ i);
        }
​
    }
​
    public static void main(String[] args) {
        //创建线程
        Demo01 demo01 = new Demo01();
        //调用start方法
        demo01.start();
​
        for (int i = 0; i < 2000; i++) {
            System.out.println("我是主线程-----"+ i);
        }
    }
}

image-20220731094514927image-20220731094532396

 

运行结果是这样的:先执行了主线程,执行到797,然后执行了创建的线程到199,执行完后,又去执行主线程到完毕

所以可以看出:线程是不一定立即执行的,由CPU 调度执行

 

案例:下载网络图片

需要导入commons-io 包

 

package com.thread.demo01;
​
import org.apache.commons.io.FileUtils;
​
import java.io.File;
import java.io.IOException;
import java.net.URL;
​
public class ThreadTest02 extends Thread{
​
    private String url;
    private String name;
​
    public ThreadTest02(String url,String name){
        this.url = url;
        this.name = name;
    }
​
    @Override
    public void run() {
        WebDownload webDownload = new WebDownload();
        webDownload.download(url,name);
        System.out.println("下载了文件名为" + name);
    }
​
    public static void main(String[] args) {
        ThreadTest02 t1 = new ThreadTest02("https://wx2.sinaimg.cn/mw1024/006tJpbVly1h4m2yozkrsj30gx0gw3zn.jpg","biaoqing1.jpg");
        ThreadTest02 t2 = new ThreadTest02("https://wx4.sinaimg.cn/mw1024/006tJpbVly1h4m2ypjyakj30j60jidgr.jpg","biaoqing2.jpg");
        ThreadTest02 t3 = new ThreadTest02("https://wx3.sinaimg.cn/mw1024/006tJpbVly1h4m2ypel8rj309w09w0sn.jpg","biaoqing3.jpg");
​
        t1.start();
        t2.start();
        t3.start();
    }
}
​
//下载器类
class WebDownload{
    public void download(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,download方法出现问题");
        }
    }
}

 

开启了三个线程,我们开启的顺序是 t1 t2 t3,但是下载的顺序却是image-20220731103405869

 

所以再次印证了线程开启不一定立即执行,CPU 调度执行

 

  1. 实现Runnable 接口

    • 定义MyRunnable类实现Runnable接口

    • 实现run() 方法,编写线程体

    • 创建线程对象,调用start() 方法启动线程

package com.thread.demo01;
​
public class ThreadTest03 implements Runnable{
​
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("我是线程----" + i);
        }
    }
​
    public static void main(String[] args) {
        ThreadTest03 t1 = new ThreadTest03();
        new Thread(t1).start();
​
        for (int i = 0; i < 1000; i++) {
            System.out.println("我是主线程----" + i);
        }
    }
}

image-20220731105129928image-20220731105157032

 

结果与继承Thread 类的方式相同

 

两者区别:

  • 继承Thread类

    • 子类继承Thread类具备多线程能力

    • 启动线程:子类对象.start()

    • 不建议使用:避免OOP单继承局限性

  • 实现Runnable接口

    • 实现接口Runnable 接口具有多线程能力

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

    • 推荐使用,避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

 

下面用买票的例子,来展现一个对象被多个线程使用,并且暴露出了线程不安全的问题

package com.thread.demo01;
​
public class ThreadTest04 implements Runnable{
​
    private int tickNum = 10;
​
    @Override
    public void run() {
        while (true){
​
            if (tickNum<=0){
                break;
            }
            //延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"----->拿到了第"+ tickNum-- + "张票");
​
        }
​
    }
​
    public static void main(String[] args) {
        ThreadTest04 ticks = new ThreadTest04();
​
        new Thread(ticks,"张三").start();
        new Thread(ticks,"李四").start();
        new Thread(ticks,"王五").start();
​
    }
}

image-20220731111327209

 

从运行结果可以发现问题:多个线程操作同一资源的情况下,线程不安全,数据紊乱

比如王五和李四同时拿到了第一张票

 

案例:龟兔赛跑

package com.thread.demo01;
​
public class Race implements Runnable{
​
    private static String winner;    //胜利者
​
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (Thread.currentThread().getName().equals("兔子") && i % 10 ==0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //判断比赛是否结束
            boolean flag = gameOver(i);
            if (flag){
                break;
            }
            System.out.println(Thread.currentThread().getName() + "--->跑了"+ i +"米");
        }
    }
​
    //判断比赛是否结束
    public boolean gameOver(int steps){
        if (winner != null){    //已经有胜利者了
            return true;
        }else if(steps >= 100){
            winner = Thread.currentThread().getName();
            System.out.println("winner is" + winner);
            return true;
        }
        return false;
    }
​
    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}

 

  1. 实现callable接口

    • 实现Callable接口,需要返回值类型

    • 重写call方法,需要抛出异常

    • 创建目标对象

    • 创建执行服务:ExecutorServiece ser = Excetor.newFixedThreadPool(3);

    • 提交执行结果:Future< Boolean > r1 = ser.submit(ti);

    • 获取结果:boolean rs1 = r1.get();

    • 关闭服务:ser.shutdownNow();

利用callable 下载图片

package com.thread.demo02;
​
import org.apache.commons.io.FileUtils;
​
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
​
public class TestCallable implements Callable {
    private String url;
    private String name;
​
    public TestCallable(String url,String name){
        this.url = url;
        this.name = name;
    }
​
    @Override
    public Boolean call() throws Exception {
        UrlDownload urlDownload = new UrlDownload();
        urlDownload.download(url,name);
        System.out.println("下载了名为:" + name);
        return true;
    }
​
    public static void main(String[] args) throws ExecutionException, InterruptedException {
​
        TestCallable t1 = new TestCallable("https://wx2.sinaimg.cn/mw1024/006tJpbVly1h4m2yozkrsj30gx0gw3zn.jpg","表情1.jpg");
        TestCallable t2 = new TestCallable("https://wx4.sinaimg.cn/mw1024/006tJpbVly1h4m2ypjyakj30j60jidgr.jpg","表情2.jpg");
        TestCallable t3 = new TestCallable("https://wx3.sinaimg.cn/mw1024/006tJpbVly1h4m2ypel8rj309w09w0sn.jpg","表情3.jpg");
​
        //创建执行服务
        ExecutorService es = Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> r1 = es.submit(t1);
        Future<Boolean> r2 = es.submit(t2);
        Future<Boolean> r3 = es.submit(t3);
        //获取结果
        boolean rs1 = r1.get();
        boolean rs2 = r2.get();
        boolean rs3 = r3.get();
        //关闭服务
        es.shutdown();
​
    }
}
​
//下载器
class  UrlDownload{
    public void download(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
posted @ 2022-07-31 16:11  GrowthRoad  阅读(45)  评论(0编辑  收藏  举报