java疯狂讲义 16章多线程习题

习题1:

写2个线程,其中一个线程打印1-52,另一个线程打印A-Z,打印顺序应该是12A34B56C……5152Z。该习题需要利用多线程通信的知识。

刚开始的思路:

既然需要两个线程打印,那就写两个类,分别继承Thread,里面的run方法写各自的打印逻辑;但是又想?这么写,该如何通信呢。。。。。。通信需要同步监视器来实现,这么写他们两个打印逻辑其实没有所谓的共享资源作为同步监视器。那么需要创建一个打印机的类,作为共享资源(竞争资源,以上两个打印线程来竞争使用打印机),那么就有了以下的代码:

  • Printer:打印机类
  • PrintNumThread:打印数字的线程
  • PrintAlphaThread:打印字母的线程
  • Main:主函数类

Printer类,其实里面什么都没有,主要是做共享资源用

//Printer类
public class Printer {
    private String name;

    public Printer(){}
    
    public Printer(String name){
        this.name=name;
    }
}

PrintAlphaThread类,打印字母线程

public class PrintAlphaThread extends Thread {
    private Printer pr;

    public PrintAlphaThread(Printer pr){
        this.pr=pr;
    }
    
    @Override
    public void run() {
        synchronized (pr) {
        for (char i=65;i<26+65;i++) {
                System.out.print(i);
//                如果打印一个字符了,发送消息到其他线程,进入阻塞状态
                pr.notifyAll();
                try {
                    pr.wait();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        pr.notifyAll();
        }
    }
}

PrintNumThread 类,打印数字线程,和上面差不多

public class PrintNumThread extends Thread {
    private Printer pr;

    public PrintNumThread(Printer pr){
        this.pr=pr;
    }

    @Override
    public void run() {
        synchronized (pr) {
        for (int i=1;i<=52;i++) {
                System.out.print(i);
                if (i % 2 == 0) {
//                如果打印一个字符了,发送消息到其他线程,进入阻塞状态
                    pr.notifyAll();
                    try {
                        pr.wait();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            }
        pr.notifyAll();
        }
    }
}

主类:创建线程

public class Main {
    public static void main(String[] args)
    {
        Printer pr=new Printer();
        PrintNumThread p1=new PrintNumThread(pr);
        PrintAlphaThread p2= new PrintAlphaThread(pr);
        p1.start();
        //此处可以写一下等待逻辑,保证p1先运行;Thread.sleep(100);
        p2.start();

}
}

总结:

  • 以上代码采用:继承Thread实现多进程;采用同步代码块实现线程同步;注意同步代码块显式指定同步监视器pr,在代码块中需要采用pr去调用wait、等方法
  • 同步代码块----可以在for循环中,也可以在for循环外;但是由于打印字母的线程打印最后Z后,进入阻塞,没有线程来唤醒,所以程序不会停止;好一点的做法是,for循环结束后再次notifyAll通知,因此,同步代码块设置为整个for循环比较合理

改进1:

因为Printer 代码看着太故意了,所以我们把打印逻辑写成-------Printer的同步方法;两个打印线程不变,只不过线程执行体中放的是打印机的两个同步方法。

  • PointMonitor1 :打印机类
  • PrintNumber :打印数字的线程
  • PrintLetter :打印字母的线程
  • Main1:主函数类

PointMonitor1 :打印机类

public class PointMonitor1 {
    public synchronized void printnum(){
        for (int i=1;i<=52;i++){
            System.out.print(i);
            if (i%2==0){
                notifyAll();
                try{wait();
                }
                catch(Exception ex){
                    ex.printStackTrace();
                }
            }
        }
        notifyAll();
    }
    public synchronized void printstr(){
        for (int i=65;i<65+26;i++){
            System.out.print((char)i);
            notifyAll();
            try{wait();
            }
            catch(Exception ex){
                ex.printStackTrace();
            }
        }
        notifyAll();
    }
}

PrintLetter 打印字母的线程

class PrintLetter implements Runnable {
    private PointMonitor1 ps;
    public PrintLetter(PointMonitor1 ps) {
        this.ps=ps;
    }
    @Override
    public void run() {
        ps.printstr();
    }
}

PrintNumber :打印数字的线程

class PrintNumber implements Runnable {
    private PointMonitor1 ps;
    public PrintNumber(PointMonitor1 ps) {
        this.ps=ps;
    }
    @Override
    public void run() {
        ps.printnum();
    }
}

Main1:主函数类

public class Main1 {
    public static void main(String[] agrs){
        PointMonitor1 pm=new PointMonitor1();
        PrintNumber t1=new PrintNumber(pm);
        PrintLetter t2=new PrintLetter(pm);

        new Thread(t1).start();
        new Thread(t2).start();
    }
}

总结1:

  • 采用runnable接口实现多线程,在主类中需要采用Thread包装一下runnable对象
  • 两个打印线程直接调用打印机的同步方法,在同步方法中,使用wait等默认对象的this当前调用该方法的对象pr

改进2:

看到有些大佬把两个打印线程综合为一个类:该类的构造器传入打印的String和打印间隔,和共享资源printer;一共使用三个类实现了相同的功能

PointMonitor

public class PointMonitor {

    public synchronized void pointAll (String[] strings,int interval)
    throws Exception
    {
        for (int i=0;i<strings.length;i++){
            System.out.print(strings[i]);
            if ((i+1)%interval==0){
                this.notifyAll();
                this.wait();
            }
        }
        notifyAll();
    }
}


PrintThread 调用打印机的通用的同步方法,完成打印

public class PrintThread extends Thread{
    private PointMonitor pm;
    private String[] strings;
    private int interval;

    public PrintThread(PointMonitor pm,String[] strings,int interval){
        this.interval=interval;
        this.pm=pm;
        this.strings=strings;
    }
    @Override
    public void run(){
        try{
        pm.pointAll(strings,interval);}
        catch (Exception ex){
            ex.printStackTrace();
        }
        System.out.println(this.getName()+"线程结束");
    }
}

Main

public class Main {
    public static void main(String[] args){
        String Num[] = new String[52];
        String Str[] = new String[26];

        for (int i=1;i<=52;i++){
            Num[i-1]=i+"";
        }
        for (int i=65;i<65+Str.length;i++){
            Str[i-65]=(char)i+"";
        }
        for (String s:Num
             ) {System.out.print(s);

        }
        System.out.println();
        for (String s:Str
        ) {System.out.print(s);

        }
        System.out.println();

        PointMonitor pm=new PointMonitor();
        PrintThread printnum=new PrintThread(pm,Num,2);
        PrintThread printstr=new PrintThread(pm,Str,1);

        printnum.start();
        try{
            Thread.sleep(10);
        }
        catch (Exception ex){
            ex.printStackTrace();
        }
        printstr.start();

//        System.out.println("run over");
    }
}

习题1进阶(三个线程顺序打印ABC):

1.循序打印ABC各100次(tx面试手写忘了。。。泪崩)

此处打印机对象就不仅仅是一个空的竞争资源了,还承担了控制多个打印线程之间顺序的作用(线程间通讯)
在这里插入图片描述

public class main {

    public static void main(String[] args) throws InterruptedException{
        Printer printer=new Printer(1);
        PrintA pA=new PrintA(printer);
        PrintB pC=new PrintB(printer);
        PrintC pB=new PrintC(printer);
        pA.start();
        pB.start();
        pC.start();
    }
}
class Printer{
    public int next;
    public Printer(){}
    public Printer(int next){
        this.next=next;
    }
}



class PrintA extends Thread{
    private Printer pr;
    public PrintA(Printer pr){
        this.pr=pr;
    }
    @Override
    public void run() {
        synchronized (pr){
            try{
                int i=0;

                while(i++<100){

                    while(pr.next!=1){pr.wait();}
                    System.out.print("A");
                    pr.next=2;
                    pr.notifyAll();
                }
            }catch (Exception ex){ex.printStackTrace();}
        }
    }
}



class PrintB extends Thread{
    private Printer pr;
    public PrintB(Printer pr){
        this.pr=pr;
    }
    @Override
    public void run() {
        synchronized (pr){
            try{
                int i=0;

                while(i++<100){
                    while(pr.next!=2){pr.wait();}
                    System.out.print("B");
                    pr.next=3;
                    pr.notifyAll();
                }
            }catch (Exception ex){ex.printStackTrace();}
        }
    }
}



class PrintC extends Thread{
    private Printer pr;
    public PrintC(Printer pr){
        this.pr=pr;
    }
    @Override
    public void run() {
        synchronized (pr){
            try{
                int i=0;
                while(i++<100){
                    while(pr.next!=3){pr.wait();}
                    System.out.print("C");
                    pr.next=1;
                    pr.notifyAll();
                }
            }catch (Exception ex){ex.printStackTrace();}
        }
    }
}

2.分别循序打印ABC,10,5,2次

这个题如果按照上面的写法去做的话,当打印完C之后,A就不能被C再次唤醒,因此B也无法被A唤醒,程序进入阻塞;暂时还没想好怎么写

习题2:

习题2.假设车库有3个车位(可以用boolean[]数组来表示车库)可以停车,写一个程序模拟多个用户开车离开、停车入库的效果。注意:车库有车时不能停车

最开始的思路:

车库作为共享资源被竞争
实现3个类:
Park1:停车场,里面含有三个停车位,提供检查车位的方法(返回空车位)、入库,出库的方法
CarThread1:停车线程,根据逻辑实现查询车位,入库,停留,出库
Main:主类,

Park1

public class Park1 {
    private boolean[] carport={true,true,true};

//    获得车库空位
    public int getempty(){
        for(int i=0;i<carport.length;i++){
            if(carport[i]){
                return i;
            }
        }
        return -1;
    }
//    入库指定车位
    public void inport(int port){
        carport[port]=false;


    }
//    指定车位出库
    public void outport(int port){
        carport[port]=true;


    }
    public void show(){
        String s="车库状态:";
        for(boolean b:carport){
            s=s+b+',';
        }
        System.out.println(s);
    }
}

CarThread1

public class CarThread1 implements Runnable{
    private Park1 pk;

    public CarThread1(Park1 pk){
        this.pk=pk;
    }
    @Override
    public void run() {
//        有车来了,首先查询,没有空位需等待下次查询
//        因此用while
        synchronized (pk) {
            for (int i=0;i<10;i++){
                while (pk.getempty() == -1) {
                    try {
                        pk.wait();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
//        有车位,记住位置,停车
                int port = pk.getempty();
                pk.inport(port);
                System.out.print(Thread.currentThread().getName()+"入库"+port+"  ");
                pk.show();
//         停留1s
                try {
                    Thread.sleep(1000);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
//         1s时间到,出库
                pk.outport(port);
                System.out.print(Thread.currentThread().getName()+"出库"+port+"  ");
                pk.show();
                pk.notifyAll();
            }

        }

    }
}

Main1

public class Main1 {
    public static void main(String[] args){
        Park1 pk=new Park1();
        CarThread1 r1=new CarThread1(pk);
        CarThread1 r2=new CarThread1(pk);
        CarThread1 r3=new CarThread1(pk);
        CarThread1 r4=new CarThread1(pk);
        CarThread1 r5=new CarThread1(pk);

        new Thread(r1).start();
        new Thread(r2).start();
        new Thread(r3).start();
        new Thread(r4).start();
        new Thread(r5).start();
    }
}

问题:

由于同步方法块,包含了整个停车逻辑,因此,在车辆停留时间内,都占用着同步监视器;出现每次只有一个车位被占后,整个停车场被锁的感觉

在这里插入图片描述
然后想,修改停车线程,把Thread.sleep排除在同步代码块之外,也就是此时释放同步锁,允许其他车辆进入车库

public class CarThread1 implements Runnable{
    private Park1 pk;

    public CarThread1(Park1 pk){
        this.pk=pk;
    }
    @Override
    public void run() {
//        有车来了,首先查询,没有空位需等待下次查询
//        因此用while
        int port;

            for (int i = 0; i < 10; i++) {
                synchronized (pk) {
                    while (pk.getempty() == -1) {
                        try {
                            pk.wait();
                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }
                    }
//        有车位,记住位置,停车
                    port = pk.getempty();
                    pk.inport(port);
                    System.out.print(Thread.currentThread().getName() + "入库" + port + "  ");
                    pk.show();
                }
//         停留1s
                try {
                    Thread.sleep(1000);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
//         1s时间到,出库
                synchronized (pk) {
                    pk.outport(port);
                    System.out.print(Thread.currentThread().getName() + "出库" + port + "  ");
                    pk.show();
                    pk.notifyAll();
                }
            }
        }
}

一切正常
在这里插入图片描述

改进1:

由于上述代码把停车逻辑全部卸载线程执行体中,看着有些庞大,可以将停车逻辑写到Park的方法中,供run执行体调用即可。

Park

public class Park {
//    private boolean[] carport=new boolean[3];
    private boolean[] carport={true,true,true};

//    public Park(){
//        for(int i=0;i<this.carport.length;i++){
//            this.carport[i]=true;
//        }
//    }
    private int GetEmptyport(){
        for(int i=0;i<carport.length;i++){
           if(carport[i]){
               return i;
           }
        }
        return -1;
    }

//入库,返回入库车位
    public synchronized int InPort(){
        while (GetEmptyport()==-1){
            mywait();
        }
        int port=GetEmptyport();
        carport[port]=false;

        System.out.print("入库车位:"+(port+1)+" ");;
        show();
        return port;

    }
//    出库:输入出库车位
    public synchronized void OutPort(int port){

        carport[port]=true;
        System.out.print("出库车位:"+(port+1)+" ");
        show();
        notifyAll();

    }

    private void mywait(){
        try{
            wait();
        }
        catch (Exception ex){
            ex.printStackTrace();
        }
    }
    private void show(){
        String s="";
        for(boolean b:carport){
            s=s+b+',';
        }
        System.out.println(s);
    }

}

CarThread

public class CarThread extends Thread{
    private Park park;

    public CarThread(Park park){
        this.park=park;

    }
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            int port=park.InPort();

            try {
                Thread.sleep(1000);
            }
            catch (Exception ex){
                ex.printStackTrace();
            }
            park.OutPort(port);
        }
    }
}

Main

public class Main {
    public static void main(String[] args){
        Park cp=new Park();
        new CarThread(cp).start();
        new CarThread(cp).start();
        new CarThread(cp).start();
        new CarThread(cp).start();
        new CarThread(cp).start();
    }
}

改进2:

以上两种都是把整个停车场当做共享资源,因此,同一时刻只能有一个车辆线程操作park,但其实一个停车场有很多车位,其实可以同时操作,这里借鉴一下大佬的思路,把共享资源细化到每个车位,就不写代码了:贴个链接https://blog.csdn.net/qq_33256688/article/details/74781752

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值