多线程有什么用

发挥多核CPU的优势

随着工业的进步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的, 4 核、 8 核甚至 16 核
的也都不少见,如果是单线程的程序,那么在双核CPU上就浪费了50%,在 4 核CPU上就浪费了75%。单
核CPU上所谓的”多线程”那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得
比较快,看着像多个线程”同时”运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻
辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。

防止阻塞

从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多
线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了
防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,
对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可
以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的
执行。

便于建模

这是另外一个没有这么明显的优点了。假设有一个大的任务A,单线程编程,那么就要考虑很多,建立
整个程序模型比较麻烦。但是如果把这个大的任务A分解成几个小任务,任务B、任务C、任务D,分别
建立程序模型,并通过多线程分别运行这几个任务,那就简单很多了。

多线程和单线程的区别和联系?

  1. 在单核 CPU 中,将 CPU 分为很小的时间片,在每一时刻只能有一个线程在执行,是一种微观上轮流

占用 CPU 的机制。

  1. 多线程会存在线程上下文切换,会导致程序执行速度变慢,即采用一个拥有两个线程的进程执行所需

要的时间比一个线程的进程执行两次所需要的时间要多一些。

结论:即采用多线程不会提高程序的执行速度,反而会降低速度,但是对于用户来说,可以减少用户的
响应时间。

简述线程、程序、进程的基本概念。以及他们之间关系是什么?

线程

与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与
进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在
各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

程序

是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代码。

进程

是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个
进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令
接着一个指令地执行着,同时,每个进程还占有某些系统资源如 CPU 时间,内存空间,文件,输入输出
设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程是进程划分成的更
小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程
中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可
以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。

线程的创建方式

继承Thread类,作为线程对象存在(继承Thread对象)

常规方法,不多做介绍了,interrupted方法,是来判断该线程是否被中断。(终止线程不允许用stop方
法,该方法不会施放占用的资源。所以我们在设计程序的时候,要按照中断线程的思维去设计,就像上
面的代码一样)。

  1. public class CreatThreadDemo1 extends Thread{
  2. /**
  3. * 构造方法: 继承父类方法的Thread(String name);方法
  4. * @param name
  5. */
  6. public CreatThreadDemo1(String name ){
  7. super(name);
  8. }
  9. @Override
  10. public void run () {
  11. while ( !interrupted()){
  12. System.out .println(getName() +"线程执行了..." );
  13. try {
  14. Thread.sleep(200 );
  15. } catch ( InterruptedException e) {
  16. e.printStackTrace();
  17. }
  18. }
  19. }
  20. public static void main (String[] args ) {
  21. CreatThreadDemo1 d1 = new CreatThreadDemo1("first");
  22. CreatThreadDemo1 d2 = new CreatThreadDemo1("second");
  23. d1.start();
  24. d2.start();
  25. d1.interrupt(); // 中断第一个线程
  26. }
  27. }

让线程等待的方法

  1. Thread.sleep(200); //线程休息2ms
  2. Object.wait(); //让线程进入等待,直到调用Object的notify或者notifyAll时,线程停止休眠

实现runnable接口,作为线程任务存在

  1. public class CreatThreadDemo2 implements Runnable {
  2. @Override
  3. public void run () {
  4. while ( true ){
  5. System.out .println("线程执行了..." );
  6. }
  7. }
  8. public static void main (String[] args ) {
  9. // 将线程任务传给线程对象
  10. Thread thread = new Thread(new CreatThreadDemo2());
  11. // 启动线程
  12. thread.start();
  13. }
  14. }

Runnable 只是来修饰线程所执行的任务,它不是一个线程对象。想要启动Runnable对象,必须将它放
到一个线程对象里。

匿名内部类创建线程对象

创建带线程任务并且重写run方法的线程对象中,为什么只运行了Thread的run方法。我们看看Thread
类的源码,我们可以看到Thread实现了Runnable接口,而Runnable接口里有一个run方法。
所以,我们最终调用的重写的方法应该是Thread类的run方法。而不是Runnable接口的run方法。

  1. public class CreatThreadDemo3 extends Thread{
  2. public static void main (String[] args ) {
  3. // 创建无参线程对象
  4. new Thread(){
  5. @Override
  6. public void run () {
  7. System.out .println("线程执行了..." );
  8. }
  9. }.start();
  10. // 创建带线程任务的线程对象
  11. new Thread(new Runnable() {
  12. @Override
  13. public void run () {
  14. System.out .println("线程执行了..." );
  15. }
  16. }).start();
  17. // 创建带线程任务并且重写run 方法的线程对象
  18. new Thread(new Runnable() {
  19. @Override
  20. public void run () {
  21. System.out .println("runnable run 线程执行了..." );
  22. }
  23. }){
  24. @Override
  25. public void run () {
  26. System.out .println("override run 线程执行了..." );
  27. }
  28. }.start();
  29. }
  30. }

创建带返回值的线程

Callable接口介绍:
返回指定泛型的call方法。然后调用FutureTask对象的get方法得道call方法的返回值。

  1. public class CreatThreadDemo4 implements Callable {
  2. public static void main (String[] args ) throws ExecutionException,InterruptedException {
  3. CreatThreadDemo4 demo4 = new CreatThreadDemo4();
  4. FutureTask<Integer> task = new FutureTask<Integer>(demo4); //FutureTask最终实现的是runnable接口
  5. Thread thread = new Thread(task );
  6. thread.start();
  7. System.out .println("我可以在这里做点别的业务逻辑... 因为 FutureTask是提前完成任务");
  8. // 拿出线程执行的返回值
  9. Integer result = task.get ();
  10. System.out .println("线程中运算的结果为:" +result);
  11. // 重写 Callable接口的 call方法
  12. @Override
  13. public Object call () throws Exception {
  14. int result = 1;
  15. System.out .println("业务逻辑计算中..." );
  16. Thread.sleep(3000 );
  17. return result;
  18. }
  19. }
  20. }

定时器

线程池创建线程

利用Java8新特性stream实现并发

线程有哪些基本状态

Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态(
image.png

  1. 新建状态 NEW :

    1. 使用new关键字创建一个thread对象,刚刚创建出的这个线程就处于新建状态。在这个状态的线程没有与操作系真正的线程产生关联,仅仅是一个java对象。
  2. 可运行 RUNABLE:

    1. 正在进行运行的线程,只有处于可运行状态的线程才会得到cpu资源。
  3. 阻塞 BLOCKED :

    1. 在可运行阶段争抢锁失败的线程就会从可运行--->阻塞
  4. 等待 WAITING :

    1. 可运行状态争抢锁成功,但是资源不满足,主动放弃锁(调用wait()方法)。条件满足后再恢复可运行状态(调用notiy()方法)。
  5. 有时限等待 TIMED_WAITING:

    1. 类似于等待,不过区别在于有一个等待的时间,到达等待时间后或者调用notiy(),都能恢复为可运行状态。<br /> 有两种方式可以进入有时限等待:wait(Long)和sleep(Long)
  6. 终结 TERMINATED:

代码全部执行完毕后,会进入到终结状态,释放所有的资源。

如何停止一个正在运行的线程

  1. 使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
  2. 使用stop方法强行终止,但是不推荐这个方法,因为stop和suspend及resume一样都是过期作废的

方法。

  1. 使用interrupt方法中断线程。

    start()方法和run()方法的区别

    只有调用了start()方法,才会表现出多线程的特性,不同线程的run()方法里面的代码交替执行。

如果只是调用run()方法,那么代码还是同步执行的,必须等待一个线程的run()方法里面的代码全部执行
完毕之后,另外一个线程才可以执行其run()方法里面的代码。

为什么我们调用start()方法时会执行run()方法,为什么我们不能直接调用run()方法?

看看Thread的start方法说明哈~

JVM执行start方法,会另起一条线程执行thread的run方法,这才起到多线程的效果。
为什么我们不能直接调用run()方法?
如果直接调用Thread的run()方法,其方法还是运行在主线程中,没有起到多线程效果。

Runnable接口和Callable接口的区别