Java多线程基础知识

【摘要】 进程、线程和程序
  所谓程序,即代码编译打包之后生成的可执行文件,是存在于硬盘中的一个静态文件而已。当执行某个程序,操作系统会将这个程序加载到内存当中,为其分配所需的资源,此时它称为一个进程,是动态的。而一个进程中可能会同时进行多个任务,它们称为线程,多个线程共享进程的资源。几乎任何一本操作系统的书籍都会告诉你,进程是操作系统分配资源的基本单位,线程是CPU调度的基…

进程、线程和程序

  所谓程序,即代码编译打包之后生成的可执行文件,是存在于硬盘中的一个静态文件而已。当执行某个程序,操作系统会将这个程序加载到内存当中,为其分配所需的资源,此时它称为一个进程,是动态的。而一个进程中可能会同时进行多个任务,它们称为线程,多个线程共享进程的资源。几乎任何一本操作系统的书籍都会告诉你,进程是操作系统分配资源的基本单位,线程是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()方法。

文章来源: blog.csdn.net,作者:EileenChang,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/qq_24358759/article/details/115909454

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享