Java的多线程基础知识点总结

多线程知识点基础总结

  • 前言
  • 1. 进程(Process)和线程(Thread)
    • 1.1 进程
    • 1.2 线程
  • 2. 线程的创建方式
    • 2.1 继承Thread类
    • 2.2 实现Runnable接口
    • 2.3 实现Callable接口和FutureTask包装
    • 2.4 三种使用方式对比
  • 3. 线程的生命周期
  • 4. 线程的控制
    • 4.1 join()
    • 4.2 sleep()
    • 4.3 yield()
    • 4.4 线程的优先级
    • 4.5 后台线程/守护线程
  • 5. 其他
    • 5.1 不要调用线程的run()方法
    • 5.2 不要对死亡线程调用start()方法
    • 5.3 有关Thread的target
  • 参考材料

更新2020年5月14日10:15:00:线程同步重新起一篇

前言

参考项目:https://github.com/wodongx123/ThreadDemo

1. 进程(Process)和线程(Thread)

1.1 进程

  • 基本上每个操作系统,都能同时运行多个任务,而这些任务,就是进程。
  • 简单来说,进程就是应用程序的实例。
  • 进程包括了三个部分:
    • 程序:描述了进程要完成的功能。
    • 数据集合:程序在执行时所需要的数据和工作区。
    • 目标控制块:包含进程的描述信息和控制信息,是进程存在的唯一标志。
  • 进程有动态性,并发性,独立性,异步性。

1.2 线程

  • 操作系统可以调度的最小单位就是线程,是进程的一部分。
  • 每个进程都能同时运行多个任务,而这些任务,就是线程。
  • 每个线程可以拥有自己的堆栈,程序计数器和局部变量,但不能拥有系统变量。
  • 每个进程下的所有线程共享该进程的所有资源。

2. 线程的创建方式

2.1 继承Thread类

  1. 创建一个继承Thread的类,并重写其中的run()方法,这个run()就是线程执行体。
  2. 创建一个该类的实例,然后通过start() 启动线程,不是用run(),具体看6.1
public class ExtendsThread extends Thread {
	
	//不设置初始值就是 i=0了
	private int i;
	
	public void run(){
		for(;i<100 ; i++){
			//在Thread类中,getName就能获取当前线程的线程名
			System.out.println(getName() + " " + i);
		}
	}

	public static void main(String[] args) {
		//这么写报错了java.lang.IllegalThreadStateException
		//Thread.currentThread().start();
		
		for(int i=0; i<100; i++){
			//由于main函数是静态函数,不能直接getName,要用Thread的静态方法
			System.out.println(Thread.currentThread().getName() + " " + i);
			if(i == 20){
				new ExtendsThread().start();
				new ExtendsThread().start();
			}
		}

	}
}

2.2 实现Runnable接口

  1. 定义Runnable接口的实现类,重写run()方法,run()是线程执行体。
  2. 创建一个实例,但是这个实例不是Thread,它只作为Thread的target。
  3. 通过target创建Thread对象,通过start()方法启动线程。
  4. 多个Runnable线程可以共享一个target,也就是说多个Runnable线程可以共享线程内部数据(其实是堆中的数据)。
public class RunnableThread implements Runnable {
	
	private int i;

	public void run() {
		// 引入Runnable必须要加入的方法
		for(;i<100 ; i++){
			//Runnable方法的话就没有getName了,所以还是用Thread的静态方法获取名字
			System.out.println(Thread.currentThread().getName()+ " " + i);
		}
	}
	
	public static void main(String[] args) {
		//这么写报错了java.lang.IllegalThreadStateException
		//Thread.currentThread().start();
		
		for(int i=0; i<100; i++){
			//由于main函数是静态函数,不能直接getName,要用Thread的静态方法获取thread实例
			System.out.println(Thread.currentThread().getName() + " " + i);
			if(i == 20){
				RunnableThread st = new RunnableThread();
				new Thread(st, "线程1").start();
				new Thread(st, "线程2").start();
			}
		}
	}
}

2.3 实现Callable接口和FutureTask包装

  1. Callable类是Java 5开始提供的接口。
  2. 近似Runnable,但是有返回值,也可以抛出异常。
  3. 通过重写其中的call()方法实现线程执行体。
  4. 用FutureTask类来包装Callable,实现返回值。
public class CallableThread  implements Callable<Integer>{

	//这个call方法就差不多是Runnable的run方法了,允许抛出异常,有返回值
	public Integer call() throws Exception {
		int i=0;
		for(; i<100; i++){
			System.out.println(Thread.currentThread().getName() + " " + i);
		}
		return i;
	}
	
	public static void main(String[] args){
		//实例化Callable对象
		CallableThread callableThread = new CallableThread();
		
		//使用FutureTask来包装对象
		FutureTask<Integer> task = new FutureTask<Integer>(callableThread);
		
		for (int i = 0; i < 100; i++) {
			System.out.println(Thread.currentThread().getName() + " " + i);
			if (i == 20) {
				//实际上就是启动了一个Callable线程
				new Thread(task, "Callable线程").start();
			}
		}
		
		try {
			//获取返回值,Callable的返回值是有可能抛出异常的,所以要try/catch
			System.out.println("Callable返回值:" + task.get());
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
    
}

2.4 三种使用方式对比

我们一般都使用实现Runnable接口、Callable接口的方式来创建新线程。

\ Thread Runnable Callable
实现方法 extends implements
可继承其他类
线程内资源共享
返回值,抛出异常

3. 线程的生命周期

线程的生命周期中一共有五种状态。Java的多线程基础知识点总结_第1张图片

  • 图中的方块代表了线程的五种状态:新建,就绪,运行,阻塞,死亡。线和箭头表示变更条件。
  • 虽然阻塞有多个,但是只代表不同的阻塞类型,实际上都是阻塞状态。
  • 新建(new):线程被new创建之后不会立刻运行,这个时候只有被分配内存和初始化内部参数。
  • 就绪(runnable):start()方法之后,线程处于就绪状态,当CPU分配资源之后才能开始运行。这一部分我们无法控制,取决于JVM里线程调度器的调度,具有一定的随机性。
  • 运行(running):除非线程的内容太短,不然一般一个线程不会总是处于运行状态,为了让并行的其他线程也能运行,线程在运行的过程中会被中断。如果是CPU分配资源消耗完,则会回到就绪状态;如果是因为其他原因被中断,就会进入到堵塞状态。
  • 阻塞(blocked):被堵塞的线程会在合适的时机中,重新回到就绪状态,再次等待CPU分配资源。
  • 死亡(dead):运行中的线程达到指定条件的时候,就会进入死亡状态,这个时候线程的运行已经结束了。调用线程的isAlive()可以查看Thread的状态,处于新建,死亡状态时会返回false,剩下的情况返回true。

4. 线程的控制

4.1 join()

  • join方法可以让线程强制等待另一个线程完成之后才能继续执行。
  • 当线程A调用线程B的join方法时,线程A必须等到线程B执行完成之后才能继续执行,谁调用谁等
  • Join有三个重载方法。
    • join():等待被join的线程执行完成。
    • join(long mills):等待时间最长为mills毫秒,如果mills毫秒后被join的线程还没执行完,则不再等待。
    • join(long mills, int nanos):等待时间最长为mills毫秒+nanos微秒。这个不建议使用,一是微秒太过精细,没有必要,二是计算机自己也没办法精确到微秒。

4.2 sleep()

  • 没有任何道理的强制一个线程堵塞一段时间。在暂停的时间内,就算CPU资源空出来了,也不会分给这个线程(毕竟只会分给处于Runnable状态的线程)。
  • 会抛出InterruptedException异常,使用时要不声明异常,要不用try/catch。
  • 有两个重载方法。
    • sleep(long mills):当前进程暂停mills毫秒。
    • sleep(long mills, long nanos):当前进程暂停mills毫秒+nanos微秒。很少使用原因同上。

4.3 yield()

  • yield(v. 放弃、让出)。
  • 调用yield()方法的线程会主动放弃本次获得的CPU时间片,重新回到就绪状态。目的是将CPU资源让给同等优先级的其他线程(包括自己)。
  • 线程是回到Runnable状态而不是Blocked状态,那么CPU资源就有可能再次分配到该线程身上。实际上使用貌似效果不太明显,毕竟该线程自己也在再次分配的范围内。
  • 真要用的话或许在线程数非常多的时候下可能有用吧(没试过)。

4.4 线程的优先级

  • 每个线程拥有一定的优先级,优先级高的线程获得的执行机会更多;优先级低的线程机会较少。
  • 优先级是int类型1 - 10范围内,有三个静态常量。
    • MAX_PRIORITY,10
    • MIN_PRIORITY,1
    • NORM_PRIORITY,5
  • 线程的默认优先级等同于创建该线程的父线程的优先级,Main线程的默认优先级是5。
  • 通过setPriority()设置优先级,通过getPriority()获取优先级。

4.5 后台线程/守护线程

一种专门在后台运行的线程,它的任务就是给其他线程提供服务。

  • 后台线程的特征:如果所有的 前台线程死亡,后台线程也会自动死亡。
  • 前台线程创建的线程自动是前台线程,后台线程创建的线程自动是后台线程。
  • setDaemon(true)将线程设置成后台线程,isDaemon()查看是否是后台线程。
  • setDaemon()一定要在start()之前调用。

5. 其他

5.1 不要调用线程的run()方法

在新建一个线程之后,一定要调用start()方法才能启动线程,系统会把run()方法当成线程执行体来执行。如果调用线程的run()方法,就会把线程类当成普通对象来处理了,run()方法会变成普通的方法,而不是线程执行体。

5.2 不要对死亡线程调用start()方法

简单来说,就是线程实际上是一个一次性用品,不像普通的类一样可以再次使用,在走完Thread的生命周期后,就不能再次使用了,想要再用就得再次new一个实例出来。如果对不是处于新建状态的线程调用start()方法,就会报java.lang.IllegalThreadStateException。
在源码的start()中,会对threadStatus这个变量做判断,如果不等于0就会报java.lang.IllegalThreadStateException。而这个变量正是代表Thread的状态,这个值会在新建的时候正好为0。


private volatile int threadStatus;

......

public synchronized void start() {

    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    
    .......
}

5.3 有关Thread的target

对于Thread类的run方法而言,其实就是判断target是否为空,非空就运行而已。
而这个target就是实现Runnable接口的类。

public class Thread implements Runnable {

    /* What will be run. */
    private Runnable target;
    
	......

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

}

参考材料

Java中的多线程你只要看这一篇就够了
https://www.cnblogs.com/wxd0108/p/5479442.html
锁和监视器的区别
https://www.cnblogs.com/keeplearnning/p/7020287.html#4181397
疯狂JAVA讲义(第5版)
p733 - p779

你可能感兴趣的:(JAVA,Thread)