1. Semaphore
限制可以访问某些资源(物理或逻辑的) 线程数目,
- 实际上是就是普通共享锁,内部静态类 Sync 继承了 AQS,实现了共享锁的 tryAcquireShared、tryReleaseShared。
- 逻辑就是正常共享锁逻辑,同时还可以实现公平和非公平
- 可以说是 ReentrantLock 的共享版,所以注意要在 finally 里面调用 semaphore.release() 释放锁
1.1 主要方法摘要:
void acquire():从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
- void acquire():从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
- void release():释放一个许可,将其返回给信号量。
- int availablePermits():返回此信号量中当前可用的许可数。
- boolean hasQueuedThreads():查询是否有线程正在等待获取。
1.2 举例
public class DasApplicationTests {
public static void main(String[] args) {
/**
* 1. 初始化线程池 创建一个可缓存线程池 如果线程池长度超过处理需要,
*/
ExecutorService service = Executors.newCachedThreadPool();
// 2. 创建Semaphore 信号量 初始化许可大小为 3
final Semaphore sp = new Semaphore(3);
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Runnable runnable = new Runnable() {
@Override
public void run() {
// 请求获取许可,如果有可获取的许可 则继续往下执行,许可数减1 否则进入阻塞状态
try {
sp.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:" + Thread.currentThread().getName() + "进入,当前有" + (3 - sp.availablePermits()) + "个并发");
try {
Thread.sleep((long) (Math.random() * 10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + Thread.currentThread().getName() +
"即将离开");
sp.release();//释放许可,许可数加1
//下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元
System.out.println("线程" + Thread.currentThread().getName() +
"已离开,当前已有" + (3 - sp.availablePermits()) + "个并发");
}
};
/**
* execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
*/
service.execute(runnable);
/**
*submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,
* 通过这个 Future 对象可以判断任务是否执行成功 ,并且可以通过 Future 的 get()方法来获取返回值,
* get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)
* 方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
*/
// Future<?> submit = service.submit(runnable);
}
/**
* shutdown() 关闭线程池 线程池状态变为SHUTDOWN 。线程池不在接收新任务 但是队列里的任务得执行完毕
*
*/
service.shutdown();
/**
* shutdownnow() 关闭线程池 线程状态变为 STOP 线程池会终止当前正在执行的任务,并停止处理排队的任务,并返回正在等待执行的list
*/
// List<Runnable> runnables = service.shutdownNow();
while (true){
// isShutDown 当调用shutdown() 方法后返回 true
// isTerminated 当调用 shutdown() 方法后,并且所有提交的任务后 返回true
if(service.isTerminated()){
System.out.println("线程池任务执行结束!!!");
break;
}
}
System.out.println(" 主线程执行结束");
}
1.3 线程池
线程池底层维护了一个用于存放待执行任务的队列,和已被实例化的几个线程的线程池。调用线程的execute() 方法,则会通过线程池中的线程执行任务。如果任务过多,没有可用的线程,则任务会被放入线程池,等待之前被执行的任务执行结束。有空闲的线程可用,接着执行后续提交的任务。
1.3.1 Executors
主要用于提供线程池相关的操作,Executors类,提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口。
- public static ExecutorService newFiexedThreadPool(int Threads) 创建固定数目线程的线程池。
- public static ExecutorService newCachedThreadPool():创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果没有可用的线程,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。缓存型池子通常用于执行一些生存期很短的异步型任务。
- public static ExecutorService newSingleThreadExecutor():创建一个单线程化的Executor。任意时间,池子里只有一个线程。
- public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
1.4 几个常见的对比
1.4.1 Runnable 和 Callable
- Runnable 接口不会返回结果或者抛出检查异常,
- Callable 接口可以返回结果和抛出异常,
工具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的转换。
- Executors.callable(Runnable task)
- Executors.callable(Runnable task,Object resule)
1.4.2 execute() 和 submit()
- execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否;
- submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功 ,并且可以通过 Future 的 get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
1.4.3 shutdown() 和 shutdownNow()
- shutdown() :关闭线程池,线程池的状态变为 SHUTDOWN。线程池不再接受新任务了,但是队列里的任务得执行完毕。
- shutdownNow() :关闭线程池,线程的状态变为 STOP。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List。
1.4.4 isTerminated() VS isShutdown()
- isShutDown 当调用 shutdown() 方法后返回为 true。
- isTerminated 当调用 shutdown() 方法后,并且所有提交的任务完成后返回为 true