CH8-多线程

【案例8-1】 龟兔赛跑

【案例介绍】

1.任务描述

​ 众所周知的“龟兔赛跑”故事,兔子因为太过自信,比赛中途休息而导致乌龟赢得了比赛.本案例要求编写一个程序模拟龟兔赛跑,乌龟的速度为1米/1500毫秒,兔子的速度为5米/500毫秒,等兔子跑到第700米时选择休息10000毫秒,结果乌龟赢得了比赛。

2.运行结果

运行结果如图8-1所示。

手机屏幕截图  描述已自动生成

【案例目标】

  • 学会分析”龟兔赛跑”任务实现的逻辑思路。

  • 能够独立完成”龟兔赛跑”程序的源代码编写、编译以及运行。

  • 能够在程序中使用多线程完成逻辑思路。

【案例思路】

​ (1) 查看运行结果分析后,首先创建一个Torist()方法作为乌龟线程的内部类,在Torist()方法中使用sleep模拟乌龟跑步。

​ (2) 查看运行结果分析后,创建一个Rabbit()方法作为兔子线程的内部类,在Torist()方法中使用sleep模拟乌龟跑步。

​ (3) 最后在main方法中调用Torist()与Rabbit()方法实现龟兔赛跑。

【案例代码】

龟兔赛跑小程序的代码如文件8-1所示。

文件8-1 race.java

package chapter0401;
public class race
{
    private int toristDistance;//乌龟跑过的距离
    private int rabbitDistance;//兔子跑过的距离
    /**
    * 乌龟线程内部类
    */
    class Torist extends Thread
    {
        @Override
        public void run()
        {
            //分析编程代码
            for(int i = 1; i <= 800; i++)
            {
                //判断兔子是否到达终点
                if(rabbitDistance == 800)
                {
                    //当兔子先800的时候 兔子就已经赢了
                    System.out.println("兔子赢得了比赛,此时乌龟才跑了" + toristDistance + "米");
                    break;
                }
                else
                {
                    //乌龟开始跑
                    toristDistance += 1;
                    //判断距离是否是100的倍数
                    if(toristDistance % 100 == 0)
                    {
                        try
                        {
                            if(rabbitDistance == 700)
                            {
                                System.out.println("乌龟跑了" + toristDistance + "米,此时兔子在睡觉");
                            }
                            else
                            {
                                System.out.println("乌龟跑了" + toristDistance + "米,此时兔子跑过段距离是" + rabbitDistance);
                            }
                            Thread.sleep(1500);
                        }
                        catch (InterruptedException e)
                        {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
    /**
    * 兔子线程内部类
    */
    class Rabbit extends Thread
    {
        @Override
        public void run()
        {
            //分析编程代码
            for(int i = 1; i <= 800 / 5; i++)
            {
                //判断兔子是否到达终点
                if(toristDistance == 800)
                {
                    //当兔子先1000的时候 兔子就已经赢了
                    System.out.println("乌龟赢得了比赛,此时兔子跑了" + rabbitDistance + "米");
                    break;
                }
                else
                {
                    //乌龟开始跑
                    rabbitDistance += 5;
                    //判断距离是否是100的倍数
                    if(rabbitDistance % 100 == 0)
                    {
                        try
                        {
                            System.out.println("兔子跑了" + rabbitDistance + "米,乌龟跑过了"
                                               + toristDistance);
                            if (rabbitDistance == 700)
                            {
                                System.out.println("兔子觉得自己怎么能可以赢得比赛,所以选择睡一会");
                                Thread.sleep(10000);
                            }
                            Thread.sleep(500);
                        }
                        catch (InterruptedException e)
                        {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
    //测试
    public static void main(String[] args)
    {
        //1 外部类实例构建
        race outer = new race();
        //2兔子 乌龟线程实例构建
        Rabbit rabbit = outer.new Rabbit();
        Torist torist = outer.new Torist();
        //3 依次启动
        //在现实中 也不可能两个同时跑 这样也是很公平的
        rabbit.start();
        torist.start();
    }
}

​ 代码第9~38行代码是乌龟线程的内部类,使用for循环嵌套Thread的sleep方法模拟乌龟跑步,代码第42~72行代码是兔子线程的内部类,与乌龟线程的内部类类似,第76~84行代码是启动线程,模拟乌龟兔子赛跑。

【案例8-2】 Svip优先办理服务

【案例介绍】

1.任务描述

​ 在日常工作生活中,无论哪个行业都会设置一些Svip用户,Svip用户具有超级优先权,在办理业务时,Svip用户具有最大的优先级。

​ 本案例要求编写一个模拟Svip优先办理业务的程序,在正常的业务办理中,插入一个Svip用户,优先为Svip用户办理业务。本案例在实现时,可以通过多线程实现。

2.运行结果

运行结果如图8-1所示。

手机屏幕截图  描述已自动生成

【案例目标】

  • 学会分析”Svip优先办理服务”任务实现的逻辑思路。

  • 能够独立完成”Svip优先办理服务”程序的源代码编写、编译以及运行。

  • 能够在程序中使用多线程的”插队”完成逻辑思路。

【案例思路】

​ (1) 查看运行结果分析后,创建一个special()方法模拟Svip办理业务。

​ (2) 查看运行结果分析后,首先创建一个normal()方法模拟正常的窗口排队,当有Svip客户是使用join线程让步,调用special()优先让Svip办理业务。

​ (3) 最后在main方法中调用normal()方法。

【案例代码】

Svip优先办理服务程序的代码实现如文件8-1所示。

文件8-1 svip.java

package chapter0402;
public class svip
{
    public static void main(String[]args) throws InterruptedException
    {
        new Thread(new normal()).start();
    }
}
class specia* extends Thread
{
    public void run()
    {
        System.out.println("svip客户开始办理业务");
        System.out.println("svip客户办理业务的倒计时");
        for(int i = 10; i >= 0; i--)
        {
            System.out.println(i + "秒");
            try
            {
                Thread.sleep(1000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
        System.out.println("svip客户办理完毕");
    }
}
class norma* extends Thread
{
    public void run()
    {
        System.out.println("业务办理窗口在正常排队中");
        System.out.println("此时来了一位svip客户");
        Thread t = new Thread(new special());
        //各走各的逻辑错误,再加入join先执行完special,再执行normal剩下的
        t.start();
        try
        {
            t.join();
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        System.out.println("业务办理窗口又恢复正常排队");
    }
}

​ 文件8-1中第8~24行代码是svip客户办理业务,使用for循环10次模拟办理业务第25~39行代码是模拟窗口正常排队,第32~35行代码是normal线程让步special线程。

【案例8-3】 模拟银行存取钱

【案例介绍】

1.任务描述

​ 在银行办理业务时,通常银行会开多个窗口,客户排队等候,窗口办理完业务,会呼叫下一个用户办理业务。本案例要求编写一个程序模拟银行存取钱业务办理。假如有两个用户在存取钱,两个用户分别操作各自的账户,并在控制台打印存取钱的数量以及账户的余额。

2.运行结果

运行结果如图8-1所示。

电脑屏幕的照片  描述已自动生成

【案例目标】

  • 学会分析”模拟银行存取钱功能”任务实现的逻辑思路。

  • 能够独立完成”模拟银行存取钱功能”程序的源代码编写、编译以及运行。

  • 通过存取款线程理解多线程安全问题的发生原因,并掌握如何解决多线程安全问题。

【案例思路】

​ (1) 通过任务描述和运行结果可以看出,该任务需要使用多线程的相关知识来是实现。由于两个用户操作各自的账户,因此我们需要创建两个线程完成每个用户的操作。这里我们使用实现Runnable接口的方法来创建线程。

​ (2) 既然是储户去银行存款,那么可以得出该任务会涉及到三个类,分别是银行类、储户类和测试类。

​ (3) 定义一个实体类作为账务的集合,包括用户名、登录名、登录密码、钱包、取钱时间和存钱时间等字段。

​ (4) 在银行类中需要定义一个账户的实体类、一个存钱的方法、一个取钱的方法、查询余额的方法和获取当前用户的方法。获取等前用户方法需要使用synchronized线程锁判断是是哪一位用户,在存钱和取钱的方法中先调用获取用户方法判断操作者,再进行存取钱操作,需要注意的是在进行取钱操作是,需要判断余额是否大于需要取的钱数。

​ (5) 在测试类中使用for循环调用线程模拟用户存取钱操作。

【案例代码】

(1) 创建用户类

定义一个用户的类,根据用户实现多人同时存取钱功能,如文件8-1所示。

文件8-1 User.java

package chapter0403;
import java.util.Date;
public class User {
    private String u_name;//用户名
    private String u_login_name;//登录名 卡的id
    private String u_login_pwd;//登录密码
    private String u_wallet;//钱包
    private Date  draw_money_time;//取钱时间
    private Date  save_money_time;//存钱时间
    public User(){}
    public User(String u_name, String u_login_name, String u_login_pwd, 
                String u_wallet) {
        this.u_name = u_name;
        this.u_login_name = u_login_name;
        this.u_login_pwd = u_login_pwd;
        this.u_wallet = u_wallet;
    }
    public User(String u_name, String u_login_name, String u_login_pwd, 
                String u_wallet, Date draw_money_time, Date save_money_time) {
        this.u_name = u_name;
        this.u_login_name = u_login_name;
        this.u_login_pwd = u_login_pwd;
        this.u_wallet = u_wallet;
        this.draw_money_time = draw_money_time;
        this.save_money_time = save_money_time;
    }
    public String getU_name() {
        return u_name;
    }
    public void setU_name(String u_name) {
        this.u_name = u_name;
    }
    public String getU_login_name() {
        return u_login_name;
    }
    public void setU_login_name(String u_login_name) {
        this.u_login_name = u_login_name;
    }
    public String getU_login_pwd() {
        return u_login_pwd;
    }
    public void setU_login_pwd(String u_login_pwd) {
        this.u_login_pwd = u_login_pwd;
    }
    public String getU_wallet() {
        return u_wallet;
    }
    public void setU_wallet(String u_wallet) {
        this.u_wallet = u_wallet;
    }
    public Date getDraw_money_time() {
        return draw_money_time;
    }
    public void setDraw_money_time(Date draw_money_time) {
        this.draw_money_time = draw_money_time;
    }
    public Date getSave_money_time() {
        return save_money_time;
    }
    public void setSave_money_time(Date save_money_time) {
        this.save_money_time = save_money_time;
    }
}

(2) 创建银行业务类

定义一个业务类,实现用户的存取钱功能,如文件8-2所示。

文件8-2 Bank.java

package chapter0403;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class Bank
{
    private List<User> userList = new ArrayList<>();
    public  Bank(List<User> userList)
    {
        this.userList = userList;
    }
    public  List<User> getUserList()
    {
        return userList;
    }
    public void setUserList(List<User> userList)
    {
        this.userList = userList;
    }
    //存钱
    public Boolean saveMoney(String card, String pwd, String moneyNum)
    {
        User u = getUserByCard(card);
        synchronized (Bank.class)
        {
            if (u.getU_login_name().equals(card) &&
                    u.getU_login_pwd().equals(pwd))
            {
                BigDecimal oldData = new BigDecimal(u.getU_wallet());
                BigDecimal money = new BigDecimal(moneyNum);
                u.setU_wallet(oldData.add(money).toString());
                u.setSave_money_time(new Date());
                System.out.println(Thread.currentThread().getName() + "存钱---->" + u.getU_name() +
                                   "在" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(u.getSave_money_time()) +
                                   "存[" + moneyNum + "]钱,余额:" + u.getU_wallet());
                return true;
            }
        }
        System.out.println(getUserByCard(card).getU_name() + "存钱失败");
        return false;
    }
    //取钱
    public Boolean getMoney(String card, String pwd, String moneyNum)
    {
        User u = getUserByCard(card);
        synchronized (Bank.class)
        {
            if (u != null && u.getU_login_name().equals(card) &&
                    u.getU_login_pwd().equals(pwd))
            {
                BigDecimal oldData = new BigDecimal(u.getU_wallet());
                BigDecimal money = new BigDecimal(moneyNum);
                if(oldData.compareTo(money) >= 0)
                {
                    u.setU_wallet(oldData.subtract(money).toString());
                    u.setDraw_money_time(new Date());
                    System.out.println(Thread.currentThread().getName() + "取钱---->" + u.getU_name() +
                                       "在" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(u.getDraw_money_time()) +
                                       "取[" + moneyNum + "]钱,余额:" + u.getU_wallet());
                    return true;
                }
                else
                {
                    System.out.println(getUserByCard(card).getU_name() + "要取[" + moneyNum + "]钱,但余额不足");
                    return false;
                }
            }
        }
        System.out.println(card + "取钱失败");
        return false;
    }
    //查询余额
    public String balanceEnquiry(String card, String pwd)
    {
        for(User u : this.userList)
        {
            if(u.getU_login_name().equals(card) &&
                    u.getU_login_pwd().equals(pwd))
            {
                System.out.println(Thread.currentThread().getName() + ":"
                                   + u.getU_name() + "余额:" + u.getU_wallet());
                return u.getU_wallet();
            }
        }
        System.out.println(Thread.currentThread().getName() + ":" + card + "操作失败");
        return null;
    }
    //获取当前用户
    public synchronized User getUserByCard(String card)
    {
        for(User u : this.userList)
        {
            if(u.getU_login_name().equals(card))
            {
                return u;
            }
        }
        return null;
    }
    public void delayTime(Integer nim)
    {
        try
        {
            Thread.sleep(nim);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

(3) 创建测试类

定义测试类,再类中创建客户对象,并创建和开启线程执行存取钱操作,如文件8-3所示。

package chapter0403;
import java.util.ArrayList;
import java.util.List;
public class BankText {
    public static void main(String[] args) throws Exception {
        User u = new User("张三", "132466", "123", "100");
        User uu = new User("李四", "4600882", "123", "0");
        List<User> list = new ArrayList<>();
        list.add(u);
        list.add(uu);
        Bank atm = new Bank(list);//初始化数据 模拟
        Thread t = new Thread() {
            public void run() {

                for (int i = 0; i < 10; i++) {
                    atm.saveMoney("132466", "123", "12");
                    atm.delayTime(250);
                    atm.getMoney("4600882", "123", "14");
                    atm.delayTime(250);
                }

            }
        };
        Thread tt = new Thread() {
            public void run() {

                for (int i = 0; i < 10; i++) {
                    atm.getMoney("132466", "123", "2");
                    atm.delayTime(250);
                    atm.saveMoney("4600882", "123", "12");
                    atm.delayTime(250);
                }
            }
        };
        t.start();
        tt.start();
    }
}

【案例8-4】 工人搬砖

【案例介绍】

1.任务描述

​ 在某个工地,需要把100块砖搬运到二楼,现在有工人张三和李四,张三每次搬运3块砖,每趟需要10分钟,李四每次搬运5块砖,每趟需要12分钟。本案例要求编写程序分别计算两位工人搬完100块砖需要多长时间。本案例要求使用多线程的方式实现。

2.运行结果

img

【案例目标】

  • 学会分析”工人搬砖”任务实现的逻辑思路。

  • 能够独立完成” 工人搬砖”程序的源代码编写、编译以及运行。

  • 能够在程序中使用多线程完成逻辑思路。

【案例思路】

​ (1) 查看运行结果分析后,需要定义一个搬砖用时的全局变量。还需要定义两个方法分别计算张三和李四搬100块砖所用的时间。

​ (2) 重写run()方法,在run()方法中使用if判断调用计算张三搬砖所用时间的方法还是计算李四搬砖所用时间的方法。

​ (3) 在测试类的main方法中创建并开启线程“张三”和“李四”。

【案例代码】

工人搬砖程序的代码实现如文件8-1所示。

文件8-1 MoveBricks.java

package chapter0404;
class Bricks implements Runnable {
    private int time = 0;
    public void run(){
      if (Thread.currentThread().getName().equals("张三")){
          zsmovebrick();    //计算张三搬砖所用时间的方法
      }else if(Thread.currentThread().getName().equals("李四")){
          lsmovebrick();    //计算李四搬砖所用时间的方法
      }
    }
    private synchronized void zsmovebrick(){
        time=(int)Math.ceil((double)100/(double)3)*10;
        System.out.println(Thread.currentThread().getName()+"搬完100块砖需
	   要"+time+"分钟");
    }
    private synchronized void lsmovebrick(){
        time= 100/5*12;
        System.out.println(Thread.currentThread().getName()+"搬完100块砖需
	   要"+time+"分钟");
    }
}
public class MoveBricks{
    public static void main(String[] args){
        Bricks bricks = new Bricks();
        new Thread(bricks,"张三").start();
        new Thread(bricks,"李四").start();
    }
}

​ 文件8-1中第3行代码定义了一个搬砖用时的全局变量;第5~9行代码用于判断调用计算张三搬砖所用时间的方法还是计算李四搬砖所用时间的方法;第11~15行代码是计算张三搬100块砖所用时间的方法;第16~21行代码是计算李四搬100块砖所用时间的方法;第24~26行代码是创建并开启线程“张三”和“李四”。

【案例8-5】 小朋友就餐问题

【案例介绍】

1.任务描述

​ 一圆桌前坐着5位小朋友,两个人中间有一只筷子,桌子中央有面条。小朋友边吃边玩,当饿了的时候拿起左右两只筷子吃饭,必须拿到两只筷子才能吃饭。但是,小朋友在吃饭过程中,可能会发生5个小朋友都拿起自己右手边的筷子,这样每个小朋友都因缺少左手边的筷子而没有办法吃饭。本案例要求编写一个程序解决小朋友就餐问题,使每个小朋友都能成功就餐。

2.运行结果

运行结果如图8-1所示。

手机屏幕截图  描述已自动生成

【案例目标】

  • 学会分析”小朋友就餐问题”任务实现的逻辑思路。

  • 能够独立完成”小朋友就餐问题”程序的源代码编写、编译以及运行。

  • 通过”小朋友就餐问题”程序理解多线程安全问题的方式原因,并掌握如果解决多线程安全问题。

【案例思路】

​ (1) 查看运行结果分析后,每个小朋友相当于一个线程,所以先创建一个Philosopher()方法作为小朋友。

​ (2) 查看运行结果分析后,创建eating()方法作为小朋友吃饭时的线程,创建thinking()方法作为小朋友玩耍是的线程。

​ (3) 查看运行结果分析后,需要在获取筷子的方法Fork中先定义一个boolean类型的数组,代表5根筷子的使用情况;再使用synchronized线程锁来控制只有左右手的筷子都未被使用时,才允许获取筷子,且必须同时获取左右手筷子。

​ (4) 查看运行结果分析后,需要在释放左右手筷子的方法putFork中使用synchronized线程锁来释放筷子。

​ (5) 最后在Test测试类中调用5次以上方法,代表5位小朋友。

【案例代码】

小朋友就餐问题的程序代码实现如文件8-1所示。

文件8-1 Philosopher.java

package chapter04061;
/*每个小朋友相当于一个线程*/
public class Philosopher extends Thread{
    private String name;
    private Fork fork;
    public Philosopher(String name,Fork fork){
        super(name);
        this.name=name;
        this.fork=fork;
    }
    public void run(){
        while(true){
            thinking();
            fork.takeFork();
            eating();
            fork.putFork();
        }
    }
    public void eating(){
        System.out.println("小朋友"+name+"在吃饭");
        try {
            sleep(1000);//模拟吃饭,占用一段时间资源
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public void thinking(){
        System.out.println("小朋友"+name+"在玩游戏");
        try {
            sleep(1000);//模拟思考
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
class Fork{
    /*5只筷子,初始为都未被用*/
    private boolean[] used={false,false,false,false,false,false};
    /*只有当左右手的筷子都未被使用时,才允许获取筷子,且必须同时获取左右手筷子*/
    public synchronized void takeFork(){
        String name = Thread.currentThread().getName();
        int i = Integer.parseInt(name);
        while(used[i]||used[(i+1)%5]){
            try {
                wait();//如果左右手有一只正被使用,等待
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        used[i ]= true;
        used[(i+1)%5]=true;
    }
    /*必须同时释放左右手的筷子*/
    public synchronized void putFork(){
        String name = Thread.currentThread().getName();
        int i = Integer.parseInt(name);
        used[i]= false;
        used[(i+1)%5]=false;
        notifyAll();//唤醒其他线程
    }
}

​ 文件8-1中第3~18行代码封装一个小朋友的方法,第19~27行代码是封装了小朋友吃饭时的方法,第28~37行代码封装了小朋友玩耍时的方法,第28~64行代码封装了筷子使用情况的方法。

测试类的代码如文件8-2所示,调用5次Fork代表5个小朋友。

文件8-2 Test.java

package chapter04061;
public class Test {
	  public static void main(String []args){
	        Fork fork = new Fork();
	        new Philosopher("0",fork).start();
	        new Philosopher("1",fork).start();
	        new Philosopher("2",fork).start();
	        new Philosopher("3",fork).start();
	        new Philosopher("4",fork).start();
	    }
}
  • 11
    点赞
  • 61
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

绿洲213

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

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

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

打赏作者

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

抵扣说明:

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

余额充值