zoukankan      html  css  js  c++  java
  • 关于一些基础的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,用于存储每一个线程的变量的副本。
     
  • 相关阅读:
    SpringBoot安装和创建简单的Web应用
    Java设计模式-原型模式
    Java设计模式-单例模式
    Java设计模式-抽象工厂模式(Abstarct Factory)
    Java设计模式-工厂方法模式(Virtual Constructor/Polymorphic Factory)
    Java设计模式-简单工厂模式(Static Factory Method)
    Angular5学习笔记
    设置Nodejs NPM全局路径
    Actor模式初步入门
    Angular5学习笔记
  • 原文地址:https://www.cnblogs.com/KingIceMou/p/6976377.html
Copyright © 2011-2022 走看看