线程入门 - 1

著作 : lss 文章若有不对的地方,请多多指教。

1.进程和线程

进程

​ 进程就是应用程序在内存中分配的空间,也就是正在运行的程序。各个进程之间互不干扰。此时,CPU采用时间片轮转的方式运行进程。CPU 为每个进程分配一个时间段,称作它的时间片。如果时间片结束的时候,进程还在运行,则暂停这个进程的运行。CPU 则将分配给另一个线程。(这个过程叫做上下文转换)。如果进程在时间片结束前阻塞或者运行结束,则 CPU 立即进行切换,不用等待时间片用完。

在这里插入图片描述

当进程运行还未结束,时间片结束的时候。这个进程暂停时,他会保存当前进程的状态。(进程标识,进程使用的资源等),在下一次转换回来时根据之前的保存情况进行恢复,继续执行。

虽然进程的出现,使得操作系统的性能大大提升。但是还不是满足不了人们的欲望。就好像满足不了你写代码没有 BUG 一样。

比如杀毒软件在检测电脑的时候。你使用了扫描病毒这个功能,那么就无法同时执行清理垃圾这个功能。这显然无法满足人们的要求,所以便提出了下一个 概念 线程

线程

​ 让一个线程执行一个子任务,这样一个进程就包含了多个线程,每个线程单独执行一个子任务。

使用线程之后,用户使用扫描病毒功能的时候,就让扫描病毒这个线程去执行。同时用户又想使用清理垃圾的功能,就让清理垃圾的线程去执行。注意 : 线程在进程中也不是同时执行的,操作系统如何分配时间片给线程,在这里不做详细说明,有兴趣可以参考 <操作系统>

总之,进程和线程极大提高了操作系统的性能。

考虑

​ 问题: 多进程也可以实现并发,为什么我们要什么多线程 ?

进程之间资源共享麻烦,因为每个进程都有属于自己的内存空间,所以就会存在内存隔离。 而线程在一个进程内,那多个线程就可以很轻松的共享到一个内存中的资源。

​ 问题: 进程和线程的区别 ?

​ 每个进程独占一份内存空间,因为存在内存隔离,数据共享复杂,数据同步简单。各个进程之间互不干扰;而线程共享所属进程的内存空间,数据共享简单,同步复杂

​ 进程单独占有一份内存空间,一个进程出现问题,不会影响到其他进程,可靠性高。线程共享所属进程内存,一个线程出现问题,可能会影响程序的稳定性,可靠性低。

​ 线程是操作系统进行资源分配的基本的单位,而线程是操作系统进行调度的基本单位。即 CPU 分配时间的单位。

2.Java多线程入门 类和接口

​ 上面了解到操作系统中多线程的基本概念。那么在Java中,我们是如何使用多线程的呢?当然 JDK 为我们提供了 Thread 类和 Runnable 接口 来写一个 Java 多线程的程序 。

Thread

​ 首先是继承 Thread

public class Demo {
    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("MyThread");
        }
    }
    public static void main(String[] args) {
        Thread myThread = new MyThread();
        // 调用 start() 方法后线程才算启动!
        myThread.start();
        // 这里这是单独的调用了 run() 方法并不是启动线程
        //myThread.run();
    }
}

​ 我们在程序里面调用 start() 方法后,虚拟机会为我们创建一个线程,然后等到这个线程第一次得到时间片时再调用 run()方法。

​ 注意不可多次调用 start() 方法,再第一次调用 start() 方法后,再次调用程序会抛出异常。

在这里插入图片描述

Runnable

public class Demo {
    public static class MyThread implements Runnable {
        @Override
        public void run() {
            System.out.println("MyThread");
        }
    }

    public static void main(String[] args) {
        new Thread(new MyThread()).start();
        // Java 8 函数式编程,可以省略MyThread类
        new Thread(() -> {
            System.out.println("Java 8 匿名内部类");
        }).start();
    }
}

Thread 类的构造方法

Thread 类是 Runnable 接口的实现类,不管是继承 Thread 类还是实现 Runnable 接口都是和 Thread 类挂钩。来看看 Thread 类的源码。其实有很多种构造方法,这里不详细列举。

// init 方法
/**
     * @param g 线程组,指定一个线程组
     * @param target 指定要执行的线程任务
     * @param name  线程的名字
     * @param stackSize 设置所需要的堆大小,这个参数可以忽略.
     * @param acc 用于初始化私有变量 inheritedAccessControlContext
     */
private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc)
// 构造方法
public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
} 
// 构造方法
public Thread(ThreadGroup group, Runnable target, String name) {
        init(group, target, name, 0);
 }

其实构造方法都是调用了一个私有的 init() 方法实现的初始化。

Thread 类的常用方法

  • currentThread() : 静态方法,返回当前正在执行的线程。

  • start() : 开始执行线程方法,Java虚拟机会调用线程内的 run() 方法。

  • yield() : 让当前线程让出 CPU 处理器的占用。就算是当前线程执行了 yield() 方法, 程序在调度的时候,也有可能继续运行这个线程。

    注意: 这个方法只是让当前线程回到可运行状态。也可能存在刚让出又抢到 CPU 的运行权限。

  • sleep() : 让当前线程睡眠一段时间。

  • join() : 使当前线程等待另一线程运行完毕之后再执行当前线程。内部调用的是 Object 类中的 wait() 方法来实现。

Callable 、 Future 和 FutureTask

通常来说我们继承 Thread 类 和 Runnable接口就可以建立一个新的线程。但是有一个缺点就是 run() 这个方法没有返回值。而有的时候我们希望一个线程执行完一个任务后可以返回一个结果。所以 JDK 为我们提供了 Callable 接口和 Future 类为我们解决这个问题。

Callable 接口

Callable 接口和 Runnable 接口 同样是只有一个抽象方法的函数式接口。不同的是 Callable 接口提供有返回值,而且是支持泛型。 Callable 接口一般是配合线程池工具类 Executors 来使用。通过使用 submit() 方法提交来让一个 Callable 接口执行。会返回一个 Future 。先看一个简单的Demo。

public class Demo {

    public static class Mythread implements Callable {
        @Override
        public Object call() throws Exception {
            return "我是返回值";
        }
    }
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Future future = executorService.submit(new Mythread());
        System.out.println(future.get());
    }
}

// 输出结果    我是返回值

Future

从上面的Demo 中可以发现通过 executorService.submit() 方法执行后返回了一个 Future接口。通过调用 get 方法拿到返回值。 其实 Future 只是一个简单的接口里面的方法如下

在这里插入图片描述

  • cancel() : 试图取消一个线程的执行

    请注意这里为什么说是试图,不一定可以取消成功。因为任务可能已经执行完毕,或者已经取消,亦或者因为其他的一些因素不能取消。就会导致使得这个线程取消失败,返回值 boolean 代表返回是否取消成功的意思。参数值 boolean 代表是否采用中止的方式取消线程。

  • get() : 获取线程返回值

  • isCancelled() : 如果此任务是在正常完成前取消 返回 true。

  • isDone() : 此任务完成。包括(正常完成,异常或者取消等等情况都会返回true)

FutureTask

FutureTask 是 Future 接口的实现类。下面看一看这个类的结构

在这里插入图片描述
FutureTask 实现了 RunnableFuture 接口 而RunnableFuture 同时继承了 Future 和 Runnable 接口。那么这个FutureTask 类和 Future 接口有什么区别呢? 看下面的Demo

![线程的状态](D:\自身笔记\images\线程的状态.jpg)public class Demo {

    public static class Mythread implements Callable {
        @Override
        public Object call() throws Exception {
            return "我是返回值";
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        FutureTask futureTask = new FutureTask<>(new Mythread());
        executorService.submit(futureTask);
        System.out.println(futureTask.get());
    }
}

宏观上面去看的话这个 Demo 和 上面 Future 接口好像很相似,其实大不为然。 上面我们的通过 submit 方法会有一个返回值 Future 接口 然后通过返回值调用的 get() 方法 而这里的 submit() 方法是没有返回值的。get() 方法也是我们通过 new FutrueTask() 调用的。实际上我们这里调用的是 submit( Runnable task) 方法 而上面的Demo 调用的是 submit(Callable task) 方法。

3.线程的状态

// Thread.State 源码
public enum State {
 NEW,
 RUNNABLE,
 BLOCKED,
 WAITING,
 TIMED_WAITING,
 TERMINATED;
}

在这里插入图片描述

NEW

NEW 状态的线程此时就是处于我们新创建了一个线程,但是并没有调用 start() 方法。

考虑一个问题: 如果一个线程调用 start() 方法 当这个线程执行完毕到了 终止的状态 是否可以继续调用 start() ?

从表面上看好像是可以继续的。但是小伙伴们不要忘记了在刚开始创建线程的时候有看过一点源码的哦,底层有一个 threadStatus 变量来维护的。所以是不可以继续来调用的。

RUNNABLE

代表当前线程处于可运行的状态。

BLOCKED

代表当前线程处于阻塞状态。

WAITING

代表当前的线程处于等待状态,此时需要其他线程来唤醒 也就是调用 notify() 方法。

TIMED_WAITING

代表当前线程处于超时等待状态,也就是说调用了 sleep()方法。线程等待一个具体时间,当时间到了之后会被自动唤。醒。

TERMINATED

终止状态。此时线程已经执行完毕。

4.线程组和线程的优先级

在刚开始通过 Thread 和 Runnable 创建线程的时候,有看过 Thread 类中的构造方法 其实有一个参数名为 g 的一个参数代表的是一个线程组,我们可以使用线程组对线程进行批量的控制。每个Thread 必然存在一个 ThreadGroup 中。如果我们在创建 Thread 的是没有指定线程组,那么会默认将父线程组设置为自己的线程组。

public class Demo {
    public static void main(String[] args) {
        Thread testThread = new Thread(() -> {
            System.out.println("testThread当前线程组名字:" +
                    Thread.currentThread().getThreadGroup().getName());
            System.out.println("testThread线程名字:" +
                    Thread.currentThread().getName());
        });
        testThread.start();
        System.out.println("执⾏main⽅法线程名字:" + Thread.currentThread().getNa
    }
}

输出结果

执⾏main⽅法线程名字:main
testThread当前线程组名字:main
testThread线程名字:Thread-0

线程的优先级

Java中线程的优先级可以指定。Java中默认的线程优先级为 5 ,线程的执行顺序是由程序调度来决定的,线程的优先级会在线程调用之前设定好。那么就会有人说是不是优先级越高的就一定会执行呢?答案是不一定。线程优先级别越高只是代表有更高的几率可以拿到 CPU 的执行权。我们可以通过 Thread 类中的 setPriority() 方法设定线程的优先级。

public class Demo {
    public static void main(String[] args) {
        Thread a = new Thread();
        System.out.println("我是默认线程优先级:"+a.getPriority());
        Thread b = new Thread();
        b.setPriority(10);
        System.out.println("我是设置过的线程优先级:"+b.getPriority());
    }
}

输出结果

我是默认线程优先级:5 
我是设置过的线程优先级:10

在上面我们说到一个线程必定存在一个线程组中,那么线程组的有没有优先级呢? 当然线程组也有是优先级的,如果说线程的优先级大于线程组的优先级会是什么情况。请看Demo

public class Demo {
    public static void main(String[] args) {
        ThreadGroup threadGroup = new ThreadGroup("t1");
        threadGroup.setMaxPriority(6);
        Thread thread = new Thread(threadGroup,"thread");
        thread.setPriority(9);
        System.out.println("我是线程组的优先级"+threadGroup.getMaxPriority());
        System.out.println("我是线程的优先级"+thread.getPriority());
    }
}

这里我们创建了一个线程组 t1 将线程组的优先级设置为 6。紧跟着我们创建了一个线程,并将此线程放入这个线程组中,这个线程我们设置的优先级为 9 。 那么看下输出结果吧。

输出结果

我是线程组的优先级6 
我是线程的优先级6

很明显我们看到 原本线程的优先级为9 ,现在却成为了 6。刚好是我们线程组的优先级。所以我们得到一个结论就是:如果当线程组中的线程优先级大于线程组的时候,那么这个线程的优先级会失效,取而代之的是该线程组的优先级。

    System.out.println("我是线程的优先级"+thread.getPriority());
}

}


这里我们创建了一个线程组 t1 将线程组的优先级设置为 6。紧跟着我们创建了一个线程,并将此线程放入这个线程组中,这个线程我们设置的优先级为 9 。 那么看下输出结果吧。

**输出结果**

```java
我是线程组的优先级6 
我是线程的优先级6

很明显我们看到 原本线程的优先级为9 ,现在却成为了 6。刚好是我们线程组的优先级。所以我们得到一个结论就是:如果当线程组中的线程优先级大于线程组的时候,那么这个线程的优先级会失效,取而代之的是该线程组的优先级。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值