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. gradle入门(1-3)使用gradle开发一个发布版本

    需求描述 1.使用Maven central仓库.2.使用Log4j写入日志.3.包含单元测试,保证正确的信息返回,单元测试必须使用JUnit编写.4.创建一个可执行的Jar文件. 我们来看一下怎样实 ...

  2. Python/ MySQL练习题(一)

    Python/ MySQL练习题(一) 查询“生物”课程比“物理”课程成绩高的所有学生的学号 SELECT * FROM ( SELECT * FROM course LEFT JOIN score ...

  3. 文本处理三剑客之sed

    sed 1.简介 sed是一种流编辑器,它一次处理一行内容.处理时,把当前处理的行存储在临时缓冲区中,称为"模式空间"(patternspace),接着用sed命令处理缓冲区中的内 ...

  4. UVA-624 CD---01背包+输出路径

    题目链接: https://vjudge.net/problem/UVA-624 题目大意: 这道题给定一个时间上限,然后一个数字N,后面跟着N首歌的时间长度,要我们 求在规定时间w内每首歌都要完整的 ...

  5. ZOJ-2750 Idiomatic Phrases Game---Dijk最短路

    题目链接: https://vjudge.net/problem/ZOJ-2750 题目大意: 给定一本字典,字典里有很多成语,要求从字典里的第一个成语开始,运用字典里的成语变到最后一个成语,变得过程 ...

  6. Menu-右键弹出菜单

    #右键弹出菜单 from tkinter import * root=Tk() def callback(): print('我被调用了') menubar =Menu(root) menubar.a ...

  7. java中的多态案例

    多态性实际上有两种: 1.方法的多态性: 1.1方法重载:相同的方法名,会根据传入的参数的类型和个数不同执行不同的方法 1.2方法覆写:同一个方法名称,会根据子类的不同实现不同的功能 2.对象的多态性 ...

  8. [LeetCode] Longest Word in Dictionary 字典中的最长单词

    Given a list of strings words representing an English Dictionary, find the longest word in words tha ...

  9. [JSOI2007]文本生成器

    题目描述 JSOI交给队员ZYX一个任务,编制一个称之为“文本生成器”的电脑软件:该软件的使用者是一些低幼人群,他们现在使用的是GW文本生成器v6版. 该软件可以随机生成一些文章―――总是生成一篇长度 ...

  10. [JLOI2015]管道连接

    题目描述 小铭铭最近进入了某情报部门,该部门正在被如何建立安全的通道连接困扰.该部门有 n 个情报站,用 1 到 n 的整数编号.给出 m 对情报站 ui;vi 和费用 wi,表示情报站 ui 和 v ...