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

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

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

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

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

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

3.使用ExecutorService类

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

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

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

上面的代码创建了两个线程操作Test类中的静态变量value,调用next方法每次会为value的值加2,理论上来说isEven方法的返回值应该总是true,两个线程的工作会不停止的执行下去。但事实是:

因此在我们进行多线程并发编程时,使用同步技术是非常重要的。

1.synchronized

Java以提供关键字synchronized的形式,为防止资源冲突提供了内置支持。当某个线程处于一个对于标记为synchronized的方法的调用中,那么在这个线程从方法返回前,其他所有要调用类中任何标记为synchronized方法的线程都会被阻塞。对刚才的代码稍作修改,如下:
  1. /**
  2. * 为静态变量value加2
  3. * @return
  4. */
  5. public synchronized int next() {
  6. value++;
  7. Thread.yield(); // 加速问题的产生
  8. value++;
  9. return value;
  10. }
 
除了锁定方法,synchronized关键字还能锁定固定代码块:
  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对象必须被显示地创建、锁定和释放:
  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对象,比如尝试去获得锁:

  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关键字可以用来锁定对象的非静态方法或其中的代码块,此时关键字是为对象的实例加锁了,所以称为对象锁:
  1. public synchronized void f() {};
  2. public void g() {
  3. synchronized (this) {
  4. }
  5. }

另外,synchronized也可以用来锁定类的静态方法和其中的代码块,此时关键字就是为类(类的Class对象)加锁了,因此被称为类锁:

  1. public class Test {
  2. public static synchronized void f() {};
  3. public static void g() {
  4. synchronized (Test.class) {
  5. }
  6. }
  7. }

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

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

第三种方法(BlockingQueue阻塞队列)实现:

  1. import java.util.concurrent.BlockingQueue;
  2. import java.util.concurrent.LinkedBlockingQueue;
  3. class MyQueue {
  4. BlockingQueue<Integer> q; // 阻塞队列
  5. int size; // 队列持有产品数(此例无用)
  6. final int MAX_SIZE = 5; // 队列最大容量
  7. public MyQueue() {
  8. q = new LinkedBlockingQueue<>(MAX_SIZE);
  9. }
  10. /**
  11. * 生产产品
  12. *
  13. * @param num
  14. *            产品号码
  15. */
  16. public void produce(int num) {
  17. // 阻塞队列会自动阻塞,不需要处理
  18. try {
  19. q.put(num);
  20. System.out.println("produce " + num);
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. /**
  26. * 消费产品
  27. */
  28. public void comsume() {
  29. // 阻塞队列会自动阻塞,不需要处理
  30. try {
  31. System.out.println("comsume " + q.take());
  32. } catch (InterruptedException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. }
  37. class Producer extends Thread {
  38. private MyQueue q;
  39. public Producer(MyQueue q) {
  40. this.q = q;
  41. }
  42. @Override
  43. public void run() {
  44. for (int i = 0; i < 10; i++)
  45. q.produce(i);
  46. }
  47. }
  48. class Consumer extends Thread {
  49. private MyQueue q;
  50. public Consumer(MyQueue q) {
  51. this.q = q;
  52. }
  53. @Override
  54. public void run() {
  55. for (int i = 0; i < 10; i++)
  56. q.comsume();
  57. }
  58. }
  59. public class Main {
  60. public static void main(String[] args) {
  61. MyQueue q = new MyQueue();
  62. Producer producer = new Producer(q);
  63. Consumer consumer = new Consumer(q);
  64. producer.start();
  65. consumer.start();
  66. }
  67. }

第四种方法(PipedInputStream和PipedOutputStream):

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

输出结果:

 

25. ThreadLocal的设计理念与作用

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

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

 

关于一些基础的Java问题的解答(五)的更多相关文章

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

    学习一门语言基础是非常重要的,因此本文总结了一些常见的Java基础问题的解答,希望可以帮到大家. 1. 九种基本数据类型的大小,以及他们的封装类. 9种基本数据类型 基本类型 包装类型 大小 bool ...

  2. 关于一些基础的Java问题的解答(七)

    31. 反射的作用与原理 简单的来说,反射机制其实就是指程序在运行的时候能够获取自身的信息.如果知道一个类的名称或者它的一个实例对象, 就能把这个类的所有方法和变量的信息(方法名,变量名,方法,修饰符 ...

  3. 关于一些基础的Java问题的解答(六)

    26. ThreadPool用法与优势 ThreadPool即线程池,它是JDK1.5引入的Concurrent包中用于处理并发编程的工具.使用线程池有如下好处: 降低资源消耗:通过重复利用已创建的线 ...

  4. 关于一些基础的Java问题的解答(四)

    16. Java面向对象的三个特征与含义 java中的面向对象的三大基本特征分别是:封装.继承.多态: 封装:把过程和数据包围起来,对数据的访问只能通过已定义的界面,主要是方便类的修改 继承:对象的一 ...

  5. 关于一些基础的Java问题的解答(三)

    11. HashMap和ConcurrentHashMap的区别   从JDK1.2起,就有了HashMap,正如上一个问题所提到的,HashMap与HashTable不同,不是线程安全的,因此多线程 ...

  6. 关于一些基础的Java问题的解答(二)

    6. Hashcode的作用 官方对于hashCode的解释如下: Whenever it is invoked on the same object more than once during an ...

  7. 黑马程序员:Java基础总结----java注解

    黑马程序员:Java基础总结 java注解   ASP.Net+Android+IO开发 . .Net培训 .期待与您交流! java注解 lang包中的基本注解 @SuppressWarnings ...

  8. java面试题—精选30道Java笔试题解答(二)

    摘要: java面试题-精选30道Java笔试题解答(二) 19. 下面程序能正常运行吗() public class NULL { public static void haha(){ System ...

  9. JAVA基础再回首(二十五)——Lock锁的使用、死锁问题、多线程生产者和消费者、线程池、匿名内部类使用多线程、定时器、面试题

    JAVA基础再回首(二十五)--Lock锁的使用.死锁问题.多线程生产者和消费者.线程池.匿名内部类使用多线程.定时器.面试题 版权声明:转载必须注明本文转自程序猿杜鹏程的博客:http://blog ...

随机推荐

  1. maven构建spring报错org.springframework.core.NestedRuntimeException cannot be resolved.

    Error:The type org.springframework.core.NestedRuntimeException cannot be resolved. It is indirectly ...

  2. iot前台开发环境:前后台访问映射

    一.前端映射- java代码 二.路由设置 -前台代码 三.访问应用

  3. spring-oauth-server实践:access_token的有效期分析

    1.access_token有效期检查 用expiration和new Date()比较!!!!!! 分析目标-->expiration什么时候设置,设置规则如何配置!!!!!!! 2.acce ...

  4. 将Tomcat添加进服务启动

    tomcat有解压版和安装版2种版本,安装版已经做好了将tomcat添加进服务的操作,而解压版需要我们自己来实现,应用场景主要是在服务器端需要在服务器启动时就启动tomcat. 1.首先需要配置好jd ...

  5. POJ-1861 Network---最小生成树

    题目链接: https://vjudge.net/problem/POJ-1861 题目大意: 有一些公司,公司之间需要连接起来.给出了哪些公司可以连接以及连接边的长度.求最小生成树中最大的边,以及最 ...

  6. js中闭包的讲解

    一.变量的作用域 要理解闭包,首先必须理解Javascript特殊的变量作用域. 变量的作用域无非就是两种:全局变量和局部变量. Javascript语言的特殊之处,就在于函数内部可以直接读取全局变量 ...

  7. 阿里移动云专场专题.md

    小激动 一年一度的阿里云栖大会是我们开发者的盛会,带着着激动的心情参加了这次开发者盛会,二话不说进入会场就被震感到了,先来张图聊表敬意. 主会场马云爸爸还是很有范的,将未来定义为无法定义,在这里宣布成 ...

  8. 用Python浅析股票数据

    用Python浅析股票数据 本文将使用Python来可视化股票数据,比如绘制K线图,并且探究各项指标的含义和关系,最后使用移动平均线方法初探投资策略. 数据导入 这里将股票数据存储在stockData ...

  9. bzoj 2436: [Noi2011]Noi嘉年华

    Description NOI2011 在吉林大学开始啦!为了迎接来自全国各地最优秀的信息学选手,吉林大学决定举办两场盛大的 NOI 嘉年华活动,分在两个不同的地点举办.每个嘉年华可能包含很多个活动, ...

  10. ●2301 [HAOI2011] Problem b

    题链: http://www.lydsy.com/JudgeOnline/problem.php?id=2301 题解: 莫比乌斯反演,入门题. 类似●HDU 1695 GCD 只是多了一个下界,(另 ...