JUC并发编程
- java.util.concurrent
- java.util.concurrent.atomic
- java.util.concurrent.locks
java真得可以开启多线程吗?
不可以
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
// native是本地方法,底层是c++,java不能直接操作硬件
private native void start0();
并发和并行
并发:多线程操作同一个资源
- 单核CPU模拟出多条线程,快速交替,实际上是产生的假象。
并行
- CPU多核,多个线程可以同时执行;线程池。
线程有几种状态?
源码
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
// 新建状态(线程一旦被创建就进入了新建状态)
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
// 可运行状态(调用了start方法之后就变成可运行状态)
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
// 阻塞状态(线程因为某种原因放弃了CPU的权利),暂时停止运行,直到线程进入可运行(runnable)状
/// 态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种:
// 一). 等待阻塞:运行(running)的线程执行o.wait()方法,JVM会把该线程放入等待队列(waitting queue)中。
// (二). 同步阻塞:运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(lock pool)中。
// (三). 其他阻塞:运行(running)的线程执行Thread.sleep(long ms)或t.join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入可运行(runnable)状态。
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called {@code Object.wait()}
* on an object is waiting for another thread to call
* {@code Object.notify()} or {@code Object.notifyAll()} on
* that object. A thread that has called {@code Thread.join()}
* is waiting for a specified thread to terminate.
*/
// 等待
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
// 超时等待
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
// 终止
TERMINATED;
}
1、wait和sleep的区别
- 他们来自不同的类,wait来自Object类,sleep来自Thread类
// 一般使用TimeUnit方式让线程休眠
TimeUnit.HOURS.sleep(11);
2、关于锁的释放
1、wait会释放锁,sleep不会释放锁。
3、使用的范围是不同的
- wait必须使用在同步代码块中不需要捕获异常
- sleep必须要捕获异常。
LOCK锁
传统Synchribuzed
package com.xiaohei.synchronizedTest;
public class SynchronizedTest {
public static void main(String[] args) {
SendTickets tickets = new SendTickets();
new Thread(()->{
for (int i = 0; i < 30; i++) {
tickets.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 30; i++) {
tickets.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 30; i++) {
tickets.sale();
}
},"C").start();
}
}
// 模拟卖票
class SendTickets {
private static int TICKET_NUMBERS = 30;
public synchronized void sale() {
if (TICKET_NUMBERS > 0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(TICKET_NUMBERS--)+"票,剩余"+TICKET_NUMBERS);
}
}
}
LOCK锁
三种实现类:ReentrantLock 可重入锁, ReentrantReadWriteLock.ReadLock 读锁, ReentrantReadWriteLock.WriteLock 写锁
new ReentrantLock() 默认实现为非公平锁。new ReentrantLock(true)实现公平锁
1、公平锁能保证:老的线程排队使用锁,新线程仍然排队使用锁。
2、非公平锁保证:老的线程排队使用锁;但是无法保证新线程抢占已经在排队的线程的锁。
// 非公平锁 默认
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
// 公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
package com.xiaohei.locktTest;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
public static void main(String[] args) {
Ticket tickets = new Ticket();
new Thread(()->{
for (int i = 0; i < 30; i++) {
tickets.sale();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 30; i++) {
tickets.sale();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 30; i++) {
tickets.sale();
}
},"C").start();
}
}
// 模拟卖票
/**
* lock三部曲
* 1、new ReentrantLock();
* 2、 lock.lock() // 加锁
* 3.lock.unlock() 解锁
* */
class Ticket {
private int TICKET_NUMBERS = 30;
Lock lock = new ReentrantLock();
public void sale() {
lock.lock(); // 枷锁
lock.tryLock(); // 尝试获取锁
try {
if (TICKET_NUMBERS > 0){
System.out.println(Thread.currentThread().getName()+"卖出了"+(TICKET_NUMBERS--)+"票,剩余"+TICKET_NUMBERS);
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
synchronized和Lock的区别
- Synchronized内置的Java关键字,Lock是一个Java类
- Synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
- Synchronized会自动释放锁,lock必须要手动释放锁,如果不释放,会造成死锁
- Synchronzied 线程1(获得锁,阻塞),线程2(一直等); Lock锁不一定会一直等待下去(lock,tryLock() // 尝试获取锁)
- Synchronized 可重入锁,不可以中断的,非公平;Lock,可重入锁,可以判断锁,非公平(可以自己设置)
- Synchronized适合锁少量代码同步问题,Lock适合锁大量的同步代码;
生产者和消费者问题
面试问题:
- 单例模式
- 8种排序算法
- 生产者消费者问题
- 死锁问题
生产者消费者问题
// 两个线程米有问题,如果在加两个线程C和D呢
package com.xiaohei.pc;
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class Data {
private int number = 0;
// +1
public synchronized void increment() throws InterruptedException {
if (number!=0){
// 等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知可以消费
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
if (number==0){
// 等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知可以消费
this.notifyAll();
}
}
// 使用While代替If防止虚假唤醒
package com.xiaohei.pc;
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class Data {
private int number = 0;
// +1
public synchronized void increment() throws InterruptedException {
while (number!=0){
// 等待
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知可以消费
this.notifyAll();
}
// -1
public synchronized void decrement() throws InterruptedException {
while (number==0){
// 等待
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知可以消费
this.notifyAll();
}
}
JUC生产者和消费者问题(LOCK)
与synchnoized的却别:synchronized是使用wait和notify,Lock锁通过lock创建Condition对象,通过condition对象调用await方法和singal方法等待和唤醒。
Condition优势: 实现的精准通知唤醒线程
if换成while条件判断,在多线程条件下会产生虚假唤醒的情况。
package com.xiaohei.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class B {
public static void main(String[] args) {
Data2 data = new Data2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
}
}
class Data2 {
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// +1
public void increment() throws InterruptedException {
lock.lock();
try {
while (number!=0){
// 等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知可以消费
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
// -1
public synchronized void decrement() throws InterruptedException {
lock.lock();
try {
while (number==0){
// 等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知可以消费
condition.signalAll();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
实现顺序执行任务链
Condition优势: 实现的精准通知唤醒线程
package com.xiaohei.pc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class C {
public static void main(String[] args) {
Data3 data = new Data3();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
data.printC();
}
},"C").start();
}
}
class Data3{
private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int flag = 1;
public void printA(){
lock.lock();
try {
while (flag != 1){
// 等待
condition1.await();
}
flag = 2;
condition2.signal();
System.out.println(Thread.currentThread().getName()+"AAAA=>>>AAAAAA");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (flag != 2){
// 等待
condition2.await();
}
flag = 3;
condition3.signal();
System.out.println(Thread.currentThread().getName()+"BBB=>>>BBBBB");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (flag != 3){
// 等待
condition3. ();
}
flag = 1;
condition1.signal();
System.out.println(Thread.currentThread().getName()+"BBB=>>>CCCC");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
什么是锁,如何判断锁是谁?8锁现象
锁只会锁对象还有Class
synchronized锁的是方法调用者
直接上代码
两个线程是先打印打电话还是先打印发短信?
- 第1锁:锁的是同一个Phone对象,
个人理解:因为锁的是当前Phone对象,程序运行开始,首先是A线程拿到方法执行权,然后进入阻塞状态,线程B进入可运行状态调用callPhone()方法,但是由于锁住了同一个对象,A不释放锁,B是无法执行,所以是先调用sendEmail()在调用callPhone()方法。两个方法是同一个锁,谁先拿到谁先执行。
此时有个疑问?调用Sleep()方法,睡眠结束后不是不释放锁吗,为什么B方法还能获取到锁?是因为锁的不是Phone对象吗?那Sleep锁的是谁呢?
package com.xiaohei.lock8;
import java.util.concurrent.TimeUnit;
// 1、发短信 2、打电话
public class Test1 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendEmail();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.callPhone();
},"B").start();
}
}
class Phone{
// 都是同一个锁,谁先拿到谁执行。
public synchronized void sendEmail(){
System.out.println("发短信");
}
public synchronized void callPhone(){
System.out.println("打电话");
}
}
第2个锁
package com.xiaohei.lock8;
import java.util.concurrent.TimeUnit;
// 线程A调用打电话同步方法,线程B调用普通不同步方法,先输出什么?
// 1、biubiubiu 2、发短信
public class Lock2 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sendEmail();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 调用biu方法
new Thread(()->{
phone.biu();
},"B").start();
}
}
class Phone2{
public synchronized void sendEmail(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void callPhone(){
System.out.println("打电话");
}
// **没有加锁,不是同步方法,不受锁的影响,不需要A执行完在执行B**
public void biu(){
System.out.println("biubiubiu");
}
}
第3个锁
两个对象,两个同步方法, 打电话方法加了延时为了结果明贤
package com.xiaohei.lock8;
import java.util.concurrent.TimeUnit;
// 结果:先输出打电话,在输出发短信.
// 因为是两个对象,synchronized锁的是调用方,此时是两把锁,锁不一样,结果按照时间来。没有延时就不一定的。
public class Test1 {
public static void main(String[] args) {
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone1.sendEmail();
},"A").start();
try {
TimeUnit.SECONDS.sleep(2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone1.callPhone();
},"B").start();
}
}
class Phone{
public synchronized void sendEmail(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public synchronized void callPhone(){
System.out.println("打电话");
}
public void zz(){
System.out.println("zzzz");
}
}
第4锁
增加了两个静态同步方法
static 静态方法,类一加载就出现。锁的是 Class 模板
一个类只有一个Class对象
测试案例: 两个对象
package com.xiaohei.lock8;
import java.util.concurrent.TimeUnit;
public class Test1 {
public static void main(String[] args) {
// 两个对象只有一个Class,谁先拿到谁先执行
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone1.sendEmail();
},"A").start();
try {
TimeUnit.SECONDS.sleep(2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.callPhone();
},"B").start();
}
}
// 两个对象,两个调用者,锁的是Class对象,因为两个对象来自同一个Class所以谁先拿到谁先执行
class Phone{
public static synchronized void sendEmail(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
public static synchronized void callPhone(){
System.out.println("打电话");
}
public void zz(){
System.out.println("zzzz");
}
}
第5锁
一个静态同步方法,一个普通同步方法,一个对象
结果:先输出打电话,在输出发短信啊,因为一个是Class锁,一个是调用者锁(对象锁),是两个锁
package com.xiaohei.lock8;
import java.util.concurrent.TimeUnit;
public class Test1 {
public static void main(String[] args) {
Phone phone1 = new Phone();
// Phone phone2 = new Phone();
new Thread(()->{
phone1.sendEmail();
},"A").start();
try {
TimeUnit.SECONDS.sleep(2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone1.callPhone();
},"B").start();
}
}
class Phone{
// static锁,锁的是Class
public static void sendEmail(){
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("发短信");
}
// 锁的是调用者 不是一个锁
public synchronized void callPhone(){
System.out.println("打电话");
}
}
总结
- new this对象 调用者
- Class 类模板锁 ,只有一个
多线程下集合类不安全类
在并发下ArrayList是不安全的
解决方案:
1、使用List list = new Vector<>();线程安全
2、使用Collections集合的同步方法, List list = Collections.synchronizedList(new ArrayList<>());
3、并发包下: CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();
Vector和CopyOnWriteArrayList区别: Vector底层实现synchronized关键字效率低,CopyOnWriteArrayList使用Lock锁
CopyOnWriteArrayList<>();底层实现
final Object[] getArray() {
return array;
}
final void setArray(Object[] a) {
array = a;
}
public boolean add(E e) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
setArray(es);
return true;
}
}
HashSet的底层是什么?
// 创建HashSet
Set<Object> hashSet = new HashSet<>();
// 底层实际上是通过创建HshMap实现
public HashSet() {
map = new HashMap<>();
}
// 添加元素 add方法
// 直接把元素通过map put进去,实际上就是存的map的key,因为map的key是不可重复的,所以set也是不可重复的。
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
// PRESENT参数,是一个固定不变的对象,不可修改。
private static final Object PRESENT = new Object();
Callable接口(多线程创建方式)
- 会抛出异常
- 有返回值
- 调用方法为call()方法
实现
package com.xiaohei.callableTest;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Test test = new Test();
FutureTask<String> futureTask = new FutureTask<>(test); // 适配类
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start(); // 结果会被缓存 提高效率
// 返回值
// V - 此 FutureTask 的 get 方法所返回的结果类型。
String result = futureTask.get(); // 可能会产生阻塞,因为需要等线程查询结束
System.out.println(result);
}
}
class Test implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("Call");
return "我是小黑人";
}
}
为什么Callable接口使用FutureTask来启动线程?
1、启动线程方式通过Thread的构造方法 new Thread().start();
2、也可以通过new Thread(new Runable()).start()方法启动
3、看Runable接口的实现类有一个FutureTask类
4、FutureTask的构造方法需要传入Callable接口实现类.
5、这样就形成了new Thread(new FuntureTask(new Callable()).start();启动线程模式
6、Callable和FutureTask中的泛型V标识返回值类型
7、通过futureTask.get(); 方法获取返回值,注意: 这里可能会导致线程阻塞问题,因为需要等待返回结果。
8、多个线程一起执行,结果会有缓存,提高效率
常用辅助类
- CountDownLatch
package com.xiaohei.countDownLatchDemo;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"奥利给");
countDownLatch.countDown();
}
// 县城没有执行完,一直会处于等待状态,直到countDownLatch为0,自动释放
countDownLatch.await();
System.out.println("奥利给嗷嗷嗷嗷");
}
}
- CyclicBarrier(加法计数器)
package com.xiaohei.countDownLatchDemo;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CilicBarrier {
public static void main(String[] args) {
// 加法计数器,当所有线程都到达某个屏障点后再进行后续的操作
CyclicBarrier cyclicBarrier = new CyclicBarrier(10,()->{
System.out.println("奥利给1111");
});
// 如果当达到屏障点时,会触发执行任务,然后清空计数器,这里有一个清空操作,当每次在达到阈值时,再次触发逻辑
for (int i = 0; i < 20; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"奥利给");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
CountDownLatch和CyclicBarrier的区别
CountDownLatch | CyclicBarrier |
---|---|
减计数方式 | 加计数方式 |
计数为0时释放所有等待线程 | 计数达到指定屏障值时释放所有线程 |
计数为0时无法重置 | 计数达到指定屏障值时可以重置 |
调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没有影响 | 调用await()方法计数加1,加1后的值不等于构造方法给的值则线程阻塞 |
不可重复利用 | 可以重复利用 |
- semaphore(信号量)
package com.xiaohei.countDownLatchDemo;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
// 信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。
public class SemaphoreTest {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 6; i++) {
int finalI = i;
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+ "进来");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+ "出去");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
}).start();
}
}
}
阻塞队列 BlockingQueue
Collection下的一个接口
使用阻塞队列
四组API
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add() | offer() | pu(t) | offer() |
移除 | remove() | poll() | take() | poll() |
检查队首元素 | elment() | peek() | - | - |
测试
- 抛出异常,add()和remove()
// 测试1: 抛出异常
public static void throwExceptionBlockingDueue(){
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
//
System.out.println(arrayBlockingQueue.add("1"));
System.out.println(arrayBlockingQueue.add("2"));
System.out.println(arrayBlockingQueue.add("3"));
// 如果添加元素大于指定阻塞队列大小就会抛出异常:
// Exception in thread "main" java.lang.IllegalStateException: Queue full
// System.out.println(arrayBlockingQueue.add("4"));
System.out.println("===========");
// 如果移除元素数量超出阻塞队列长度抛出异常:
// Exception in thread "main" java.util.NoSuchElementException
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
}
- 有返回值,不抛出异常,如果添加元素数量超出阻塞队列长度不会抛出异常,而是返回false,如果阻塞队列为空获取元素会返回null, offer()和poll()
public static void noThrowExpectionBlockingDueue(){
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.offer("1"));
System.out.println(arrayBlockingQueue.offer("2"));
System.out.println(arrayBlockingQueue.offer("3"));
// 如果添加元素数量超出阻塞队列长度不会抛出异常,而是返回false
System.out.println(arrayBlockingQueue.offer("4"));
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
// 如果添加元素数量超出阻塞队列长度不会抛出异常,而是返回null
System.out.println(arrayBlockingQueue.poll());
}
- 阻塞等待(一直等待) put()和take()方法
// 测试3: 阻塞等待
public static void blockingDueueWait() throws InterruptedException {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
arrayBlockingQueue.put("1");
arrayBlockingQueue.put("2");
arrayBlockingQueue.put("3");
// 如果添加元素超过阻塞队列长度,会一直等待
// arrayBlockingQueue.put("4");
arrayBlockingQueue.take();
arrayBlockingQueue.take();
arrayBlockingQueue.take();
// 如果阻塞队列为空,获取元素会一直等待
arrayBlockingQueue.take();
}
- 超时等待(超出一定时间自动结束) offer和poll的重载方法
public static void chaoBlockingDueueWait() throws InterruptedException {
ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer("1",1, TimeUnit.SECONDS);
blockingQueue.offer("2",1, TimeUnit.SECONDS);
blockingQueue.offer("3",1, TimeUnit.SECONDS);
// 添加元素超出阻塞队列长度,过1秒会自动结束
blockingQueue.offer("4",1, TimeUnit.SECONDS);
blockingQueue.poll(1,TimeUnit.SECONDS);
blockingQueue.poll(1,TimeUnit.SECONDS);
blockingQueue.poll(1,TimeUnit.SECONDS);
// 如果阻塞队列元素为空.再移除元素会等待,过1秒会自动结束
blockingQueue.poll(1,TimeUnit.SECONDS);
}
- 获取队首元素
抛异常方法使用element()
有返回值,不抛出异常使用take()
线程池
线程池优点
1、降低资源消耗:(通过重复利用已创建的线程降低线程创建和销毁造成的消耗。)
2、提高响应速度:(任务到达时,任务可以不需要等到线程创建就能立即执行)
3、方便管理:(线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控)
线程服用、可以控制最大并发数、便于管理
- 三大方法
- Executors.newSingleThreadExecutor(); //创建单一线程池,
- 特点:只有1个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。
- 应用场景:不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作、文件操作等。
- Executors.newSingleThreadExecutor(); //创建单一线程池,
package com.xiaohei.threadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolTest {
public static void main(String[] args) {
// 创建单一线程池,
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 创建固定大小线程
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
// 创建可缓存线程池, 根据并发数量自动扩容或伸缩, 最大线程数为Intger.Max 21亿
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
run(executorService);
// run(newFixedThreadPool);
// run(cachedThreadPool);
}
public static void run(ExecutorService executorService){
for (int i = 1; i <= 5; i++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
// 执行完任务必须要关闭线程池,否则会一直等待
executorService.shutdown();
}
}
- Executors.newFixedThreadPool(5); // 固定大小线程池,传入参数
- 特点:只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。
- 应用场景:控制线程最大并发数。
package com.xiaohei.threadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolTest {
public static void main(String[] args) {
// 创建单一线程池,
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 创建固定大小线程
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
// 创建可缓存线程池, 根据并发数量自动扩容或伸缩, 最大线程数为Intger.Max 21亿
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// run(executorService);
run(newFixedThreadPool);
// run(cachedThreadPool);
}
public static void run(ExecutorService executorService){
for (int i = 1; i <= 5; i++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
// 执行完任务必须要关闭线程池,否则会一直等待
executorService.shutdown();
}
}
// 打印输出结果,五个线程同时执行
D:\environmentConfig\jdk11\bin\java.exe "
pool-2-thread-5ok
pool-2-thread-1ok
pool-2-thread-2ok
pool-2-thread-4ok
pool-2-thread-3ok
Process finished with exit code 0
- Executors.newCachedThreadPool(); // 可缓存线程池,根据任务数量自动扩容和缩减线程池大小
- 特点:无核心线程,非核心线程数量无限,执行完闲置60s后回收,任务队列为不存储元素的阻塞队列。
- 应用场景:执行大量、耗时少的任务。
package com.xiaohei.threadPool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolTest {
public static void main(String[] args) {
// 创建单一线程池,
ExecutorService executorService = Executors.newSingleThreadExecutor();
// 创建固定大小线程
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);
// 创建可缓存线程池, 根据并发数量自动扩容或伸缩, 最大线程数为Intger.Max 21亿
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// run(executorService);
// run(newFixedThreadPool);
run(cachedThreadPool);
}
public static void run(ExecutorService executorService){
for (int i = 1; i <= 10; i++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
// 执行完任务必须要关闭线程池,否则会一直等待
executorService.shutdown();
}
}
// 执行结果,可以发现,当我们同时开启10个任务时,可缓存线程池可以根据任务数量自动创建适合大小线程池
pool-3-thread-3ok
pool-3-thread-1ok
pool-3-thread-8ok
pool-3-thread-6ok
pool-3-thread-2ok
pool-3-thread-4ok
pool-3-thread-7ok
pool-3-thread-10ok
pool-3-thread-9ok
pool-3-thread-5ok
Process finished with exit code 0
定时线程池(ScheduledThreadPool )
Executors.newScheduledThreadPool(3);
特点:核心线程数量固定,非核心线程数量无限,执行完闲置10ms后回收,任务队列为延时阻塞队列。
应用场景:执行定时或周期性的任务。
- 三大方法底层实现(线程池的真正实现类是ThreadPoolExecutor,都是调用的其构造方法)
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- 七大参数
手动创建线程池,参数含义
* 1、corePoolSize:核心线程数量,一直启用的线程数
* 2、maximumPoolSize: 最大启用线程数量,只有当阻塞队列中的任务满时,开启最大线程数,当任务不饱和时,失效时间结束,非核心线程被收回
* 3、keepAliveTime:线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将allowCoreThreadTimeout设置为true时,核心线程也会超时回收。
* 4、TimeUnit:指定keepAliveTime参数的时间单位
* 5、BlockingQueue<>(): 通过线程池的execute()方法提交的Runnable对象将存储在该参数中。其采用阻塞队列实现。任务超过核心线程数会放到阻塞队列中,如果阻塞队列任务也满了,会启动maximumPoolSize最大线程数,当启动完毕还有任务进来时,此时采用拒绝策略。
* 6、threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
* 7、handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
* -AbortPolicy()(默认):丢弃任务并抛出RejectedExecutionException异常。
* -CallerRunsPolicy():由调用线程处理该任务。
* -DiscardPolicy():丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
* -DiscardOldestPolicy():丢弃队列最早的未处理任务,然后重新尝试执行任务。
*/
package com.xiaohei.threadPool;
import java.util.concurrent.*;
public class ThreadPoolTest {
public static void main(String[] args) {
/**
* 手动创建线程池,参数含义
* 1、corePoolSize:核心线程数量,一直启用的线程数
* 2、maximumPoolSize: 最大启用线程数量,只有当阻塞队列中的任务满时,开启最大线程数,
* 当任务不饱和时,失效时间结束,非核心线程被收回
* 3、keepAliveTime:线程闲置超时时长。如果超过该时长,非核心线程就会被回收。
* 4、TimeUnit:指定keepAliveTime参数的时间单位
* 5、BlockingQueue<>(): 通过线程池的execute()方法提交的Runnable对象将存储在该参数中。其采用阻塞队列实现。
* 任务超过核心线程数会放到阻塞队列中,如果阻塞队列任务也满了,会启动maximumPoolSize最大线程数,当启动完毕还有
* 任务进来时,此时采用拒绝策略。
* 6、threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
* 7、handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
* -AbortPolicy()(默认):丢弃任务并抛出RejectedExecutionException异常。
* -CallerRunsPolicy():由调用线程处理该任务。
* -DiscardPolicy():丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
* -DiscardOldestPolicy():丢弃队列最早的未处理任务,然后重新尝试执行任务。
*/
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2,
5,
2,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() // 抛出异常RejectedExecutionException
);
try {
for (int i = 1; i <= 10; i++) {
executor.execute(()->{
System.out.println("测试手动创建线程池:"+Thread.currentThread().getName());
});
}
} finally {
executor.shutdown();
}
}
}
// 因为拒绝策略使用的默认的,并且当前任务数量已经超过阻塞队列+最大线程数容量,所以再有任务采用拒绝策略抛出异常.
- 四大拒绝策略
AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。
CallerRunsPolicy:由调用线程处理该任务。
DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务,如果尝试失败,放弃任务。
线程池使用方法
// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
MAXIMUM_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
sPoolWorkQueue,
sThreadFactory);
// 向线程池提交任务,lambda表达式
threadPool.execute(()->{
// 执行任务
});
// 关闭线程池
threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
threadPool.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返
任务队列(workQueue)
任务队列是基于阻塞队列实现的,即采用生产者消费者模式,在Java中需要实现BlockingQueue接口。但Java已经为我们提供了7种阻塞队列的实现:
1、ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
2、LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为Integer.MAX_VALUE。
3、PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现Comparable接口也可以提供Comparator来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
4、DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现Delayed接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
5、SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用take()方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用put()方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
6、LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样FIFO(先进先出),也可以像栈一样FILO(先进后出)。
7、LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue和SynchronousQueue的结合体,但是把它用在ThreadPoolExecutor中,和LinkedBlockingQueue行为一致,但是是无界的阻塞队列。
注意
注意有界队列和无界队列的区别:如果使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略;而如果使用无界队列,因为任务队列永远都可以添加任务,所以设置maximumPoolSize没有任何意义。
对比
类型 | 池内 线程类型 | 池内 线程数量 | 处理特点 | 应用场景 |
---|---|---|---|---|
定长线程池 (FixedThreadPool) | 核心线程 | 固定 | 1、核心线程处于空闲状态时也不会被回收,除非线程被关闭 2、当所有线程都处于活跃状态时,新的任务会处于等待状态,直到有线程空闲出来 3、任务队列无线大小(超出线程的任务会在队列中等待) | 控制线程最大并发数 |
定时线程池 (ScheduledThreadPool) | 核心线程和非核心线程 | 核心线程 固定 非核心线程 无限制 | 非核心线程闲置时,会立刻被回收 | 执行定时/周期性任务 |
可缓存线程池 (CachedThreadPool) | 非核心线程 | 不固定 无限制 | 优先利用闲置线程处理新任务 (既 重用线程) 无线程可用时,创建新的线程 (既任何任务来了都会立刻执行,不需要等待) 灵活回收空闲线程 (具备超时机制=60s,空闲60s才会回收,全部回收时几乎不占用系统资源) | 执行数量多、耗时少的任务。 |
单线程化线程池 (SingleThreadPool) | 核心线程 | 1个 | 保证所有任务按照指定顺序在一个线程中执行 (相当于顺序执行) | 单线程 (不适合并发但可能引起IO阻塞性及影响UI线程响应的操作,如数据库操作、文件操作等。) |
总结
Executors的4个功能线程池虽然方便,但现在已经不建议使用了,而是建议直接通过使用ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
其实Executors的4个功能线程有如下弊端:
FixedThreadPool和SingleThreadExecutor:主要问题是堆积的请求处理队列均采用LinkedBlockingQueue(可以存储无线个任务),可能会耗费非常大的内存,甚至OOM。
CachedThreadPool和ScheduledThreadPool:主要问题是线程数最大数是Integer.MAX_VALUE(最多处理将近无限个任务),可能会创建数量非常多的线程,甚至OOM。
最大线程应该如何定义?
- CPU密集型: 几核CPU,最大线程池就设置多少
- IO密集型: 判断程序中十分消耗IO的数量,大于即可,一搬2倍
ThreadPoolExecutor executor = new ThreadPoolExecutor(
2,
Runtime.getRuntime().availableProcessors(), // 获取CPU核数
2,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy() // 抛出异常
);
函数式接口(只有一个方法的接口)
- Function<T,R>: 函数式接口,
- Predicate: 断定式接口,返回Boolean类型,需要传入一个参数
- Consumer: 消费型接口,只有输入,么有返回值
- Supplier: 供给型接口
- Function<T,R>: 函数式接口,泛型两个参数,T:传入参数类型,R:方法返回值类型
// 函数式接口,泛型<T,R> 参数和返回值
Function<String, String> function = new Function<String, String>() {
@Override
public String apply(String s) {
return s;
}
};
System.out.println(function.apply("123213"));
// lambda表达式
Function<String, Boolean> function2 = (str)->{return str.isEmpty();};
System.out.println(function2.apply(""));
- Predicate: 断定式接口,返回Boolean类型,需要传入一个参数
package com.xiaohei.functionInterface;
import java.util.function.Predicate;
public class PredicateInterfaceDemo {
public static void main(String[] args) {
// 断定式接口,<T> 泛型 传入参数
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.isEmpty();
}
};
System.out.println(predicate.test("asdasd"));
// lambda表达式<T> 传入参数
Predicate<String> predicateLambda = (str)->{
System.out.println(str.isEmpty());
return str.isEmpty();
};
predicateLambda.test("");
}
}
- Consumer: 消费型接口,没有返回值,需要传入一个参数
package com.xiaohei.functionInterface;
import java.util.function.Consumer;
public class ConusmerInterfaceDemo {
public static void main(String[] args) {
// 消费型接口,没有返回值,传入参数
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s+"是不是空:"+s.isEmpty());
}
};
consumer.accept("asda ");
// lambda表达式
Consumer<String > consumerLambda = (str)->{
// System.out.println("ok");
};
consumerLambda.accept("asd");
}
}
- Supplier: 供给型接口,没有返回值,需要传入一个参数
package com.xiaohei.functionInterface;
import java.util.function.Supplier;
public class SupplierInterfaceDemo {
public static void main(String[] args) {
// 供给型接口,没有输入参数,只有返回值
Supplier<String> supplier = new Supplier<String>() {
@Override
public String get() {
System.out.println("get()1");
return "11231";
}
};
System.out.println(supplier.get());
// lambda
Supplier<String> supplierLambda = ()->{
System.out.println("sad立刻就爱上了打开静安寺");
return "122";
};
System.out.println(supplierLambda.get());
}
}