总结部分多线程代码
1. CountDownLath
同时等待n个任务执行结束
public static void main1(String[] args) {
CountDownLatch latch = new CountDownLatch(10);
Runnable r = new Runnable() {
@Override
public void run() {
try {
Thread.sleep((int)Math.random()*100);
System.out.println(Thread.currentThread().getName());
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for(int i=0;i<10;i++){
new Thread(r).start();
}
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("比赛结束");
}
2. Callable
Callable 是一个interface,相当于吧线程封装了一个“返回值”
public static void main(String[] args) {
Callable<Integer> call = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int num=0;
for(int i=1;i<=100;i++){
num+=i;
}
return num;
}
};
FutureTask<Integer> futureTask = new FutureTask<>(call);
Thread t=new Thread(futureTask);
t.start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
3. Atomic 相关类
public static void main2(String[] args) {
AtomicInteger num = new AtomicInteger();
Thread t1 = new Thread(()->{
for(int i=0;i<10;i++){
num.getAndDecrement();
System.out.println(Thread.currentThread().getName()+num);
}
});
Thread t2 = new Thread(()->{
for(int i=0;i<10;i++) {
num.getAndDecrement();
System.out.println(Thread.currentThread().getName() + num);
}
});
t1.start();
t2.start();
}
4. 线程池
ExecutorService 和 Executors(对ThreadPoolExecutor类的封装)
ExecutorService 表示一个线程池实例.
Executors 是一个工厂类, 能够创建出几种不同风格的线程池.
ExecutorService 的 submit 方法能够向线程池中提交若干个任务.
Executors 创建线程池的几种方式
newFixedThreadPool: 创建固定线程数的线程池
newCachedThreadPool: 创建线程数目动态增长的线程池.
newSingleThreadExecutor: 创建只包含单个线程的线程池.
newScheduledThreadPool: 设定 延迟时间后执行命令,或者定期执行命令. 是进阶版的 Timer.
submit 它可以执行有返回值的任务或者是无返回值的任务;而 execute 只能执行不带返回值的任务。
第二个那个补充,突发的带有大量任务的情况下使用这种方式会比较合适,因为它可以根据任务量来帮你分配线程数并且对资源的分配也相对比较合适。
虽然创建销毁线程比创建销毁进程更轻量, 但是在频繁创建销毁线程的时候还是会比较低效。如果某个线程不再使用了, 并不是真正把线程释放, 而是放到一个 “池子”
中, 下次如果需要用到线程就直接从池子中取, 不必通过系统来创建了.
ThreadPoolExecutor
- corePoolSize: 正式员工的数量. (正式员工, 一旦录用, 永不辞退)
- maximumPoolSize: 正式员工 + 临时工的数目. (临时工: 一段时间不干活, 就被辞退).
- keepAliveTime: 临时工允许的空闲时间.
- unit: keepaliveTime 的时间单位, 是秒, 分钟, 还是其他值.
- workQueue: 传递任务的阻塞队列,比如基于数组的有界 ArrayBlockingQueue、基于链表的无界 LinkedBlockingQueue、最多只有一个元素的同步队列 SynchronousQueue 及优先级队列 PriorityBlockingQueue 等。
- threadFactory: 创建线程的工厂, 参与具体的创建线程工作.
- RejectedExecutionHandler: 拒绝策略, 如果任务量超出公司的负荷了接下来怎么处理.
AbortPolicy(): 超过负荷, 直接抛出异常.
CallerRunsPolicy(): 调用者负责处理,使用调用线程池的这个线程帮助执行这个任务
DiscardOldestPolicy(): 丢弃队列中最老的任务.
DiscardPolicy(): 丢弃新来的任务.
也可以自定义
shutdown VS shutdownNow
shutdown 执行时线程池终止接收新任务,并且会将任务队列中的任务处理完;
shutdownNow 执行时线程池终止接收新任务,并且会给终止执行任务队列中的任务。
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolDemo9 {
public static void main(String[] args) {
ThreadFactory factory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
return thread;
}
};
// 手动方式创建线程池
ThreadPoolExecutor executor =
new ThreadPoolExecutor(5,10,10, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100),factory,
new ThreadPoolExecutor.DiscardPolicy());
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
System.out.println("线程名称:" + Thread.currentThread().getName());
});
}
}
}
线程工作流程
面试题:为什么在线程池执行流程中,当任务数大于核心线程数时,是先判断阻塞队列是否已满,如果阻塞队列没有满是先将任务放进阻塞队列等待执行,而不是直接判断当前线程数是否小于最大线程数去直接创建线程执行的。
线程池的执行机制是会延迟创建线程的,
那么为什么会选择延迟创建,这是和线程池资源利用的策略有关系的,也就是并不是在创建线程池时候直接创建好核心线程数量的线程的,
而是在有任务来了之后,线程工厂才会根据任务需要去创建线程,而先进行任务队列的判断也就是为了保证资源利用的最大化和开销的最小化
。
- 当任务来了之后,判断线程池中实际线程数是否小于核心线程数,如果小于就直接创建线程并执行任务。
- 当实际线程数大于核心线程数,他会判断任务队列是否已满,如果未满直接将任务存放队列即可。
- 判断线程池的实际线程数是否大于最大线程数,如果小于最大线程数直接创建线程执行任务;如果小于,采取线程池拒绝策略
5. 单例模式
保证一个类在系统中只有一个实例,并提供一个访问它的全局访问点。
- 私有的构造方法
- 指向自己实例的私有静态引用
- 以自己的实例为返回值的静态的公共的方法
线程池、缓存、对话框、打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。
为什么不使用全局变量确保一个类只有一个实例呢
全局变量分为静态变量和实例变量,静态变量也可以保证该类的实例只存在一个。只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。
但是,如果说这个对象非常消耗资源,而且程序某次的执行中一直没用,这样就造成了资源的浪费。
例如单例模式的话,我们就可以实现在需要使用时才创建对象,这样就避免了不必要的资源浪费。不仅仅是因为这个原因,在程序中我们要尽量避免全局变量的使用,大量使用全局变量给程序调试、维护等带来困难。
6.信号量Semaphore
信号量,用来表示“可用资源的个数” ,本质上是一个计数器
。
Semaphore 的 PV 操作中的加减计数器操作都是原子的, 可以在多线程环境下直接使用.
创建 Semaphore 示例, 初始化为 4, 表示有 4 个可用资源.
acquire 方法表示申请资源(P操作), release 方法表示释放资源(V操作)
创建 20 个线程, 每个线程都尝试申请资源, sleep 1秒之后, 释放资源. 观察程序的执行效果.