并发编程1
一.如何创建一个线程?
1.继承Thread类、实现Runnable接口。
2.ExecutorService、Callable<T>、Future有返回结果的线程。
public static void main(String[] args) {
//创建线程池
ExecutorService pool = Executors.newFixedThreadPool(5);
//收集线程执行的结果
List<Future<Integer>> list = new ArrayList<>();
// 进行20次计算
for (int i = 0; i < 20; i++) {
MyCallable myCallable = new MyCallable(i);
Future<Integer> future = pool.submit(myCallable);
list.add(future);
}
//关闭线程池
pool.shutdown();
//输出计算结果
for (Future<Integer> integerFuture : list) {
try {
System.out.println(integerFuture.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
3.基于线程池的方式。
二:线程池
Java中线程池的顶级接口是Executor,严格意义上Executor是一个线程执行器接口。真正的线程池接口是ExecutorService。
2.1 newCachedThreadPool
创建一个可根据需要创建新线程的线程池,可以重用之前构造的线程。对于执行大量短期异步任务而言适合使用这个线程池。(调用execute时将优先重用空闲的之前构造好的线程,如果没有线程可以使用则创建一个新的线程执行任务。终止并从缓存中移除已经有60秒未被使用的线程)。所以长时间保持空线程池也不会造成资源浪费。
2.2 newFixedThreadPool
创建一个可重用的固定线程数量的线程池,以共享无界队列的方式来运行这些线程。如果在线程池关闭前某个线程执行期间由于失败或其他原因终止,则重新创建一个线程代替他执行任务。在线程池被显式的关闭前,池中的线程一直存在。
2.3 newScheduledThreadPool
创建一个线程池,他可安排在给定延时后运行命令或者循环执行命令。
public static void main(String[] args) {
ScheduledExecutorService scheduledPool = Executors.newScheduledThreadPool(5);
System.out.println("===============================");
// scheduledPool.schedule(()->{
// System.out.println("三秒后执行");
// },3, TimeUnit.SECONDS);
scheduledPool.scheduleAtFixedRate(()->{
System.out.println("1秒后执行一次,之后每两秒执行一次");
},1,2,TimeUnit.SECONDS);
// scheduledPool.shutdown();
}
2.4 newSingleThreadExecutor
创建一个仅有一个线程的线程池。在线程池关闭之前线程一直存在。如果因为异常被中断则新建一个线程继续执行任务。
三、线程的生命周期
new=>runnable=>running=>block=>running=>dead。wait
线程被new关键字创建出来之后在执行start方法之前都是new状态。调用start方法后进入就绪(runnable)状态,与其他线程一起竞争cpu时间片。抢到cpu时间片后在cpu执行计算期间为running状态。
阻塞状态(Blocked):
阻塞状态是指线程因为某些原因放弃了cpu的使用权,重新回到runnable状态。进入阻塞状态的三种方式
1.等待阻塞(o.wait->等待队列)
在运行时的线程中调用o.wait方法,JVM会把该线程让如等待队列(waitting queue)中。
2.同步阻塞(lock->锁池)
运行(running)中的线程在获取对象的同步锁的时候,若该同步锁被别的线程占有,JVM会把该线程放入该对象同步锁的锁池(lock pool)中。
3.其他阻塞(sleep/join)
运行(running)中的线程执行Thread.sleep(long ms)或者t.join方法,或者发出I/O请求时。JVM会把该线程设置为阻塞状态。当sleep超时、join线程执行中止或超时、或I/O处理完毕时,线程重新进入就绪(runnable)状态。
死亡状态(DEAD)
线程以一下三种方式结束后就是死亡状态。
1.正常结束。run()或call()方法执行结束。
2.异常结束。线程抛出了一个未捕获的Exception或Error。
3.调用stop。容易造成死锁不建议使用。
四、线程终止的四种方式
1.线程正常运行结束。
2.使用退出标志退出。可以搭配volatile关键字使用。
3.Interrupt
通过Interrupt终止线程有两种情况。
1.被终止线程处于阻塞状态,阻塞中的线程调用interrupt()方法时会抛出InterruptException异常。通过代码捕获该异常之后brak跳出循环状态,从而终止线程。仅调用interrupt()方法没有捕获异常并跳出循环是不会终止线程的。
2.被终止的线程没有处理阻塞状态则通过isInterrupt方法判断线程的中断标志来退出循环。当使用interrupt()方法时,中断标志会设置为true,和使用自定义的中断标志退出循环终止线程是一个道理。
所以interrupt终止线程时需要isInterrupt方法和异常捕获一起使用。
4.stop终止(线程不安全)不推荐使用。