深刻理解Java并发编程(一) 多线程的概念及创建线程的几种方式

多线程是程序能够高效运行的一种很重要的方法,但是Java多线程中的概念、方法很多,如何深刻理解这些概念并且用好就变得非常有意义了,所以准备写一个系列的文章,来把Java多线程相关的知识做一个总结。

多线程的概念

多线程的概念无非包括两个方面:

  1. 什么是多线程
  2. 为什么要使用多线程

想要理解什么是多线程,就得先理解为什么多线程,想要理解为什么要使用多线程,就不得不从计算机的发展史来讲起。

多线程的发展大致经过的三个历史阶段:

  1. 计算机最初只是为了解决复杂的计算问题,且只能够接受一些特定的指令,由于当时的计算机本身并不存储指令,所以每次都需要用户输入了指令,计算机才会去工作。这就导致很多情况下,计算机都会处于等待状态,并没有真正利用计算机本身的资源。为了改善这种情况,就有了批处理操作系统。
  2. 批处理操作系统是用户把需要执行的多个指令写在磁带上,然后让计算机去读取这个磁带执行相应的程序,并把结果输出在另外一个磁带上。这样既提高了CPU的利用率,也使计算机可以服用程序指令。
  3. 虽然批处理使计算机更高效了但是人们发现CPU的利用率并没有提高,比如,当操作系统由于IO、循环等发生阻塞时,CPU要一直等到这个操作完成,才会执行下一个指令,这样也造成了CPU资源的浪费。于是便出现了进程和线程的概念。

进程和线程

进程是程序的一次执行过程,也是计算机进行资源分配的基本单位。程序开始运行,即产生了一个进程,计算机会为每个进程分配内存地址,且这些地址之间互不干扰。正是由于内存地址的相互独立,加上时间片切换机制,CPU有了宏观意义上的并行执行。

虽然进程可以实现宏观意义上的并行执行,但在一个进程内部,同一时间只能干一件事,在计算机多核的情况下,这样显然也造成了资源的浪费。于是将进程再细分为了线程。
线程是程序运行的最小单位。不像进程一样,线程并不会分配独立资源,这也是进程是资源分配的基本单位的体现。但是线程可以进行CPU的调度,每个线程负责一个独立的子任务,再配合多核CPU,可以进行真正意义上的并行操作。

所以总结起来,多线程就是在一个进程里面可以同时执行多个任务,使用多线程的目的也就是更加高效地利用CPU资源。

创建线程的方式

创建线程的方式总结起来可以分为四种:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 基于线程池创建
  4. 使用FutrureTask并实现Callable接口

1. 继承Thread类

使用这种方式创建线程的流程:

  1. 定义类继承Thread类
  2. 在该类中重写run方法
  3. 实例化继承了Thread的类对象
  4. 调用实例化对象的start方法
    start方法是一个native方法,调用该方法之后,系统创建一个线程,并执行run方法中的逻辑。
    如:
public class MyThread extends Thread {
	@Override
	public void run() {
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("hello!");
	}
	
	public static void main(String[] args) {
		MyThread myThread=new MyThread();
		myThread.start();
	}
}

2. 实现Runnable接口

继承Thread类来创建线程的方式简单易用,但是由于Java单线程,导致一个子类如果已经继承了Thread就无法再继承其他类的情况,这种情况下可以通过实现Runnable接口来创建线程。实现Runnable接口创建线程的流程:

  1. 定义类实现Runnable接口
  2. 在该类中重写run方法
  3. 实例化实现了Runnable接口的对象
  4. 将实现了Runnable接口的对象作为Thread的参数,创建Thread对象
  5. 调用Thread实例化对象的start方法

实现代码如下:

public class MyThread implements Runnable {
	@Override
	public void run() {
		try {
			Thread.sleep(10000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("hello!");
	}
	
	public static void main(String[] args) {
		MyThread myThread=new MyThread();
		Thread thread=new Thread(myThread);
		thread.start();
	}
}

可以看到,实际实现Runnble接口和继承Thread这两种方式都是重写了run方法。通过查看JDK源码我们可以知道,Thread类本身就是实现了Runable接口来实现创建线程的,他们的本质是一样的。创建的Thread创建线程后执行逻辑时,调用的就是实现了Runnable类中的run方法的逻辑。

3. 基于线程池创建线程

基于线程池的方式,本质上是创建一给匿名内部类实现Runnable接口,实现代码如下:

ExecutorService threadPool=Executors.newFixedThreadPool(5);
threadPool.execute(new Runnable() {
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName());
	}
});

4.使用FutureTask及实现Callable接口

以上三种创建线程的方式,都是创建了线程之后就交由计算机执行了,并没有返回值信息,但有些时候我们想要获得线程执行后的结果,那该怎么办呢?FutureTask和Callable接口就是用来解决这个问题的。
这种方式的具体做法是:

  1. 创建类实现Callable接口
  2. 实例化实现了Callable接口的对象
  3. 实例化FutureTask对象,将之前实例化Callable接口的对象作为参数传入
  4. 实例化Thread对象,将之前实例化的FutrureTask对象作为参数传入
  5. 调用Thread的start方法启动线程
  6. 通过FutureTask的get方法获取线程执行后的返回值
    实例代码如下:
public class MyThread implements Callable<String> {

	public static void main(String[] args) {
		FutureTask<String> futureTask = new FutureTask<>(new MyThread());
		Thread thread = new Thread(futureTask);
		thread.start();
		String result;
		try {
			result = futureTask.get();
			System.out.println(result);
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}
	}

	@Override
	public String call() throws Exception {

		return "this is result";
	}
}

总结

使用继承Thread的方式使用简单,但是类只能单继承,所以Runnable适用范围更广,基于线程池创建本质上也是实现了Runnble接口,使用FutureTask和Callable接口的方式可以获取线程运行的返回值。以上各种方式可以根据不同的需求选择不同的方式来完成创建线程的方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值