Java多线程第二篇--聊聊线程(1)

259 阅读5分钟

进程与线程

进程

进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。进程是一种抽象的概念,从来没有统一的标准定义。进程一般由程序,数据集合和进程控制块三部分组成。程序用于描述进程要完成的功能,是控制进程执行的指令集;数据集合是程序在执行时所需要的数据和工作区;程序控制块包含进程的描述信息和控制信息是进程存在的唯一标志

线程

线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID,当前指令指针PC,寄存器和堆栈组成。而进程由内存空间(代码,数据,进程空间,打开的文件)和一个或多个线程组成

其实对照JVM的设计规范,可以看出JVM的设计都是与操作系统的进程与线程的设计理论对应着的。

进程线程关系

  1. 线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
  2. 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
  3. 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;
  4. 调度和切换:线程上下文切换比进程上下文切换要快得多。 如下图 111.png

Java线程

创建线程的方法

  1. 直接newThread,或者继承Thread,重写run接口
  2. 实现Runnable接口
  3. 实现Callable接口
  4. 线程池创建线程
package com.example.demo.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.FutureTask;

public class CreateThread {
	public static void main(String[] args) {
		/**
		 * 匿名类 继承Thread,重写run方法
		 */
		Thread thread1 = new Thread() {
			@Override
			public void run() {
				super.run();
				System.out.println("继承Thread,重写run方法");
			}
		};
		thread1.start();
		/**
		 * 实现Runnable接口
		 */
		Thread thread2 = new Thread(new MyRunnable());
		thread2.start();
		/**
		 * 实现Callable接口
		 */
		FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
		Thread thread3 = new Thread(futureTask, "thread3");
		thread3.start();
		/**
		 * 线程池方式
		 */
		ExecutorService executorService = Executors.newSingleThreadExecutor();
		executorService.submit(new MyTaskForPool());
	}

}

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

class MyCallable implements Callable<String> {
	@Override
	public String call() throws Exception {
		System.out.println("实现Callable接口");
 		return null;
	}
}

class MyTaskForPool implements Runnable{
	@Override
	public void run() {
		System.out.println("线程池创建线程");
	}
}

Runnable和Callable的区别

自从JDK 1.5开始,就提供了Callable和Future,通过它们可以在任务执行完毕之后得到任务执行结果;从使用方式就可以知道,Callable接口是有返回值的,且接口可以检测执行过程中的异常并将之抛出处理

线程的生命周期及其状态转换

  • NEW:毫无疑问表示的是刚创建的线程,还没有开始启动。
  • RUNNABLE: 表示线程已经触发start()方式调用,线程正式启动,线程处于运行中状态。
  • BLOCKED:表示线程阻塞,等待获取锁,如碰到synchronized、lock等关键字等占用临界区的情况,一旦获取到锁就进行RUNNABLE状态继续运行。
  • WAITING:表示线程处于无限制等待状态,等待一个特殊的事件来重新唤醒,如通过wait()方法进行等待的线程等待一个notify()或者notifyAll()方法,通过join()方法进行等待的线程等待目标线程运行结束而唤醒,一旦通过相关事件唤醒线程,线程就进入了RUNNABLE状态继续运行。
  • TIMED_WAITING:表示线程进入了一个有时限的等待,如sleep(3000),等待3秒后线程重新进行RUNNABLE状态继续运行。
  • TERMINATED:表示线程执行完毕后,进行终止状态。
  • 需要注意的是,一旦线程通过start方法启动后就再也不能回到初始NEW状态,线程终止后也不能再回到RUNNABLE状态。

图片1.png

线程的常用操作方法

中断操作(interrupt)

Java Thread中涉及到中断的操作有三个函数:

  1. interrupt()方法:对目标线程发送中断请求,看其源码会发现最终是调用了一个本地方法实现的线程中断;
  2. interrupted()方法:返回目标线程是否中断的布尔值(通过本地方法实现),且返回后会重置中断状态为未中断;
  3. isInterrupted()方法:该方法返回的是线程中断与否的布尔值(通过本地方法实现),不会重置中断状态;

image.png

睡眠(sleep)

public static native void sleep(long millis)方法显然是Thread的静态方法,很显然它是让当前线程按照指定的时间休眠,其休眠时间的精度取决于处理器的计时器和调度器。需要注意的是如果当前线程获得了锁,sleep方法并不会失去锁。

让出/放弃(yield)

public static native void yield();这是一个静态方法,一旦执行,它会是当前线程让出CPU,但是,需要注意的是,让出的CPU并不是代表当前线程不再运行了,如果在下一次竞争中,又获得了CPU时间片当前线程依然会继续运行。另外,让出的时间片只会分配给当前线程相同优先级的线程。

join操作

join方法可以看做是线程间协作的一种方式,很多时候,一个线程的输入可能非常依赖于另一个线程的输出。如果一个线程实例A执行了threadB.join(),其含义是:当前线程A会等待threadB线程终止后threadA才会继续执行

sleep VS yield

sleep()和yield()方法,同样都是当前线程会交出处理器资源,而它们不同的是,sleep()交出来的时间片其他线程都可以去竞争,也就是说都有机会获得当前线程让出的时间片。而yield()方法只允许与当前线程具有相同优先级的线程能够获得释放出来的CPU时间片。