对于前面重要的线程的线程,由于接口太多,为了不被混淆,做以下总结。下面接口的运用,在前面的生产者消费者模型中。可以选择对比浏览。(生产者消费者模型)
需求阅读
创建与初始化
接口作用 | 接口名称 | 细节或参数意义 |
线程的创建 | pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); | thread:返回线程ID attr:设置线程的属性,attr通常为NULL表示使用默认属性 start_routine:是个函数地址,线程启动后要执行的函数 arg:传给线程启动函数的参数,没有参数就给空 |
互斥锁初始化 | pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *attr); | mutex:要初始化的互斥量 attr:通常NULL |
条件变量初始化 | pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *attr); | cond:要初始化的条件变量 attr:通常NULL |
信号量初始化 | sem_init(sem_t *sem, int pshared, unsigned int value); | sem:信号量 pshared: 0表⽰示线程间共享,非零表⽰示进程间共享 value:信号量初始值 |
终止与销毁
接口作用 | 接口名称 | 细节或参数意义 |
退出调用线程 | pthread_exit(void *value_ptr); | 并且返回一个value_ptr, exit(0): exit退出的是整个进程(所有线程都会退出) |
互斥锁的销毁 | pthread_mutex_destroy(pthread_mutex_t *mutex) | 使⽤静态初始化的互斥量不需要销毁 |
条件变量销毁 | pthread_cond_destroy(pthread_cond_t *cond) | cond:条件变量 |
信号量销毁 | sem_destroy(sem_t *sem); | sem:信号量 |
等待和加锁
接口作用 | 接口名称 | 细节或参数意义 |
线程等待 | pthread_join(pthread_t thread, void **value_ptr); | thread:线程ID value_ptr:它指向⼀个指针,后者指向线程的返回值 |
互斥锁的加锁 | pthread_mutex_lock(pthread_mutex_t *mutex); | 能加则加,不加阻塞 |
条件变量等待 | pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex); | cond:要在这个条件变量上等待 mutex:互斥量 |
信号量等待 | sem_wait(sem_t *sem); | 会将信号量的值减1,若小于0则等待,大于0就使用 |
唤醒和分离
接口作用 | 接口名称 | 细节或参数意义 |
线程分离 | pthread_detach(pthread_t thread) | 更改线程创建时默认属性,可以不用等待 |
互斥锁解锁 | pthread_mutex_unlock(pthread_mutex_t *mutex); | 有加锁必解锁 |
条件变量唤醒 | pthread_cond_signal(pthread_cond_t *cond); | cond:条件变量 |
信号量唤醒 | sem_post(sem_t *sem); | 表⽰示资源使⽤用完毕。将信号量值加1。 |
四种常见的线程池
-
线程池参数
下面说的是Java下的线程。在Java中,线程池的概念是Executor这个接口,具体实现为ThreadPoolExecutor类。它的构造函数中有七个的参数,下面来认识一下。
- corePoolSize: 核心线程池的大小,也可以理解为核心线程数的最大数量。在线程池新建线程的时候,如果当前线程总数小于核心线程数,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程。核心线程默认情况下会一直存活在线程池中,没有执行任务会处于闲置状态;若ThreadPoolExecutor的allowCoreThreadTimeOut这个属性为true,限制状态下的核心线程,超过一定时间会被销毁掉。
- maximumPoolSize:线程池中线程总数最大值 = 核心线程数 + 非核心线程数。活动线程数量超过它,后续任务就会排队 。
- keeplivetime:非核心线程闲置超时时长。一个非核心线程,闲置状态的时长超过这个参数所设定的时长,就会被销毁掉。如果设置allowCoreThreadTimeOut = true,则会作用于核心线程,对应corePoolSize。
- unit:枚举类型;设置keepAliveTime的单位,有MILLISECONDS(ms)、SECONDS(s)等
- workQueue:任务队列或者是阻塞队列,用来存放等待的任务。当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务。
- threadFactory:为线程池提供创建新线程,一般使用默认。
- handler:拒绝策略,当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,会抛出一个异常。
-
四种线程池
(1)FixedThreadPool:
一个只有数量固定的核心线程的线程池。核心线程是没有超时机制的,队列大小没有限制,除非线程池关闭了核心线程才会被回收。因为队列没有限制大小,新任务会等待执行。其响应的速度快。
(2)SingleThreadPool:
只有一个核心线程的线程池。其corePoolSize=maximumPoolSize=1,且keepAliveTime为0。它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行,每次任务到来后都会进入阻塞队列,然后按指定顺序执行。适合线程同步操作的场所。
(3)CachedThreadPool:
缓存线程池。只有非核心线程,线程数无限制,每新来一个任务,当没有空余线程的时候就会重新创建一个线程,这边有一个超时机制,当空闲的线程超过60s内没有用到的话,就会被回收。它可以一定程序减少频繁创建/销毁线程,减少系统开销,适用于执行时间短并且数量多的任务场景。
(4)ScheduledThreadPool:
创建一个定长线程池,支持定时及周期性任务执行。核心线程数固定,非核心线程数没有限制,但闲置状态的会被立即回收,主要用于执行定时任务以及有固定周期的重复任务
各种设计模式种类和使用场景
-
概念
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式是为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。
-
几种常见设计模式和使用场景
- 单例模式
一个类仅有一个对象,且无法再次创建。单例模式又分为懒汉和饿汉。前面已经详细解释过。(单例模式)
- 迭代器模式
提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示。(迭代器模式)
- 适配器模式
当一些接口不适用或者不兼容的时候,将接口转换成另外一种接口。(适配器模式)
- 线程池模式
该模式事先启动一定数目的工作线程。当没有请求工作的时候,所有的工人线程都会等待新的请求过来,一旦有工作到达,就马上从线程池中唤醒某个线程来执行任务,执行完毕后继续在线程池中等待任务池的工作请求的到达。(线程池模式)
- 模版模式
模板通俗理解就是,先把框架架起来,当不同的对象有不同的实现。类似一个模具,不同颜色的浇筑,生成不同颜色的铸件。(模板模式)
各种锁的种类和使用场景
-
互斥锁
互斥锁:保证在任何时刻,都只能有一个线程访问该对象。当加锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒
使用场景:多个线程会访问它们共享的资源,为了保证资源的一致性时使用。
-
自旋锁
自旋锁:在任何时刻同样只能有一个线程访问对象。但是当获取锁操作失败时,不会进入睡眠,而是会在原地自旋,直到锁被释放。节省了线程从睡眠状态到被唤醒期间的消耗,在加锁时间短暂的环境下会极大的提高效率。但如果加锁时间过长,则会非常浪费CPU资源
使用场景:在多核/多CPU的系统上,大量的线程只会短时间的持有锁的时候, 使用自旋锁, 降低在使线程睡眠和唤醒线程上浪费大量的时间,提升程序的运行性能。
-
可中断锁:
可中断锁:可被取消的锁
使用场景:主要用于取消某些操作对资源的占用。如:(取消正在同步运行的操作,来防止不正常操作长时间占用造成的阻塞)
-
可重入锁:
可重入锁:可以被线程多次重复进入进行获取操作。即当一个线程获取了某个对象锁以后,还可以再次获得该对象锁。
使用场景:防止在同一线程中多次获取锁而导致死锁发生。
-
分段锁:
分段锁:一组独立对象上的锁进行分解,即容器里有多把锁,每一把锁用于锁容器其中一部分数据
使用场景:取hashmap全局信息
-
公平锁、非公平锁
公平锁:加锁前先查看是否有排队等待的线程,有的话优先处理排在前面的线程,先来先得。
非公平锁:线程加锁时直接尝试获取锁,获取不到就自动到队尾等待。
使用场景:一般使用非公平锁,效率高。多线执行的顺序维度
-
独享锁、共享锁:
独享锁:该锁一次只能被一个线程所持有
共享锁:指该锁可被多个线程所持有
使用场景:类似读者写者问题场景
-
乐观锁、悲观锁:
乐观锁:假设其他线程不会修改数据,不加锁。
悲观锁:理解为互斥锁。假设其他线程会修改数据,通过阻塞其他所有线程来保证数据的完整性。
使用场景:乐观锁适用于数据争用不严重/重试代价不大/需要相应速度快的场景。悲观锁适用于数据争用严重/重试代价大的场景。
-
偏向锁、轻量级锁、重量级锁:
偏向锁:一段同步代码一直被一个线程所访问,那么该线程会自动获取锁
轻量级锁:当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能
重量级锁:即互斥锁。降低性能
使用场景:在Java中,对Synchronized的优化