Java多线程基础

前言

本文的主要内容是多线程的一些基础知识以及 Java 创建线程的方式


1. 什么是进程与线程 / 进程于线程的区别

进程是程序的一次执行过程,系统运行一个程序即是一个进程从创建,运行到消亡的过程,在 Java 中启动 main 函数其实就是启动了一个进程,同时这个进程也是一个线程,称为主线程。

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


2. 并发和并行的区别

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

3. 为什么需要多线程

  • 线程是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程
  • 现在的系统动不动就要求百万级甚至千万级的并发量,多线程并发编程是高并发系统的基础
  • 多核时代,多线程可以提高 CPU 的利用率

4. 使用线程可能带来的问题

并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、死锁、线程不安全等等


5. 什么是线程死锁,如何避免线程死锁

如下图,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。

线程死锁示意图


避免线程死锁,从死锁产生的四个必要条件入手:

  1. 破坏互斥条件 :这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
  2. 破坏请求与保持条件 :一次性申请所有的资源。
  3. 破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  4. 破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。

6. 什么是上下文切换

在多核编程中,线程数一般大于 CPU 的核心数,而一个 CPU 核心同一时间只能被一个线程使用,为了让线程都得到有效的执行,CPU 采取了时间片轮转算法,当一个线程时间片用完后会重新处于就绪状态,把 CPU 资源让给其他线程使用,这个过程就是一次上下文切换。


7. 线程的生命周期

image-20210228200342874

结合上图:

  1. 当线程线程被创建后处于 **New(新建)**状态,当调用 start() 方法后开始运行,进入了 **Ready(就绪)**状态。就绪状态的线程如果获得了 CPU 的时间片,就会处于 **Running(运行)**状态。Java 将就绪态和运行态统称为 **Runnable(运行)**状态。
  2. 当线程执行 wait() 方法后,线程进入 Waiting(等待)状态,这个状态需要依靠其他线程通知才能返回到运行状态。
  3. **TimeWaiting(超时等待)**则是等待的基础上加了时间限制,比如调用了 sleep(long mills)wait(long mills),当时间到达后会自动返回运行状态。
  4. 当线程调用同步方法时,没有获取到锁,便会进入 **Blocked(阻塞)**状态。
  5. 线程执行了 run() 方法后就会进入终止状态。

8. sleep() 方法和 wait() 的区别

  • sleep() 方法没有释放锁,而 wait() 方法释放了锁
  • 两者都可以暂停线程的执行
  • wait() 通常被用于线程间交互/通信,sleep()通常被用于暂停执行
  • wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify()或者 notifyAll() 方法。sleep()方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout) 超时后线程会自动苏醒

9. 在 Java 中如和创建线程

  • 实现 Callable 接口:将线程的任务从线程的子类中分离了出来,进行了单独的封装
  • 继承 Thread 类(不推荐):Java 是有单继承的局限性,继承了 Thread 类就无法继承其他类

实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。


1)实现 Runnable 接口

需要实现 run() 方法,通过 Thread 调用 start() 方法来启动线程

创建:

public class MyRunnable implements Runnable {
    public void run() {
        // do something
    }
}

运行:

public static void main(String[] args) {
    MyRunnable instance = new MyRunnable();
    Thread thread = new Thread(instance);
    thread.start();
}

2)实现 Callable 接口

与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。

创建:

public class MyCallable implements Callable<Integer> {
    public Integer call() {
        return 123;
    }
}

运行:

public static void main(String[] args) throws ExecutionException, InterruptedException {
    MyCallable mc = new MyCallable();
    FutureTask<Integer> ft = new FutureTask<>(mc);
    Thread thread = new Thread(ft);
    thread.start();
    System.out.println(ft.get());
}

3)继承 Thread 类

创建:

public class MyThread extends Thread {
    public void run() {
        // do something
    }
}

运行:

public static void main(String[] args) {
    MyThread mt = new MyThread();
    mt.start();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值