Java多线程基础知识

进程、线程和程序

  所谓程序,即代码编译打包之后生成的可执行文件,是存在于硬盘中的一个静态文件而已。当执行某个程序,操作系统会将这个程序加载到内存当中,为其分配所需的资源,此时它称为一个进程,是动态的。而一个进程中可能会同时进行多个任务,它们称为线程,多个线程共享进程的资源。几乎任何一本操作系统的书籍都会告诉你,进程是操作系统分配资源的基本单位,线程是CPU调度的基本单位。但其实,线程和进程在本质上是相同的。在Linux操作系统中,一个进程通过调用fork()函数得到一个子进程,这个子进程共享其资源,这个子进程其实就是线程。通俗地说,线程即一个进程不同的执行路径。

线程之间的切换

  线程是通过获取CPU的时间片在CPU上执行的,一个CPU核同一个时刻只能执行一个线程。如下图所示,CPU由运算器ALU、控制器OC、寄存器Register和缓存Cache等部分组成。线程A在CPU上执行时,寄存器中存放了程序执行的位置以及操作数等相关数据。当CPU从线程A切换到线程B时,先要将寄存器Register中的数据保存到缓存Cache当中,再执行线程B。这样做的目的是从线程B切换回线程A的时候,可以回到线程A之前执行的地方。这就是线程之间的切换,也称为CPU上下文切换。在这里插入图片描述

线程数量

  多线程的意义在于压榨CPU的计算能力,使其不会闲下来。当一个线程等待IO的时候,CPU可以执行其他线程的运算逻辑。那么,是不是线程数量越多越好呢?其实不然。CPU在上下文切换的时候,也是需要时间开销的。当CPU频繁地进行上下文切换,会导致其上下文切换的时间过长,导致程序执行效率降低。而线程数设置太少,又无法最大化地利用CPU性能。所以,根据如下公式,可得到一个合理的线程数,NThread = Ncpu * Ucpu * (1 + W / C)。Ncpu表示CPU的总核数,Ucpu表示期望的CPU利用率,W表示线程等待的时间,C表示线程在CPU上执行的时间。假设CPU核数为1,期望的CPU利用率是100%,线程等待时间和执行时间之比W/C为1 : 1,可得出2为比较合理的线程数。那么问题来了,如何知道程序W和C的比值呢?严谨一点的话,肯定要在程序部署运行之后,通过统计数据才能得出结论。一般可以使用Jprofiler或Arthas进行分析,也可以在代码中通过日志的方式记录代码各部分的执行时间。

如何创建线程

  从形式上来说,Java提供了多种创建线程的方式。但是从本质上来说,它们都属于同一种方法。

方式1:继承Thread类

public class TestThread {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("Hello world !");
    }
}

方式2:实现Runnable接口

public class TestThread {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable).start();
    }
}

class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("Hello world !");
    }
}

方式3:Lambda表达式

public class TestThread {
    public static void main(String[] args) {
        new Thread(
                () -> System.out.println("Hello world !")
        ).start();
    }
}

方式4:线程池

public class TestThread {
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        service.execute(new MyRunnable());
        service.shutdown();
    }
}

方式4:通过Callable接口实现带返回值的线程创建

public class TestThread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        Future<String> f = service.submit(new MyCallable());
        String result = f.get();
        System.out.println(result);
        service.shutdown();
    }
}

class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "success";
    }
}

方式5:构建FutureTask对象

  在方式4中,Callable对象只能通过线程池执行。如果不用线程池,可以通过构造FutureTask对象来实现。

public class TestThread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> task = new FutureTask<>(new MyCallable());
        new Thread(task).start();
        String result = task.get();
        System.out.println(result);
    }
}

  以上是Java提供的5中创建线程的方式,而归根结底,他们的最终实现,都是创建Thread类,最后执行其start()方法。

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值