服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - 详解JUC并发编程中的进程与线程学习

详解JUC并发编程中的进程与线程学习

2022-10-21 15:39夸父号 Java教程

这篇文章主要为大家详细介绍了JUC并发编程中的进程与线程学习,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助

进程线程

进程

  • 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的
  • 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
  • 进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)

线程

线程是主要负责运行指令,进程是主要管加载指令。

一个进程之内可以分为一到多个线程。

一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行

Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作为线程的容器

同步异步

  • 需要等待结果返回,才能继续运行就是同步
  • 不需要等待结果返回,就能继续运行就是异步

串行并行执行时间

使用多核cpu并行执行可以明显的提高执行效率

  • 串行执行时间 = 各个线程时间累加和 + 汇总时间
  • 并行执行时间 = 最慢的线程时间 + 汇总时间

注意:单核依然是并发的思想(即:cpu轮流去执行线程,微观上仍旧是串行),使用单核的多线程可能会比使用单核的单线程慢,这是因为多线程上下文切换反而浪费了时间。

创建和运行线程

1.使用 Thread

public static void main(String[] args) {
      // 创建线程对象
      Thread t = new Thread("线程1") {
          public void run() {
              // 要执行的任务
              log.debug("线程1被启动了");
          }
      };
      // 启动线程
      t.start();
      log.debug("测试");
  }

2.使用 Runnable 配合 Thread

public static void main(String[] args) {
      Runnable runnable = new Runnable() {
          public void run() {
              // 要执行的任务
              log.debug("线程1被启动了");
          }
      };
      // 创建线程对象
      Thread t = new Thread(runnable);
      t.setName("线程1");
      // 启动线程
      t.start();
      log.debug("测试");
  }

这里的Runnable是一个接口,接口中只有一个抽象方法,由我们来提供实现,实现中包含线程的代码就可以了。

@FunctionalInterface
public interface Runnable {
  /**
   * When an object implementing interface <code>Runnable</code> is used
   * to create a thread, starting the thread causes the object's
   * <code>run</code> method to be called in that separately executing
   * thread.
   * <p>
   * The general contract of the method <code>run</code> is that it may
   * take any action whatsoever.
   *
   * @see     java.lang.Thread#run()
   */
  public abstract void run();
}

Thread 与 Runnable 的关系原理分析

方法1原理分析

方法2是使用runnable对象,当成参数传给Thread构造方法,其中又调用了init方法,下面是Thread构造方法的源码

public Thread(Runnable target) {
      init(null, target, "Thread-" + nextThreadNum(), 0);
  }

继续跟踪查看runnable对象传到哪里去了,可以看到又传给了另一个重载的init,如下

private void init(ThreadGroup g, Runnable target, String name,                      long stackSize) {        init(g, target, name, stackSize, null, true);    }private void init(ThreadGroup g, Runnable target, String name,
                    long stackSize) {
      init(g, target, name, stackSize, null, true);
  }

再次跟踪可以看到是把runnable对象传给了一个thread的一个成员变量

//省略部分代码
this.target = target;

那么这个成员变量在哪里在使用了呢,经过查找可以发现是在run方法里面,只不过Thread发现有runnable对象就会先采用runnable的run方法。

@Override
  public void run() {
      if (target != null) {
          target.run();
      }
  }

方法2原理分析

通过创建一个子类去重写Thread类的run方法,这样就不会执行父类的run方法。

1.用 Runnable 更容易与线程池等高级 API 配合

2.用 Runnable 让任务类脱离了 Thread 继承体系,更灵活

方法3 FutureTask配合Thread创建线程

详解JUC并发编程中的进程与线程学习

Future接口中含有get方法来返回结果的

//省略部分代码
V get() throws InterruptedException, ExecutionException;
V get(long timeout, TimeUnit unit)
      throws InterruptedException, ExecutionException, TimeoutException;

而runnable本身是没有返回结果的,runnable不能将结果传给其他线程。

public interface Runnable {
  public abstract void run();
}

要注意到FutureTask也实现了Runnable接口,也可以传给Thread的有参构造里面。

创建线程的代码

public static void main(String[] args) throws ExecutionException, InterruptedException {
      // 创建任务对象
      FutureTask<Integer> task3 = new FutureTask<>(new Callable<Integer>() {
          @Override
          public Integer call() throws Exception {
              log.debug("线程1被执行了");
              return 666;
          }
      });
      // 参数1 是任务对象; 参数2 是线程名字,推荐
      new Thread(task3, "线程1").start();
      // 主线程阻塞,同步等待 task 执行完毕的结果
      Integer result = task3.get();
      log.debug("结果是:{}", result);
  }

查看进程

  • 任务管理器可以查看进程和线程数,也可以用来杀死进程,也可以在控制台使用tasklist查看进程taskkill杀死进程
  • jconsole 远程监控配置来查看

线程运行原理

JVM 中由堆、栈、方法区所组成,其中栈就是给线程使用的。

详解JUC并发编程中的进程与线程学习

方法调用时,就会对该方法产生一个栈帧,方法的局部变量都会在栈帧中存储。栈是后进先出,当method2执行完就会回收,在执行完同时会记录返回地址,然后在method1中继续执行。

线程之间的栈帧是相互独立的,之间互不干扰。

线程上下文切换

当上下文切换时,要保存当前的状态,因为可能是时间片用完了,此时线程还没有结束。Java中对应的就是程序计数器

start与run方法

启动一个线程必须要用start方法,如果直接调用类里面的run方法实际走的是main主线程。

线程start前getState()得到的是NEW

线程start后getState()得到的是RUNNABLE

public static void main(String[] args) {
      Thread t1 = new Thread("t1") {
          @Override
          public void run() {
              log.debug("t1被启动");
          }
      };
      System.out.println(t1.getState());
      t1.start();
      System.out.println(t1.getState());
  }

sleep方法

在sleep期间调用getState()方法可以得到TIMED_WAITING

public static void main(String[] args) {
      Thread t1 = new Thread("线程1") {
          @Override
          public void run() {
              try {
                  Thread.sleep(3000);
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      };
      t1.start();
      log.debug("线程1 state: {}", t1.getState());
      try {
          Thread.sleep(500);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      log.debug("线程1 state: {}", t1.getState());
  }

sleep打断

sleep可以使用interrupt方法打断,打断后会触发InterruptedException异常

public static void main(String[] args) throws InterruptedException {
      Thread t1 = new Thread("t1") {
          @Override
          public void run() {
              log.debug("进入睡眠");
              try {
                  Thread.sleep(2000);
              } catch (InterruptedException e) {
                  log.debug("被唤醒");
                  e.printStackTrace();
              }
          }
      };
      t1.start();
      Thread.sleep(1000);
      log.debug("打断");
      t1.interrupt();
  }

sleep防止cpu使用100%

在没有利用cpu来计算时,不要让while(true)空转浪费cpu,这时可以使用yield或 sleep 来让出cpu的使用权给其他程序

yield方法会把cpu的使用权让出去,然后调度执行其它线程。线程的调度最终还是依赖的操作系统的调度器。

join方法

该方法会等待线程的结束

static int r = 11;
  public static void main(String[] args) throws InterruptedException {
      test1();
  }
  private static void test1() throws InterruptedException {
      log.debug("主线程开始");
      Thread t1 = new Thread(() -> {
          sleep(1);
          r = 888;
      },"线程1");
      t1.start();
//        t1.join();
      log.debug(String.valueOf(r));
      log.debug("主线程线程结束");
  }

join没有使用时,返回的是11,若是使用join返回的是888,是主线程在同步等待线程1。

当然还有其他的方法等待

1.sleep方法等待线程1结束

2.利用FutureTask的get方法

join(long n)方法可以传入参数,等待线程运行结束,最多等待 n 毫秒,假如执行时间大于等待的时间,就会不再等待。那么该线程会直接结束吗?答案是不会。

如下代码

public class Test10 {
  static int r = 11;
  public static void main(String[] args) throws InterruptedException {
      test1();
  }
  private static void test1() throws InterruptedException {
      log.debug("主线程开始");
      Thread t1 = new Thread(() -> {
          sleep(2);
          r = 888;
          log.debug("线程1结束");
      },"线程1");
      t1.start();
      t1.join(1000);
      log.debug(String.valueOf(r));
      log.debug("主线程线程结束");
  }
}

输出结果,可以看到这里只是主线程不再等待。

16:28:53.360 c.Test10 [main] - 主线程开始
16:28:54.411 c.Test10 [main] - 11
16:28:54.411 c.Test10 [main] - 主线程线程结束
16:28:55.404 c.Test10 [线程1] - 线程1结束

interrupt 方法

interrupt可以用来打断处于阻塞状态的线程。在打断后,会有一个打断标记(布尔值)会提示是否被打断过,被打断过标记为true否则为false.
但是sleep、wait和join可以来清空打断标记。

代码如下

public static void main(String[] args) throws InterruptedException {
      Thread t1 = new Thread(() -> {
          log.debug("sleep...");
          try {
              Thread.sleep(4000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      },"t1");
      t1.start();
      Thread.sleep(1000);
      log.debug("interrupt");
      t1.interrupt();
      log.debug("打断标记:{}", t1.isInterrupted());
  }

线程被打断后并不会结束运行,有人就会问了,那我们如何在打断线程后关闭线程呢?答案就是利用打断标记去实现。

可以在线程的死循环之中加入一个判断去实现。

boolean interrupted = Thread.currentThread().isInterrupted();
              if(interrupted) {
                  log.debug("退出循环");
                  break;
              }

守护进程

Java 进程通常需要所有线程都运行结束,才会结束。

但是存在一种守护进程,只要其他非守护进程结束,守护进程就会结束。垃圾回收器就使用的守护进程。

线程的状态

操作系统层面(早期进程的状态)

详解JUC并发编程中的进程与线程学习

  • 初始状态 在语言层面创建了线程对象,还未与操作系统线程关联
  • 可运行状态(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行任务。
  • 运行状态 获取了 CPU 时间片运行中的状态
  • 调用阻塞api使运行状态转为阻塞状态
  • 终止状态 表示线程已经执行完毕

Java API 层面

详解JUC并发编程中的进程与线程学习

1、新建状态(New)

Thread t1 = new Thread("t1") {
          @Override
          public void run() {
              log.debug("running...");
          }
      };
log.debug("t1 state {}", t1.getState());

2、就绪状态(Runnable)与运行状态(Running)

Thread t2 = new Thread("t2") {
          @Override
          public void run() {
              while(true) { // runnable
              }
          }
      };
t2.start();
log.debug("t2 state {}", t2.getState());

3、阻塞状态(Blocked)

用一个线程拿到锁,使得当前线程没拿到锁会出现阻塞状态。

Thread t6 = new Thread("t6") {
          @Override
          public void run() {
              synchronized (TestState.class) { // blocked
                  try {
                      Thread.sleep(90000);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          }
      };
t6.start();

4、等待状态(Waiting)

等待一个未执行完成的线程

t2.join(); //等待状态

5、超时等待(Time_Waiting)

可以在指定的时间自行返回的。

6、终止状态(TERMINATED)

线程执行完了或者因异常退出了run()方法,该线程结束生命周期。 终止的线程不可再次复生。

 

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注服务器之家的更多内容! 

原文链接:https://blog.csdn.net/qq_45773419/article/details/123660043

延伸 · 阅读

精彩推荐
  • Java教程java计算两点间的距离方法总结

    java计算两点间的距离方法总结

    小编给大家总结了在java中计算两点之家距离的方法以及相关实例代码分享,有需要的读者参考下。...

    彬菌11822021-04-07
  • Java教程SpringMvc web.xml配置实现原理过程解析

    SpringMvc web.xml配置实现原理过程解析

    这篇文章主要介绍了SpringMvc web.xml配置实现原理过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可...

    錵開や落幕4732020-08-11
  • Java教程idea中database不显示问题的解决

    idea中database不显示问题的解决

    这篇文章主要介绍了idea中database不显示问题的解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下...

    Evonew12622020-08-01
  • Java教程redisson分布式锁的用法大全

    redisson分布式锁的用法大全

    这篇文章主要介绍了redisson分布式锁的用法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...

    miyouc10222021-08-25
  • Java教程Spring动态数据源实现读写分离详解

    Spring动态数据源实现读写分离详解

    这篇文章主要为大家详细介绍了Spring动态数据源实现读写分离,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    二十六度半4442020-12-01
  • Java教程java排序算法图文详解

    java排序算法图文详解

    这篇文章主要为大家详细介绍了Java经典排序算法之归并排序,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望可以对你有所帮助...

    Stars-Nine6262021-09-24
  • Java教程Java 容器化有哪些好处?

    Java 容器化有哪些好处?

    Java容器化是将Java服务或应用程序打包到软件容器中的过程,通常包括执行该包所需的一切。容器通常用于创建基于微服务的应用程序,并允许在应用程序...

    粤嵌教育4402022-01-07
  • Java教程Java编程内功-数据结构与算法「前缀,中缀,后缀」

    Java编程内功-数据结构与算法「前缀,中缀,后缀」

    本篇继续给大家介绍关于Java编程的相关知识,今天主要介绍数据结构与算法「前缀,中缀,后缀」,希望能够帮助到你!...

    Java精髓11252021-03-14