并发编程解惑之线程

并发编程解惑之线程,第1张

主要内容:

进程是资源分配的最小单位,每个进程都有独立的代码和数据空间,一个进程包含 1 到 n 个线程。线程是 CPU 调度的最小单位,每个线程有独立的运行栈和程序计数器,线程切换开销小。

Java 程序总是从主类的 main 方法开始执行,main 方法就是 Java 程序默认的主线程,而在 main 方法中再创建的线程就是其他线程。在 Java 中,每次程序启动至少启动 2 个线程。一个是 main 线程,一个是垃圾收集线程。每次使用 Java 命令启动一个 Java 程序,就相当于启动一个 JVM 实例,而每个 JVM 实例就是在 *** 作系统中启动的一个进程。

多线程可以通过继承或实现接口的方式创建。

Thread 类是 JDK 中定义的用于控制线程对象的类,该类中封装了线程执行体 run() 方法。需要强调的一点是,线程执行先后与创建顺序无关。

通过 Runnable 方式创建线程相比通过继承 Thread 类创建线程的优势是避免了单继承的局限性。若一个 boy 类继承了 person 类,boy 类就无法通过继承 Thread 类的方式来实现多线程。

使用 Runnable 接口创建线程的过程:先是创建对象实例 MyRunnable,然后将对象 My Runnable 作为 Thread 构造方法的入参,来构造出线程。对于 new Thread(Runnable target) 创建的使用同一入参目标对象的线程,可以共享该入参目标对象 MyRunnable 的成员变量和方法,但 run() 方法中的局部变量相互独立,互不干扰。

上面代码是 new 了三个不同的 My Runnable 对象,如果只想使用同一个对象,可以只 new 一个 MyRunnable 对象给三个 new Thread 使用。

实现 Runnable 接口比继承 Thread 类所具有的优势:

线程有新建、可运行、阻塞、等待、定时等待、死亡 6 种状态。一个具有生命的线程,总是处于这 6 种状态之一。 每个线程可以独立于其他线程运行,也可和其他线程协同运行。线程被创建后,调用 start() 方法启动线程,该线程便从新建态进入就绪状态。

NEW 状态(新建状态) 实例化一个线程之后,并且这个线程没有开始执行,这个时候的状态就是 NEW 状态:

RUNNABLE 状态(就绪状态):

阻塞状态有 3 种:

如果一个线程调用了一个对象的 wait 方法, 那么这个线程就会处于等待状态(waiting 状态)直到另外一个线程调用这个对象的 notify 或者 notifyAll 方法后才会解除这个状态。

run() 里的代码执行完毕后,线程进入终结状态(TERMINATED 状态)。

线程状态有 6 种:新建、可运行、阻塞、等待、定时等待、死亡。

我们看下 join 方法的使用:

运行结果:

我们来看下 yield 方法的使用:

运行结果:

线程与线程之间是无法直接通信的,A 线程无法直接通知 B 线程,Java 中线程之间交换信息是通过共享的内存来实现的,控制共享资源的读写的访问,使得多个线程轮流执行对共享数据的 *** 作,线程之间通信是通过对共享资源上锁或释放锁来实现的。线程排队轮流执行共享资源,这称为线程的同步。

Java 提供了很多同步 *** 作(也就是线程间的通信方式),同步可使用 synchronized 关键字、Object 类的 wait/notifyAll 方法、ReentrantLock 锁、无锁同步 CAS 等方式来实现。

ReentrantLock 是 JDK 内置的一个锁对象,用于线程同步(线程通信),需要用户手动释放锁。

运行结果:

这表明同一时间段只能有 1 个线程执行 work 方法,因为 work 方法里的代码需要获取到锁才能执行,这就实现了多个线程间的通信,线程 0 获取锁,先执行,线程 1 等待,线程 0 释放锁,线程 1 继续执行。

synchronized 是一种语法级别的同步方式,称为内置锁。该锁会在代码执行完毕后由 JVM 释放。

输出结果跟 ReentrantLock 一样。

Java 中的 Object 类默认是所有类的父类,该类拥有 wait、 notify、notifyAll 方法,其他对象会自动继承 Object 类,可调用 Object 类的这些方法实现线程间的通信。

除了可以通过锁的方式来实现通信,还可通过无锁的方式来实现,无锁同 CAS(Compare-and-Swap,比较和交换)的实现,需要有 3 个 *** 作数:内存地址 V,旧的预期值 A,即将要更新的目标值 B,当且仅当内存地址 V 的值与预期值 A 相等时,将内存地址 V 的值修改为目标值 B,否则就什么都不做。

我们通过计算器的案例来演示无锁同步 CAS 的实现方式,非线程安全的计数方式如下:

线程安全的计数方式如下:

运行结果:

线程安全累加的结果才是正确的,非线程安全会出现少计算值的情况。JDK 15 开始,并发包里提供了原子 *** 作的类,AtomicBoolean 用原子方式更新的 boolean 值,AtomicInteger 用原子方式更新 int 值,AtomicLong 用原子方式更新 long 值。 AtomicInteger 和 AtomicLong 还提供了用原子方式将当前值自增 1 或自减 1 的方法,在多线程程序中,诸如 ++i 或 i++ 等运算不具有原子性,是不安全的线程 *** 作之一。 通常我们使用 synchronized 将该 *** 作变成一个原子 *** 作,但 JVM 为此种 *** 作提供了原子 *** 作的同步类 Atomic,使用 AtomicInteger 做自增运算的性能是 ReentantLock 的好几倍。

上面我们都是使用底层的方式实现线程间的通信的,但在实际的开发中,我们应该尽量远离底层结构,使用封装好的 API,例如 JUC 包(javautilconcurrent,又称并发包)下的工具类 CountDownLath、CyclicBarrier、Semaphore,来实现线程通信,协调线程执行。

CountDownLatch 能够实现线程之间的等待,CountDownLatch 用于某一个线程等待若干个其他线程执行完任务之后,它才开始执行。

CountDownLatch 类只提供了一个构造器:

CountDownLatch 类中常用的 3 个方法:

运行结果:

CyclicBarrier 字面意思循环栅栏,通过它可以让一组线程等待至某个状态之后再全部同时执行。当所有等待线程都被释放以后,CyclicBarrier 可以被重复使用,所以有循环之意。

相比 CountDownLatch,CyclicBarrier 可以被循环使用,而且如果遇到线程中断等情况时,可以利用 reset() 方法,重置计数器,CyclicBarrier 会比 CountDownLatch 更加灵活。

CyclicBarrier 提供 2 个构造器:

上面的方法中,参数 parties 指让多少个线程或者任务等待至 barrier 状态;参数 barrierAction 为当这些线程都达到 barrier 状态时会执行的内容。

CyclicBarrier 中最重要的方法 await 方法,它有 2 个重载版本。下面方法用来挂起当前线程,直至所有线程都到达 barrier 状态再同时执行后续任务。

而下面的方法则是让这些线程等待至一定的时间,如果还有线程没有到达 barrier 状态就直接让到达 barrier 的线程执行任务。

运行结果:

CyclicBarrier 用于一组线程互相等待至某个状态,然后这一组线程再同时执行,CountDownLatch 是不能重用的,而 CyclicBarrier 可以重用。

Semaphore 类是一个计数信号量,它可以设定一个阈值,多个线程竞争获取许可信号,执行完任务后归还,超过阈值后,线程申请许可信号时将会被阻塞。Semaphore 可以用来 构建对象池,资源池,比如数据库连接池。

假如在服务器上运行着若干个客户端请求的线程。这些线程需要连接到同一数据库,但任一时刻只能获得一定数目的数据库连接。要怎样才能够有效地将这些固定数目的数据库连接分配给大量的线程呢?

给方法加同步锁,保证同一时刻只能有一个线程去调用此方法,其他所有线程排队等待,但若有 10 个数据库连接,也只有一个能被使用,效率太低。另外一种方法,使用信号量,让信号量许可与数据库可用连接数为相同数量,10 个数据库连接都能被使用,大大提高性能。

上面三个工具类是 JUC 包的核心类,JUC 包的全景图就比较复杂了:

JUC 包(javautilconcurrent)中的高层类(Lock、同步器、阻塞队列、Executor、并发容器)依赖基础类(AQS、非阻塞数据结构、原子变量类),而基础类是通过 CAS 和 volatile 来实现的。我们尽量使用顶层的类,避免使用基础类 CAS 和 volatile 来协调线程的执行。JUC 包其他的内容,在其他的篇章会有相应的讲解。

Future 是一种异步执行的设计模式,类似 ajax 异步请求,不需要同步等待返回结果,可继续执行代码。使 Runnable(无返回值不支持上报异常)或 Callable(有返回值支持上报异常)均可开启线程执行任务。但是如果需要异步获取线程的返回结果,就需要通过 Future 来实现了。

Future 是位于 javautilconcurrent 包下的一个接口,Future 接口封装了取消任务,获取任务结果的方法。

在 Java 中,一般是通过继承 Thread 类或者实现 Runnable 接口来创建多线程, Runnable 接口不能返回结果,JDK 15 之后,Java 提供了 Callable 接口来封装子任务,Callable 接口可以获取返回结果。我们使用线程池提交 Callable 接口任务,将返回 Future 接口添加进 ArrayList 数组,最后遍历 FutureList,实现异步获取返回值。

运行结果:

上面就是异步线程执行的调用过程,实际开发中用得更多的是使用现成的异步框架来实现异步编程,如 RxJava,有兴趣的可以继续去了解,通常异步框架都是结合远程 >首先看看 阿里java规范 中对线程池使用的规范
不允许使用Executors创建线程池,创建的线程池数量可以为IntegerMAX_VALUE,可能导致OOM
推荐使用
ThreadPoolExecutor创建
看看其4构造方法

看参数最多的一个,上面的都是调这个方法

翻译一下
ThreadPoolExecutor(核心线程大小, 最大线程数,线程保持时间,保持时间单位, 缓存队列,线程工厂,拒绝策略)
前四个比较容易理解,看后面几个就好了
缓存队列:任务多的时候,没有多余线程执行任务,这些任务去队列排队,先进先出的原则,这里队列大小是有限制的,队排满了怎么办,用后面的拒绝策略来处理
线程工厂:
继承javautilconcurrentThreadFactory,下面是可用的两个工厂

拒绝策略:队列满了,后面的任务排不进去怎么处理,四种策略

1AbortPolicy(终止策略)
这是一个处理器针对被拒绝的任务 直接抛出RejectedExecutionException异常(任务无法执行抛出异常)

2DiscardPolicy(丢弃策略)
什么都不做,也不抛出异常

3DiscardOldestPolicy(丢弃旧任务策略)
一种被拒绝任务的处理程序,它丢弃(workQueue队列)最老的未被处理请求(队列最先被放进去的任务),然后调用eexecute(r)重试执行当前任务(注意依然要走流程),除非执行器关闭,在这种情况下任务被丢弃。

4CallerRunsPolicy(调用方运行策略)
被拒绝任务的处理程序,它直接由提交任务的线程来运行这个提交的任务,除非executor已关闭,在这种情况下任务被丢弃。

测试一下

执行结果
1、核心线程先初始化一个线程
2、第1个任务直接给线程
3、第2-6加入队列,装满了
4、第7-10需创建线程用于执行任务
5、到此队列5个和线程五个一起最多处理10任务,第11个任务拒绝处理,等待中
6、继续执行,利用释放的线程和队列继续处理(优先使用线程,没有加入队列)

canvas保存到本地三种方法
canvas保存本地
第一种方法(修改的媒体类型,windowopen直接下载)
第二种方法(创建a标签,通过自己触发点击来下载)
第三种方法(将数据转换成Blob数据,可以保存大)
canvas保存本地
在公司做一个canvas的项目,遇到个需求 :将canvas保存为,尺寸为120004000,一共用了好几种方法,最后才成功,特此记录一下。
第一种方法(修改的媒体类型,windowopen直接下载)
我看了一些教程,这种方法直接修改imagesrc最前面的媒体类型,将data:image改为data:application/octet-stream,然后调用windowopen(url)这个函数,浏览器直接将会下载这个保存的。不过大小一旦超过限制,就会下载出现网络错误
// 第一种方法 采用data:application/octet-stream ,让浏览器识别为下载,有大小限制,120004000无法保存let image = new Image();imagesrc = canvastoDataURL({format: 'image/png', quality:1, width:14000, height:4000});var url = imagesrcreplace(/^data:image\/[^;]/, 'data:application/octet-stream');return image;windowopen(url);
第二种方法(创建a标签,通过自己触发点击来下载)
// 第二种 方法一样,无法保存较大的,会出现错误var imgURL = canvastoDataURL({format: "image/png", quality:1, width:12000, height:4000});var dlLink = documentcreateElement('a');dlLinkdownload = "fileName";dlLinkhref = imgURL;dlLinkdatasetdownloadurl = [MIME_TYPE, dlLinkdownload, dlLinkhref]join(':');documentbodyappendChild(dlLink);dlLinkclick();documentbodyremoveChild(dlLink);
第三种方法(将数据转换成Blob数据,可以保存大)
这个方法的核心是将的imgData转换成Blob数据,然后通过a标签触发保存,可以存储较大的,刚好满足我的需求。
var link = documentcreateElement("a");var imgData =canvastoDataURL({format: 'png', quality:1, width:20000, height:4000});var strDataURI = imgDatasubstr(22, imgDatalength);var blob = dataURLtoBlob(imgData);var objurl = URLcreateObjectURL(blob);linkdownload = "grid1png";linkhref = objurl;linkclick();function dataURLtoBlob(dataurl: string) {var arr = dataurlsplit(','), mime = arr[0]match(/:();/)[1],bstr = atob(arr[1]), n = bstrlength, u8arr = new Uint8Array(n);while(n--){u8arr[n] = bstrcharCodeAt(n);}eturn new Blob([u8arr], {type:mime});}
相关文章:
vue 引入项目的几种方式
MarkDown添加的三种方式
Java代码读取的两种方式
ecilpse写html,eclipse怎么导入
移动端上传的两种方式
html 如何给打标签,图文详解HTML页面中添加的三种方法
python OpenCV 相似度 5种算法
word涂改涂掉_怎么在word中修改的两种方法
图种制作方法 vbs脚本
安全技术揭秘系列探索隐写的奥秘
图种
Qt线程池
线程池专栏
SpringBoot线程池
Java Review - 创建线程和线程池时建议指定与业务相关的名称
java线程池存在时间_Java线程池基础
tomcat线程池
Netty 线程池
线程池-ThreadPoolExecutor
Executors类创建四种常见线程池

QtConcurrent 实际是一个命名空间,该命名空间提供了高级API,从而可以在不使用低级线程原语(启动线程、线程间同步、锁等)的情况下编写多线程程序。

但是QtConcurrent仅支持接受纯函数或者lambda表达式,不支持信号和槽,如果需要监听任务执行结果可以通过与QFuture和QFutureWatcher配合来达到。

QFuture 类表示异步计算的结果,使用Qt Concurrent框架中的API启用。

要使用信号和插槽与正在运行的任务进行交互,则需要使用QFutureWatcher。

QFutureWatcher 类允许使用信号和插槽监视QFuture。

简单的例子:

通过运行结果可以发现,QtConccurent管理的线程实际是从线程池分配线程资源的,而绑定QFutureWatcher的槽是在主线程中执行的。
在需要单次执行且内部逻辑较简单的时候使用QtConccurrent+QFuture+QFutureWatcher是很方便的,可以减少很多编码工作量,而且在多cpu环境中,QtConccurent也会启用多核。

线程池,thread pool,是一种线程使用模式,线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。

功能:应用程序可以有多个线程,这些线程在休眠状态中需要耗费大量时间来等待事件发生。其他线程可能进入睡眠状态,并且仅定期被唤醒以轮循更改或更新状态信息,然后再次进入休眠状态。

为了简化对这些线程的管理,NET框架为每个进程提供了一个线程池,一个线程池有若干个等待 *** 作状态,当一个等待 *** 作完成时,线程池中的辅助线程会执行回调函数。线程池中的线程由系统管理,程序员不需要费力于线程管理,可以集中精力处理应用程序任务。

扩展资料:

应用范围

1、需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。

2、对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。

3、接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分 *** 作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,并出现"OutOfMemory"的错误。

参考资料来源:百度百科—线程池


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/13398073.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-07-28
下一篇 2023-07-28

发表评论

登录后才能评论

评论列表(0条)

保存