多线程

进程和线程

进程与线程最主要的区别是它们是操作系统管理资源的不同方式的体现。
准确来说进程与线程属于衍生关系。
进程是操作系统执行程序的一次过程,在这个过程中可能会产生多个线程。

比如在使用QQ时,有窗口线程,
文字发送的线程,语音输入的线程,可能不是很恰当,但是就是这个意思。

由于系统在线程之间的切换比在进程之间的切换更高效率,所以线程也被成为轻量级进程。

并发和并行

  1. 并发: 多个程序可以同时运行的现象,更细化的是多进程可以同时运行或者多指令可以同时运行。多个线程任务被一个cpu轮流执行。
    并发强调的是计算机应用程序有处理多个任务的能力。并发的”同时”是经过上下文快速切换,使得看上去多个进程同时都在运行的现象,是一种OS欺骗用户的现象

    image-20200719145125356
  2. 并行:多个线程被多个cpu同时执行。这里也并不是只允许多个cpu处理多任务,一个cpu也是可以的,
    只要cpu能在同一时刻处理多任务。并行的”同时”是同一时刻可以多个进程在运行(处于running)

    并行强调的是计算机应用程序拥有同时处理多任务的能力。

    image-20200719145208852

并发和并行对比

并发,指的是多个事情,在同一时间段内同时发生了。

并行,指的是多个事情,在同一时间点上同时发生了。

并发的多个任务之间是互相抢占资源的。并行的多个任务之间是不互相抢占资源的。

只有在多CPU或者一个CPU多核的情况中,才会发生并行。

否则,看似同时发生的事情,其实都是并发执行的。

多线程的利弊

  • 利:

    • 线程可以比作轻量级的进程,cpu在线程之间的切换比在进程之间的切换,耗费的资源要少的多。

    • 现在是多核cpu时代,意味着多个线程可以被多个cpu同时运行(并行),如果可以利用好多线程,那么可以编写出高并发的程序。

  • 弊:

    • 虽然线程带来的好处很多,但是并发编程并不容易,如果控制不好线程,那么就可能造成死锁,资源闲置,内存泄露等问题。

什么是上下文切换?

cpu是采用时间片的轮转制度,在多个线程之间来回切换运行的。
当cpu切换到另一个线程的时候,它会先保存当前线程执行的状态,
以便在下次切换回来执行时,可以重新加载状态,继续运行。
从保存线程的状态再到重新加载回线程的状态的这个过程就叫做上下文切换。

线程的优先级

在Java中可以通过Thread类的setPriority方法来设置线程的优先级,
虽然可以通过这样的方式来设置线程的优先级,但是线程执行的先后顺序并不依赖与线程的优先级。
换句话说就是,线程的优先级不保证线程执行的顺序。

线程的几种状态

见:jdk Thread类源码中的state枚举类

1
NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED

sleep方法和wait方法的区别

  1. sleep方法是Thread类的方法;而wait方法是Object类的方法。

  2. sleep方法会使当前线程让出cpu的调度资源,从而让其他线程有获得被执行的机会,

但是并不会让当前线程释放锁对象。
而wait方法是让当前线程释放锁并进入wait状态,
不参与获取锁的争夺,从而让其他等待资源的线程有机会获取锁,
只有当其他线程调用notify或notifyAll方法是,被wait的线程才能重新与其他线程一起争夺资源。

stop,suspend,resume等方法为什么会被遗弃

  • stop: stop方法被弃用很好理解,因为stop方法是强行终止线程的执行,
    不管线程的run方法是否执行完,资源是否释放完,它都会终止线程的运行,并释放锁。
    显然,这在设计上就不合理。

  • suspend和resume: suspend方法用于阻塞一个线程,但并不释放锁,
    而resume方法的作用只是为了恢复被suspend的线程。
    假设A,B线程都争抢同一把锁,A线程成功的获得了锁,
    然后被suspend阻塞了,却并没有释放锁,它需要其他线程来唤醒,
    但此时B线程需要获得这把锁才能唤醒A,所以此时就陷入了死锁。

interrupt,interrupted,isInterrupted方法区别

  • interrupt: 这个方法并不是中断当前线程,而是给当前线程设置一个中断状态。

  • isInterrupted: 当线程调用interrupt方法后,线程就有了一个中断状态,
    而使用isInterrupted方法就可以检测到线程的中断状态。

  • interrupted: 这个方法用于清除interrupt方法设置的中断状态。
    如果一个线程之前调用了interrupt方法设置了中断状态,
    那么interrupted方法就可以清除这个中断状态。

join方法

join方法的作用是让指定线程加入到当前线程中执行。

假如在main方法里面创建一个线程A执行,并调用A的join方法,
那么当前线程就是main,指定的A线程就会在main之前执行,
等A执行完后,才会继续执行main。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

public static void main(String[] args) throws Exception
{

Thread a = new Thread(()->
{
try
{
TimeUnit.SECONDS.sleep(1);

}catch (Exception e){}

System.out.println("thread join");
});
a.start();

//a会在main线程之前执行
a.join();

System.out.println("main");
}

join方法的底层是wait方法,调用A线程(子线程)的join方法实际上是让main线程wait,
等A线程执行完后,才能继续执行后面的代码。

yield方法

yield属于Thread的静态方法,
它的作用是让当前线程让出cpu调度资源。

yield方法其实就和线程的优先级一样,你虽然指定了,
但是最后的结果不由得你说了算,
即使调用了yield方法,最后仍然可能是这个线程先执行,
只不过说别的线程可能先执行的机会稍大一些。