java并发编程

不堪回首的面试中,几乎都有问 Java,或是几乎都在问 Java,O__O"… Java 在企业中使用的太多,更重要的也许是因为它 open source 勒。相对来说,c++ 就不大成功,语言极其复杂;而 c# 的可移植性则是它的最大问题,虽然 linux 或是 mac 下也有移植的项目 Mono 。本篇就是关于 Java 并发多线程的部分,因为 me 最初面试的时候,人家问的就是多线程,而且 me 回答的很不好,O__O"…

  1. 创建线程
  2. 线程池
  3. 线程本地存储

A. 创建线程

Java 中创建线程有三种方式,一种是继承 java.lang.Thread 类,重写其中的 public void run() 方法;另一种是实现 java.lang.Runnable 接口,同样重写 public void run() 方法。这两种线程执行方法是不带返回值的,所以 java 另外提供了一种 Callable 的创建方法。

A.1 继承 Thread 类

  1. package test;
  2.  
  3. import java.lang.Thread;
  4.  
  5. class Task extends Thread{
  6.     public void run(){
  7.         System.out.println("hello,task!");
  8.     }
  9. }
  10.  
  11. class Hello{
  12.     public static void main(String[] args){
  13.         Thread th = new Task();
  14.         th.start(); // 启动线程执行
  15.     }
  16. }

A.2 实现 Runnable 接口

  1. package test;
  2.  
  3. import java.lang.Thread;
  4.  
  5. class Task implements Runnable{
  6.     public void run(){
  7.         System.out.println("hello, my name is: " + Thread.currentThread().getName());    // print current thread's name
  8.     }
  9. }
  10.  
  11. class Hello{
  12.     public static void main(String[] args){
  13.         for(int i = 0; i < 10; ++i){
  14.             Thread th = new Thread(new Task());
  15.             th.start();    // start thread
  16.         }
  17.     }
  18. }

A.3 使用 Callable + Future

  1. package test;
  2.  
  3. import java.util.concurrent.Callable;package test;
  4.  
  5. import java.util.concurrent.Callable;
  6. import java.util.concurrent.FutureTask;
  7.  
  8. class Task implements Callable<Integer>{
  9.     public Integer call() throws Exception {
  10.         return 42;
  11.     }
  12. }
  13.  
  14. class Hello{
  15.     public static void main(String[] args){
  16.         FutureTask<Integer> task = new FutureTask<Integer>(new Task());
  17.         new Thread(task).start();    // start
  18.         try{
  19.             Integer result = task.get();    // get result
  20.             System.out.println("callable result: " + result);
  21.         }catch(Exception e){
  22.             e.printStackTrace();
  23.         }
  24.     }
  25. }

实现 Callable 的 call() 方法,需要 Thread 来执行线程,同时借助 Future 来获取线程的执行结果。FutureTask 同时实现了 Runnable 接口和 Future 接口,所以可以作为 Thread() 的参数传递。

Thread 的常用构造方法:

  • Thread() :默认构造方法;
  • Thread(Runnable target):带 target 的构造方法;
  • Thread(String name):带 name 的构造方法;name 作为线程名字;
  • Thread(Runnable target, String name):带 target 和 name 的构造方法;

Java 线程跟其他语言的线程可能用法上有些诧异,这里补充说明一点。

  • 创建线程之后,需要手工启动它,然后调度器才会对其进行调度:th.start(); 启动线程流;
  • main 线程流执行完之后会等待其他线程执行完毕(如果还有其他线程的话),而不是主动结束这个进程;
  • JVM 按线程的优先级调度线程执行,优先级 MIN_PRIORITY (0)、NORM_PRIORTY (5)和 MAX_PRIORITY (10);优先级相同的可能是轮流执行,也可能不是;int p = th.getPriority(); th.setPriority(p); 获取优先级和设置优先级;(0-10);

A.4 线程 API

  • Thread.currentThread() : 静态方法,获取当前执行线程;
  • Thread.sleep(ms); 当前线程休息或是说睡眠 ms 毫秒;Thread.sleep(ms,ns); 睡眠 ms毫秒+ns纳秒; sleep 睡眠的时间未必非常精确,它们也有可能被打断;
  • Thread.yield(); 当前线程让出 cpu 使用权,重新进行任务调度执行;
  • th.getId():获取线程 th 的 id;线程 id 是线程的标识,在线程存活期内唯一且不变;线程结束后 id 可能会被重用;
  • th.getName():获取线程 th 的名字;
  • th.join(); 当前线程等待 th 结束;th.join(ms);th.join(ms,ns); join 操作可能会抛出 InterruptException;所以要异常处理;join 和 sleep 的精度都依赖于底层系统,不应该认为绝对精确;
  • th.interrupt(); 打断 th,也许它在 sleep,或是在 join,或是在 wait;
  • th.isAlive(); 线程是不是还活着?没有 start() 的线程 isAlive() == false,执行结束的线程 isAlive() == false;
  • th.stop(); 强行结束 th 的执行;
  • th.suspend(); th.resume(); 挂起 th 线程和继续执行 th 线程;

B. 线程池

上面两种创建线程的方式自然是在执行多任务,但是通过手动创建线程并显式地管理线程的生命周期(比如 start() 线程)并不是太好的方式。显而易见,我们更应该组织一种线程池的工具,来回收线程重复利用等等。Java 提供了类似的工具 —— Executors。

B.1 线程池 Executor

  1. package test;
  2.  
  3. import java.util.concurrent.ExecutorService;
  4. import java.util.concurrent.Executors;
  5.  
  6. class Task implements Runnable{
  7.     private static long count = 0;
  8.     private final long id = ++count;
  9.  
  10.     public Task(){
  11.         System.out.println("#" + id +" task constructor.");
  12.     }
  13.     public void run(){
  14.         System.out.println("#" + id + " task exec.");
  15.     }
  16. }
  17.  
  18. public class Hello{
  19.     public static void main(String[] args){
  20.         ExecutorService executor = Executors.newCachedThreadPool();  // newFixedThreadPool(5), newSingleThreadExecutor()
  21.         for(int i = 0; i < 5; i++){
  22.             executor.execute(new Task());
  23.         }
  24.         executor.shutdown();    //
  25.     }
  26. }

Executors 主要有下面几类:

  • CachedThreadPool : 缓冲线程池,线程会重复利用,但如果需要就会不断地创建新的线程来满足新的task;
  • FixedThreadPool : 带固定数目线程的线程池,一开始便初始化线程池,然后重复利用;
  • SingleThreadExecutor : 单线程Executor,一次最多一个task执行,其他task等待;好处task执行序列化,不需要复杂的同步操作;

B.2 带返回值的线程 Callable

Runnable 的 run 方法是没有返回值的,也就是这种 task 只能默默滴干活,他们做出来的成绩无法直接反馈给其他人。当然可以使用共享变量来实现数据传递,但这有些别扭,其次数据竞争也是一个问题。 Java 提供的 Callable 正是为了为了解决这个问题滴。

  1. package test;
  2.  
  3. import java.util.LinkedList;
  4. import java.util.List;
  5. import java.util.concurrent.Callable;
  6. import java.util.concurrent.ExecutorService;
  7. import java.util.concurrent.Executors;
  8. import java.util.concurrent.Future;
  9.  
  10. class TaskResult implements Callable{
  11.     private static long count = 0;
  12.     private final long id = ++count;
  13.     public TaskResult(){
  14.         System.out.println("#" + id +" task constructor.");
  15.     }
  16.     @Override
  17.     public Long call(){
  18.         return id;
  19.     }
  20. }
  21. public class Hello{
  22.     public static void main(String[] args){
  23.         List<Future<Long>> futures = new LinkedList<Future<Long>>();
  24.         ExecutorService executor = Executors.newCachedThreadPool();  // newFixedThreadPool(5), newSingleThreadExecutor()
  25.         for(int i = 0; i < 5; i++){
  26.             Future<Long> future = executor.submit(new TaskResult());    // submit callable task
  27.             futures.add(future);
  28.         }
  29.         executor.shutdown();    //
  30.         for(Future<Long> future : futures){
  31.             Long result = null;
  32.             try{
  33.                 result = future.get();
  34.             }catch(Exception e){
  35.                 e.printStackTrace();
  36.             }finally {
  37.                 System.out.println(result);
  38.             }
  39.         }
  40.     }
  41. }

C. 线程本地存储

线程本地存储(thread local storage, TLS)就是线程级的存储,而非进程一级。在 run 或是 callable 中定义的局部变量是 TLS 的一种,除此之外 java 还提供了一种 ThreadLocal 变量。

使用方法也比较简单:

  • ThreadLocal local = new ThreadLocal();
  • local.set(42);
  • Integer v = local.get();
  • local.remove();

Tags: 

Article type: