【Thread】线程的基本概念及创建方式(一)

wuchangjian2021-10-25 20:50:23编程学习

先了解一下基本概念

1. 程序、进程、线程

程序、进程、线程

  • 程序(静态):为完成特定的任务,用某种语言编写的一组指令的集合。即:一段静态的代码
  • 进程(动态):程序的一次执行的过程,或者是正在执行的一个程序。它是一个动态的过程,它有自己的生命周期:产生、存在、消亡。如:运行中的 QQ
    • 进程作为资源分配的单位,系统在运行时会为每个进行分配不同的内存区域
  • 线程:进程可进一步细化为线程,是一个程序内部的一条执行路径
    • 多线程:若一个进程同一时间并行执行多个线程,则是支持多线程的
    • 线程作为调度和执行的单位,每个线程拥有独立的运行栈(虚拟机栈)和程序计数器 PC,线程切换开销小
    • 一个进程中的多个线程共享相同的内存单元:它们从同一个堆中分配对象,可以访问相同的对象,这使得线程间的通信更简洁、高效。但是,多个线程操作共享的系统资源可能就会带来安全隐患。
    • 方法区、堆:线程共享;虚拟机栈、程序计数器:线程独有

2. 单核 CPU、多核 CPU

  • 单核 CPU:它并不支持多线程,因为,在同一时间内,它只能执行一个线程
  • 多核 CPU:才能更好地发挥多线程

一个 JAVA 应用程序 至少有三个线程:main() 主线程、gc() 垃圾回收线程、异常处理线程。如果发生异常,会影响主线程。

3. 并发、并行

  • 并行:多个 CPU 同时执行多个任务
  • 并发:一个 CPU 通过时间片轮询的方式去“同时”执行多个任务

4. 线程创建

线程创建的方式有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口、使用线程池

4.1 继承 Thread 类

步骤:

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

例:

public class ThreadTest extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("我在看代码=====" + i);
        }
    }

    public static void main(String[] args) {
        ThreadTest threadTest = new ThreadTest();
        // 线程开启后,不一定立即执行,由 CPU 调度
        threadTest.start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("【主线程】我在学习多线程" + i);
        }
    }
}

上述代码执行后,main 线程打印:【主线程】我在学习多线程;子线程打印:我在看代码=====

  • run():表示此线程中要干的什么事
  • start():调用完 start() 方法后,线程不一定立即执行,由 CPU 调度,调度完后,会执行线程中的 run() 方法

问:如果我不调用 start() 方法,直接调用 run() 方法,会有什么结果?

答:程序依旧能执行,只不过是没有创建出一个新的线程,就只有一个 main() 线程在运行,但依旧会运行 run() 方法哈。

可以将代码修改为:

@Override
public void run() {
    for (int i = 0; i < 200; i++) {
        System.out.println("我在看代码=====" + i);
        // 打印出当前线程
        System.out.println(Thread.currentThread().getName());
    }
}

可以看出,都是 main() 线程在执行。

问:如果我已经调用完某个线程的 start() 方法后,再次调用一下 start() 方法,会有什么结果?

答:会抛出异常 IllegalThreadStateException。当某个线程调用完 start() 方法后,就不能再次调用 start() 方法了。

案例:

使用多线程的方式去下载图片

public class WebDownLoader {

    public void downloader(String url, String fileName) {
        try {
            FileUtils.copyURLToFile(new URL(url), new File(fileName));
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("【异常】出现IO异常");
        }
    }
}

调用 FileUtils.copyURLToFile() 方法需要引入 commons-io-2.6.jar

此类就是专门下载图片的

public class TestThread2 extends Thread{
    private String url;
    private String fileName;

    public TestThread2(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) {
        String url = "https://www.baidu.com/img/flexible/logo/pc/result.png";
        String fileName = "result.png";
        TestThread2 t1 = new TestThread2(url, fileName);
        TestThread2 t2 = new TestThread2(url, fileName);
        TestThread2 t3 = new TestThread2(url, fileName);

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

上述代码:创建了 3 个线程,每一个线程都会去执行 run() 方法,去下载相应的图片

4.2 实现 Runnable 接口

步骤:

  1. 新建一个类,并实现 Runnable 接口,重写 run() 方法
  2. new 一个线程对象,并将其放入 Thread 类的构造方法中

例:

public class TestThread3 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("我在看代码=====" + i);
        }
    }

    public static void main(String[] args) {
        TestThread3 testThread3 = new TestThread3();
        new Thread(testThread3).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("【主线程】我在学习多线程" + i);
        }
    }
}

接下使用实现 Runnable 接口的方法来演示一个案例,但这个案例有并发问题哈。

并发问题

并发问题产生的原因:多个线程同时访问了共享资源,并对共享资源做了相应的处理。

场景:有3个人在不同的窗口同时卖10张票。

public class TicketThreadTest implements Runnable {

    private int ticketNums = 10;

    @Override
    public void run() {
        while (true) {
            if (ticketNums <= 0) {
                break;
            }
            // 模拟延迟
            try {
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "----->卖了第" + ticketNums-- + "张票");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        TicketThreadTest ticketThreadTest = new TicketThreadTest();

        Thread t1 = new Thread(ticketThreadTest, "小明");
        Thread t2 = new Thread(ticketThreadTest, "小红");
        Thread t3 = new Thread(ticketThreadTest, "小张");
        t1.start();
        t2.start();
        t3.start();
    }
}

运行程序后,发现:有不同的人卖了相同的票。
在这里插入图片描述
这就是并发问题哈。后面会介绍到的。

案例:使用实现 Runnable 接口方式来实现龟兔赛跑

public class RaceTest implements Runnable{

    private static String winner;

    @Override
    public void run() {
        for (int i = 1; i < 101; i++) {
            if ("兔子".equals(Thread.currentThread().getName()) && i % 10 == 0) {
                try {
                    Thread.sleep(10);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            boolean flag = gameOver(i);
            if (flag) {
                break;
            }
            System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
        }
    }

    private boolean gameOver(int step) {
        boolean flag = false;
        if (null != winner) {
            flag = true;
        }
        if (100 == step) {
            winner = Thread.currentThread().getName();
            System.out.println("The winner is " + winner);
            flag = true;
        }
        return flag;
    }

    public static void main(String[] args) {
        RaceTest raceTest = new RaceTest();
        new Thread(raceTest, "兔子").start();
        new Thread(raceTest, "乌龟").start();
    }
}

4.3 实现 Callable 接口

步骤:

  1. 新建一个类,实现 Callable 接口,重写其 call() 方法
  2. new 一个 实现 Callable 接口的对象
  3. new 一个 FutureTask 对象,其中参数是 Callable 接口对应的对象
  4. new 一个 Thread 对象,其中参数是 FutureTask 对象
  5. 执行线程:Thread#start()方法

【注意】:FutureTask 用于接收 Callable 执行线程完毕之后的结果,在线程结果没有被 get 到的时候,它会阻塞后面的内容

public class CallableTest implements Callable<Integer> {
    private int x;
    private int y;

    public CallableTest(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public Integer call() throws Exception {
        int result = this.x + this.y;
        return result;
    }

    public static void main(String[] args) {
        try {
            CallableTest callableTest = new CallableTest(1, 2);
            FutureTask<Integer> task = new FutureTask<>(callableTest);
            new Thread(task).start();
            Integer result = task.get();
            System.out.println(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

【使用线程池】

public static void main(String[] args) {
    try {
        CallableTest callableTest = new CallableTest(1, 2);
        // 1.开启线程池服务
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        // 2.调度线程
        Future<Integer> submit = executorService.submit(callableTest);
        // 3.获取线程执行后的结果
        Integer integer = submit.get();
        System.out.println(integer);
        // 4.关闭线程池服务
        executorService.shutdown();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

4.4 线程池

public class TestThreadPool {

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        // 执行
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        // 关闭
        service.shutdown();
    }
}

class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

相关文章

“21天好习惯“第一期——21

   大家好,今天又到了分享学习经历的时候了,今天的题目...

一维数组的使用

数组: 定义 :数组类型 数组类型 数组名[ 长度]&#x...

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。