当前位置: 首页 > 编程日记 > 正文

Java并发编程有多难?这几个核心技术你掌握了吗?

本文主要内容索引

1、Java线程

2、线程模型

3、Java线程池

4、Future(各种Future)

5、Fork/Join框架

6、volatile

7、CAS(原子操作)

8、AQS(并发同步框架)

9、synchronized(同步锁)

10、并发队列(阻塞队列)

本文仅分析java并发编程中的若干核心问题,对于上面没有提到但是又和java并发编程有密切关系的技术将会不断添加进来完善文章,本文将长期更新,不断迭代。本文试图从一个更高的视觉来总结Java语言中的并发编程内容,希望阅读完本文之后,可以收获一些内容,至少应该知道在java中做并发编程实践的时候应该注意什么,应该关注什么,如何保证线程安全,以及如何选择合适的工具来满足需求。当然,更深层次的内容就会涉及到jvm层面的知识,包括底层对java内存的管理,对线程的管理等较为核心的问题,当然,本文的定位在于抽象与总结,更为具体而深入的内容就需要自己去实践,考虑到可能篇幅过长、重复描述某些内容,以及自身技术深度等原因,本文将在深度和广度上做一些权衡,某些内容会做一些深入的分析,而有些内容会一带而过,点到为止,总之,本文就当是对学习java并发编程内容的一个总结,以及给哪些希望快速了解java并发编程内容的读者抛砖引玉,不足之处还望指正。

一、Java线程

一般来说,在java中实现高并发是基于多线程编程的,所谓并发,也就是多个线程同时工作,来处理我们的业务,在机器普遍多核心的今天,并发编程的意义极为重大,因为我们有多个cpu供线程使用,如果我们的应用依然只使用单线程模式来工作的话,对极度浪费机器资源的。所以,学习java并发知识的首要问题是:如何创建一个线程,并且让这个线程做一些事情?这是java并发编程内容的起点,下面将分别介绍多个创建线程,并且让线程做一些事情的方法。

继承Thread类

继承Thread类,然后重写run方法,这是第一种创建线程的方法。run方法里面就是我们要做的事情,可以在run方法里面写我们想要在新的线程里面运行的任务,下面是一个小例子,我们继承了Thread类,并且在run方法里面打印出了当然线程的名字,然后sleep1秒中之后就退出了:

/*** Created by hujian06 on 2017/10/31.** the demo of thread*/public class ThreadDemo { public static void main(String ... args) { AThread aThread = new AThread(); //start the thread aThread.start(); }}class AThread extends Thread { @Override public void run() { System.out.println("Current Thread Name:" + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }}

如果我们想要启动这个线程,只需要像上面代码中那样,调用Thread类的start方法就可以了。

实现Runnable接口

启动一个线程的第二种方法是实现Runnable接口,然后实现其run方法,将你想要在新线程里面执行的业务代码写在run方法里面,下面的例子展示了这种方法启动线程的示例,实现的功能和上面的第一种示例是一样的:

/*** Created by hujian06 on 2017/10/31.** the demo of Runnable*/public class ARunnableaDemo { public static void main(String ... args) { ARunnanle aRunnanle = new ARunnanle(); Thread thread = new Thread(aRunnanle); thread.start(); }}class ARunnanle implements Runnable { @Override public void run() { System.out.println("Current Thread Name:" + Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }}

在启动线程的时候,依然还是使用了Thread这个类,只是我们在构造函数中将我们实现的Runnable对象传递进去了,所以在我们执行Thread类的start方法的时候,实际执行的内容是我们的Runnable的run方法。

使用FutureTask

启动一个新的线程的第三种方法是使用FutureTask,下面来看一下FutureTask的类图,就可以明白为什么可以使用FutureTask来启动一个新的线程了:

Java并发编程有多难?这几个核心技术你掌握了吗?

Java并发编程有多难?这几个核心技术你掌握了吗?

Java并发编程有多难?这几个核心技术你掌握了吗?

Java并发编程有多难?这几个核心技术你掌握了吗?

Java并发编程有多难?这几个核心技术你掌握了吗?

Java并发编程有多难?这几个核心技术你掌握了吗?

FutureTask的类图

从FutureTask的类图中可以看出,FutureTask实现了Runnable接口和Future接口,所以它兼备Runnable和Future两种特性,下面先来看看如何使用FutureTask来启动一个新的线程:

import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;/*** Created by hujian06 on 2017/10/31.** the demo of FutureTask*/public class FutureTaskDemo { public static void main(String ... args) { ACallAble callAble = new ACallAble(); FutureTask<String> futureTask = new FutureTask<>(callAble); Thread thread = new Thread(futureTask); thread.start(); do { }while (!futureTask.isDone()); try { String result = futureTask.get(); System.out.println("Result:" + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }}class ACallAble implements Callable<String> { @Override public String call() throws Exception { Thread.sleep(1000); return "Thread-Name:" + Thread.currentThread().getName(); }}

可以看到,使用FutureTask来启动一个线程之后,我们可以监控这个线程是否完成,上面的示例中主线程会一直等待这个新创建的线程直到它返回,其实只要是Future提供的接口,我们在FutureTask中都可以使用,这极大的方便了我们,Future在并发编程中的意义极为重要,Future代表一个未来会发生的东西,它是一种暗示,一种占位符,它示意我们它可能不会立即得到结果,因为它的任务还在运行,但是我们可以得到一个对这个线程的监控对象,我们可以对线程的执行做一些判断,甚至是控制,比如,如果我们觉得我们等了太久,并且我们觉得没有必要再等待下去的时候,就可以将这个Task取消,还有一点需要提到的是,Future代表它可能正在运行,也可能已经返回,当然Future更多的暗示你可以在等待这个结果的同时可以使用其他的线程做一些其他的事情,当你真的需要这个结果的时候再来获取就可以了,这就是并发,理解这一点非常重要。

本小节通过介绍三种创建并启动一个新线程的方法,为进行并发编程开了一个头,目前,我们还只是在能创建多个线程,然后让多个线程做不同个的事情的阶段,当然,这是学习并发编程最为基础的,无论如何,现在,我们可以让我们的应用运行多个线程了,下面的文章将会基于这个假设(一个应用开启了多个线程)讨论一些并发编程中值得关注的内容。关于本小节更为详细的内容,可以参考文章Java CompletableFuture中的部分内容。

二、线程模型

我们现在可以启动多个线程,但是好像并没有形成一种类似于模型的东西,非常混乱,并且到目前为止我们的多个线程依然只是各自做各自的事情,互不相干,多个线程之间并没有交互(通信),这是最简单的模型,也是最基础的模型,本小节试图介绍线程模型,一种指导我们的代码组织的思想,线程模型确定了我们需要处理那些多线程的问题,在一个系统中,多个线程之间没有通信是不太可能的,更为一般的情况是,多个线程共享一些资源,然后相互竞争来获取资源权限,多个线程相互配合,来提高系统的处理能力。正因为多个线程之间会有通信交互,所以本文接下来的讨论才有了意义,如果我们的系统里面有几百个线程在工作,但是这些线程互不相干,那么这样的系统要么实现的功能非常单一,要么毫无意义(当然不是绝对的,比如Netty的线程模型)。

继续来讨论线程模型,上面说到线程模型是一种指导代码组织的思想,这是我自己的理解,不同的线程模型需要我们使用不同的代码组织,好的线程模型可以提高系统的并发度,并且可以使得系统的复杂度降低,这里需要提一下Netty 4的线程模型,Netty 4的线程模型使得我们可以很容易的理解Netty的事件处理机制,这种优秀的设计基于Reactor线程模型,Reactor线程模型分为单线程模型、多线程模型以及主从多线程模型,Netty的线程模型类似于Reactor主从多线程模型。

当然线程模型是一种更高级别的并发编程内容,它是一种编程指导思想,尤其在我们进行底层框架设计的时候特别需要注意线程模型,因为一旦线程模型设计不合理,可能会导致后面框架代码过于复杂,并且可能因为线程同步等问题造成问题不可控,最终导致系统运行失控。类似于Netty的线程模型是一种好的线程模型,下面展示了这种模型:

Netty线程模型

简单来说,Netty为每个新建立的Channel分配一个NioEventLoop,而每个NioEventLoop内部仅使用一个线程,这就避免了多线程并发的同步问题,因为为每个Channel处理的线程仅有一个,所以不需要使用锁等线程同步手段来做线程同步,在我们的系统设计的时候应该借鉴这种线程模型的设计思路,可以避免我们走很多弯路。关于线程池以及Netty线程池这部分的内容,可以参考文章Netty线程模型及EventLoop详解。Java线程池池化技术是一种非常有用的技术,对于线程来说,创建一个线程的代价是很高的,如果我们在创建了一个线程,并且让这个线程做一个任务之后就回收的话,那么下次要使用线程来执行我们的任务的时候又需要创建一个新的线程,是否可以在创建完成一个线程之后一直缓冲,直到系统关闭的时候再进行回收呢?java线程池就是这样的组件,使用线程池,就没必要频繁创建线程,线程池会为我们管理线程,当我们需要一个新的线程来执行我们的任务的时候,就向线程池申请,而线程池会从池子里面找到一个空闲的线程返回给请求者,如果池子里面没有可用的线程,那么线程池会根据一些参数指标来创建一个新的线程,或者将我们的任务提交到任务队列中去,等待一个空闲的线程来执行这个任务。细节内容在下文中进行分析,目前我们只需要明白,线程池里面有很多线程,这些线程会一直到系统关系才会被回收,否则一直会处于处理任务或者等待处理任务的状态。首先,如何使用线程池呢?下面的代码展示了如何使用java线程池的例子:

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.ThreadFactory;import java.util.concurrent.TimeUnit;import java.util.concurrent.atomic.AtomicInteger;/*** Created by hujian06 on 2017/10/31.** the demo of Executors*/public class ExecutorsDemo { public static void main(String ... args) { int cpuCoreCount = Runtime.getRuntime().availableProcessors(); AThreadFactory threadFactory = new AThreadFactory(); ARunnanle runnanle = new ARunnanle(); ExecutorService fixedThreadPool= Executors.newFixedThreadPool(cpuCoreCount, threadFactory); ExecutorService cachedThreadPool = Executors.newCachedThreadPool(threadFactory); ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(cpuCoreCount, threadFactory); ScheduledExecutorService singleThreadExecutor = Executors.newSingleThreadScheduledExecutor(threadFactory); fixedThreadPool.submit(runnanle); cachedThreadPool.submit(runnanle); newScheduledThreadPool.scheduleAtFixedRate(runnanle, 0, 1, TimeUnit.SECONDS); singleThreadExecutor.scheduleWithFixedDelay(runnanle, 0, 100, TimeUnit.MILLISECONDS); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } fixedThreadPool.shutdownNow(); cachedThreadPool.shutdownNow(); newScheduledThreadPool.shutdownNow(); singleThreadExecutor.shutdownNow(); }}class ARunnable implements Runnable { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Current Thread Name:" + Thread.currentThread().getName()); }}/*** the thread factory*/class AThreadFactory implements ThreadFactory { private final AtomicInteger threadNumber = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { return new Thread("aThread-" + threadNumber.incrementAndGet()); }}

更为丰富的应用应该自己去探索,结合自身的需求来借助线程池来实现,下面来分析一下Java线程池实现中几个较为重要的内容。

ThreadPoolExecutor和ScheduledThreadPoolExecutor

ThreadPoolExecutor和ScheduledThreadPoolExecutor是java实现线程池的核心类,不同类型的线程池其实就是在使用不同的构造函数,以及不同的参数来构造出ThreadPoolExecutor或者ScheduledThreadPoolExecutor,所以,学习java线程池的重点也在于学习这两个核心类。前者适用于构造一般的线程池,而后者继承了前者,并且很多内容是通用的,但是ScheduledThreadPoolExecutor增加了schedule功能,也就是说,ScheduledThreadPoolExecutor使用于构造具有调度功能的线程池,在需要周期性调度执行的场景下就可以使用ScheduledThreadPoolExecutor。关于ThreadPoolExecutor与ScheduledThreadPoolExecutor较为详细深入的分析可以参考下面的文章:

Java线程池详解(二)

Java线程池详解(一)

Java调度线程池ScheduleExecutorService

Java调度线程池ScheduleExecutorService(续)

相关文章:

这届 AI 预测欧洲杯冠军,通通被打脸

持续了一个月的欧洲杯&#xff0c;终于落下帷幕。北京时间 7 月 12 日&#xff08;周一&#xff09;凌晨&#xff0c;本届欧洲杯决赛中&#xff0c;意大利对阵英格兰。两队在 120 分钟时间里 1-1 战平&#xff0c;意大利在欧洲杯中通过点球大战以 3:2击败英格兰夺冠。意大利上次…

资源的正确引用

对资源的引用应该发生在对资源的保护期间。 比如在所保护内hold住资源、local_bh_disable内hold住资源&#xff1b; 否则对资源的使用可能发生不一致的情况。 PS&#xff1a; 代码逻辑应该符合真实世界的合理逻辑。转载于:https://www.cnblogs.com/kernel521/p/4045976.html

给网站管理员的建议:创建可利用的、可抓取的网站

转载自 谷歌中文网站管理员博客 发表者 T.V. Raman&#xff0c;研究学者 原文&#xff1a; Webmaster tips for creating accessible, crawlable sites 发表于&#xff1a;2008年4月14日 上午10:47 Hubbell和我正在我们位于加州的家中度假。欢迎您随时 阅读在此之前我为网站管…

iptables如何开放被动模式的FTP服务

如何开放被动模式的FTP服务? 1.装载FTP追踪时的专用的模块; # modprobe nf_conntrack_ftp # lsmod | grep ftp 2.放行请求报文 命令连接&#xff1a; NEW&#xff0c;ESTABLISHED 数据连接&#xff1a; RELATED&#xff0c;ESTABLISHED #iptables -I INPUT -d 192.168.141.10 …

KNN 分类算法原理代码解析

作者 | Charmve来源 | 迈微AI研习社k-最近邻算法是基于实例的学习方法中最基本的&#xff0c;先介绍基x于实例学习的相关概念。基于实例的学习已知一系列的训练样例&#xff0c;很多学习方法为目标函数建立起明确的一般化描述&#xff1b;但与此不同&#xff0c;基于实例的学习…

Roadsend PHP-开源的PHP代码编译器

Roadsend PHP 是一个开源的php compiler, 可以将你的PHP代码编译成原生的二进制代码, 无需分发php源码. Roadsend 可以将你的PHP web项目编译成FastCGI的可执行文件,这样apache,nginx可以通过fastcgi方式和编译后的 程序进行通讯. 看起来,PHP 编写的程序可以和C编写的程序有同…

Android安卓开发中图片缩放讲解

安卓开发中应用到图片的处理时候&#xff0c;我们通常会怎么缩放操作呢&#xff0c;来看下面的两种做法&#xff1a; 方法1&#xff1a;按固定比例进行缩放 在开发一些软件&#xff0c;如新闻客户端&#xff0c;很多时候要显示图片的缩略图&#xff0c;由于手机屏幕限制&#x…

a标签是什么意思 怎么使用?

转自&#xff1a;https://www.imooc.com/qadetail/190881 (1) a标签的作用&#xff1a;超链接&#xff0c;用于跳转到别的网页。 (2) a标签的用法&#xff1a;<a href"网址" target"_blank" >到百度首页</a> 其中target是a标签的一个属性&…

【Java】Lucene检索引擎详解

基于Java的全文索引/检索引擎——Lucene Lucene不是一个完整的全文索引应用&#xff0c;而是是一个用Java写的全文索引引擎工具包&#xff0c;它可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能。 Lucene的作者&#xff1a;Lucene的贡献者Doug Cutting是一位资深全…

大手笔 !Julia Computing 获 2400 万美元融资,前 Snowflake CEO 加入董事会

整理 | 梦依丹出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;2021 年 7 月 19 日&#xff0c;由 Julia 高性能编程语言创始人成立的 Julia Computing 公司完成了 2400 万美元的 A 轮融资&#xff0c;这笔融资由 Dorilton Ventures 领投&#xff0c;Menlo Ventures…

(Mirage系列之六)在Mirage里使用Collection

在Mirage中&#xff0c;Collection是包含一个或多个CVD的集合。 Collection的主要作用是简化操作。比如我有一百个终端设备需要分配基础层&#xff0c;如果没有Collection&#xff0c;那么管理员需要逐个点击。这是无趣的重复劳动。有了Collection&#xff0c;只要选择一个Coll…

WAS服务器负载测试软件导读

转帖&#xff1a;出处未知 你的Web服务器和应用到底能够支持多少并发用户访问&#xff1f;在出现大量并发请求的情况下&#xff0c;软件会出现问题吗&#xff1f;这些问题靠通常的测试手段是无法解答的。本文介绍了Microsoft为这个目的而提供的免费工具WAS及其用法。另外&#…

深度学习实现场景字符识别模型|代码干货

作者|李秋键出品|AI科技大本营(ID:rgznai100)# 前言 #文字是人从日常交流中语音中演化出来&#xff0c;用来记录信息的重要工具。文字对于人类意义非凡&#xff0c;以中国为例&#xff0c;中国地大物博&#xff0c;各个地方的口音都不统一&#xff0c;但是人们使用同一套书写体…

SQL Server 一些重要视图3

1、 sys.dm_tran_locks; 为每一把锁返回一行、request_session_id 可以与sys.dm_tran_session_transactions \sys.dm_exec_connections相关联。 request_status 查看锁的分配情况 2、 sys.dm_os_waiting_tasks; 为每一个等待的任务返回一行、blocking_session_id标记是因为谁而…

Linux下将Mysql和Apache加入到系统服务里的方法

Apache加入到系统服务里面: cp /安装目录下/apache/bin/apachectl /etc/rc.d/init.d/httpd 修改httpd 在文件头部加入如下内容&#xff1a; ### # Comments to support chkconfig on RedHat Linux # chkconfig: 2345 90 90 # description:http server ### 保存 在打入 #chkconf…

int、bigint、smallint 和 tinyint

int、bigint、smallint 和 tinyint使用整数数据的精确数字数据类型。 bigint 从 -2^63 (-9223372036854775808) 到 2^63-1 (9223372036854775807) 的整型数据&#xff08;所有数字&#xff09;。存储大小为 8 个字节。 int 从 -2^31 (-2,147,483,648) 到 2^31 - 1 (2,147,483,6…

阿里技术文档:Redis+Spring全家桶+Dubbo精选+高性能+高并发

最近花了很长的时间去搜罗整理Java核心技术好文&#xff0c;我把每个Java核心技术的优选文章都整理成了一个又一个的文档。今天就把这些东西分享给老铁们&#xff0c;也能为老铁们省去不少麻烦&#xff0c;想学什么技能了&#xff0c;遇到哪方面的问题了 直接打开文档学一学就好…

SDN:软件定义网络

近期高级网络课的小组任务是在老师给定的范围内自选方向主题研究并做展示报告。我们组选了sdn。原以为这东西会是工业界无人问津的概念化产品&#xff0c;Google了一下却发现事实上sdn挺火的&#xff0c;因为它可能带来的可扩展性&#xff0c;一些大互联网企业也在開始涉足相关…

Linux之文本处理

1 cut&#xff1a;按某种方式对文件进行分割然后输出 选项&#xff1a;-b 按字节选取 -d 自定义分隔符 -f 和-d一起使用&#xff0c;指定哪个区域或字段 [rootlocalhost ~]# cat /etc/passwd | cut -d : -f 1 #以&#xff1a;为分隔符&#xff0c;打印第一个字段 […

Yahoo javascript 开源界面库YUI 和EXT

清清月儿整理 [yui][译]Yahoo!User Interface Libray 介绍 Yahoo! User Interface Library(简称yui) 是一个使用JavaScript编写的工具和控件库。它利用DOM脚本,DHTML和AJAX来构造具有丰富交互功能的Web程序。yui也包含几个核心的CSS文件。yui中的所有组件已经以开源的形式发布…

芯片开发语言:Verilog 在左,Chisel 在右

来源 | 老石谈芯在最近召开的RISC-V中国峰会上&#xff0c;中科院计算所的包云岗研究员团队正式发布了名为“香山”的开源高性能处RISC-V处理器。前不久我有幸和包老师就这个事情做了一次深度的交流&#xff0c;我们聊了关于RISC-V、还有“香山”处理器的前世今生。包老师也分享…

1.1GTK+ 的简单程序HelloWorld

1.1GTK 的简单程序HelloWorld 编译执行如图所看到的&#xff1a;

struts2 validate验证

转自&#xff1a;https://blog.csdn.net/houpengfei111/article/details/9038233 自定义拦截器 要自定义拦截器需要实现com.opensymphony.xwork2.interceptor.Interceptor接口&#xff1a; [java] view plaincopypublic class PermissionInterceptor implements Interceptor { …

PHP中文乱码

页面顶端加 <?php header("content-Type: text/html; charsetgbk"); ?>

收藏 | 提高数据处理效率的 Pandas 函数方法

作者&#xff1a;俊欣来源&#xff1a;关于数据分析与可视化前言大家好&#xff0c;这里是俊欣&#xff0c;今天和大家来分享几个Pandas方法可以有效地帮助我们在数据分析与数据清洗过程当中提高效率&#xff0c;加快工作的进程&#xff0c;希望大家看了之后会有收获。首先导入…

如何让两个div在同一行显示?一个float搞定

最近在学习div和css&#xff0c;遇到了一些问题也解决了很多以前以为很难搞定的问题。比如&#xff1a;如何让两个div显示在同一行呢&#xff1f;&#xff08;不是用table表格&#xff0c;table对SE不太友好&#xff09;其实&#xff0c;<div> 是一个块级元素&#xff0c…

使用sudo进入root权限,以及防止root密码被恶意篡改

一、前言 sudo是允许系统管理员让普通用户执行一些或者全部的root命令的一个工具&#xff0c;减少了root用户的登陆和管理时间&#xff0c;提高了安全性。Sudo不是对shell的一个代替&#xff0c;它是面向每个命令的。 而防止root用户密码被恶意更改&#xff0c;主要是用户基本上…

赠书 | 干货!用 Python 动手学强化学习

01了解强化学习新闻报道中很少将强化学 习与机器学习、深度学习、人工智能这些关键词区分开来&#xff0c;所以我们要先介绍什么是强化学习&#xff0c;再讲解其基本机制。强化学习与机器学习、人工智能这些关键词之间的关系&#xff1b;强化学习相对于其他机器学习方法的优点和…

php如何调用c接口无错版

1.首先是要安装好PHP 2.进入PHP的下载解压目录下的ext目录 #cd /root/php-5.3.6/ext #./ext_skel --extnamehmc 说明&#xff1a; ./ext_skel --extnamemodule_name module_name是你自己可以选择的扩展模块的名字&#xff0c;例如我选择的hmc。执行工具后会自动在ext目录下建…

onchange事件只生效一次的问题

今天遇到一个file表单元素&#xff0c;对onchange事件的响应问题&#xff0c;发现仅响应一次&#xff0c;网上查到解决方法&#xff0c;在这里转载一下文件选择框的onchange事件只在第一次改变时生效&#xff0c;以后再选择文件不会触发onchange事件。解决方法1&#xff1a;用j…