关于一些基础的Java问题的解答(五)

21. 实现多线程的两种方法:Thread与Runable

在Java中实现多线程编程有以下几个方法:

1.继承Thread类,重写run方法

[java] view plain copy
 
  1. public class Test {  
  2.       
  3.     public static void main(String[] args) {  
  4.         new MyThread().start();  
  5.     }  
  6.       
  7.     private static class MyThread extends Thread {  
  8.         @Override  
  9.         public void run() {  
  10.             System.out.println("run!");  
  11.         }  
  12.     }  
  13.       
  14. }  
 

2.实现Runnable接口,作为参数传入Thread构造函数

[java] view plain copy
 
  1. public class Test {  
  2.       
  3.     public static void main(String[] args) {  
  4.         new Thread(new Runnable() {  
  5.               
  6.             @Override  
  7.             public void run() {  
  8.                 System.out.println("run!");  
  9.                   
  10.             }  
  11.         }).start();  
  12.     }  
  13.       
  14. }  

3.使用ExecutorService类

[java] view plain copy
 
  1. import java.util.concurrent.ExecutorService;  
  2. import java.util.concurrent.Executors;  
  3.   
  4. public class Test {  
  5.       
  6.     public static void main(String[] args) {  
  7.         ExecutorService service = Executors.newCachedThreadPool();  
  8.         service.execute(new Runnable() {  
  9.               
  10.             @Override  
  11.             public void run() {  
  12.                 System.out.println("Run!");  
  13.             }  
  14.         });  
  15.     }  
  16.       
  17. }  

22. 线程同步的方法:sychronized、lock、reentrantLock等

多线程编程时同步一直是一个非常重要的问题,很多时候我们由于同步问题导致程序失败的概率非常低,往往存在我们的代码缺陷,但他们看起来是正确的:
[java] view plain copy
 
  1. public class Test {  
  2.       
  3.     private static int value = 0;  
  4.       
  5.     public static void main(String[] args) {  
  6.         Test test = new Test();  
  7.         // 创建两个线程  
  8.         MyThread thread1 = test.new MyThread();  
  9.         MyThread thread2 = test.new MyThread();  
  10.         thread1.start();  
  11.         thread2.start();  
  12.     }  
  13.       
  14.     /** 
  15.      * 为静态变量value加2 
  16.      * @return 
  17.      */  
  18.     public int next() {  
  19.         value++;  
  20.         Thread.yield(); // 加速问题的产生  
  21.         value++;  
  22.         return value;  
  23.     }  
  24.       
  25.     /** 
  26.      * 判断是否偶数 
  27.      * @param num 
  28.      * @return boolean 是否偶数 
  29.      */  
  30.     public boolean isEven(int num) {  
  31.         return num % 2 == 0;  
  32.     }  
  33.       
  34.     class MyThread extends Thread {  
  35.         @Override  
  36.         public void run() {  
  37.             System.out.println(Thread.currentThread() + " start!");  
  38.             while(isEven(next()));  
  39.             System.out.println(Thread.currentThread() + " down!");  
  40.         }  
  41.     }  
  42.       
  43. }  

上面的代码创建了两个线程操作Test类中的静态变量value,调用next方法每次会为value的值加2,理论上来说isEven方法的返回值应该总是true,两个线程的工作会不停止的执行下去。但事实是:
线程停止
因此在我们进行多线程并发编程时,使用同步技术是非常重要的。

1.synchronized

Java以提供关键字synchronized的形式,为防止资源冲突提供了内置支持。当某个线程处于一个对于标记为synchronized的方法的调用中,那么在这个线程从方法返回前,其他所有要调用类中任何标记为synchronized方法的线程都会被阻塞。对刚才的代码稍作修改,如下:
[java] view plain copy
 
  1. /** 
  2.      * 为静态变量value加2 
  3.      * @return 
  4.      */  
  5.     public synchronized int next() {  
  6.         value++;  
  7.         Thread.yield(); // 加速问题的产生  
  8.         value++;  
  9.         return value;  
  10.     }  
 
除了锁定方法,synchronized关键字还能锁定固定代码块:
[java] view plain copy
 
  1. /** 
  2.      * 为静态变量value加2 
  3.      *  
  4.      * @return 
  5.      */  
  6.     public int next() {  
  7.         synchronized (this) {  
  8.             value++;  
  9.             Thread.yield(); // 加速问题的产生  
  10.             value++;  
  11.             return value;  
  12.         }  
  13.     }  

在synchronized关键字后的小括号内加入要加锁的对象即可。通过这种方法分离出来的代码段被称为临界区,也叫作同步控制块。
加入了synchronized后,在一个线程访问next方法的时候,另一个线程就无法访问next方法了,使得两个线程的工作互不干扰,循环也变得根本停不下来:
 
线程循环不停止

2.ReentrantLock

除了synchronized关键字外,我们还可以使用Lock对象为我们的代码加锁,Lock对象必须被显示地创建、锁定和释放:
[java] view plain copy
 
  1. private static Lock lock = new ReentrantLock();  
  2. /** 
  3.      * 为静态变量value加2 
  4.      * @return 
  5.      */  
  6.     public int next() {  
  7.         lock.lock();  
  8.         try {  
  9.             value++;  
  10.             Thread.yield(); // 加速问题的产生  
  11.             value++;  
  12.             return value;  
  13.         } finally {  
  14.             lock.unlock();  
  15.         }  
  16.     }  
一般而言,当我们使用synchronized时,需要写的代码量更少,因此通常只有我们在解决某些特殊问题时,才需要使用到Lock对象,比如尝试去获得锁:
[java] view plain copy
 
  1. /** 
  2.      * 为静态变量value加2 
  3.      * @return 
  4.      */  
  5.     public int next() {  
  6.         boolean getLock = lock.tryLock();  
  7.         if (getLock) {  
  8.             try {  
  9.                 value++;  
  10.                 Thread.yield(); // 加速问题的产生  
  11.                 value++;  
  12.                 return value;  
  13.             } finally {  
  14.                 lock.unlock();  
  15.             }  
  16.         } else {  
  17.             // do something else  
  18.             System.out.println(Thread.currentThread() + "say : I don't get the lock, QAQ");  
  19.             return 0;  
  20.         }  
  21.     }  
除了ReentrantLock外,Lock类还有众多子类锁,在此不做深入讨论。值得注意的是,很明显,使用Lock通常会比使用synchronized高效许多,但我们并发编程时都应该从synchronized关键字入手,只有在性能调优时才替换为Lock对象这种做法。
 

23. 锁的等级:对象锁、类锁

这是关于synchronized关键字的概念,synchronized关键字可以用来锁定对象的非静态方法或其中的代码块,此时关键字是为对象的实例加锁了,所以称为对象锁:
[java] view plain copy
 
  1. public synchronized void f() {};  
  2.     public void g() {  
  3.         synchronized (this) {  
  4.               
  5.         }  
  6.     }  
另外,synchronized也可以用来锁定类的静态方法和其中的代码块,此时关键字就是为类(类的Class对象)加锁了,因此被称为类锁:
[java] view plain copy
 
  1. public class Test {  
  2.     public static synchronized void f() {};  
  3.     public static void g() {  
  4.         synchronized (Test.class) {  
  5.               
  6.         }  
  7.     }  
  8. }  


24. 写出生产者消费者模式

生产者消费者模式一般而言有四种实现方法:
  1. wait和notify方法
  2. await和signal方法
  3. BlockingQueue阻塞队列方法
  4. PipedInputStream和PipedOutputStream管道流方法
第一种方法(wait和notify)的实现:
[java] view plain copy
 
  1. import java.util.LinkedList;  
  2. import java.util.Queue;  
  3.   
  4. class MyQueue {  
  5.     Queue<Integer> q;  
  6.     int size; // 队列持有产品数  
  7.     final int MAX_SIZE = 5; // 队列最大容量  
  8.   
  9.     public MyQueue() {  
  10.         q = new LinkedList<>();  
  11.         size = 0;  
  12.     }  
  13.   
  14.     /** 
  15.      * 生产产品 
  16.      *  
  17.      * @param num 
  18.      *            产品号码 
  19.      */  
  20.     public synchronized void produce(int num) {  
  21.         // 容量不足时,等待消费者消费  
  22.         try {  
  23.             while (size > MAX_SIZE)  
  24.                 wait();  
  25.         } catch (InterruptedException e) {  
  26.         }  
  27.         ;  
  28.         System.out.println("produce " + num);  
  29.         q.add(num);  
  30.         size++;  
  31.         // 提醒消费者消费  
  32.         notifyAll();  
  33.     }  
  34.       
  35.     /** 
  36.      * 消费产品 
  37.      */  
  38.     public synchronized void comsume() {  
  39.         // 没有产品时,等待生产  
  40.         try {  
  41.             while (size < 1)  
  42.                 wait();  
  43.         } catch (InterruptedException e) {  
  44.         }  
  45.         ;  
  46.         System.out.println("comsume " + q.poll());  
  47.         size--;  
  48.         // 提醒生产者生产  
  49.         notifyAll();  
  50.     }  
  51. }  
  52.   
  53. class Producer extends Thread {  
  54.     private MyQueue q;  
  55.   
  56.     public Producer(MyQueue q) {  
  57.         this.q = q;  
  58.     }  
  59.   
  60.     @Override  
  61.     public void run() {  
  62.         for (int i = 0; i < 10; i++)  
  63.             q.produce(i);  
  64.     }  
  65. }  
  66.   
  67. class Consumer extends Thread {  
  68.     private MyQueue q;  
  69.   
  70.     public Consumer(MyQueue q) {  
  71.         this.q = q;  
  72.     }  
  73.   
  74.     @Override  
  75.     public void run() {  
  76.         for (int i = 0; i < 10; i++)  
  77.             q.comsume();  
  78.     }  
  79. }  
  80.   
  81. public class Test {  
  82.     public static void main(String[] args) {  
  83.         MyQueue q = new MyQueue();  
  84.         Producer producer = new Producer(q);  
  85.         Consumer consumer = new Consumer(q);  
  86.         producer.start();  
  87.         consumer.start();  
  88.     }  
  89. }  
 
第二种方法(await和signal)实现:
[java] view plain copy
 
  1. import java.util.LinkedList;  
  2. import java.util.Queue;  
  3. import java.util.concurrent.locks.Condition;  
  4. import java.util.concurrent.locks.Lock;  
  5. import java.util.concurrent.locks.ReentrantLock;  
  6.   
  7. class MyQueue {  
  8.     Queue<Integer> q;  
  9.     int size; // 队列持有产品数  
  10.     final int MAX_SIZE = 5; // 队列最大容量  
  11.     private Lock lock; // 锁  
  12.     private Condition condition; // 条件变量  
  13.   
  14.     public MyQueue() {  
  15.         q = new LinkedList<>();  
  16.         size = 0;  
  17.         lock = new ReentrantLock();  
  18.         condition = lock.newCondition();  
  19.     }  
  20.   
  21.     /** 
  22.      * 生产产品 
  23.      *  
  24.      * @param num 
  25.      *            产品号码 
  26.      */  
  27.     public void produce(int num) {  
  28.         // 进入临界区上锁  
  29.         lock.lock();  
  30.         // 容量不足时,等待消费者消费  
  31.         try {  
  32.             while (size > MAX_SIZE)  
  33.                 condition.await();  
  34.         } catch (InterruptedException e) {  
  35.             e.printStackTrace();  
  36.         };  
  37.         System.out.println("produce " + num);  
  38.         q.add(num);  
  39.         size++;  
  40.         // 提醒消费者消费  
  41.         condition.signalAll();  
  42.         // 退出临界区解锁  
  43.         lock.unlock();  
  44.     }  
  45.   
  46.     /** 
  47.      * 消费产品 
  48.      */  
  49.     public void comsume() {  
  50.         // 上锁进入临界区  
  51.         lock.lock();  
  52.         // 没有产品时,等待生产  
  53.         try {  
  54.             while (size < 1)  
  55.                 condition.await();  
  56.         } catch (InterruptedException e) {  
  57.             e.printStackTrace();  
  58.         };  
  59.         System.out.println("comsume " + q.poll());  
  60.         size--;  
  61.         // 提醒生产者生产  
  62.         condition.signalAll();  
  63.         // 退出临界区解锁  
  64.         lock.unlock();  
  65.     }  
  66. }  
  67.   
  68. class Producer extends Thread {  
  69.     private MyQueue q;  
  70.   
  71.     public Producer(MyQueue q) {  
  72.         this.q = q;  
  73.     }  
  74.   
  75.     @Override  
  76.     public void run() {  
  77.         for (int i = 0; i < 10; i++)  
  78.             q.produce(i);  
  79.     }  
  80. }  
  81.   
  82. class Consumer extends Thread {  
  83.     private MyQueue q;  
  84.   
  85.     public Consumer(MyQueue q) {  
  86.         this.q = q;  
  87.     }  
  88.   
  89.     @Override  
  90.     public void run() {  
  91.         for (int i = 0; i < 10; i++)  
  92.             q.comsume();  
  93.     }  
  94. }  
  95.   
  96. public class Main {  
  97.     public static void main(String[] args) {  
  98.         MyQueue q = new MyQueue();  
  99.         Producer producer = new Producer(q);  
  100.         Consumer consumer = new Consumer(q);  
  101.         producer.start();  
  102.         consumer.start();  
  103.     }  
  104. }  
 
第三种方法(BlockingQueue阻塞队列)实现:

[java] view plain copy
 
  1. import java.util.concurrent.BlockingQueue;  
  2. import java.util.concurrent.LinkedBlockingQueue;  
  3.   
  4. class MyQueue {  
  5.     BlockingQueue<Integer> q; // 阻塞队列  
  6.     int size; // 队列持有产品数(此例无用)  
  7.     final int MAX_SIZE = 5; // 队列最大容量  
  8.   
  9.     public MyQueue() {  
  10.         q = new LinkedBlockingQueue<>(MAX_SIZE);  
  11.     }  
  12.   
  13.     /** 
  14.      * 生产产品 
  15.      *  
  16.      * @param num 
  17.      *            产品号码 
  18.      */  
  19.     public void produce(int num) {  
  20.         // 阻塞队列会自动阻塞,不需要处理  
  21.         try {  
  22.             q.put(num);  
  23.             System.out.println("produce " + num);  
  24.         } catch (InterruptedException e) {  
  25.             e.printStackTrace();  
  26.         }  
  27.     }  
  28.   
  29.     /** 
  30.      * 消费产品 
  31.      */  
  32.     public void comsume() {  
  33.         // 阻塞队列会自动阻塞,不需要处理  
  34.         try {  
  35.             System.out.println("comsume " + q.take());  
  36.         } catch (InterruptedException e) {  
  37.             e.printStackTrace();  
  38.         }  
  39.     }  
  40. }  
  41.   
  42. class Producer extends Thread {  
  43.     private MyQueue q;  
  44.   
  45.     public Producer(MyQueue q) {  
  46.         this.q = q;  
  47.     }  
  48.   
  49.     @Override  
  50.     public void run() {  
  51.         for (int i = 0; i < 10; i++)  
  52.             q.produce(i);  
  53.     }  
  54. }  
  55.   
  56. class Consumer extends Thread {  
  57.     private MyQueue q;  
  58.   
  59.     public Consumer(MyQueue q) {  
  60.         this.q = q;  
  61.     }  
  62.   
  63.     @Override  
  64.     public void run() {  
  65.         for (int i = 0; i < 10; i++)  
  66.             q.comsume();  
  67.     }  
  68. }  
  69.   
  70. public class Main {  
  71.     public static void main(String[] args) {  
  72.         MyQueue q = new MyQueue();  
  73.         Producer producer = new Producer(q);  
  74.         Consumer consumer = new Consumer(q);  
  75.         producer.start();  
  76.         consumer.start();  
  77.     }  
  78. }  
第四种方法(PipedInputStream和PipedOutputStream):
[java] view plain copy
 
  1. import java.io.PipedInputStream;  
  2. import java.io.PipedOutputStream;  
  3.   
  4. class MyQueue {  
  5.     int size; // 队列持有产品数(此例无用)  
  6.     final int MAX_SIZE = 5; // 队列最大容量  
  7.     PipedInputStream pis;  
  8.     PipedOutputStream pos;  
  9.   
  10.     public MyQueue() {  
  11.         // 初始化流  
  12.         pis = new PipedInputStream(MAX_SIZE);  
  13.         pos = new PipedOutputStream();  
  14.         // 管道流建立连接  
  15.         try {  
  16.             pos.connect(pis);  
  17.         } catch (Exception e) {  
  18.             e.printStackTrace();  
  19.         }  
  20.     }  
  21.   
  22.     /** 
  23.      * 生产产品 
  24.      *  
  25.      * @param num 
  26.      *            产品号码 
  27.      */  
  28.     public void produce(int num) {  
  29.         // 管道流会自动阻塞,不需要处理  
  30.         try {  
  31.             // 输出写在前面,否则会有奇怪的事情发生~  
  32.             System.out.println("produce " + num);  
  33.             pos.write(num);  
  34.             pos.flush();  
  35.         } catch (Exception e) {  
  36.             e.printStackTrace();  
  37.         }  
  38.     }  
  39.   
  40.     /** 
  41.      * 消费产品 
  42.      */  
  43.     public void comsume() {  
  44.         // 管道流会自动阻塞,不需要处理  
  45.         try {  
  46.             System.out.println("comsume " + pis.read());  
  47.         } catch (Exception e) {  
  48.             e.printStackTrace();  
  49.         }  
  50.     }  
  51.   
  52.     @Override  
  53.     protected void finalize() throws Throwable {  
  54.         pis.close();  
  55.         pos.close();  
  56.         super.finalize();  
  57.     }  
  58. }  
  59.   
  60. class Producer extends Thread {  
  61.     private MyQueue q;  
  62.   
  63.     public Producer(MyQueue q) {  
  64.         this.q = q;  
  65.     }  
  66.   
  67.     @Override  
  68.     public void run() {  
  69.         for (int i = 0; i < 10; i++)  
  70.             q.produce(i);  
  71.     }  
  72. }  
  73.   
  74. class Consumer extends Thread {  
  75.     private MyQueue q;  
  76.   
  77.     public Consumer(MyQueue q) {  
  78.         this.q = q;  
  79.     }  
  80.   
  81.     @Override  
  82.     public void run() {  
  83.         for (int i = 0; i < 10; i++)  
  84.             q.comsume();  
  85.     }  
  86. }  
  87.   
  88. public class Main {  
  89.     public static void main(String[] args) {  
  90.         MyQueue q = new MyQueue();  
  91.         Producer producer = new Producer(q);  
  92.         Consumer consumer = new Consumer(q);  
  93.         producer.start();  
  94.         consumer.start();  
  95.     }  
  96. }  




输出结果:
生产者消费者
 

25. ThreadLocal的设计理念与作用

ThreadLocal即线程本地存储。防止线程在共享资源上产生冲突的一种方式是根除对变量的共享。ThreadLocal是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储,ThreadLocal对象通常当做静态域存储,通过get和set方法来访问对象的内容:
[java] view plain copy
 
  1. import java.util.Random;  
  2. import java.util.concurrent.ExecutorService;  
  3. import java.util.concurrent.Executors;  
  4. import java.util.concurrent.TimeUnit;  
  5.   
  6. class Accessor implements Runnable {  
  7.     private final int id; // 线程id  
  8.     public Accessor(int id) {  
  9.         this.id = id;  
  10.     }  
  11.     @Override  
  12.     public void run() {  
  13.         while(!Thread.currentThread().isInterrupted()) {  
  14.             ThreadLocalVariableHolder.increment();  
  15.             System.out.println(this);  
  16.             Thread.yield();  
  17.         }  
  18.     }  
  19.     @Override  
  20.     public String toString() {  
  21.         return "#" + id + " : " + ThreadLocalVariableHolder.get();  
  22.     }  
  23. }  
  24. public class ThreadLocalVariableHolder {  
  25.     private static ThreadLocal<Integer> value = new ThreadLocal<Integer>() {  
  26.         // 返回随机数作为初始值  
  27.         protected Integer initialValue() {  
  28.             return new Random().nextInt(10000);  
  29.         }  
  30.     };  
  31.       
  32.     /** 
  33.      * 为当前线程的value值加一 
  34.      */  
  35.     public static void increment() {  
  36.         value.set(value.get() + 1);  
  37.     }  
  38.       
  39.     /** 
  40.      * 返回当前线程存储的value值 
  41.      * @return 
  42.      */  
  43.     public static int get() {  
  44.         return value.get();  
  45.     }  
  46.       
  47.     public static void main(String[] args) throws InterruptedException {  
  48.         ExecutorService service = Executors.newCachedThreadPool();  
  49.         // 开启5个线程  
  50.         for (int i = 0; i < 5; i++)  
  51.             service.execute(new Accessor(i));  
  52.         // 所有线程运行3秒  
  53.         TimeUnit.SECONDS.sleep(1);  
  54.         // 关闭所有线程  
  55.         service.shutdownNow();  
  56.     }  
  57. }  
 
运行部分结果如下:
ThreadLocal运行结果

在上面的例子中虽然多个线程都去调用了ThreadLocalVariableHolder的increment和get方法,但这两个方法都没有进行同步处理,这是因为ThreadLocal保证我们使用的时候不会出现竞争条件。从结果来看,每个线程都在单独操作自己的变量,每个单独的线程都被分配了自己的存储(即便只有一个ThreadLocalVariableHolder对象),线程之间并没有互相造成影响。对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。
 
原文地址:https://www.cnblogs.com/KingIceMou/p/6976377.html