【Java核心知识】线程基础知识

文章目录

  • 线程
    • 线程与进程的区别
    • 创建线程的方法
      • 方法一:继承Thread类
      • 方法二:实现Runnable接口
      • 方法三:使用Callable和FutureTask创建带返回值的线程
      • 方法四:通过线程池创建线程
    • 线程的基本操作
      • 线程的状态
      • 守护线程

线程

线程与进程的区别

一个进程由程序段数据段进程控制块三部分组成。程序段也称为代码段,是进程的程序指令;数据段是进程的操作数据在内存中的位置;进程控制端(PCB)包含进程的描述信息(如进程ID和进程名称,进程状态。进程优先级)、控制信息(如程序起始地址)、资源信息(内存信息,设备信息,文件句柄)、进程上下文(CPU寄存器的值、程序计数器PC的值),是进程存在的唯一标识

各个进程之间不共享内存,但是线程之间会共享进程的方法区内存空间、堆内存、系统资源。

进程是操作系统分配资源的最小单位,线程CPU调度的最小单位。

每一个线程都有自己独立的栈内存JDK1.8默认是1MB栈内存和堆内存是不一样的,栈内存不会被GC回收。栈内存分配的基本单位是栈帧,线程每进入一个方法,就会分配一个栈帧,栈帧保存着方法中的局部变量、方法的返回值以及方法的其他信息。当方法退出后,栈内存就会弹出该栈帧,进而释放内存空间。

创建线程的方法

方法一:继承Thread类

  • 继承Thread类,创建一个新的线程类,同时重写run方法。

  • 实例化该类,调用start方法启动线程

public class MyThread extends Thread{
    //线程的编号
    static int threadNo = 1;

    public MyThread() {
        super("DemoThread-" + threadNo++);
    }

    public void run() {
        for (int i = 1; i < 2; i++) {
            System.out.println(getName() + "轮次" + i);
        }
        System.out.println(getName() + "运行结束");
    }
}
@Test
public void testCreateThread() throws InterruptedException {
    Thread thread = null;
    //方法一:使用Thread子类创建和启动线程
    for (int i = 0; i < 2; i++) {
        thread = new MyThread();
        thread.start();
    }
    Thread.sleep(3000);
    System.out.println(Thread.currentThread().getName() + " 运行结束.");
}

方法二:实现Runnable接口

  • 新建类实现Runnable接口
  • 将实现Runnable接口的类传入Thread类中,作为Thread类的目标方法
public class Thread2 implements Runnable{
    @Override
    public void run() {
        System.out.println("我是通过实现Runnable接口创建的线程");
    }
}
@Test
public void testThread2() throws InterruptedException {
    Thread thread = new Thread(new Thread2(), "线程2");
    thread.start();
    Thread.sleep(1000);
}

或者可以直接传入Runnable接口匿名实现类,还可以使用lambda表达式

@Test
public void testThread2() throws InterruptedException {
    // Thread thread = new Thread(new Thread2(), "线程2");
    // 使用lambda表达式
    // Thread thread = new Thread(() -> System.out.println("我是匿名实现类"));
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("我是匿名实现类");
        }
    });
    thread.start();
    Thread.sleep(1000);
}

如果使用第一种方法创建线程,创建线程时需要new Thread,如果采用继承方法,那么每个线程的数量都是各自享有的,不能共同合作;如果采用实现Runnable接口,创建线程时,可以多个线程共享一个Runnable接口实例,从而多个线程共享数据,相互合作。

实现Runnable接口的方法本质上是Thread的run方法源码会判断自己的target域是否为null,如果不为空,则执行target.run()方法,而target域就是我们传入的Runnable接口实例

方法三:使用Callable和FutureTask创建带返回值的线程

前面实现Runnable接口不能有返回值,如果新的线程需要有返回值,需要实现Callable接口。但是Callable接口跟Runnable接口没什么关系,所以不能作为new Thread(Runnable run)的传入参数。所以为了桥接Callable接口和Runnable接口,定义了一个新的接口RunnableFuture接口,该接口继承了Runnable接口使其可以作为Thread类的传入参数)和Future<>接口(1、取消异步任务,2、判断任务是否执行完毕,3、返回异步任务执行结果)。

FutureTask类实现了RunnableFuture接口,可以作为Thread的传入参数,同时他又拥有一个callable实例,用来执行任务。FutureTask内部还有另一个非常重要的Object 类型的成员——outcome实例属性,用来保存返回结果。

所以整个执行流程为:

  • 首先为了声明一个异步任务,我们需要实现Callable接口的call方法,这是第一步,声明了任务。

  • 可是如何新建线程执行该任务呢?Callable接口实现类并不能作为Thread的target;于是我们把实现Callable接口的任务类传递给了FutureTask类,FutureTask实现了RunnableFuture接口,该接口继承了Runnable和Future接口,可以作为Thread类的target。

从直觉上讲,RunnableCallable接口的地位一致,Future接口是为了管理Callable接口返回值锁增加的功能增强接口,它们两个总是成对出现。关系如下:

【Java核心知识】线程基础知识_第1张图片

@Test
public void testThread3() throws ExecutionException, InterruptedException {
    // 将Callable接口实例传入FutureTask类
    FutureTask<Long> futureTask = new FutureTask<>(() -> {
        long startTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName() + " 线程运行开始.");
        Thread.sleep(1000);
        for (int i = 0; i < 200; i++) {
            int j = i * 10000;
        }
        long used = System.currentTimeMillis() - startTime;
        return used;
    });
    Thread thread = new Thread(futureTask);
    thread.start();
    // 等子线程执行完毕后返回
    Long res = futureTask.get();
    System.out.println(res);
}

如果子线程没执行完毕,主线程就调用get()函数获取执行结果,那么主线程会阻塞直到子线程执行完毕

方法四:通过线程池创建线程

可以将一个Runnable接口匿名类或者一个Callable接口匿名类提交给线程池对象来创建线程。

线程池对象由线程池工厂创建。

  • execute方法不带返回值
  • submit方法带返回值:可以看到submit()方法只需要传入Callable接口,不需要创建FutureTask对象,这是因为submit方法内部会创建FutureTask对象。
@Test
public void testExecutor() throws ExecutionException, InterruptedException {
    ExecutorService executorService = Executors.newCachedThreadPool();
    // 通过execute方法向线程池提交一个runnable接口匿名类,不带返回值
    executorService.execute(() -> {
        System.out.println("我是Runnable接口线程");
    });
    Future<Integer> future = executorService.submit(() -> {
        System.out.println("我是Callable或者Runnable接口线程");
        return 2;
    });
    // 获取返回结果
    System.out.println(future.get());
}

线程的基本操作

  • Thread.sleep():使线程睡眠,从运行态转为阻塞态
  • Thread.interrupt():将线程的interrupt标志设为true,可以通过isInterrupt()方法判断是否中断。interrupt()方法只是会改变线程中断状态,不会真正中断一个线程,仍需要用户调用isInterrupted()方法进行监控后处理。
  • Thread.join():join()方法是实例方法,需要使用被合并线程的句柄(或者指针、变量)去调用, 主线程必须等待被合并线程执行完毕后才能继续执行
  • Thread.yield():让线程由运行态转为就绪状态,并不会阻塞该线程

线程的状态

  • 新建:新建状态
  • 就绪:等待线程调度
  • 运行:运行中
  • 阻塞:不满足某种条件,被挂起
  • 结束:线程结束

守护线程

  • 守护线程必须在调用start方法前设置
  • 当main函数退出后,守护线程也会退出。如果JVM进程终止,守护线程也会被强制关闭
  • 守护线程创建的线程也是守护线程

你可能感兴趣的:(#,Java核心知识,java,开发语言,面试)