创建多线程的四种方法

概念

  • 程序(program)是为完成特定任务、用某种语言编写的一组指令的集合,即一段静态的代码,静态对象

  • 进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡过程,(生命周期)

    • 比如:运行中的微信、运行中的网易云音乐播放器

    • 程序是静态的,而进程是动态的

    • 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域

  • 线程(thread) 进程进一步细化即为线程,是一个程序内部的一条执行路径

    • 若一个进程同一时间可以并行执行多个线程,就是支持多线程的

    • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的花销小

    • 一个进程的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通讯功能简便、高效。但多个线程操作共享的系统资源可能会带来安全隐患,这就需要线程的同步

单核CPU和多核CPU的理解

        在单核CPU中,我们似乎也能感受到多线程,但是其是一个假的多线程。因为单核CPU在单位时间元内,只能执行一个线程的任务。但是CPU的时间单元特别短,因此我们感觉不出来。比如:在超市购物时,有多个购物口,但是只有一个收银员,顾客只有付款后才能离开。此时CPU就好比收银员。若某个人网络不好,付款失败,那么收银员就可以先让他等网络好了再来付款,然后让其他人先过来付款。因为收银员的提醒时间非常短(CPU的时间单元特别短),因此后面的顾客感觉不出来有阻塞。

    现在我们身边的设备,例如智能手机、电脑、服务器,它们的CPU都是多核的,多核CPU才能极致地发挥出多线程的效率。

    一个Java应用程序 java.exe, 其至少拥有三个线程,分别是:main()主线程 、gc()垃圾回收线程、异常处理线程。如果应用程序在执行中发生了异常,则会影响主线程。

并行与并发

  • 并行:多个CPU同时执行多个任务(多人干多件事)。

  • 并发:一个CPU(采用时间片)同时执行多个任务(电商秒杀活动、多个人做一件事)

使用多线程的优点

        问题:以单核CPU为例,只使用单个线程先后完成多个任务,可能比多个线程来完成用的时间更短,为何仍然需要多线程呢?

    多线程程序的优点:

    • 提高应用程序的响应能力,对于满足图形化界面的设置更有意义,可以增强用户的使用体验

    • 提高计算机CPU的利用率

    • 改善程序结构,将一些执行流程长且复杂的进程分为多个线程,独立运行,方便开发人员进行开发、维护。

何时需要用到多线程

  • 程序需要同时执行多个任务

  • 程序需要实现一些需要等待的任务,比如用户输入、文件读写操作、付款等

  • 需要一些后台运行的程序等

创建多线程的四种方法

方法一:继承Thread类

public class ThreadTest {

    public static void main(String[] args) {
        MyThread1 myThread1 = new MyThread1();
        MyThread2 myThread2 = new MyThread2();
        myThread1.start();
        myThread2.start();
    }

}

class MyThread1 extends Thread{
    @Override
    public void run() {
        for(int i = 0; i < 100; i++){
            if(i % 2 == 0) System.out.println(Thread.currentThread().getName() + "偶数:" + i);
        }
    }
}

class MyThread2 extends Thread{
    @Override
    public void run() {
        for(int i = 0; i < 100; i++){
            if(i % 2 != 0) System.out.println(Thread.currentThread().getName() + "奇数:" + i);
        }
    }
}

    上述代码中我们创建了两个线程,让其分别输出100以内的奇数和偶数。在主程序中,我们创建其实例对象,执行线程的start()方法,即可看到控制台输出效果:

     可见两个线程是交替打印的。

方法二:继承Runnable接口


public class ThreadTest {
     public static void main(String[] args) {
        MyThread3 myThread3 = new MyThread3();
        Thread myThread = new Thread(myThread3);
        myThread.start();
     }
}
class MyThread3 implements Runnable{

    @Override
    public void run() {
        System.out.println("66666");
    }
}

    在该实现方法中,是先创建一个继承Runnable接口的类,然后在主程序中,利用这个类,创建Thread的实例化对象,再调用start() 方法即可。此外,还有一个更简洁的写法,即创建匿名对象:


public class ThreadTest {
     public static void main(String[] args) {
        //匿名创建
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名创建的线程...");
            }
        }).start();
     }
}

比较方法一和方法二

    • 开发中应优先选择方法二,原因是:避免了类单继承的局限性、更适合用来处理有共享数据的情况

         接下来两种创建多线程的方法是jdk5.0新增的方法。

方法三:实现Callable接口

public static void main(String[] args) {
            MyThread4 myThread4 = new MyThread4();
        //将此对象作为参数,传入FutureTask构造器中,创建FutureTask对象
        FutureTask<Integer> futureTask = new FutureTask<>(myThread4);
        //将futureTask作为参数,传入Thread的构造方法中,创建Thread对象
        Thread thread = new Thread(futureTask);
        thread.start();

        try{
            //获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
            Integer sum = futureTask.get();
            System.out.println("[1,100]中所有偶数总和为:" + sum);

        }catch (InterruptedException e){
            e.printStackTrace();
        }catch (ExecutionException e1){
            e1.printStackTrace();
        }
}
//创建线程方法3:实现callable接口
class MyThread4 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for(int i = 1;i <= 100; i++) {
            if(i % 2 == 0) {
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

与使用Runnable相比,Callable功能更为强大

    • 相比run()方法,可以有返回值

    • 方法可以抛出异常

    • 支持泛型的返回类

    • 获取结果需要借助FutureTask类

方式四:使用线程池

    在实际开发中,需要频繁地创建和销毁、使用量特别大的资源(比如高并发下的线程),一般都是使用线程池来创建多线程。

    线程池,顾名思义即存放线程的池子,与此相似的是数据库连接池。

    实现思路:提前创建好多个线程,放入线程池中,使用时直接通过线程池获取,使用完成后放回池子中。这样,就实现了重复利用,避免频繁地创建销毁,提高了系统的性能。

使用线程池的好处~~

    • 提高响应速度(减少了创建、销毁线程的时间)

    • 降低资源消耗(重复利用线程池中的线程)

    • 便于线程管理

        话不多说,上代码~~

public class ThreadPoolTest {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();

        //1、提供指定线程数量的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //执行指定线程的操作,需要提供实现Runnable接口或者Callable接口实现的对象
        executorService.execute(myThread); //适用于Runnable
        //executorService.submit(Callable callable); //适用于Callable

        //关闭连接池
        executorService.shutdown();

    }
}

class MyThread implements Runnable{
    @Override
    public void run() {
        for(int i = 0; i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

控制台输出

 线程池相关API

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

 

ExecutorService:线程池接口

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值