前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java多线程一:基础知识与线程创建的几种方式

Java多线程一:基础知识与线程创建的几种方式

作者头像
全栈学习笔记
发布2022-04-24 15:29:02
2200
发布2022-04-24 15:29:02
举报
文章被收录于专栏:全栈学习笔记全栈学习笔记

线程进程

进程概念

进程就是运行中的程序,程序是由一段指令组成

线程概念

线程必须依赖于进程,进程存在,线程才存在

进程和线程的区别

进程是系统资源分配的基本单位,线程是CPU调度的基本单位,一个进程可以包含多个线程,同一个进程下面的资源共享很容易,但是进程之间的资源共享相对较难。

进程的几种状态

一共五种状态:新建 ,就绪 ,运行,阻塞,终止

其中三种基本状态:就绪,运行,阻塞

线程的几种状态

线程的状态: Thread类源码中定义了6种

代码语言:javascript
复制
 public enum State {
     NEW, //新建状态
     RUNNABLE, //运行中状态
     BLOCKED, //阻塞
     WAITING,// 等待
     TIMED_WAITING, //超时等待
     TERMINATED;// 停止
 }

注意:Java中,将就绪状态与运行状态统称为运行中

获取电脑逻辑处理器的数量:

代码语言:javascript
复制
 Runtime.getRuntime().availableProcessors()

Java创建线程的三种基本方式

1. 继承Thread类
代码语言:javascript
复制
class MyTheard1 extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"extends Thread");
    }
 }

启动线程(启动线程,此时线程应该是处于就绪状态,当CPU将时间片给到这个线程的时候,这个线程才会处于运行状态),这里有两种写法

代码语言:javascript
复制
MyTheard1 myTheard1 = new MyTheard1();
myTheard1.start();
Thread thread1 = newMyTheard1();
thread1.start();

第二种写法其实就是多态的体现

这里有一个问题。我们调用的是start方法,但是我们用户重写的是run方法,如果我们直接调用run方法实际上是不会走创建线程这个流程的,run方法就是一个普通方法,可以直接调用,但是起不到开启线程调用的作用。

那么run方法是什么时候调用的呢?

这里看源码:

代码语言:javascript
复制
 public synchronized void start() {
    start0();
 }
 private native void start0();

这里将start方法里面的其他代码删除了,只保留了调用的start0,start0我们可以看到这是一个本地方法,底层是采用c++实现的,所以,我们的Java实际上是不能开启一个线程的,它实际上是通过底层的c++去调用操作系统的api来创建一个线程,当这个线程创建成功并且获取到了CPU的时间片的时候,就开始执行run方法了。

缺点:扩展性太差,Java中类只能单继承

2. 实现Runnable接口
代码语言:javascript
复制
 class MyTheard2 implements Runnable{
     
     @Override
     public void run() {
         System.out.println(Thread.currentThread().getName()+"implements  Runnable");
     }
 }

启动线程:

代码语言:javascript
复制
 MyTheard2 myTheard2 = new MyTheard2();
 Thread thread2 = new Thread(myTheard2);
 thread2.start();

这里启动线程的代码,实际上也是交由Thread来执行,我们分析一下,Thread的run和Runnable的run方法,看到底是执行的哪一个的。

看源码

代码语言:javascript
复制
 private Runnable target;
 @Override
 public void run() {
     if (target != null) {
         target.run();
     }
 }

这里就可以看出来,当我们的Runnable 不为空的时候,执行的是Runnable里面的run方法,这个target也就对应着源码里面的操作!

代码语言:javascript
复制
 public Thread(Runnable target) {
     init(null, target, "Thread-" + nextThreadNum(), 0);
 }
 
 init 里面有一个地方 this.target = target;

整个逻辑就是,当我的Thread通过传入Runnable的构造函数创建的时候,执行的run方法就是Runnable的run方法,如果是直接new的话,就是继承Thread的那个类的run方法;

当然还有一种简单的写法

代码语言:javascript
复制
 Thread thread = new Thread(()->{
     System.out.println(Thread.currentThread().getName());
 });
 thread.start();

实际上这也是传入的一个Runnable实例,因为Runnable是一个函数式接口,可以用lambda表达式书写,这样代码写着很清晰。

3. 实现Callable接口
代码语言:javascript
复制
 class MyThread3 implements Callable<String>{
     
     @Override
     public String call() throws Exception {
         System.out.println(Thread.currentThread().getName()+"implements  Callable");
         return Thread.currentThread().getName()+"implements  Callable";
     }
 }

Callable 接口创建线程稍微显得与上面的有区别,但是实际上也是一样的,只是这里我们实现的是call()方法并且这里有一个返回值。

先来看启动线程:

代码语言:javascript
复制
 FutureTask futureTask = new FutureTask(new MyThread3());
 Thread thread3 = new Thread(futureTask);
 thread3.start();

首先,你会发现Thread类没有与Callable相关的构造函数,Thread其实是Runnable的一个实现类,这里的FutureTask其实也是Runnable的一个实现类,并且这个实现类有与Callable相关的构造函数,这样就能将Thread类也联系到一起了。他们之间的关系图如下:

另外,需要说明的是,通过继承Thread或者是实现Runnable接口实现的线程都没用返回值,但是通过Callable实现的线程会有一个返回值,返回值的类型可以根据实际情况设置。通过futureTask.get();可以获取这个值,值得注意的是,这个get方法会导致主线程阻塞,直到该线程执行完毕并返回值。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-04-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 全栈学习笔记 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 线程进程
  • Java创建线程的三种基本方式
    • 1. 继承Thread类
      • 2. 实现Runnable接口
        • 3. 实现Callable接口
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档