java的多线程学习笔记

本文介绍了Java多线程的实现方式,包括实现Runnable接口、继承Thread类、使用Callable和Future,以及线程同步和线程池。还讨论了Python多线程的基础用法,并提到了并发控制工具如CyclicBarrier和信号量。最后,文章强调了Java中synchronized关键字的应用,线程池的创建及其优点,并探讨了不同的拒绝策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

小看一下python多线程

threading.Thread(target=receive, args=(destination,)).start()
threading.Thread(target=senddata, args=(packet_data,)).start()

我对多线程真没什么研究,这是之前做项目时候需要收发双线程工作,然后简单的用了一下python的多线程,当然也是最基础的,就

import threading

threading.Thread(target=函数名, args=(参数,)).start()

虽然可能简单了,但是就这么多了,接下来看看

java多线程

进程:一个进程包括由操作系统分配的内存空间,包含一个或多个线程。
在这里插入图片描述

/*创建线程的方式
* 通过实现 Runnable 接口;
* 通过继承 Thread 类本身;
* 通过 Callable 和 Future 创建线程。
*/

第一种 实现 Runnable 接口

在这里插入图片描述
在这里插入图片描述

第二种 实现Thread

丢一个Thread类实例进去,但是本质上也是实现了 Runnable 接口的一个实例。
在这里插入图片描述
常用方法:
在这里插入图片描述

方法三 通过 Callable 和 Future 创建线程

注意这里继承的是接口和runnable一样,不和第两种一样是类。
必须重写call,否则报错
在这里插入图片描述
在这里插入图片描述
四步走:

  1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。

  2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。

  3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。

  4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

做点题

在这里插入图片描述

为了防止出现并发竞争状态,需要一种具有两种功能的机制:1)关键部分的访问控制;2)通知阻塞线程。

private AtomicInteger firstJob = new AtomicInteger(0);

高并发的情况下,i++无法保证原子性,往往会出现问题,所以引入AtomicInteger类。

		  RunnableDemo R2 = new RunnableDemo( "Thread-2");
	      R2.start();
	      System.out.println("===============");
	      R2.firstJob.incrementAndGet();

它有incrementAndGet和GetAndincrement两个方法实现自增,其中区别是先增再取值还是先取值再增,类似于i++和++i两种。

方法二CyclicBarrier

在这里插入图片描述
在这里插入图片描述

CyclicBarrier cb = new CyclicBarrier(2);

可以通过传值实例化,当里边装到2个线程的时候才会一起执行。
可以用于多线程计算数据,最后合并计算结果的场景。

(插播一条,此处大概学习了一下java反射机制,防止面试被问到,用一句话就是通过字节码寻找class文件中的信息,从而达到获取任何类或对象的方法等信息)

还可以使用信号量

private Semaphore bar = new Semaphore(0);

具体使用是

bar.acquire();
bar.release();

如果要练手,去leet做点题,多线程

9.2号进行补充

经常看到的sync关键词和voliate关键词,从来没用过,今天学习一下,此外梳理一下线程锁相关。
在这里插入图片描述
synchronized关键字不能继承。如果在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的。

值得注意的是当修饰代码块的时候,sync锁的是对象,如果是实例化了一个对象,两个线程去调用两次,这时候会发生阻塞,反之当实例化两个对象的时候,两个线程去调用并不会发生任何的阻塞。
如图
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

!!!java中long和double类型操作是原子的(个别特殊的除外)

Synchronized也可修饰一个静态方法,静态方法是属于类的而不属于对象的。

1.因此synchronized修饰的静态方法锁定的是这个类的所有对象。
2.在定义接口方法时不能使用synchronized关键字。
3.构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步。
给类加sync
在这里插入图片描述

线程池端上来

java自带的线程池创建:Executors
简单实用可以支持四种线程池的创建,分别是single(单线程),newcached(无界的,适合很多短线程),fixed(定长)和Scheduled(定时或延时)这四种,具体的使用就是Executors.newFixedThreadPool(int nThreads)
但缺点呢也很明显,我并不能过多的自定义设置,而且线程创建是无界的,很快就out of memory 了。
在这里插入图片描述
通过官方文档说道的:线程池的优点是由于减少了每个任务的调用开销,它们通常在执行大量异步任务时可提供改进的性能,并且它们提供了一种绑定和管理资源(包括线程)的方法,该资源在执行集合的执行时消耗任务。
在这里插入图片描述
推荐使用ThreadPoolExecutor构造函数去灵活创建线程池

在这里插入图片描述
参数:
corePoolSize:一个维持的长期核心线程数,当有一个新任务提交时,若是线程数是小于corePoolSize的,那么即便现有线程有的是空闲的,也不会去使用,而是创建一个新线程,如果线程数大于corePoolSize但是小于第二个参数max,那么查看下面的队列是不是满的,如果满才创建线程,如果线程数大于max参数,那么采用对应的丢弃参数的丢弃策略。
maximumPoolSize:指定了线程池中的最大线程数量,这个参数会根据你使用的workQueue任务队列的类型,决定线程池会开辟的最大线程数量,一般情况下,core和max参数值设置相等,也就是为了创建固定大小的线程池。
keepAliveTime:表面理解是存活时间,但是还是有学问的,当前线程数多余core数,那么多余的线程在KAT这个时间后终止,参数类型其实是两个值前者是一个数字,后者是如图 这样一个参数,组合起来表示一段时间。
在这里插入图片描述
另外这个参数也是可以动态更改的,调用函数setKeepAliveTime(long, java.util.concurrent.TimeUnit)。

workQueue:任务队列,有四种:
1、直接交付,官方说这样的交付方式默认使用的队列是 SynchronousQueue,看了一下官方对这个队列的解释,继承了抽象队列类,实现了阻塞队列和serial接口,

A blocking queue in which each insert operation must wait for a 
corresponding remove operation by another thread, and vice 
versa. A synchronous queue does not have any internal capacity,
 not even a capacity of one. You cannot peek at a synchronous 
 queue because an element is only present when you try to 
 remove it; you cannot insert an element (using any method) 
 unless another thread is trying to remove it; you cannot 
 iterate as there is nothing to iterate. The head of the 
 queue is the element that the first queued inserting 
 thread is trying to add to the queue; if there is no 
 such queued thread then no element is available for 
 removal and poll() will return null. For purposes of 
 other Collection methods (for example contains), a 
 SynchronousQueue acts as an empty collection. This 
 queue does not permit null elements.

英语不好的话就来点中文吧
一个阻塞队列,其中每个插入操作必须等待另一个线程进行相应的删除操作,反之亦然。同步队列没有任何内部容量,甚至没有一个容量。您无法 窥视同步队列,因为仅当您尝试删除它时,该元素才存在。您不能插入元素(使用任何方法),除非另一个线程试图将其删除;您无法进行迭代,因为没有要迭代的内容。队列的 头部是第一个排队的插入线程试图添加到队列中的元素;如果没有这样的排队线程,则没有元素可用于删除,并且 poll()将返回null。为了其他目的 集合方法(例如contains), SynchronousQueue充当空集合。此队列不允许空元素。

使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的进程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略;
2、无界队列,LinkedBlockingQueue,当队列类型为此种时,max参数的作用将消失,线程数最多为core的数量,多来的任务将会排队,任务提交与处理之间的协调与控制显得非常重要,如果处理速度低于添加速度,队列无限增长,从而导致资源耗尽。
3、有界队列,是一个池大小与队列大小的一个折中,可以防止资源耗尽,但是调优和控制可能会更加困难,大队列小池情况:会减少开销,人为地降低吞吐量;
小队列大池情况:线程调度的时间超出您的允许范围,导致CPU繁忙,也会降低吞吐率。
4、优先级队列:除了第一个任务直接创建线程执行外,其他的任务都被放入了优先任务队列,按优先级进行了重新排列执行,且线程池的线程数一直为corePoolSize。

threadFactory:线程工厂,用于创建新线程的方式,默认的是Executors.defaultThreadFactory(),这样创建出来的线程具有相同的优先级和非守护状态,当需要自定义时,
在这里插入图片描述
这里的newThread方法传入的是Runnable,我尝试着传入其他
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
core为2时
在这里插入图片描述

RejectedExecutionHandler handler:
线程池的工作顺序:corePoolSize -> 任务队列 -> maximumPoolSize -> 拒绝策略。

一个拒绝策略指的是当任务队列占满的时候,这时再submit()提交新的任务会进行一个怎么样的处理策略,默认的策略有四种
在这里插入图片描述
线程池默认的拒绝策略是abort。同时也可以自己扩展RejectedExecutionHandler接口,定义自己的拒绝策略,但是需要非常注意,官方是这样说的:
在这里插入图片描述

这样了解后线程池该如何使用
在这里插入图片描述
execute函数经过了解是提交任务,传入对应的线程即可。

这下关于多线程应该是理清楚了吧

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值