zoukankan      html  css  js  c++  java
  • 谈谈多线程

    前言:

      一直以来,对于多线程的理解总是赶在前一秒翻书时回忆起,后一秒放下书即忘。甚是可恼!今晚对多线程总结一下,也好有个了断~

    概念引入:

      首先,我们想了解的是:什么是线程,跟进程有什么关联?

      其实是这样的:线程是程序执行流的最小单元。其一般有3种状态:就绪,执行和阻塞(因本文注重实例,就不对概念作过多的解释~)。在计算机中,一个代码块(block)运行时产生一个或多个进程(process),而每一个进程又对应一至多个线程(thread),每一个线程又可以分为一至多个任务(task).

      因此,以上的关系我们可以通过下面一张图进行理解。

    • 1.创建线程的两种实现方式:

    1)继承Thread

    2)实现Runnable接口

     1 package com.gdufe.thread;
     2 
     3 public class ThreadTest {
     4 
     5     public static void main(String[] args) {
     6 
     7         Thread thread1 = new Thread(new TaskA(),"thread1");
     8         Thread thread2 = new Thread(new TaskB('a',100),"thread2");
     9         Thread thread3 = new Thread(new TaskB('b',100),"thread3");
    10         
    11         thread1.start();;
    12         thread2.start();
    13         thread3.start();
    14         System.out.println("--End--");
    15         
    16         
    17     }
    18     
    19     /*
    20      * 任务A通过实现Runnable接口创建任务
    21      */
    22     private static class TaskA implements Runnable{
    23 
    24         @Override
    25         public void run() {
    26             for(int i=0;i<100;i++){
    27                 System.out.print(i+" ");
    28             }
    29         }
    30         
    31     }
    32     /*
    33      * 任务B通过继承Thread类并重写run()方法来创建任务
    34      */
    35     private static class TaskB extends Thread{
    36         
    37         private char ch;
    38         private int times;
    39         
    40         public TaskB(char ch,int times){
    41             this.ch=ch;
    42             this.times=times;
    43         }
    44         @Override
    45         public void run() {
    46             for(int i=0;i<times;i++){
    47                 System.out.print(ch+" ");
    48             }
    49         }
    50     }
    51 }

    输出结果:

    • 2.线程池thread-pool 

      线程池指的是不需要每个任务用一个线程来存储,而是初始就给出一个“池”。线程池首先会初始池的大小,即可供同时进入的“任务”的数量,这样的话每当来一个新任务就直接往线程池里面扔,不需要再单独创建一个个线程。

    • 3.线程不安全

    【不安全实例代码】

     1 package com.gdufe.thread;
     2 
     3 import java.util.concurrent.ExecutorService;
     4 import java.util.concurrent.Executors;
     5 
     6 public class ThreadNotSafe {
     7     static Account account = new Account();
     8 
     9     public static void main(String[] args) {
    10 
    11         ExecutorService executor = Executors.newCachedThreadPool();
    12         for (int i = 0; i < 100; i++) {
    13             executor.execute(new Deposit());
    14         }
    15         executor.shutdown();
    16         while (!executor.isTerminated()) {
    17         }
    18         System.out.println("Finally, the account get the total balance:"
    19                 + account.getBalance());
    20     }
    21     /*
    22      * 存钱操作,每次存入1
    23      */
    24     static class Deposit implements Runnable {
    25 
    26         @Override
    27         public void run() {
    28             account.deposit(1);
    29         }
    30     }
    31 
    32     static class Account {
    33         private int balance = 0;
    34 
    35         public int getBalance() {
    36             return balance;
    37         }
    38 
    39         public void deposit(int value) {
    40             int newBalance = balance + value;
    41             try {
    42                 Thread.sleep(5);    //故意将原有的简单操作拆分两步,并且中间延迟5毫秒
    43             } catch (Exception e) {
    44             }
    45             balance = newBalance;
    46         }
    47     }
    48 
    49 }

    输出结果:

    Finally, the account get the total balance:3

    (注意:执行多次的结果不一样)

    分析

      上述实例进行100个任务,每个任务都是往账号里面存“1”,正确的结果应该输出“100”,那为什么最终的结果好像“不正确”。原因是,当多个任务同时访问一个资源时,就出现了所谓的资源“竞争”。


    解决方法:

    1)使用关键字“synchronized”对资源进行封锁,此方式称作“隐式加锁”;

    2)采用java内部工具类“Lock”进行“显式加锁”。

    【方式1-代码修改部分】:

     1 static class Account {
     2         private int balance = 0;
     3 
     4         public int getBalance() {
     5             return balance;
     6         }
     7         /*
     8          * 增加关键字‘synchronized’,方法未执行完时,仅限一个任务访问该方法
     9          */
    10         public synchronized void deposit(int value) {
    11             int newBalance = balance + value;
    12             try {
    13                 Thread.sleep(5);    //故意延迟5毫秒
    14             } catch (Exception e) {
    15             }
    16             balance = newBalance;
    17         }
    18     }

    【方式2-代码修改部分】:

     1 static class Account {
     2         private int balance = 0;
     3         private static Lock lock = new ReentrantLock();
     4 
     5         public int getBalance() {
     6             return balance;
     7         }
     8         /*
     9          * 采用显示加锁,方法开始执行时加锁,执行结束前解锁
    10          */
    11         public void deposit(int value) {
    12             lock.lock();    //加锁
    13             int newBalance = balance + value;
    14             try {
    15                 Thread.sleep(5);    //故意延迟5毫秒
    16                 balance = newBalance;
    17             } catch (Exception e) {
    18             
    19             }finally{
    20                 lock.unlock();     //解锁
    21             }
    22         }
    23     }

    输出结果:

    Finally, the account get the total balance:100

    (多次执行,输出结果总是100)

    • 4.线程协作实例:

      实例情境:

      假如现在要对一个银行账号(Account)进行存(Deposit)取(Withdraw)钱操作。故确定了2个线程,这里的协作需要注意一点的是当银行账号的余额不足时,取钱操作必须等待。因此,除了前面设定的锁之外,我们还得加一个信号量signal。信号量的作用是当取钱的数量大于当前账号余额时,停止该操作,发出等待的信号量signal;存钱时,有多少即存多少就是了。不过,每进行一次存钱操作,都必须发出信号提醒还在等待的取钱操作,不然取钱操作将一直等下去...

    代码实现:

     1 package com.gdufe.thread;
     2 
     3 import java.util.concurrent.ExecutorService;
     4 import java.util.concurrent.Executors;
     5 import java.util.concurrent.locks.Condition;
     6 import java.util.concurrent.locks.Lock;
     7 import java.util.concurrent.locks.ReentrantLock;
     8 
     9 public class ThreadCooperation {
    10     private static Account account = new Account();
    11 
    12     public static void main(String[] args) {
    13         //开启大小为2线程池的,依次加入任务
    14         ExecutorService executor = Executors.newFixedThreadPool(2);
    15         executor.execute(new DepositTask());
    16         executor.execute(new WithdrawTask());
    17         //关闭线程池入口
    18         executor.shutdown();
    19         System.out.println("Operation------Balance");
    20     }
    21     //从账号取出钱
    22     static class WithdrawTask implements Runnable {
    23         @Override
    24         public void run() {
    25             try {
    26                 while (true) {
    27                     account.withdraw((int) (Math.random() * 10));
    28                 }
    29             } catch (Exception e) {
    30             }
    31         }
    32     }
    33     //往账号存钱
    34     static class DepositTask implements Runnable {
    35         @Override
    36         public void run() {
    37             try{
    38             while (true) {
    39                 account.deposit((int) (Math.random() * 10));
    40                 Thread.sleep(1000);
    41             }
    42             }catch(Exception e){
    43                 e.printStackTrace();
    44             }
    45         }
    46     }
    47     //内部类,只有‘balance’属性
    48     static class Account {
    49         private int balance = 0;
    50         private static Lock lock = new ReentrantLock();
    51         private static Condition signal = lock.newCondition();
    52 
    53         public int getBalance() {
    54             return balance;
    55         }
    56         //前后加锁,解锁
    57         public void deposit(int amount) {
    58             lock.lock();
    59             try {
    60                 balance += amount;
    61                 System.out.println("deposit "+amount+"----total:" + account.getBalance());
    62                 signal.signalAll();        //not 'notifyAll()'
    63             } finally {
    64                 lock.unlock();
    65             }
    66         }
    67         //同样加锁,解锁
    68         public void withdraw(int amount) {
    69             lock.lock();
    70             try {
    71                 while (balance < amount) {
    72                     signal.await();
    73                     System.out.println("signal await...");
    74                 }
    75                 balance -= amount;
    76                 System.out.println("whthdraw "+amount+"----total:" + account.getBalance());
    77             } catch (Exception e) {
    78                     e.printStackTrace();
    79             } finally {
    80                 lock.unlock();
    81             }
    82         }
    83     }
    84 
    85 }

    输出结果:

    • 5.经典生产者消费者问题

    (抱歉,时间关系!后续补上关于生产者消费者的实例,敬请关注~~)

  • 相关阅读:
    spark streaming 整合kafka(二)
    JAVA设计模式之动态代理
    使用org.apache.commons.cli包来设计JAVA命令行工具
    HTML教程
    Java InputStream和Reader
    Java IO
    程序员怎么把自己的招牌打出去?
    Java设计模式之单例模式
    JAVA NIO
    Java文件流字节流和字符流的区别
  • 原文地址:https://www.cnblogs.com/SeaSky0606/p/4722413.html
Copyright © 2011-2022 走看看