定义任务:
一个线程驱动一个任务,你需要一个方式来描述这种任务。可以通过实现Runnable接口的run方法来定义一个任务。
package concurrency;
public class LiftOff implements Runnable {
protected int countDown = 10;
private static int taskCount = 0;
private final int id = taskCount++;
public LiftOff() {
}
public LiftOff(int countDown) {
this.countDown = countDown;
}
public String status() {
return "#" + id + "(" +
(countDown > 0 ? countDown : "LiftOff!") + "),";
}
@Override
public void run() {
while (countDown-- > 0) {
System.out.println(status());
Thread.yield();
}
}
}
- 直接调用
package concurrency; public class MainThread { public static void main(String[] args) { LiftOff liftOff = new LiftOff(); liftOff.run(); } }
在这个例子中,这个run方法没有被其他线程驱动,只是使用main方法驱动,只在主线程上运行。
- Thread Class
package concurrency; public class MainThread { public static void main(String[] args) { Thread t = new Thread(new LiftOff()); t.start(); System.out.println("waiting for lift off"); } }
传统的方式是把Runable对象送入Thread对象中。Thread会自动配置并且自动call,thread里面的run()方法来开始任务。
- 更多的线程
package concurrency; public class MoreBasicThreads { public static void main(String[] args) { for (int i = 0; i < 5; i++) { new Thread(new LiftOff()).start(); System.out.println("waiting for lift off"); } } }
结果:
waiting for lift off
waiting for lift off
waiting for lift off
waiting for lift off
waiting for lift off
#0(9),#1(9),#2(9),#4(9),#3(9),#0(8),#1(8),#2(8),#4(8),#3(8),#0(7),#1(7),#2(7),#4(7),#3(7),#0(6),#1(6),#2(6),#4(6),#3(6),#0(5),#1(5),#2(5),#4(5),#3(5),#0(4),#4(4),#2(4),#0(3),#4(3),#1(4),#3(4),#2(3),#0(2),#1(3),#3(3),#4(2),#2(2),#0(1),#1(2),#3(2),#4(1),#2(1),#1(1),#0(LiftOff!),#2(LiftOff!),#1(LiftOff!),#4(LiftOff!),#3(1),#3(LiftOff!),
Process finished with exit code 0
可以看到主线程和子线程交错执行!在这里我们的id是不同的,是因为我们是用一个主线程创建的那么多线程,不涉及线程同步的问题。 -
使用 executors
-
Executors通过为你管理线程Thread对象来简化了并发编程。Executors为客户端(client)和任务的执行提供了非直接的一层。客户端不是直接执行一个任务,而是一个中间对象来执行这个任务。
Executors允许你在不显示管理线程对象的生命周期的情况下管理异步的任务! - 使用Executors有一点像命令模式,LiftOff知道怎么执行这个任务,ExecutorService提供了执行的环境
public class MoreBasicThreads { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++) { executorService.execute(new LiftOff()); } executorService.shutdown(); } }
-
- 线程池的种类
- FixedThreadPool: 使用一个limited数量的线程来执行提交的任务
- 好处:线程只分配一次,限制线程的数量。
- 在一个事件驱动的系统,事件处理器可以通过从线程池中取一个线程来应对请求。也因此不会超出avaiable的资源!
- CachedThreadPools
- 会创建需要数量的线程。当可以重复使用现有的线程的时候停止创建!
- SingleThreadExecutors
- 是一个固定长度大小为1的线程池。
- 这对一个你想要持续在另一个线程中执行任务很有用。
- 例如:一个任务监听将要来到的socket连接。
- 如果超过一个任务被提交到SingleThreadExecutor,任务将会被排序。一个一个任务被完成
- 代码:
public class MoreBasicThreads { public static void main(String[] args) { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 5; i++) { executorService.execute(new LiftOff()); } executorService.shutdown(); } }
- FixedThreadPool: 使用一个limited数量的线程来执行提交的任务
- 从任务中获得返回值
- Runnable 接口的任务没法有返回值,我们可以使用Callable接口。通过方法call()来拿到返回值。必须使用线程池来调用。
-
public class TaskWithResult implements Callable<String> { private int id; public TaskWithResult(int id) { this.id = id; } @Override public String call() throws Exception { Thread.sleep(id * 1000); return "result of Task with Result " + id; } } public class TestCallable { public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); //Executors.callable() 可以使用 runnable 对象 ArrayList<Future<String>> results = new ArrayList<>(); for (int i = 0; i < 10; i++) { results.add(executorService.submit(new TaskWithResult(i))); } for (Future<String> result : results) { try { System.out.println(result.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } }
3. submit 方法的返回值会是一个Future对象。你可以使用isdone方法来查询future对象的任务有没有完成。如果你的任务完成了并且有返回值,那么你可以用get()方法来拿这个值。