文章目录

  • 1、线程与进程、并行并发、同步异步概念
  • 1.1、进程与线程
  • 1.2、 并行与并发
  • 1.3、同步和异步
  • 2、线程的创建
  • 2.1、通过继承Thread创建线程
  • 2.2、使用Runnable配合Thread
  • 2.3、使用FutureTask与Thread结合
  • 2.4、使用线程池来创建线程
  • 3、线程运行原理
  • 3.1、虚拟机栈与栈帧
  • 3.2、线程上下文切换(Thread Context Switch)
  • 3.3、Thread的常见方法
  • 3.4、sleep 与 yield
  • 3.5、线程优先级
  • 3.6、 join方法
  • 3.7 interrupt 方法详解
  • 3.8、终止模式之两阶段终止模式
  • 3.9、sleep,yiled,wait,join 对比
  • 3.10、过时方法
  • 3.11、守护线程
  • 4、线程状态
  • 4.1、五种状态
  • 4.2、六种状态


1、线程与进程、并行并发、同步异步概念

1.1、进程与线程

  • 进程: 资源分配的最小单位(进程是线程的容器, 一个进程中包含多个线程, 真正执行任务的是线程
  • 线程: 资源调度的最小单位

进程

  • 程序由指令和数据组成,但是这些 指令要运行,数据要读写,就必须将指令加载到cpu,数据加载至内存。在指令运行过程中还需要用到磁盘,网络等设备,进程就是用来加载指令,管理内存,管理IO的
  • 当一个指令被运行,从磁盘加载这个程序的代码到内存,这时候就开启了一个进程
  • 进程就可以视为程序的一个实例,大部分程序都可以运行多个实例进程(例如记事本,浏览器等),部分只可以运行一个实例进程(例如360安全卫士)

线程

  • 一个进程之内可以分为多个线程
  • 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
  • Java 中,线程作为资源的最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作为线程的容器。

对比

  • 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
  • 进程拥有共享的资源,如内存空间等,供其内部的线程共享; 进程间通信较为复杂
  • 同一台计算机的进程通信称为 IPC(Inter-process communication)
  • 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
  • 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低

1.2、 并行与并发

  • 并发: 在单核CPU下, 一定是并发执行的, 也就是在同一个时间段内一起执行. 实际还是串行执行, CPU的时间片切换非常快, 给人一种同时运行的感觉
  • 并行: 在多核CPU下, 能真正意义上实现并行执行, 在同一个时刻, 多个线程同时执行; 比如说2核cpu, 同时执行4个线程. 理论上同时可以有2个线程是并行执行的. 此时还是存在并发, 因为2个cpu也会同时切换不同的线程执行任务罢了

对比

  • 并发(concurrent): 是同一时间应对(dealing with)多件事情的能力
  • 并行(parallel): 是同一时间动手做(doing)多件事情的能力

1.3、同步和异步

  • 如果需要等待结果返回才能继续运行的话就是同步
  • 如果不需要等待就是异步

2、线程的创建

2.1、通过继承Thread创建线程

public class CreateThread {
	public static void main(String[] args) {
		Thread myThread = new MyThread();
        // 启动线程
		myThread.start();
	}
}

class MyThread extends Thread {
	@Override
	public void run() {
		System.out.println("my thread running...");
	}
}
  • 优点:使用继承方式的好处是,在run()方法内获取当前线程直接使用this就可以了,无须使用Thread.currentThread()方法
  • 缺点:Java不支持多继承,如果继承了Thread类,那么就不能再继承其他类。另外任务与代码没有分离,当多个线程执行一样的任务时需要多份任务代码

2.2、使用Runnable配合Thread

public class Test {
	public static void main(String[] args) {
		//创建线程任务
		Runnable r = new Runnable() {
			@Override
			public void run() {
				System.out.println("Runnable running");
			}
		};
		//将Runnable对象传给Thread
		Thread t = new Thread(r);
		//启动线程
		t.start();
	}
}
  • 通过实现Runnable接口,并且实现run()方法。在创建线程时作为参数传入该类的实例即可

使用lambda表达式简化操作

public class Test {
	public static void main(String[] args) {
		//创建线程任务
		Runnable r = () -> {
            //直接写方法体即可
			System.out.println("Runnable running");
			System.out.println("Hello Thread");
		};
		//将Runnable对象传给Thread
		Thread t = new Thread(r);
		//启动线程
		t.start();
	}
}

2.3、使用FutureTask与Thread结合

使用FutureTask可以用泛型指定线程的返回值类型(Runnable的run方法没有返回值)

public class Test3 {
	public static void main(String[] args) throws ExecutionException, InterruptedException {
        //需要传入一个Callable对象
		FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
			@Override
			public Integer call() throws Exception {
				System.out.println("线程执行!");
				Thread.sleep(1000);
				return 100;
			}
		});

		Thread r1 = new Thread(task, "t2");
		r1.start();
		//获取线程中方法执行后的返回结果
		System.out.println(task.get());
	}
}

public class UseFutureTask {
   public static void main(String[] args) throws ExecutionException, InterruptedException {
      FutureTask<String> futureTask = new FutureTask<>(new MyCall());
      Thread thread = new Thread(futureTask);
      thread.start();
      // 获得线程运行后的返回值
      System.out.println(futureTask.get());
   }
}

class MyCall implements Callable<String> {
   @Override
   public String call() throws Exception {
      return "hello world";
   }
}

2.4、使用线程池来创建线程

/**
 * 创建线程的方式四:使用线程池
 *
 * 好处:
 * 1.提高响应速度(减少了创建新线程的时间)
 * 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
 * 3.便于线程管理
 *      corePoolSize:核心池的大小
 *      maximumPoolSize:最大线程数
 *      keepAliveTime:线程没有任务时最多保持多长时间后会终止
 *
 *
 * 面试题:创建多线程有几种方式?四种!
 */

class NumberThread implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

class NumberThread1 implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

public class ThreadPool {
    public static void main(String[] args) {
        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //设置线程池的属性
//        System.out.println(service.getClass());
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();

        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合适用于Runnable
        service.execute(new NumberThread1());//适合适用于Runnable

//        service.submit(Callable callable);//适合使用于Callable
        //3.关闭连接池
        service.shutdown();
    }
}

3、线程运行原理

3.1、虚拟机栈与栈帧

虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(stack frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息,是属于线程的私有的。当Java中使用多线程时,每个线程都会维护它自己的栈帧!每个线程只能有一个活动栈帧(在栈顶),对应着当前正在执行的那个方法

3.2、线程上下文切换(Thread Context Switch)

因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程

  • 线程的 cpu 时间片用完(每个线程轮流执行,看前面并行的概念)
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
  • 当Thread Context Switch发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
  • 线程的状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
  • Context Switch 频繁发生会影响性能

3.3、Thread的常见方法

方法名

static

功能说明

注意

start()

启动一个新线程,在新的线程运行run方法中的代码

start方法只是让线程进入就绪,里面代码不一定立刻运行(CUP的诗句片还没分给它)。每个线程对象的start方法只能调用一次,如果调用多次会出现IllegalThreadStateException

run()

新线程启动后哦会调用的方法

如果在构造Thread对象时传递了Runnable参数,则线程启动后会调用Runnable中的run方法,否则默认不执行任何操作。但可以创建Thread的子类对象,来覆盖默认行为

join()

等待线程运行结束

join(long n)

等待线程运行结束,最多等待n毫秒

getId()

获取线程长整型的id

唯一id

getName()

获取线程名

setName(String)

修改线程名

getPriority()

获取线程优先级

setPriority(int)

修改线程优先级

java中规定线程优先级1~10的整数,较大的优先级能提高该线程被CUP调度的几率

getState()

获取线程状态

isInterrupted()

判断是否被打断

不会清除,打断标记(是否被线程打断过)

isAlive()

线程是否存活(还没有运行完毕)

interrupt()

打断线程

如果被打断线程正在sleep,wait,join会导致被打断的线程抛出InterruptedException,并清除打断标记;如果打断的正在运行的线程,则会设置打断标记;park的线程被打断,也会设置打断标记

interrupted()

static

判断当前线程是否被打断

会清除打断标记

currentThread()

static

获取当前正在执行的线程

3.4、sleep 与 yield

sleep方法

  • 调用 sleep() 会让当前线程从 Running(运行状态) 进入 Timed Waiting 状态(阻塞)
  • 其它线程可以使用interrupt 方法打断正在睡眠的线程,那么被打断的线程这时就会抛出 InterruptedException异常【注意:这里打断的是正在休眠的线程,而不是其它状态的线程】
  • 睡眠结束后的线程未必会立刻得到执行 (需要分配到cpu时间片)
  • 建议用 TimeUnit 的 sleep() 代替 Thread 的 sleep()来获得更好的可读性

yield方法

  • 调用 yield 会让当前线程从Running 进入 Runnable 就绪状态,然后调度执行其它线程
  • 具体的实现依赖于操作系统的任务调度器(cup重新选择,可以是其他线程,也可能还是自己)

3.5、线程优先级

线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它, 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用

thread.setPriority(Thread.MAX_PRIORITY); //设置为优先级最高

3.6、 join方法

在主线程中调用t1.join,则主线程会等待t1线程执行完之后再继续执行

private static void test1() throws InterruptedException {
    log.debug("开始");
    Thread t1 = new Thread(() -> {
        log.debug("开始");
        sleep(1);
        log.debug("结束");
        r = 10;
    },"t1");
    t1.start();
    // t1.join(); 
    // 这里如果不加t1.join(), 此时主线程不会等待t1线程给r赋值, 主线程直接就输出r=0结束了
    // 如果加上t1.join(), 此时主线程会等待到t1线程执行完才会继续执行.(同步), 此时r=10;
    log.debug("结果为:{}", r);
    log.debug("结束");
}

3.7 interrupt 方法详解

  • 打断标记(是否被其他线程打断过),默认为false
  • 如果一个真正运行线程被打断,打断标记会被置为true
  • 如果是打断因sleep wait join方法而被阻塞的线程,打断标记会被置为false,抛异常

运行时打断示例

public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while(true) {
                boolean interrupted = Thread.currentThread().isInterrupted();
                if(interrupted) {
                    System.out.println("被打断了, 退出循环");
                    break;
                }
            }
        }, "t1");
        t1.start();
        Thread.sleep(1000);
        System.out.println("interrupt");
        t1.interrupt();
        System.out.println("打断标记为: "+t1.isInterrupted());
    }
}

结果:

interrupt
打断标记为: true
被打断了, 退出循环

Process finished with exit code 0
  • 打断正常运行的线程, 线程并不会暂停
  • 被打断前Thread.currentThread().isInterrupted()的返回值为false,打断后返回值为true
  • 通过Thread.currentThread().isInterrupted()的返回值来手动停止线程

阻塞时打断示例

public class Test3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            System.out.println("sleep...");
            try {
                Thread.sleep(5000); // wait, join
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        Thread.sleep(1000);
        System.out.println("iterrupt..");
        t1.interrupt();
        System.out.println(t1.isInterrupted()); // 如果是打断sleep,wait,join的线程, 即使打断了, 标记也为false
    }
}

结果:

sleep...
iterrupt..
false
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.xc.day1.Test2.lambda$main$0(Test2.java:8)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

3.8、终止模式之两阶段终止模式

在一个线程T1中如何优雅终止线程T2?这里的优雅指的是给T2一个料理后事的机会。

java并行编程 java并发编程从入门到精通_System

public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        Monitor monitor = new Monitor();
        monitor.start();
        Thread.sleep(3500);
        monitor.stop();
    }
}

class Monitor {
    Thread monitor;
    /**
     * 启动监控器线程
     */
    public void start() {
        //设置线控器线程,用于监控线程状态
        monitor = new Thread(() -> {
            //开始不停的监控
            while (true) {
                Thread thread = Thread.currentThread();
                //判断当前线程是否被打断了
                if(thread.isInterrupted()) {
                    System.out.println("处理后续任务");
                    //终止线程执行
                    break;
                }
                System.out.println("监控器运行中...");
                try {
                    //线程休眠
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //如果是在休眠的时候被打断,不会将打断标记设置为true,这时要重新设置打断标记
                    thread.interrupt();
                }
            }
        });
        monitor.start();
    }
    /**
     * 	用于停止监控器线程
     */
    public void stop() {
        //打断线程
        monitor.interrupt();
    }
}

结果:

监控器运行中...
监控器运行中...
监控器运行中...
监控器运行中...
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.xc.day1.Monitor.lambda$start$0(Test2.java:32)
	at java.lang.Thread.run(Thread.java:748)
处理后续任务

Process finished with exit code 0

3.9、sleep,yiled,wait,join 对比

  1. sleep,join,yield,interrupted是Thread类中的方法
  2. wait/notify是object中的方法
  3. sleep 不释放锁、释放cpu
  4. join 释放锁、抢占cpu
  5. yiled 不释放锁、释放cpu
  6. wait 释放锁、释放cpu

3.10、过时方法

不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁。(原因:不释放锁

方法名

功能说明

stop()

停止线程运行

suspend()

挂起(暂停)线程运行

resume()

恢复线程运行

3.11、守护线程

  • 当Java进程中有多个线程在执行时,只有当所有非守护线程都执行完毕后,Java进程才会结束
  • 但当非守护线程全部执行完毕后,守护线程无论是否执行完毕,也会一同结束。
  • 垃圾回收器线程就是一种守护线程

正常线程

public class Test5 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {

            }
        },"t1");
        t1.start();
        System.out.println("main线程执行结束");
    }
}
  • main线程结束后,t1线程会一直处于运行中
  • 只要有一个线程不停止,整个进程就不会终止

守护线程

t1.setDaemon(true);将t1设置为守护线程

public class Test5 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            while (true) {
            }
        },"t1");
        t1.setDaemon(true);
        t1.start();
        System.out.println("main线程执行结束");
    }
}

结果:t1线程为守护线程,其他线程执行完,则t1被强制停止

4、线程状态

4.1、五种状态

操作系统的层面上

java并行编程 java并发编程从入门到精通_后端_02

  1. 初始状态,仅仅是在语言层面上创建了线程对象,即Thead thread = new Thead();,还未与操作系统线程关联
  2. 可运行状态,也称就绪状态,指该线程已经被创建,与操作系统相关联,等待cpu给它分配时间片就可运行
  3. 运行状态,指线程获取了CPU时间片,正在运行
      1)当CPU时间片用完,线程会转换至【可运行状态】,等待 CPU再次分配时间片,会导致我们前面讲到的上下文切换
  4. 阻塞状态
      1)如果调用了阻塞API,如BIO读写文件,那么线程实际上不会用到CPU,不会分配CPU时间片,会导致上下文切换,进入【阻塞状态】
      2)等待BIO操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
      3)与【可运行状态】的区别是,只要操作系统一直不唤醒线程,调度器就一直不会考虑调度它们,CPU就一直不会分配时间片
  5. 终止状态,表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

4.2、六种状态

Thread.State 枚举,分为六种状态

java并行编程 java并发编程从入门到精通_java_03

新建状态、运行状态(就绪状态, 运行中状态)、阻塞状态、等待状态、定时等待状态、终止状态

  • NEW (新建状态) 线程刚被创建,但是还没有调用 start() 方法
  • RUNNABLE (运行状态) 当调用了 start() 方法之后,注意,Java API 层面的RUNNABLE 状态涵盖了操作系统层面的 【就绪状态】、【运行中状态】和【阻塞状态】(由于 BIO 导致的线程阻塞(线程等待io操作),在 Java 里无法区分,仍然认为 是可运行)
  • BLOCKED (阻塞状态) , WAITING (等待状态) , TIMED_WAITING(定时等待状态) 都是 Java API 层面对【阻塞状态】的细分,如线程等待io操作或等待cup分配时间片就为BLOCKED,sleep就为TIMED_WAITING, join为WAITING状态。后面会在状态转换一节详述。
  • TERMINATED (结束状态) 当线程代码运行结束
public class TestState {
    public static void main(String[] args) throws IOException {
        Thread t1 = new Thread("t1") {	// new 状态
            @Override
            public void run() {
                System.out.println("running...");
            }
        };

        Thread t2 = new Thread("t2") {
            @Override
            public void run() {
                while(true) { // runnable 状态

                }
            }
        };
        t2.start();

        Thread t3 = new Thread("t3") {
            @Override
            public void run() {
                System.out.println("running...");
            }
        };
        t3.start();

        Thread t4 = new Thread("t4") {
            @Override
            public void run() {
                synchronized (TestState.class) {
                    try {
                        Thread.sleep(1000000); // timed_waiting 显示阻塞状态
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t4.start();

        Thread t5 = new Thread("t5") {
            @Override
            public void run() {
                try {
                    t2.join(); // waiting 状态
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t5.start();

        Thread t6 = new Thread("t6") {
            @Override
            public void run() {
                synchronized (TestState.class) { // blocked 状态
                    try {
                        Thread.sleep(1000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t6.start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1 state "+t1.getState());
        System.out.println("t2 state "+t2.getState());
        System.out.println("t3 state "+t3.getState());
        System.out.println("t4 state "+t4.getState());
        System.out.println("t5 state "+t5.getState());
        System.out.println("t6 state "+t6.getState());
    }
}

结果:

running...
t1 state NEW
t2 state RUNNABLE
t3 state TERMINATED
t4 state TIMED_WAITING
t5 state WAITING
t6 state BLOCKED