前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >【多线程】线程初体验

【多线程】线程初体验

作者头像
用户8902830
发布于 2021-08-12 03:05:40
发布于 2021-08-12 03:05:40
28200
代码可运行
举报
文章被收录于专栏:CodeNoneCodeNone
运行总次数:0
代码可运行

上节讲了下线程和进程的基础知识,但是对于Java来说,可能讨论线程的时间会更多些,所以接下来的一系列文章都是着重在讨论线程。

创建线程

创建的线程的方式是老生常谈也是面试中喜欢问的问题之一了,网上的说法众说纷纭,说什么实现Runnable接口和实现Callable接口是同一种类型,这种说法也不是说错误,只不过需要看站在哪个角度看。但是这种细节其实没有必要太在意,不要钻牛角尖。

实现Runnable接口

实现Runnable接口,然后重写run() 方法,该方法定义了线程的工作方式和工作内容。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ImplementsRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "------Runnable线程工作中。。。");
    }

    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            new Thread(new ImplementsRunnable()).start();
        }
    }
}

在main方法中,开启了50个线程运行,「开启线程其实就是新建了一个Thread,然后把实现Runnable接口的类作为参数传进去」,现在我们来看看运行的结果

可以看到虽然我们是按照顺序来新建线程的,但是线程的先后执行顺序是由CPU来控制的,可以说是「不可控」的,也正是这样才能说明了多线程在运行。

实现Callable接口

实现了接口后需要重写的是call() 方法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ImplementsCallable implements Callable {

    @Override
    public Object call() {
        System.out.println(Thread.currentThread().getName() + "--------callable线程工作中");
        return "实现callable,有返回值";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        for (int i = 0; i < 50; i++) {
            ImplementsCallable callable = new ImplementsCallable();
            FutureTask<String> task = new FutureTask<String>(callable);
            new Thread(task).start();
            System.out.println(task.get());
        }
    }
}

值得注意的是,在Thread类中的构造函数中,并没有参数为Callable的重载构造函数,基本上都是Runnable

而借助了FutureTask 这个类算是线程工作原理中比较重要的一个类,以后可能会专门出一篇文章来学习,FutureTask 是实现了RunnableFuture 接口,而该接口又是继承了RunnableFuture

与实现Runnable接口方式最大的不同就是,「Callable接口有返回值」 ,这个返回值使用的场景是什么呢,比如在http调用中,需要返回某个结果,在多线程使用的情况下就会用到Callable和Future来实现。如何获取返回值呢,就是使用FutureTask中的get() 方法,让我们来看看运行结果

这里出现了一个有意思的问题,当我把「第14行代码注释后」 运行,出现以下结果,线程是混乱无序的,也正是期待的结果。

但是,当我「保留第14行代码多次运行」 ,又会出现以下结果,线程竟然变得有序了,「如果有知道为什么的小伙伴可以留言呀」

继承Thread类

继承Thread类后,Idea甚至没有提醒需要重写,需要「手动去重写」 run()方法

整体代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ExtendsThread extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "--------继承Thread的线程工作中");
    }

    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            new ExtendsThread().start();
        }
    }
}

代码比较简单,我们来看下结果,也是和预期一样

❝两种方式优先选择实现接口,因为Java不支持多继承,继承了Thread类就不能继承其他类,但是可以实现多个接口。而且就性能开销方面来看,继承整个Thread类显得比较臃肿。 ❞

线程常用方法

线程有关的方法有比较多种,这里着重讲下4种常用的方法。

start

在上述例子中可以发现每次开启一个线程基本都是使用了start() 方法来开启,那它是run() 方法的区别是什么呢

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class CommonMethod {
    public static void main(String[] args) throws InterruptedException {
        startRunExample();
    }

    //start,run
    public static void startRunExample() {
        new MyThread().start();
        new MyThread().run();
    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " is running");
    }
}

新建了一个类,然后创建一个内部类继承了Thread,调用了start()run() 两种方法,在主函数里面再调用封装的方法,来看下结果如何。

可以看到一个线程名字是主线程,一个是子线程,所以start() 方法是开启了一个线程,然后这个线程执行了run() 方法的内容。但是如果直接用run() 方法呢,就是主线程单纯地执行run() 方法的内容,并没有开启新的线程。

sleep

sleep是让当前线程睡眠,让出cpu给其它线程执行。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class CommonMethod {
    public static void main(String[] args) throws InterruptedException {
//        startRunExample();
        sleepExample();
//        yieldExample();
//        waitExample();
        
    }

    //省略start,run
    
    //sleep
    public static void sleepExample() throws InterruptedException {
        new MyThread().start();
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " is running");

    }
}

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " is running");
    }
}

比如我开启了一个新的线程,但是我让主线程休眠3s再运行,结果应该先是Thread-0 is running 然后3s后输出main is running

为了做个对比,我把sleep代码给注释掉,再来看多几遍结果

可以看到两个线程的结果几乎是「同时出来」,至于哪个前哪个后在这个例子里不是我们能控制的。

yield

yield是指程序员「建议」计算机把当前线程占用的CPU让给其它线程,但是CPU鸟不鸟我们,又是另外一回事了,通俗地来说就是把线程从Running状态转换成Runnable状态。

❝再次强调是建议计算机把当前线程挂起,执行其它线程,但是做不做是计算机的事情。 ❞

再次新建一个内部类YieldThread

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class CommonMethod {
    public static void main(String[] args) throws InterruptedException {
//        startRunExample();
//        sleepExample();
        yieldExample();
//        waitExample();
        
    }

    //省略start,run
    
    //省略sleep
 
    //yield
    public static void yieldExample() throws InterruptedException {
        YieldThread yieldThread = new YieldThread();
        Thread thread = new Thread(yieldThread, "thread1");
        Thread thread1 = new Thread(yieldThread, "thread2");
        thread.start();
        thread1.start();
    }
}

class YieldThread extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " is running " + i);
            if (Thread.currentThread().getName().equals("thread1")) {
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName() + " yield " + i);
        }
    }
}

其实这个「例子不太准确」,但是能够勉强看,整个run的逻辑就是每个线程跑10遍,每遍输出一个running,一个yield。但是当我们加了Thread.yield() 之后,预期结果是

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
thread1 is running
thread2 is running
thread2 yield
thread1 yield

就是thread1执行了running语句后,把cpu使用权交出来,cpu选择了执行thread2的一套逻辑后thread1再拿到cpu时间片来执行thread1 yield语句

接着来看下结果是否能和预期一样

可以看到只有「部分」能够和预期结果一样,当我们去掉了Thread.yield() 这行代码后呢

没错,你会发现「偶尔」也有这种情况发生,但是没有上面存在的频繁。是因为这两个线程有可能是并行的,而不是并发(交替运行的),所以两者同时执行了running语句,然后线程2接着执行了yield,线程1执行了yield。

❝这里说得「不一定准确」,所以说是不太准确的例子,如果有更好的理解和例子可以留言呀!!! ❞

wait

相比于前面的yield而言,接下来的例子可控性更强一点,前者是建议,后者可以对应地说成强制。是把线程从Running状态转变成Block状态,直接挂起线程,没有外力唤醒前不会执行。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class CommonMethod {
    public static void main(String[] args) throws InterruptedException {
//        startRunExample();
//        sleepExample();
//        yieldExample();
        waitExample();
        
    }

    //省略start,run
    
    //省略sleep
 
    //省略yield
    
    //wait
    public static void waitExample() {
        WaitThread waitThread = new WaitThread();
        Thread thread1 = new Thread(waitThread, "thread1");
        Thread thread2 = new Thread(waitThread, "thread2");
        thread1.start();
        thread2.start();
    }
    
}

class WaitThread extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " is running " + i);
            if (Thread.currentThread().getName().equals("thread1")) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

逻辑都差不多,只不过把Thread.yield() 换成了wait() ,正常来说是线程名为thread1的线程只要执行一次就不再执行了,让我们来看下结果

和预期结果是一样的,并且还报错java.lang.IllegalMonitorStateException

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

本文分享自 CodeNone 微信公众号,前往查看

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

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Java多线程与并发
文章目录 线程和进程的区别 JDK选择长期版本 线程和进程的区别 基础 由来 区别 关系 代码查看主线程 Thread中的start方法和run方法的区别 效果图 代码 分析源码 没有可比性run和star Thread和Runnable是什么关系 本质 源码 代码演示 普通Thread.start()线程-效果图 MyThread 通过Thread类启动Runnable多线程-效果图 MyRunnalbe MyRunnableMain 如何实现处理线程的返回值 如何给run()方法传参 实现的方式主要有三
瑞新
2022/05/11
4910
Java多线程与并发
Java 多线程学习
这些动作都可以抽象为任务,虽然看起来一心二用,但人只有一个大脑,在一个时间片刻只能处理一个任务。
默 语
2024/11/20
980
Java 多线程学习
多线程详解
假如计算机只有一个CPU,那么CPU在某一时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性的。
秋落雨微凉
2022/10/25
4440
轻松掌握Java多线程 - 第一章:多线程入门
线程是操作系统能够进行运算调度的最小单位,也是程序执行流的最小单位。简单来说,线程就是一个单独的执行路径,它可以独立执行特定的代码片段。
程序猿梦工厂
2025/03/28
2830
轻松掌握Java多线程 - 第一章:多线程入门
Java多线程详解_java支持多线程
一个线程运行中,放弃了已经获取的CPU时间片,不再参与CPU时间片的抢占,此时线程处于挂起状态
全栈程序员站长
2022/09/23
1.4K0
Java多线程详解_java支持多线程
多线程基础
例子:单核CUP执行两件事,串行执行时间快,还是多线程执行快? 答:串行执行快。因为单核,执行的总时间一样,而多线程增加了线程切换的时间。
冬天vs不冷
2025/01/20
820
多线程基础
多线程(一):创建线程和线程的常用方法
进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
全栈程序员站长
2022/08/11
1.1K0
多线程(一):创建线程和线程的常用方法
创建线程的8种方法
优点: 解耦任务逻辑和线程对象,灵活性更高。 缺点: 需要额外创建Thread对象。
用户11397231
2025/01/02
1190
创建线程的8种方法
【Java学习笔记之三十四】超详解Java多线程基础
前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧。 正文 线程与进程 1 线程:进程中负责程序执行的执行单元 线程本身依靠程序进行运行 线程是程序中的顺序控制流,只能使用分配给程序的资源和环境 2 进程:执行中的程序 一个进程至少包含一个线程 3 单线程:程序中只存在一个线程,实际上主方法就是一个主线程 4 多线程:在一个程序中运行多个任务 目的是更好地使用CPU资源 线程的实现 继承Thread类
Angel_Kitty
2018/04/09
9000
【Java学习笔记之三十四】超详解Java多线程基础
有趣的多线程和无趣的线程锁
Java 中的多线程实现较为简单,这篇文章主要讲解多线程操作中状态操作相关的代码示例,希望可以帮助你提高对多线程的理解。
代码宇宙
2023/02/23
2480
Java多线程的常见方法
计算机中的CPU,在任意时刻只能执行一条机器指令。每个线程只有获得CPU的使用权才能执行代码。各个线程轮流获得CPU的使用校,分别执行各自的任务。
乐心湖
2020/07/31
3380
Java多线程的常见方法
【原创】Java并发编程系列2:线程概念与基础操作
本篇为【Dali王的技术博客】Java并发编程系列第二篇,讲讲有关线程的那些事儿。主要内容是如下这些:
王金龙
2020/03/23
3900
【原创】Java并发编程系列2:线程概念与基础操作
《JavaSE-第二十章》之线程的创建与Thread类
​ 我们平时安装的程序都放在硬盘上,当我们在windows上双击可执行程序(exe)时才会将其运行起来。本质上就是将这个程序加载到内存中,然后CPU才会对该程序的数据和代码进行读取并逐行的执行,一旦将程加载到内存后,此时程序从静态的趟在硬盘上到动态的运行在内存中,我们就将在内存中的程序,称之为进程。
用户10517932
2023/10/07
1630
《JavaSE-第二十章》之线程的创建与Thread类
Java 多线程系列(1) —— 线程入门
Java 中线程的优先级范围为 1~10,是一个 int 类型的值 其中最小的优先级 (MIN_PRIORITY) 为 1 正常优先级 (NORM_PRIORITY) 为 5 最高优先级 (MAX_PRIORITY) 为 10 【源码中定义的线程优先级】
求和小熊猫
2020/11/25
3590
Java 多线程系列(1) —— 线程入门
Java多线程基础知识
进程:一个正在操作系统中运行的exe程序可以理解为一个进程,完全可以将运行在内存中的exe文件理解为进程-----进程就是受操作系统管理的基本运行单元。一个最简单的Java程序的运行也可以叫做一个进程。
害恶细君
2022/11/22
2440
Java多线程基础知识
Hello,Thread
Java 中创建线程的方法有三种: 1. 继承 Thread 类创建线程 新建一个类继承 Thread 类,并重写 Thread 类的 run() 方法。 创建 Thread 子类的实例。 调用该子类实例的 start() 方法启动该线程。 代码举例如下:
Java学习录
2019/04/18
5280
Hello,Thread
创建线程的 8 种方法
无论是为了提高程序运行效率,还是为了处理复杂的并发任务,我们都需要在代码中使用线程。
苏三说技术
2024/12/30
3590
创建线程的 8 种方法
java基础第十六篇之多线程
1:线程的概念 进程(任务):一个正在运行的程序 进程的调度:CPU来决定什么时候该运行哪个进程 (时间片轮流法) 线程在一个应用程序中,同时,有多个不同的执行路径,是进程中的实际运作单位。 好处是提高程序效率。
海仔
2019/08/05
2850
Java多线程与并发
进程是资源分配的基本单位,所有与进程有关的资源都记录在进程控制块PCB中,以表示进程拥有这些资源或者正在使用它们,进程也是抢占处理机的调度单位,它拥有完整的虚拟内存地址空间,当进程发生调度时,不同的进程拥有不同的地址空间,而同一进程内的不同线程共享同一地址空间。与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其它线程共享进程的资源。
ha_lydms
2023/08/10
2020
Java多线程与并发
多线程编程学习一(Java多线程的基础).
一、进程和线程的概念 进程:一次程序的执行称为一个进程,每个 进程有独立的代码和数据空间,进程间切换的开销比较大,一个进程包含1—n个线程。进程是资源分享的最小单位。 线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小,线程是CPU调度的最小单位。 多进程:指操作系统能同时运行多个任务(程序)。 多线程:指同一个程序中有多个顺序流在执行,线程是进程内部单一控制序列流。        线程和进程一样包括:创建、就绪、运行、阻塞、销毁 五个状态: 1、新建状态(New
JMCui
2018/03/16
8320
多线程编程学习一(Java多线程的基础).
相关推荐
Java多线程与并发
更多 >
领券
社区富文本编辑器全新改版!诚邀体验~
全新交互,全新视觉,新增快捷键、悬浮工具栏、高亮块等功能并同时优化现有功能,全面提升创作效率和体验
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文