挖坑记录~并发进阶

根据尚硅谷课程整理

尚硅谷Java大厂面试题第二季(java面试必学,周阳主讲)

阻塞队列

阻塞队列blockingQueue与队列list不同是, 操作时如果不满足条件, 行为将被阻塞;
一般阻塞队列都有一个边界;
添加操作时, 如果达到边界, 将会被阻塞;
获取操作时, 如果队列是空的时候, 将会被阻塞;
blockingQueue可以自动决定什么时候阻塞队列, 不需要人为判断
在这里插入图片描述
blockingQueue和list都是Collection接口的同级实现类

blockingQueue中又有这些实现类
在这里插入图片描述

ArrayBlockingQueue

由数组结构组成的有界队列
创建时可以设置队列的长度

ArrayBlockingQueue<String> queue = new ArrayBlockingQueue(1);

add()
将元素添加至队尾
当队列已满时再添加抛出非法状态异常
在这里插入图片描述
remove()
删除队首元素
当队列中没有元素时, 再移除会抛出 NoSuchElementException

offer(E e, long timeout, TimeUnit unit)
添加元素至队尾, 不能添加返回false
可以设置阻塞时长, 如果设置阻塞时长后, 队列已经满的情况下, 将会阻塞

peek()
查看队首的元素, 不影响队列

poll()
返回队首的元素, 无元素时返回null

put()
添加元素至队尾, 队列已满时阻塞
可以被打断会抛出interrupt异常

take()
获取一个元素, 如果没有的话就阻塞

LinkedBlockQueue

由链表结构组成的有界队列

SynchronousQueue

仅含有一个元素的队列

线程生产者消费者传统版

生产后再消费, 生产一个, 消费一个
使用的lock, condition组合
需要注意条件判断需要使用while, 如果用if判断一次阻塞后, 唤醒继续向下跑, 如果多个线程的话就会出现重复操作资源类的情况

public class ConsumerProducerTraditional {
	public static void main(String[] args) {
		ResourceTarget resourceTarget = new ResourceTarget();
		for (int i = 0; i < 2; i++) {
			new Thread(()->{
				resourceTarget.increment();
			},"生产者").start();
		}
		for (int i = 0; i < 2; i++) {
			new Thread(()->{
				resourceTarget.decrement();
			},"消费者").start();
		}
	}
}
class ResourceTarget{
	private ReentrantLock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	private int num = 0;
	
	public void increment(){
		lock.lock();
		try {
			//当数量为0时才生产
			while(!(num==0)){
				System.out.println(Thread.currentThread().getName()+" 发现还有商品,等待商品被消费..");
				//如果不等于0, 也就是有商品的时候就阻塞等待通知
				condition.await();
			}
			System.out.println(Thread.currentThread().getName()+" 开始生产..耗时2s");
			TimeUnit.SECONDS.sleep(2);
			num++;
			System.out.println(Thread.currentThread().getName()+" 生产后通知消费者..");
			condition.signal();
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
	public void decrement(){
		lock.lock();
		try {
			//当数量为0时就等待生产
			while(num==0){
				System.out.println(Thread.currentThread().getName()+" 发现没有商品,等待商品生产..");
				condition.await();
			}
			System.out.println(Thread.currentThread().getName()+" 开始消费..耗时2s");
			TimeUnit.SECONDS.sleep(2);
			num--;
			System.out.println(Thread.currentThread().getName()+" 消费后通知生产者..");
			condition.signal();
		} catch (Exception e) {
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}
}

线程生产者消费者阻塞队列版

使用阻塞队列实现
生产和消费时可以自动阻塞, 通过改变生产的睡眠时间来观察超时获取的效果
同样需要使用while进行判断

public class ConsumerProducerAdvanced {
	public static void main(String[] args) {
		ResourceTargetAdvanced r = new ResourceTargetAdvanced(new ArrayBlockingQueue<Integer>(1));
		new Thread(()->{
			System.out.println("生产者 start");
			r.increament();
		},"生产者").start();
		
		new Thread(()->{
			System.out.println("消费者 start \n \n");
			r.decreament();
		},"消费者").start();
		
		try {
			TimeUnit.SECONDS.sleep(5);
			r.FLAG = false;
			System.out.println("5时已到,将FLAG标识改为false");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
	}
}
class ResourceTargetAdvanced{
	private BlockingQueue<Integer> queue;
	private AtomicInteger atomic = new AtomicInteger();
	public static volatile boolean FLAG = true;
	public ResourceTargetAdvanced(BlockingQueue<Integer> queue){
		this.queue = queue;
	}
	public void increament(){
		Integer value = null;
		boolean valueFlag;
		while(FLAG){
			try {
				value = atomic.getAndIncrement();
				valueFlag = queue.offer(value,2L,TimeUnit.SECONDS);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName()+" 插入元素:"+value);
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+" 停工,flag:"+FLAG);
	}
	public void decreament(){
		Integer value =null;
		while(FLAG){
			try {
				value = queue.poll(2,TimeUnit.SECONDS);
				/*try {
					TimeUnit.SECONDS.sleep(2);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}*/
				if(null==value){
					System.out.println(Thread.currentThread().getName()+" 获取元素超时,停工");
					FLAG = false;
				}else{
					System.out.println(Thread.currentThread().getName()+" 获取到元素:"+value);
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName()+" 停工,flag:"+FLAG);
	}
}

在这里插入图片描述

Callable

callable是线程创建的另一种方式;
callable同样需要类去实现, 相比runnable来说, callable向外抛出异常并返回结果;
从结构上看callable与runnable同级, 但callable的实现类需要借助FutureTask才可以放入Thread执行
在这里插入图片描述

public class CallableDemo {
	public static void main(String[] args) {
		FutureTask task = new FutureTask(new Task());
		new Thread(task).start();
		new Thread(task).start();
		/*
		FutureTask task = new FutureTask(new Task());
		FutureTask task2 = new FutureTask(new Task());
		new Thread(task).start();
		new Thread(task2).start(); 
		*/
	}
	@Test
	public void getTest(){
		FutureTask<Integer> task = new FutureTask(new Task());
		new Thread(task).start();
		try {
			Integer integer = task.get();
			System.out.println(Thread.currentThread().getName()+" main...");
		} catch (Exception e) {
			e.printStackTrace();
		}
		/*
		 * result:
		 	Thread-0 start...
			main main... 
		 */
	}
	@Test
	public void getTest2(){
		FutureTask<Integer> task = new FutureTask(new Task());
		new Thread(task).start();
		try {
			System.out.println(Thread.currentThread().getName()+" main...");
			Integer integer = task.get();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
class Task implements Callable<Integer>{
	@Override
	public Integer call() throws Exception {
		TimeUnit.SECONDS.sleep(2);
		System.out.println(Thread.currentThread().getName()+" start...");
		return 1024;
	}
}

对同一个futuretask , 同一时间仅能有一个运行

FutureTask task = new FutureTask(new Task());
new Thread(task).start();
new Thread(task).start();

在这里插入图片描述


通过get方法可以获取callable任务返回值, 但是在获取时, 如果callable任务没有执行完, 将阻塞当前线程直到获取callable任务执行结果

Integer integer = task.get();
System.out.println(Thread.currentThread().getName()+" main...");

可以通过这种方式在外部方法最后判断任务执行状态, 不会造成当前线程阻塞

while(!task.isDone()){
	Integer integer = task.get();
	System.out.println(integer);
}

get() 还可以设置阻塞超时时间, 超时后将抛出超时异常
在这里插入图片描述


线程池

构造配置

池化的优势在于控制资源及执行效率;
线程池结构, 最上层接口为Excutor, 实现有线程池和可调度的线程池, 具体有ThreadPoolExcutor实现,

在这里插入图片描述
可通过线程池工具类Excutors快速创建线程池
在这里插入图片描述
都是通过 ThreadPoolExcutor创建, 前两个参数是线程池中的核心线程与最大线程数
1.FixedThreadPool(int)
核心与最大相同, 固定长度的线程池
固定数量的线程池

2.SingleThreadExecutor()
只有一个线程的线程池
在这里插入图片描述

3.CachedThreadPool()
相当于无边界的线程池
在这里插入图片描述
ThreadPoolExecutor构造参数
在这里插入图片描述

  1. 核心线程数
    池创建时初始化的线程, 即使处于空闲也不会销毁
  2. 最大线程数
    当核心线程满时, 会继续创建的线程, 最大值不超过最大线程数
  3. 存活时长
    核心线程以外的线程, 如果没有执行任务, 并且等待时间等于存活时长, 就会被销毁
  4. 时间单位
    秒,分…
  5. 阻塞队列
    阻塞队列, 用于接收任务
  6. 线程工厂
    可以用来初始化线程时自定义配置, 比如线程名字
  7. 拒绝策略
    当阻塞队列已满时, 之后提交的任务不会再进入队列, 而是执行该策略

一个线程被提交到线程池并执行的流程为:
核心线程是否已满 , 未满直接执行, 已满判断最大线程数
->最大线程是否已满, 已满进入阻塞队列
->阻塞队列是否已满 ,满了执行拒绝策略

拒绝策略

jdk默认提供了4中策略 , 可以通过实现RejectedExecutorHandler实现自己的策略, rejectedExecution()方法

  1. abortPolicy(默认) 直接抛异常
 /**
     * A handler for rejected tasks that throws a
     * {@code RejectedExecutionException}.
     */
    public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }
  1. callerRunsPolicy
    退回调用者, r.run() , 并没有使用线程池去运行, 而是用的调用方的线程
/**
     * A handler for rejected tasks that runs the rejected task
     * directly in the calling thread of the {@code execute} method,
     * unless the executor has been shut down, in which case the task
     * is discarded.
     */
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }
  1. DiscardOldestPolicy
    抛弃队列中等待最久的任务, 然后加入队列并尝试运行
 /**
     * A handler for rejected tasks that discards the oldest unhandled
     * request and then retries {@code execute}, unless the executor
     * is shut down, in which case the task is discarded.
     */
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }
  1. DiscardPolicy
    直接丢弃任务, 不处理也不抛异常, 也就是拒绝策略中什么也没写
/**
     * A handler for rejected tasks that silently discards the
     * rejected task.
     */
    public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

实际使用

通过工具类创建出的线程池不推荐在项目中使用
以下引用阿里开发手册中对并发的要求

  1. 【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
    说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。
    如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
  2. 【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这 样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors 返回的线程池对象的弊端如下: 1)
    FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为
    Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 2) CachedThreadPool: 允许的创建线程数量为
    Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM

自定义线程池

实际使用中一般是自己创建ThreadPoolExcutor

private volatile static ExecutorService service = 
				new ThreadPoolExecutor(
							corePoolSize,
							maximumPoolSize, 
							60L, 
							TimeUnit.SECONDS, 
							new ArrayBlockingQueue<Runnable>(workQueueSize));

核心数
最大
拒绝策略一般选择
在这里插入图片描述


这是一个CallerRunsPolicy策略demo
线程池最大线程3, 阻塞队列为1, 额外闲置时长为3s;

public class ThreadPoolDemo {
	public static void main(String[] args) {
		ExecutorService service = new ThreadPoolExecutor(1,3,3,TimeUnit.SECONDS, 
				new ArrayBlockingQueue<Runnable>(1),
//				new MyThreadFactory("ordinary"),
				new ThreadPoolExecutor.CallerRunsPolicy());
		
		for (int i = 0; i < 7; i++) {
			service.submit(() -> {
				System.out.println(Thread.currentThread().getName()
						+ ": start and sleep 1s");
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (Exception e) {
					e.printStackTrace();
				}
			});
		}
		System.out.println("线程数为:"+Thread.activeCount());
		boolean flag= true;
		while(flag = Thread.currentThread().activeCount()>2){}
		System.out.println("线程数为:"+Thread.activeCount());
		service.shutdown();
	}
}

可以看到线程池中同一时间仅能跑一个任务, 阻塞队列中也能存一个, 第3个被提交的任务根据拒绝策略返回给了main线程, 通过main线程的资源去执行了任务, 而且因为后面提交的任务也碰到了相同的情况, 所以又返回给了main线程
如果返回给main线程的任务, main线程正在睡眠阻塞的话, 返回的任务会等到main线程运行完再去运行;
核心以外的线程到达了闲置时间及被销毁, 线程池收缩, 核心线程被回收, 不再活跃;
线程数最后为2因为除了main线程之外, 还有一个GC垃圾回收线程;
在这里插入图片描述


线程池配置

线程池配置需要根据自己的业务场景, 硬件配置来调整参数
cpu密集&io密集

  1. cpu密集型
    cpu性能还有利用率比内存和硬盘读写要高, 单个线程中的任务量大, 需要进行大量的计算工作, 这种情况下, 保证cpu的一个核尽量维持在这个线程进行计算处理效果最佳, 如果线程数量较多, 每个线程都在争抢资源, 导致线程间切换频繁, 既不利于任务中的计算, 也会将性能浪费在上下文切换上, 但实际情况肯定会有其他线程, 所以要尽可能降低线程的数量;
  2. io密集型
    内存和硬盘读写利用率比cpu要高, 像读取大文件, 进行网络传输这种动作, 都需要阻塞线程, 等待动作创建完对象实例, cpu的利用率低, 需要开多线程进行读写, 保证磁盘的io利用率, 提升效率;

对于cpu密集型的: 最大线程数采用cpu核数+1配置;
io密集型的: cpu核数*2 或 cpu核数 / (0.1~0.2)

Runtime对象可以提供硬件资源 , 系统等一些有用的信息

System.out.println(Runtime.getRuntime().availableProcessors());

自定义线程工厂

默认的线程工厂是这个
使用了原子类计数和默认名称前缀, 还有当前线程所在的组进行命名 ,创建出了新的线程
在这里插入图片描述

public class ThreadPoolDemo {
	public static void main(String[] args) {
		ExecutorService service = new ThreadPoolExecutor(1,3,3,TimeUnit.SECONDS, 
				new ArrayBlockingQueue<Runnable>(1),
				new MyThreadFactory("ordinary"),
				new ThreadPoolExecutor.CallerRunsPolicy());
		
		for (int i = 0; i < 7; i++) {
			service.submit(() -> {
				System.out.println(Thread.currentThread().getName()
						+ ": start and sleep 1s");
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (Exception e) {
					e.printStackTrace();
				}
			});
		}
		System.out.println("线程数为:"+Thread.activeCount());
		boolean flag= true;
		while(flag = Thread.currentThread().activeCount()>2){}
		System.out.println("线程数为:"+Thread.activeCount());
		service.shutdown();
	}
}

class MyThreadFactory implements ThreadFactory {
	private final String namePrefix;
	private final AtomicInteger nextId = new AtomicInteger(1);

	// 定义线程组名称,在利用 jstack 来排查问题时,非常有帮助
	public MyThreadFactory(String whatFeatureOfGroup) {
		namePrefix = "From testThreadFactory's " + whatFeatureOfGroup
				+ "-Worker-";
	}

	@Override
	public Thread newThread(Runnable task) {
		String name = namePrefix + nextId.getAndIncrement();
		Thread thread = new Thread(null, task, name, 0);
//		System.out.println(thread.getName());
		return thread;
	}
}

在这里插入图片描述


线程问题排查

通过线程池统一规范命名后, 通过jstack方便定位线程所在代码

死锁案例

public class JstackDemo {
	public static void main(String[] args) {
		Object a = new Object();
		Object b = new Object();
		Thread thread1 = new Thread(new Mythread(a,b),"thread1");
		Thread thread2 = new Thread(new Mythread(b,a),"thread2");
		ExecutorService service = 
				new ThreadPoolExecutor(2,3,3,TimeUnit.SECONDS, 
		new ArrayBlockingQueue<Runnable>(1),
		new PBThreadFactory("pbpool"));
		
		service.execute(thread1);
		service.execute(thread2);
	}
	static class Mythread extends Thread{
		Object a;
		Object b;
		public Mythread(Object a,Object b){
			 this.a = a;
			 this.b = b;
		}
		/* (non-Javadoc)
		 * @see java.lang.Thread#run()
		 */
		@Override
		public void run() {
			System.out.println(Thread.currentThread().getName()+"未持有锁,尝试获取锁a");
			synchronized (a) {
				System.out.println(Thread.currentThread().getName()+"持有锁a, 尝试获取锁b");
				synchronized (b) {
					System.out.println(Thread.currentThread().getName()+"未产生死锁");
				}
			}
		}
	}
	static class PBThreadFactory implements ThreadFactory {
		private static final AtomicInteger poolNumber = new AtomicInteger(1);
	    private final ThreadGroup group;
	    private final AtomicInteger threadNumber = new AtomicInteger(1);
	    private final String namePrefix;

	    PBThreadFactory(String poolName) {
	        SecurityManager s = System.getSecurityManager();
	        group = (s != null) ? s.getThreadGroup() :
	                              Thread.currentThread().getThreadGroup();
	        namePrefix = "From PBThreadFactory's poolName-" +
	                      poolNumber.getAndIncrement() +
	                     "-thread-";
	    }

	    public Thread newThread(Runnable r) {
	        Thread t = new Thread(group, r,
	                              namePrefix + threadNumber.getAndIncrement(),
	                              0);
	        //是否为守护线程
	        if (t.isDaemon())
	            t.setDaemon(false);
	        //线程优先级
	        if (t.getPriority() != Thread.NORM_PRIORITY)
	            t.setPriority(Thread.NORM_PRIORITY);
	        return t;
	    }
	}
}

win环境查询
pid可以通过jconsole获取
在这里插入图片描述

jstack -l pid  

在这里插入图片描述
死锁情况
在这里插入图片描述

定位java线程

jstack命令
linux环境
1.top展示进程
在这里插入图片描述
2.top展示cpu利用率高线程

top -H -p 进程号 

在这里插入图片描述
3.转换成线程号
在这里插入图片描述
4.jstack 展示具体线程栈信息

jstack 17850|grep 45d8 -A 30

在这里插入图片描述

AQS

AbstractQueuedSynchronizer是java并发包的抽象框架, 描述了并发中线程对共享/非共享资源的获取方式, 使用了模板模式给出了默认实现, 其他类继承后只需要实现自己逻辑相关的方法即可, 常以各种lock的内部类方式存在
推荐一篇AQS的文章

LockSupport

顾名思义, 锁的工具类, 依赖于unsafe类进行原子操作, 方便对线程进行阻塞, 释放操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值