• 多线程-作业练习


    1. 写2个线程,其中一个线程打印1~52,另一个线程打印A~Z,打印顺序应该是12A34B56C…5152Z。该题需要用到多线程通信的知识。
    解题思路:首先从最简单的功能着手,先思考如何写一个方法打印1~52,再写一个方法打印A~Z。

    打印1~52 的方法方法简单,可以通过一个for循环来解决:

    for (int i=1 ; i<52; i++)
    {
    System.out.print(i);
    }

    打印A~Z的方法一开始迷了一下,后来参考网上的方法,用char[]数组来实现,比打印数字稍微复杂一些,先创建一个长度为26的char[]数组,然后用ASSCAL码对其赋值,类型转换为字符型:

    char[] zimu = new char[26];
    for (int i = 0; i < 26; i++)
    {
    zimu[i] = (char) (i + 65);
    }

    ==然后通过foreach循环来遍历循环输出该数组的元素==

    for(char c : zimu)
    {
    System.out.print(c);
    }

    然后考虑创建一个包含这两种方法的类,

    public class PrintStore {
    //这里一开始我使用public修饰flag变量,后来想了一下,这个变量值只应该被该类自身访问修改,所以改为了private
    //通过一个标识flag来确保打印字母的线程一定要等到打印数字线程执行后才得到执行机会。
    private boolean flag = false;

    public synchronized void printNumber() {
    // System.out.printf("打印数字线程启动,此时flag为 %s ", flag);

    for (int i = 1; i < 53; i++) {
    System.out.print(i);
    if (i % 2 == 0) {
    flag = true;
    notifyAll();
    try {
    wait();

    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }

    public synchronized void printLetter() {
    // System.out.printf("打印字母线程启动,此时flag为 %s ", flag);
    char[] zimu = new char[26];
    for (int i = 0; i < 26; i++) {
    zimu[i] = (char) (i + 65);
    }

    for (char c : zimu) {
    if (flag) {
    System.out.print(c);
    flag = false;
    notify();
    try {
    wait();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    }
    }
    }

    然后写两个线程类

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

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

    }

    最后是写主程序,也就是main方法所在的程序,其实功能就是给一个程序的入口,创建线程,启动线程

    public class HomeWork1 {

    public static void main(String[] args) {
    PrintStore ps=new PrintStore();
    PrintNumber pn=new PrintNumber(ps);
    PrintLetter pl=new PrintLetter(ps);
    new Thread(pn).start();
    new Thread(pl).start();
    }

    }

    写这个程序过程中遇到了不少问题,一开始我创建线程时,没有用到共享资源,导致两个线程都是独立的,然后发现线程A的wait()方法和notify()方法,只能暂停和启动A线程本身,并不会通知B线程启动。后来仔细研究了一下这里的问题:

    Object类的wait(),notify(),notifyAll()方法必须由同步监视器对象来调用。
    * 对于使用synchronized修饰的同步方法,因为该类的默认实例(this)就是同步监视器,所以可以在同步方法中直接调用这三个方法;
    * 对于使用synchronized修饰的同步代码块,同步监视器是synchronized后括号里的对象,所以必须使用该对象调用。

    这三个方法,都只能对同一个同步监视器上的线程有效,所以我最开始的那种方式,其实存在着两个同步监视器,线程A和线程B各自有各自的同步监视器,所以A不能成功通知到B。后来参考这个:
    深入理解Java多线程之线程间的通信方式
    发现两个线程之间如果想要成功的进行通信,必须要创建一个线程之间的共享资源,然后把这个共享资源当做同步监视器。

    使用synchronized关键字同步来实现线程间的通信
    public class MyObject {

    synchronized public void methodA() {
    //do something....
    }

    synchronized public void methodB() {
    //do some other thing
    }
    }

    public class ThreadA extends Thread {

    private MyObject object;
    //省略构造方法
    @Override
    public void run() {
    super.run();
    object.methodA();
    }
    }

    public class ThreadB extends Thread {

    private MyObject object;
    //省略构造方法
    @Override
    public void run() {
    super.run();
    object.methodB();
    }
    }

    public class Run {
    public static void main(String[] args) {
    MyObject object = new MyObject();

    //线程A与线程B 持有的是同一个对象:object
    ThreadA a = new ThreadA(object);
    ThreadB b = new ThreadB(object);
    a.start();
    b.start();
    }
    }

    由于线程A和线程B持有同一个MyObject类的对象object,尽管这两个线程需要调用不同的方法,但是它们是同步执行的,比如:线程B需要等待线程A执行完了methodA()方法之后,它才能执行methodB()方法。这样,线程A和线程B就实现了 通信。

    这种方式,本质上就是“共享内存”式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行。

    还遇到了程序打印结果是12A4C68E..这样的情况,是代码的循环逻辑出了问题,在for循环中我只让它在满足条件时打印出来,却忘了不满足条件时这个循环其实也在进行着,所以出现了这种隔着打印的情况。

    2. 假设车库有3个车位(可以通过boolean[]数组来表示车库)可以停车,写一个程序模拟多个用户开车离开,停车入库的效果。注意:车位有车时不能停车。
    这道题也想了很久,使用数组来表示车库的话,我的思路大概是这样的
    写一个Car类,里面定义了车库,还有停车,开车的方法.
    但是这里面出现了很多想不通的问题,数组标识车库的话,每当有车驶入,驶出时,怎么修改数组中相应元素的状态。觉得很麻烦,想参考别人的做法,发现他用的是BlockingQueue阻塞队列来完成的,确实利用阻塞队列的特点这道题很容易,==因为阻塞队列就是队列已满时,再试图添加元素就会自动线程阻塞。队列已空时试图取出元素也会自动阻塞。==

    public class Car {
    // 使用boolean数组表示停车场,FALSE表示车位空,可停车,TRUE表示车位有车
    private boolean[] Park = new boolean[] { false, false, false };

    int CarNo;
    //重写toString方法,否则打印出来的是hash地址值
    @Override
    public String toString() {
    return (CarNo + "号车");
    };
    //写了有参数的构造器后系统将不再主动提供无参数构造器,自己写
    public Car() {
    }

    public Car(int CarNo) {
    this.CarNo = CarNo;
    }

    public synchronized void CarIn(Car c) throws InterruptedException {
    for (boolean b : Park) {

    if (b == false) {
    System.out.println("有空车位," + c.toString() + "已停入停车场");
    b = true;

    }
    else {
    System.out.println("车位已满,请耐心等待");
    wait();
    }


    }
    }

    public synchronized void CarOut(Car c) {

    System.out.println(c.toString() + "已离开停车场");

    notifyAll();
    }
    }

    这是最初尝试使用数组来模拟停车场,未成功完成。可以参考网上这篇博客 数组模拟停车场 但是这个程序写的太复杂了,他还考虑到了每辆车停进去使用的是哪个车位。
    从他这个程序中想到了另一种思路: 只用一个state成员变量来定义停车场中剩余的车位数,这样可以简化程序。

    public class Park {

    private int state = 3;

    public synchronized void CarIn(int i) {

    try {
    while (state == 0) {
    System.out.println("目前空余车位为 " + state + " 请等待");
    wait();
    }
    System.out.println(i+"号车停车成功");
    state = state - 1;
    System.out.println("目前剩余车位: " + state);
    notifyAll();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }

    public synchronized void CarOut(int i) {

    try {
    while (state == 3) {
    wait();
    }
    System.out.println(i +"号车已驶出");
    state = state + 1;
    System.out.println("目前剩余车位: " + state);
    notifyAll();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }

    }


    public class CarInThread extends Thread{
    Park park=new Park();
    public CarInThread(Park park) {
    this.park=park;
    }

    @Override
    public void run() {
    super.run();
    for(int i=1;i<20;i++){
    park.CarIn(i);
    }
    }
    }

    public class CarOutThread extends Thread{
    Park park=new Park();
    public CarOutThread(Park park) {
    this.park=park;
    }
    @Override
    public void run() {
    super.run();
    for(int i=1;i<20;i++){
    park.CarOut(i);
    }
    }
    }

    public class HomeWork2 {
    public static void main(String[] args){
    Park park =new Park();
    CarInThread cInThread=new CarInThread(park);
    CarOutThread cOutThread=new CarOutThread(park);
    new Thread(cInThread).start();
    new Thread(cOutThread).start();


    }
    }

    这样就算是成功完成了这道题,输出结果是这样的:
    1号车停车成功
    目前剩余车位: 2
    1号车已驶出
    目前剩余车位: 3
    2号车停车成功
    目前剩余车位: 2
    3号车停车成功
    目前剩余车位: 1
    4号车停车成功
    目前剩余车位: 0
    目前空余车位为 0 请等待
    2号车已驶出
    目前剩余车位: 1
    3号车已驶出
    目前剩余车位: 2
    4号车已驶出
    目前剩余车位: 3
    5号车停车成功
    目前剩余车位: 2
    6号车停车成功
    目前剩余车位: 1
    7号车停车成功
    目前剩余车位: 0
    目前空余车位为 0 请等待
    5号车已驶出
    目前剩余车位: 1

    改为使用Condition控制线程通信:

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;

    public class Park {
    private Lock lock=new ReentrantLock();
    private Condition con=lock.newCondition();
    private int state = 3;

    public void CarIn(int i) {
    lock.lock();
    try {
    while (state == 0) {
    System.out.println("目前空余车位为 " + state + " 请"+i+"号车等待");
    con.await();
    }
    System.out.println(i+"号车停车成功");
    state = state - 1;
    System.out.println("目前剩余车位: " + state);
    con.signalAll();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }finally {
    lock.unlock();
    }
    }

    public void CarOut(int i) {
    lock.lock();
    try {
    while (state == 3) {
    con.await();
    }
    System.out.println(i +"号车已驶出");
    state = state + 1;
    System.out.println("目前剩余车位: " + state);
    con.signalAll();
    } catch (InterruptedException e) {
    e.printStackTrace();
    }finally {
    lock.unlock();
    }
    }

    }

    原文:https://blog.csdn.net/oh_mygarden/article/details/53648665
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    Map总结(HashMap, Hashtable, TreeMap, WeakHashMap等使用场景)
    IP地址资源的分配和管理
    破解中常见的指令及修改
    8086 CPU 寻址方式
    汇编指令速查
    关于ida pro的插件keypatch
    动态方式破解apk进阶篇(IDA调试so源码)
    IDA7.0安装keypatch和findcrypt-yara插件
    Android逆向之旅---动态方式破解apk进阶篇(IDA调试so源码)
    IDA动态调试技术及Dump内存
  • 原文地址:https://www.cnblogs.com/flutehand/p/10242920.html
Copyright © 2020-2023  润新知