Java并发编程(一):基础(一)

167 阅读6分钟

1、什么是线程和进程

何为进程?

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。

何为线程?

线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的方法区资源,但每个线程有自己的程序计数器虚拟机栈本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

2、并发与并行的区别

  • 并发:  同一时间段,多个任务都在执行 (单位时间内不一定同时执行);
  • 并行:  单位时间内,多个任务同时执行。

3、为什么要使用多线程

  • 更好的利用处理器的多个核心。
  • 更快的响应时间。
  • 更快的响应时间

4、线程优先级

现代操作系统基本采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当线程的时间片用完了就会发生线程调度,并等待着下次分配。线程分配到的时间片多少也就决定了线程使用处理器资源的多少,而线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。

优先级的范围从1~10,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。设置线程优先级时,针对频繁阻塞(休眠或者I/O操 作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者 偏运算)的线程则设置较低的优先级,确保处理器不会被独占。在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定

5、线程的生命周期和状态

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

Snipaste_2022-03-23_22-33-34.png

线程在生命周期中并不是固定处于某一个状态而是随着代码的执行在不同状态之间切换。Java 线程状态变迁如下图所示:

Snipaste_2022-03-23_22-37-28.png 线程创建之后,调用start()方法开始运行。当 线程执行wait()方法之后,线程进入等待状态。进入等待状态的线程需 要依靠其他线程的通知才能够返回到运行状态,而超时等待状态相当 于在等待状态的基础上增加了超时限制,也就是超时时间到达时将会 返回到运行状态。当线程调用同步方法时,在没有获取到锁的情况 下,线程将会进入到阻塞状态。线程在执行Runnable的run()方法之后将 会进入到终止状态。

注意:

Java将操作系统中的运行和就绪两个状态合并称为运行 状态。阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代 码块(获取锁)时的状态,但是阻塞在java.concurrent包中Lock接口的 线程状态却是等待状态,因为java.concurrent包中Lock接口对于阻塞的 实现均使用了LockSupport类中的相关方法。

6、Daemon线程

Daemon线程又称为后台线程。当一个Java虚拟机中不存在非Daemon 线程的时候,Java虚拟机将会退出。可以通过调用 Thread.setDaemon(true)将线程设置为Daemon线程。

注意:

Daemon属性需要在启动线程之前设置,不能在启动线程之后设置。

Daemon线程被用作完成支持性工作,但是在Java虚拟机退出时 Daemon线程中的finally块并不一定会执行。

在构建Daemon线程时,不能依靠finally块中的内容来 确保执行关闭或清理资源的逻辑。

7、启动和终止线程

7.1、创建启动线程有三种方式

  • 继承Thread类
public class MyThread extends Thread{
    //重写run方法
}
//启动线程
new MyThread().start();
  • 实现接口Runabble
public class MyThread implements Runable{ 
    //重写run方法 
}
//启动线程
new Thread(new MyThread()).start();
  • 实现接口Callable
public class Demo implements Callable {

   @Override
   public Object call() throws Exception {
      return null;
   }
}
//启动线程 
new Thread(new MyThread()).start();

7.2、线程中断

中断:可以理解为线程的一个标识位属性,它表示一个运行中的线程是 否被其他线程进行了中断操作。中断好比其他线程对该线程打了个招呼, 其他线程通过调用该线程的interrupt()方法对其进行中断操作

线程通过检查自身是否被中断来进行响应,线程通过方法 isInterrupted()来进行判断是否被中断,也可以调用静态方法 Thread.interrupted()对当前线程的中断标识位进行复位。如果该线程已经处 于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted()时 依旧会返回false。

从Java的API中可以看到,许多声明抛出InterruptedException的方法 (例如Thread.sleep(long millis)方法)这些方法在抛出InterruptedException 之前,Java虚拟机会先将该线程的中断标识位清除,然后抛出 InterruptedException,此时调用isInterrupted()方法将会返回false。

public class Demo {

   public static void runable(){
      new Thread(() -> {
         System.out.println("线程开始执行");
         //Thread.interrupted();
         Thread.currentThread().interrupt();
         System.out.println("线程的中断标志位:" + Thread.currentThread().isInterrupted());
         Thread.interrupted();
         if (!Thread.currentThread().isInterrupted()){
            System.out.println("线程被中断.......");
         }
      }).start();
   }

   public static void main(String[] args) throws InterruptedException {
      runable();
      Thread.sleep(100000);
   }
}

7.3、suspend()、resume()和stop()

在Java API中,这几个方法已经废弃,不建议使用。暂停、恢复和停止操作对应在线程Thread的 API就是suspend()、resume()和stop()

不建议使用的原因主要有:

以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。但暂停和恢复操作可以用后面提到的等待/通知机制来替代。