目录
一 多线程的作用
从一个简单的问题来进入多线程: 多线程有什么作用,用它来干什么?
(1)发挥多核CPU的优势
现在的电脑至少也都是双核的,4核、8核甚至16核的,如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。单核CPU上所谓的”多线程”那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程”同时”运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。
(2)防止阻塞
从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。
二 如何创建一个线程
一般两种
(1)继承Thread类
(2)实现Runnable接口
实现Runnable接口会好一点,因为实现接口的方式比继承类的方式更灵活,也能减少程序之间的耦合度
1.继承Thread类
来一个简单的例子看下如何使用
DemoThread
class DemoThread extends Thread {
@Override public void run() {
System.out.println("非主线程运行");
}
}
public static void main(String[] args)
DemoThread thread = new DemoThread();
thread .start();
System.out.println("主线程运行");
}
2.实现Runnable接口
DemoRunnable.java
class DemoRunnable implements Runnable {
@Override public void run() {
System.out.println("非主线程运行");
}
}
public static void main(String[] args) {
Thread thread = new Thread(new DemoRunnable);
thread.start();
System.out.println("主线程运行");
}
三 有返回值的情况
如果多线程有返回值该如何处理呢?
对此 JDK1.5加入了Callable方法,它们的主要区别是 Callable 的 call() 方法可以返回值和抛出异常,而 Runnable 的 run() 方法没有这些功能。Callable 可以返回装载有计算结果的 Future 对象,下面我们来看下Callable如何使用
DemoCallable
四 线程池使用
JDK1.5 之后有个JUD java 并发包 提供了一个执行器,Executor为我们管理Thread对象,简化并发编程 看一个简单的使用例子:
线程池的优点
1)避免线程的创建和销毁带来的性能开销。
2)避免大量的线程间因互相抢占系统资源导致的阻塞现象。
3}能够对线程进行简单的管理并提供定时执行、间隔执行等功能。
public class CachedThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i = 0;i < 5;i++) {
executorService.execute(new LiftOff());
}
executorService.shutdown();
}
}
Executor同样可以使我们可以管理异步任务的执行,而无需显示的管理线程的生命周期,是比较推荐的方法。在最后记得调用shutdown()方法 ,防止不断创建线程
线程也有不同的执行器,我们简单列举下 比较下区别
-
1)newCachedThreadPool 是一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute() 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。注意,可以使用 ThreadPoolExecutor 构造方法创建具有类似属性但细节不同(例如超时参数)的线程池。
-
2)newSingleThreadExecutor 创建是一个单线程池,也就是该线程池只有一个线程在工作,所有的任务是串行执行的,如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它,此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
-
3)newFixedThreadPool 创建固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小,线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
-
4)newScheduledThreadPool 创建一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。
线程池返回值实例
CallableDemo.java
public class CallableDemo {
public static void main(String[] args) {
//使用线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
//创建返回的Array
ArrayList<Future<String>> results = new ArrayList<Future<String>>();
for (int i = 0; i < 10;i++) {
results.add(executor.submit(new TaskWithResult(i)));
}
for(Future<String> fs : results){
try {
System.out.println(fs.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
executor.shutdown();
}
}
}
}
TaskWithResult.java
public class TaskWithResult implements Callable<String>{
private int id;
public TaskWithResult(int id) {
this.id = id;
}
@Override
public String call() throws Exception {
return "result of TaskWithResult " + id;
}
}
注意调用的方法是 ExecutorService.submit()。