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. "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. "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. new Runnable() {
9.
10. @Override
11. public void run() {
12. "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. new Test();
7. // 创建两个线程
8. new MyThread();
9. new MyThread();
10. thread1.start();
11. thread2.start();
12. }
13.
14. /**
15. * 为静态变量value加2
16. * @return
17. */
18. public int next() {
19. value++;
20. // 加速问题的产生
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. " start!");
38. while(isEven(next()));
39. " 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. // 加速问题的产生
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. // 加速问题的产生
10. value++;
11. return value;
12. }
13. }
在synchronized关键字后的小括号内加入要加锁的对象即可。通过这种方法分离出来的代码段被称为临界区,也叫作同步控制块。
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. // 加速问题的产生
11. value++;
12. return value;
13. finally {
14. lock.unlock();
15. }
16. }
[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. // 加速问题的产生
11. value++;
12. return value;
13. finally {
14. lock.unlock();
15. }
16. else {
17. // do something else
18. "say : I don't get the lock, QAQ");
19. return 0;
20. }
21. }
23. 锁的等级:对象锁、类锁
这是关于synchronized关键字的概念,synchronized关键字可以用来锁定对象的非静态方法或其中的代码块,此时关键字是为对象的实例加锁了,所以称为对象锁:
[java] view plain copy
1. public synchronized void f() {};
2. public void g() {
3. synchronized (this) {
4.
5. }
6. }
[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. new LinkedList<>();
11. 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. "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. "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. new MyQueue();
84. new Producer(q);
85. 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. new LinkedList<>();
16. 0;
17. 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. "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. "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. new MyQueue();
99. new Producer(q);
100. new Consumer(q);
101. producer.start();
102. consumer.start();
103. }
104. }
[java] view plain copy
1. import java.util.concurrent.BlockingQueue;
2. import java.util.concurrent.LinkedBlockingQueue;
3.
4. class MyQueue {
5. // 阻塞队列
6. int size; // 队列持有产品数(此例无用)
7. final int MAX_SIZE = 5; // 队列最大容量
8.
9. public MyQueue() {
10. 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. "produce " + num);
24. catch (InterruptedException e) {
25. e.printStackTrace();
26. }
27. }
28.
29. /**
30. * 消费产品
31. */
32. public void comsume() {
33. // 阻塞队列会自动阻塞,不需要处理
34. try {
35. "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. new MyQueue();
73. new Producer(q);
74. new Consumer(q);
75. producer.start();
76. consumer.start();
77. }
78. }
[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. new PipedInputStream(MAX_SIZE);
13. 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. "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. "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. new MyQueue();
91. new Producer(q);
92. 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. 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. 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. new Accessor(i));
52. // 所有线程运行3秒
53. 1);
54. // 关闭所有线程
55. service.shutdownNow();
56. }
57. }
运行部分结果如下:
在上面的例子中虽然多个线程都去调用了ThreadLocalVariableHolder的increment和get方法,但这两个方法都没有进行同步处理,这是因为ThreadLocal保证我们使用的时候不会出现竞争条件。从结果来看,每个线程都在单独操作自己的变量,每个单独的线程都被分配了自己的存储(即便只有一个ThreadLocalVariableHolder对象),线程之间并没有互相造成影响。对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。