前往小程序,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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
浏览器工作原理 - 安全
浏览器默认两个相同的源之间是可以相互访问资源和操作 DOM 的。两个不同源之间要相互访问或者操作 DOM,会有一套基础的安全策略制约,即同源策略。
Cellinlab
2023/05/17
5850
浏览器工作原理 - 安全
银行木马Trickbot新模块:密码抓取器分析
Trickbot曾经是一个简单的银行木马,已经走过了漫长的道路。随着时间的推移,我们已经看到网络犯罪分子如何继续为此恶意软件添加更多功能。
FB客服
2018/12/07
1.2K0
保护个人隐私的最佳实践
对于当今的许多在线服务来说,收集过多的用户数据对其商业模式至关重要。 现代网站跟踪你的一举一动 - 不仅在他们的网站上,而且在整个网络上。然后,他们根据你的兴趣创建个人资料并将该信息出售给数据经纪人,后者又将其出售给数百名在线广告商,以“创造个性化的在线体验”。实际上,他们只是想向你展示你最有可能点击的在线广告。
星尘安全
2024/11/15
1440
保护个人隐私的最佳实践
阶段七:浏览器安全
32 | 同源策略:为什么XMLHttpRequest不能跨域请求资源–Web页面安全 浏览器安全分为三大块:Web页面安全、浏览器网络安全、浏览器系统安全。 同源策略 页面中最基础、最核心的安全策略:同源策略(same-origin policy) 如果两个URL协议相同、域名相同、端口相同,就称为这两个URL同源 同源策略就是说:相同源之间可以操作DOM、读取互相之间的Cookie、indexDB、locationStorage等页面数据以及网络层面共享。 也就解释了为什么同源策略限制了X
六个周
2022/10/28
4800
在线密码管理器LastPass被大规模撞库
在线密码管理器LastPass承认,攻击者对其用户进行了大规模的撞库攻击,试图访问他们的云托管密码库。
FB客服
2022/01/05
8570
在线密码管理器LastPass被大规模撞库
黑客攻防技术宝典Web实战篇
1.针对Web应用程序的最严重攻击,是那些能够披露敏感数据或获取对运行应用程序的后端系统的无限访问权限的攻击
硬核项目经理
2019/08/06
2.3K0
前端网络安全
1、类型 ​ 1)反射型:通过网络请求参数中加入恶意脚本,解析后执行触发。 ​ 2)文档型:请求传输数据中截取网络数据包,对html代码插入再返回。 ​ 3)存储型:通过输入发送到服务端存储到数据库。 2、防范措施 ​ 1)对用户输入进行过滤或转码。 ​ 2)csp(内容安全策略)。 ​ 使CSP可用, 你需要配置你的网络服务器返回 HTTP头部
白黎
2023/03/09
9050
PHP 安全问题入门:10 个常见安全问题 + 实例讲解
相对于其他几种语言来说, PHP 在 web 建站方面有更大的优势,即使是新手,也能很容易搭建一个网站出来。但这种优势也容易带来一些负面影响,因为很多的 PHP 教程没有涉及到安全方面的知识。
全栈程序员站长
2022/07/11
8480
PHP 安全问题入门:10 个常见安全问题 + 实例讲解
NBA已承认!敏感数据泄露,警告球迷安全风险
据bleeping computer 消息,美国国家篮球协会(NBA)公开承认,其在第三方提供商的部分球迷敏感数据已被泄露,提醒广大球迷防范可能发生的网络钓鱼攻击或诈骗。 NBA是一家全球体育和媒体组织,管理着五个职业体育联盟,包括NBA、WNBA、篮球非洲联盟、NBA G联盟和NBA 2K联盟。众所周知,NBA在全球有着极为广泛的影响力,其节目和比赛在215个国家/地区,以50 多种语言进行直播/转播。 正因为如此,NBA拥有数量庞大的粉丝群体。在此次数据泄露事件中,NBA尚未公布泄露的数据量和涉及影
FB客服
2023/03/29
1K0
NBA已承认!敏感数据泄露,警告球迷安全风险
Web Security 之 CSRF
在本节中,我们将解释什么是跨站请求伪造,并描述一些常见的 CSRF 漏洞示例,同时说明如何防御 CSRF 攻击。
凌虚
2021/03/19
2.3K0
Web Security 之 CSRF
逆天了,你知道什么是CSRF 攻击吗?如何防范?
跨站点请求伪造 (CSRF) 攻击允许攻击者伪造请求并将其作为登录用户提交到 Web 应用程序,CSRF 利用 HTML 元素通过请求发送环境凭据(如 cookie)这一事实,甚至是跨域的。
网络技术联盟站
2023/03/13
2K0
逆天了,你知道什么是CSRF 攻击吗?如何防范?
声明
本站在互联网上的地址是:https://blog.zhuxu.xyz/,为个人性质的非盈利博客,博主的其他网站不适用于本政策。本站的服务器与内容分发网络均位于中国大陆。访客在本站留下的所有痕迹均按照此政策执行。本站认为隐私权是一项人类必要的基本权利,本站充分关心您的隐私问题,为响应现行网络隐私法律法规,现将本站的隐私政策公布如下:
迷路的朱朱
2023/05/04
4580
AppScan扫描的测试报告结果,你有仔细分析过吗
通过 IIS 6 引入的 HTTP 协议堆栈 (HTTP.sys) 错误地解析了特制的 HTTP 请求。因此,远程攻击者可能执行拒绝服务供给,并可在系统帐户的环境中执行任意代码。该漏洞会影响 Windows 7、Windows Server 2008R2、Windows 8、Windows Server 2012、Windows 8.1 和 Windows Server 2012 R2 上安装的 IIS。Microsoft 发布了通过修改 Windows HTTP 堆栈处理请求的方式来解决漏洞的更新。
软件测试君
2020/01/13
9.4K0
AppScan扫描的测试报告结果,你有仔细分析过吗
还在让浏览器自动保存密码?“自动填充”功能曝重大安全隐患
《还在让浏览器自动保存密码?“自动填充”功能曝重大安全隐患》这篇文章指出,互联网广告公司或数据分析公司可以使用隐藏的登录字段从网页浏览器中提取用户保存的用于登录某些网站的登录信息,用户的个人资料或者电子邮箱地址可能在未经许可的情况下被滥用。这种滥用行为是可能的,因为几乎目前所有的主流网页浏览器中包含的登录管理器都存在设计缺陷。它们都提供了一项“便捷”的功能,允许用户保存某些网站的登录用户名和密码,并在下次访问这些网站时“自动填充”。这项功能由浏览器中的登录管理器完成。而根据专家的说法,网络追踪者可以在加载追踪脚本的网站上嵌入隐藏的登录表单,以此来窃取用户的个人信息。此外,研究人员还发现了两种利用隐藏登录表单收集用户登录信息的网络跟踪服务:Adthink和OnAudience。它们被发现用于收集Alexa Top 100万网站列表中1110个网站的用户登录信息,但庆幸的是,被收集的信息只包括用户名和电子邮箱地址,并不包括密码以及其他敏感信息。因此,用户应该在日常使用互联网的过程中加强安全意识,尤其是涉及到登录某些金融或银行网站的时候,尽量减少使用网页浏览器提供的“自动填充”功能。
企鹅号小编
2018/01/08
9720
还在让浏览器自动保存密码?“自动填充”功能曝重大安全隐患
凭据为王,如何看待凭据泄露?
信息窃取型恶意软件是企业信息安全团队面临的最重大且常被低估的风险因素之一。这类软件侵入计算机后,会盗取浏览器中储存的所有登录凭证、活跃会话的cookies及其他数据,接着将窃取到的信息发送到远程指挥控制(C2)服务器,并且在某些情况下,恶意软件还会为了消除痕迹而自动销毁。
FB客服
2024/01/20
2500
凭据为王,如何看待凭据泄露?
Web开发安全
XSS 攻击通常指的是通过利用网页开发时留下的漏洞,通过巧妙的方法注入恶意指令代码到网页,使用户加载并执行攻击者恶意制造的网页程序。
赤蓝紫
2023/01/02
9360
Web开发安全
你的手机广告被偷了!通过重定向广告窃取个人隐私,攻击者还能进行恶意广告攻击
大数据文摘出品 作者:Caleb 不得不感叹,个人信息真是越来越不值钱了。 充电插口、充电宝、公共WiFi都有可能让手机成为被攻击的对象。 只是没想到如今连手机广告也“惨遭毒手”。 是的,你没看错,就是这些每次刷手机的时候时不时碰出来恶心你的那些广告。 最近,佐治亚理工学院、伊利诺伊大学和纽约大学的研究人员发表了一份研究报告,该报告指出,攻击者能通过欺骗第三方广告网络,实现通过用户浏览器浏览的网站上的广告来窃取个人隐私信息的目的。 除此之外,黑客不仅可以窃取用户的广告,还可以在广告空间显示恶意广告。
大数据文摘
2023/04/10
4880
你的手机广告被偷了!通过重定向广告窃取个人隐私,攻击者还能进行恶意广告攻击
细思极恐,第三方跟踪器正在获取你的数据,如何防范?
当下,许多网站都存在一些Web表单,比如登录、注册、评论等操作需要表单。我们都知道,我们在冲浪时在网站上键入的数据会被第三方跟踪器收集。但是,你知道吗?第三方跟踪器甚至可在提交表单之前就获取你的数据。
Regan Yue
2023/03/30
1.3K0
细思极恐,第三方跟踪器正在获取你的数据,如何防范?
浏览器安全(上)
对于浏览器用户来说,访问网络资源只需要一台个人终端,终端有可运行浏览器的操作系统、浏览器应用、连通互联网,互联网连接可用的服务,这便是整体运行环境,其中任何环节被攻击都有可能带来安全问题,根据上诉描述,从微观到宏观、从局部到整体来对安全分类
醉酒鞭名马
2020/06/08
2.1K1
浏览器安全(上)
浏览器原理学习笔记07—浏览器安全
协议、域名 和 端口 都相同的两个 URL 同源,默认可以相互访问资源和操作 DOM,两个不同源之间通过安全策略制约隔离 DOM、页面数据和网络通信来保障隐私和数据安全。
CS逍遥剑仙
2020/05/02
1.7K0
推荐阅读
相关推荐
浏览器工作原理 - 安全
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
查看详情【社区公告】 技术创作特训营有奖征文