【并发编程】(一)并发编程基础和线程的生命周期

1.基本概念

1.1.进程和线程

进程是我们代码的一次运行过程,是CPU进行资源分配的最小单位。线程属于进程的一部分,是CPU进行调度的最小单位。在每个进程中至少得有一个线程,同一个进程中的所有线程共享当前进程中的资源。
在Java中,每一个启动的JVM虚拟机就是一个进程,其中堆、方法区就是线程共享的部分,而栈,程序计数器则是线程私有的。

1.2.并行和并发

  • 并发:多个事件在很短的时间间隔类交替执行,这个交替的时间短到难以察觉,让我们主观的认为是同时发生的。
  • 并行:多个事件同时发生。

并行存在的前提一定是多核CPU,只有多个核心的存在才能达到多个事件同时运行的目的,而并发通过单个CPU时间片的切换,给不同的线程分配调度的时间就可以实现了。

1.3.多线程的特点

也可以说是多线程的作用或使用的方式,包括异步和并行。

  • 异步:不用阻塞主流程,让主流程可以继续执行,另起一个线程做主流程之外的任务。例如申请账号成功后,发送短信通知到用户,这个发送短信的操作就可以异步执行。
  • 并行:不是指的CPU层面的并行,而是通过多个线程去同时执行多个耗时的操作。例如需要做一个复杂的数据报表是,启动多个线程同时从不同的数据表中查询数据,最后再将结果汇总到一起。

1.4.多线程的优缺点

优点:可以提高CPU的利用率,将多核CPU的特点发挥到极致。比如当一个线程必须阻塞的时候,另外的线程可以不用等待,继续执行。

缺点:创建更多的线程意味着更多的内存消耗和更频繁的上下文切换,上下文切换会耗费CPU的资源。此外,多线程并发还需要考虑对共享变量操作的线程安全问题。

1.5.线程的上下文切换

在并发编程中,线程的数量往往是大于CPU的核心数的,也就是说会出现多个核心共用一个CPU核心的情况,在这种情况下,为了让用户感觉多个线程是在同时执行的,CPU资源的分配采用了时间片分配的策略。

给每个线程分配一定的执行时间,过了执行时间后就会保存当前执行的状态并让出CPU资源,让其他的线程获取CPU时间片,并加载上次让出资源时的状态。

这种让出资源,并让其它线程加载资源继续执行的过程,就是一次线程的上下文切换。

2.Java线程的创建和启动

Java中一共有3中方式可以创建线程:

  • 继承Thread类并重写run方法。
  • 实现Runnable接口并重写run方法。
  • 实现Callable接口并重写call方法,这种方式也叫做FutureTask。

当然还有一种方式就是使用线程池工具——Executors,但这种方式实际上使用的还是Runnble和Callable。


三种方式的特点:
继承Thread类的方式,子类就不能再继承其它的类了。
Runable接口可以让子类继承其它的类,更加灵活。
实现Callable的方式,相对于Runnable可以通过阻塞的方式获取返回值,并且可以跑出异常。

2.1.代码示例

2.1.1.继承Thread类

public class ThreadDemo extends Thread {

    @Override
    public void run() {
        System.out.println("继承Thread类");
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadDemo threadDemo = new ThreadDemo();
        threadDemo.start();
    }
}
-- 打印结果
继承Thread类

2.1.2.实现Runnable

public class RunnableDemo implements Runnable {

    @Override
    public void run() {
        System.out.println("实现Runnable接口");
    }

    public static void main(String[] args) {
    	// runnable外还是需要包裹一层Thread
        Thread runnableDemo = new Thread(new RunnableDemo());
        runnableDemo.start();
    }
}
-- 打印结果
实现Runnable接口

2.1.3.实现Callable

public class CallableDemo implements Callable<String> {

    @Override
    public String call() {
        System.out.println("实现callable接口");
        return "callable返回值";
    }

    public static void main(String[] args) throws Exception {
    	// Callable的启动要复杂一点,需要包裹一层FutureTask,可以通过阻塞获取返回值
        FutureTask<String> futureTask = new FutureTask<>(new CallableDemo());
        Thread callableDemo = new Thread(futureTask);
        callableDemo.start();
        // 阻塞获取返回值
        String returnMsg = futureTask.get();
        System.out.println(returnMsg);
    }
}
-- 打印结果
实现callable接口
callable返回值

2.2.Java线程启动时发生了什么

Java中本身是没有线程的,线程的启动实际上是JVM对操作系统的请求。在源码中,start方法中实际上是调用了native方法,使用JNI做线程的启动。

public synchronized void start() {
    ……
	boolean started = false;
	try {
	    start0();
	    started = true;
	} finally {
	   ……
	}
}

private native void start0();

启动过程的简图如下:
在这里插入图片描述

2.3.为什么不使用run()方法

通过上面2.2.的图示可以看到start()方法做了什么事,它实际上就是将一个新的线程从NEW状态转为RUNNABLE状态(下面3.1会说到线程的各种状态)。处于RUNNABLE状态的线程会等待CPU的资源分配,获取资源后会自动调用run方法。

run()方法就是一个普通的Java函数,直接调用它就是外部的线程调用了thread对象中的一个方法而已,是同步执行的,达不到多线程并发执行的目的。

3.线程的生命周期

3.1.线程的状态

在OS中线程有5种状态,在Java中有6种,分别是NEW , RUNNABLE , WAITING , TIMED_WAITING , BLOCKED , TERMINATED ,其中NEW状态是Java独有的 。
在Threa.class中定义了一个线程状态的枚举State,可以明确的看到描述。

public enum State {
    /** 
     * 线程已创建但未启动。
     */
    NEW,
    
	/** 
     * 线程已在虚拟机中启动,等待操作系统调度后就可以运行。
     */
    RUNNABLE,
    
	/** 
     * 等待监视器锁,也就是等待进入synchronized块。
     */
    BLOCKED,
    
	/** 
     * 线程阻塞,等待其它线程将它唤醒,等待的方法包括Object.wait(),LockSupport.park()Thread.join()
     * 另外一个线程唤醒它需要使用对应的唤醒方法,Object.notify()或Object.notifyAll(),LockSupport.unPark()
     * Thread.join()会阻塞到执行join方法的线程运行结束后,才唤醒当前线程。
     */
    WAITING,

	/** 
     * 与WAITING类似,只是在Waiting的几个方法中传入等待时间的参数,没有显示的唤醒情况下,运行时间超出设置的等待时间也会唤醒。
     * 此外还有Thread.sleep(long);
     */
    TIMED_WAITING,

    /**
     * 线程执行完毕后的状态
     */
    TERMINATED;
}

3.2.线程的生命周期图示

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

挥之以墨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值