JAVA写一个多线程上锁的图书馆还书借书流程

文章目录

前言

跟着导师学Java,今天无意中发现了java多进程和多线程的语法语义,来做一个多线程带锁的图书馆流程小程序

1多进程和多线程的概念

        1.1多进程

        进程是程序在计算机上的一次执行活动。当运行一个程序,就启动了一个进程。凡是用于完成操作系统的各种功能的进程就是系统进程,而所有非系统启动的进程都是用户进程。

        而进程A和进程B的内存独立不共享,比如:魔兽游戏是一个进程,网易云音乐是一个进程
这两个进程是独立的,不共享资源。

        1.2多线程

        进程就是有一个或多个线程构成的。而线程是进程中的实际运行单位,是独立运行于进程之中的子任务。是操作系统进行运算调度的最小单位。可理解为线程是进程中的一个最小运行单元。

        进程和线程之间的关系:一个进程下包含 N 个线程。

        举例说明:玩英雄联盟的时候,打开客户端便启动了许多个线程:排队队列线程、好友聊天线程、正在支付线程。在英雄联盟这一个进程之下便启动了 N 个线程。

2.Runnable任务类和Thread线程类

        刚才我们知道线程是运算调度时最小的单位,而在Java中,Thread类就是线程类,它的作用是可以将调动Runnable类的实例对象,任务就是对象。为了创建任务,必须首先为任务定义一个实现 Runnable 接口的类Runnable接口非常简单,它只包含一个run方法。需要实现这个方法来告诉系统线程将如何运行。如图:

         一旦定义了一个 PrintStrinng,就可以用它的构造方法创建一个任务。例如

Runnable printgreet = new PrintString("你好啊",50);

        任务必须在线程中执行。Thread类包括创建线程的构造方法以及控制线程的方法。使用下面的语句创建任务的线程:调用start(方法告诉Java 虚拟机该线程准备运行,如下所示,会打印50次传入的字符串:

Thread thread = new Thread(printsee);
thread.start();

        

        注意:每个线程可以处于5种状态之一:新建、就绪、阻塞、运行、结束

3.线程池

        Thread为每个任务开始一个新线程可能会限制吞吐量并且造成性能降低。可以通过线程池是管理并发执行任务个数。Java提供Executor接口来执行线程池中的任务,提供 ExecutorService接口来管理和控制任务。

         使用线程池来运行任务对象时,可以制定要运行的线程数量,并且可以运行完后关闭线程池:

ExecutorService executor = Executors.newFixedThreadPool(2);  //指定线程池数量
executor.execute(new returnTask());     //添加线程
executor.execute(new boorroTask());     // 添加线程

//将线程池结束掉,释放资源
executor.shutdown();

4.线程同步

        4.1异步编程和同步编程

        异步编程模型线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁。异步就是并发。

        那什么时候数据在多线程并发的环境下会存在安全问题呢?

满足三个条件:

  1. 条件1:多线程并发。

  2. 条件2:有共享数据。

  3. 条件3:共享数据有修改的行为。

比如存钱程序,用线程池创建1000个存钱的线程,逻辑如下:直接运行所有存钱线程,线程的代码是获取账户上的余额,然后把余额加1,但是所有线程同时并发运行,导致大家同时读到余额初始值并加1,线程池结束后发现余额的钱是2,相当于只运行了一次,任务2覆盖了任务1的结果。任务1和任务2以一种会引起冲突的方式访问一个公共资源,称为竞争状态(race condition) 。如果一个类的对象在多线程序中没有导致竞争状态,则称这样的类为线程安全的((thread-safe)。所以图中Account类不是线程安全的。

public class AccountWithoutSync {
  private static Account account = new Account();

  public static void main(String[] args) {
    ExecutorService executor = Executors.newCachedThreadPool();

    // 创建100个存钱线程
    for (int i = 0; i < 100; i++) {
      executor.execute(new AddAPennyTask());
    }
    executor.shutdown();
    // 等待线程完成
    while (!executor.isTerminated()) {
    }

    System.out.println("现在余额: " + account.getBalance());
  }

  // 建一个存钱任务类,调用存钱类Account,每次放1块钱
  private static class AddAPennyTask implements Runnable {
    public void run() {
      account.deposit(1);
    }
  }

  // 存钱类Account,每次放1块钱
  private static class Account {
    private int balance = 0;

    public int getBalance() {
      return balance;
    }

    public void deposit(int amount) {
      int newBalance = balance + amount;

      try {
        Thread.sleep(5);
      }
      catch (InterruptedException ex) {
      }

      balance = newBalance;
    }
  }
}

5.显性同步锁

        java中有几种方法,但是我用到的是显性同步锁,使用方法为:先创建一把锁,然后设置开锁条件,在业务类运行其中一个同步方法时先上锁,运行结束就解锁,其他同步方法在此期间不能进行操作,具体细节后续会再度补充,就先大概了解到这吧。

private static Lock lock = new ReentrantLock();   //创建一把锁

private static Condition newDeposit = lock.newCondition();  //创建解锁条件

6.完整代码

public class TestThread {
    private static library library = new library();
    public static void main(String[] args) {
        System.out.println("-------------------");
        System.out.println("| 图书馆借书还书流程  |");
        System.out.println("-------------------");
        //新建线程池,把还书任务和借书任务放进去
        ExecutorService executor = Executors.newFixedThreadPool(2);
        executor.execute(new returnTask());
        executor.execute(new boorroTask());

        //将线程池结束掉,释放资源
        executor.shutdown();
    }

    //新建任务类returnTask,此任务的run方法可执行静态类图书馆的还书方法,每次随机还1-5本书,运行100次
    public static class returnTask implements Runnable{
        @Override
        public void run() {
            int j = 0;
            try{
                do {
                    library.returnBook((int)(Math.random()*5)+1);  //每次随机还书1-5本
                    Thread.sleep(1000);  //每次还书休眠1秒钟再继续
                    j++;
                    //限定每天只有一百人来借书或者还书
                }
                while(j<100);
            }
            catch(InterruptedException ex){
                ex.printStackTrace(); //有线程阻塞异常时不会中断程序
            }
        }
    }

    //新建任务类boorroTask,此任务的run方法可执行静态类图书馆的借书方法,每次随机借1-5本书,运行100次
    public static class boorroTask implements Runnable{
        public void run(){
            int i=0;
            try {
                do {
                    library.borrowBooks((int)(Math.random()*5)+1);
                    Thread.sleep(1000);   //每次借完书休眠1秒钟
                   i++;
                    //限定每天只有一百人来借书或者还书
                }
                while (i<100);
            }
            catch(InterruptedException ex){
                ex.printStackTrace(); //有线程阻塞异常时不会中断程序
            }
        }
    }

    //定义静态类:图书馆类library,里面有变量margin表示书籍余量,一个借书方法,一个还书方法
    public static class library{
        private static Lock lock = new ReentrantLock();   //创建一把锁

        private static Condition newDeposit = lock.newCondition();  //创建解锁条件

        private int margin; //书籍余量

        public int getMargin() {
            return margin;
        } //获取书籍余量

        //定义borrowBooks方法,此方法用于借书
        public void borrowBooks(int num){
            lock.lock(); //先上一把锁,防止同时借书的事情发生

            try {
                while (margin < num) {       //如果书籍余量小于要借书的余量就执行下面代码
                    System.out.println();
                    System.out.println("                                                                ");
                    System.out.println("\t\t\t 什么?你想要借"+num+"本!!!");   //打印出来,表示无能为力,先睡会觉先
                    System.out.println("\t\t\t\t\t\t没有这么多书,先等一会吧~");
                    System.out.println("                                                                ");
                    System.out.println();
                    newDeposit.await();      //把当前线程阻塞掉,不借那么快了
                }
                margin -= num; //如果余量大于要借测书,就把书借出去,余量减去借书数量
                System.out.println();
                System.out.println("****************************************************************");
                System.out.println("\t\t\t 借走了"+num+"本书"+"\t\t\t 还剩下"
                +margin+"本书");
                System.out.println("****************************************************************");
                System.out.println();

                }
            catch (InterruptedException ex){
                ex.printStackTrace();    //有线程阻塞异常时不会中断程序
            }
            finally {
                lock.unlock();  //无论有没有借书成功,都把锁打开
            }
        }

        //定义returnBook方法,此方法用于还书
        public void returnBook(int num){
            lock.lock();   //还书前先上锁
            try {
                margin += num; //书籍余量加上还书数量
                System.out.println();
                System.out.println("----------------------------------------------------------------");
                System.out.println("还书成功,数量为:"+num+"\t\t\t 当前书籍余量为:"
                +margin);
                System.out.println("----------------------------------------------------------------");
                System.out.println();
                newDeposit.signalAll();   //叫醒所有睡觉的借书进程,我还了几本书,你们可以借书了没,不够就继续碎觉吧
            }
            finally {
                lock.unlock();   //无论结果如何,开锁
            }
        }
    }
}

小结

         今天先写到这里,后面有时间再整理细致发出来。

        Java路漫漫,虽道阻且长,但行则必至!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卡丘. 钦爱

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值