小看一下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,否则报错
四步走:
-
创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
-
创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
-
使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
-
调用 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函数经过了解是提交任务,传入对应的线程即可。