java 奇偶数顺序打印和猜数字游戏

java 奇偶数顺序打印

Lock和Synchronized之间的区别
  1. synchronized 属于关键字
  • 针对同步代码块而言,它的底层是通过moniter对象的moniterenter和moniterexit来实现加锁和解锁;每个对象都有一个监视器monitor,当monitor被占用时就处于加锁状态,线程在执行monitorenter指令时就会尝试去获取monitor的执行权。
     - 如果monitor的进入数为0,则线程进入monitor,然后将进入数置为1,该线程即为monitor的所有者
     - 如果线程已经占有了该monitor,只是重新进入,然后将进入数加1(这也就是被称为可重入锁的原因)
     - 如果其他线程已经占有该monitor,则线程就会进入到阻塞状态,直到monitor的进入数为0,再去重新尝试获取
    
  • 如果是静态方法或普通方法,则是通过在flags中加 ACC_SYNCHRONIZED标识来实现的
   当方法被调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行的线程将会先获取monitor,获取成功之后才能执行方法体,方法执行完成之后将释放monitor。在方法执行期间,其他任何线程将无法获取同一个monitor对象。

javap -v -p -s -sysinfo -constants XXX.class 通过该命令可以查看java生成的对应class文件。
Lock是属于API层面

  1. synchronized 不需要用户手动去释放锁,系统会让线程主动释放对锁的占用;
    lock锁必须显示的去释放锁,否则可能会导致死锁,通常会配合try{lock.lock}catch()finally{lock.unlock}使用

  2. synchronized 是不可中断的,需要程序正常执行完方法体或者抛出异常;
    lock是可以中断的,其获取锁的方式:
    1)tryLock(long timeout,TimeUnit unit)lock.tryLock()尝试获取锁,如果获取不到,就处于等待
    2)lockInterruptibly()可中断的锁,调用intterput()方法可中断线程的运行

  3. synchronized 非公平锁,ReentrantLock默认是非公平锁,可以通过构造函数参数设置是公平锁或者非公平锁(公平或者非公平:首先申请获取锁的线程是否首先去获取锁)

  4. ReentrantLock可以通过绑定Condition,实现对线程的精确控制去唤醒其等待线程的执行,而synchronized则无法实现精确控制,只有通过notify或者notifyAll去随机唤醒或者唤醒所有等待的线程

案例一 龟兔赛跑

要求:
1 龟兔都是从起点出发,乌龟跑的慢,兔子跑的快
2 当兔子跑到40m,还没看见乌龟时,休眠一段时间(100ms)
3 当兔子跑到80m时,还是没看见乌龟,则暂停一段时间
4 乌龟处于一直跑的状态

public class GameRunnable {
    // 定义乌龟和兔子的起步线
    private int tortoiseNum = 0;
    private int rabbitNum = 0;
   // private ReentrantLock lock = new ReentrantLock();
    //private Condition condition = lock.newCondition();
    // 定义锁
    private Object lock = new Object();
    public void tortoiseRace() {
        //设置线程优先级 低
        Thread.currentThread().setPriority(Thread.NORM_PRIORITY-1);
        try {
           // lock.lock();
            synchronized (lock) {
                while (tortoiseNum < 100) {
                    tortoiseNum++;
                    System.out.println("乌龟跑:" + tortoiseNum);
                    if (tortoiseNum == 100) {
                        System.out.println("乌龟跑完100");
                    }
                }
            }
        }catch (Exception e){}finally {
            //lock.unlock();
        }
    }

    public void rabbitRace() {
        //设置线程优先级 高
        Thread.currentThread().setPriority(Thread.NORM_PRIORITY+1);
       try {
          //lock.lock();
           synchronized (lock) {
               while (rabbitNum < 100) {
                   rabbitNum++;
                   System.out.println("兔子跑完" + rabbitNum);
                   if (rabbitNum == 40 && tortoiseNum != 40) {
                       Thread.sleep(100);
                       System.out.println("兔子休眠100ms结束");
                   }
                   if (rabbitNum == 80 && tortoiseNum != 80) {
                       //condition.await();
                      // lock.wait();
                      //yield()方法只是让该线程暂时让出CPU的执行权,让其他线程执行
                       Thread.yield();
                   }
                   if (rabbitNum == 100) {
                       System.out.println("兔子跑完100");
                   }
               }
           }
       }catch (Exception e){}finally {
          // lock.unlock();
       }
    }
}

测试类如下:

    public static void main(String[] args) {
        GameRunnable runnable = new GameRunnable();

        Thread t1 = new Thread(() -> {
            runnable.rabbitRace();
        });
        Thread t2 = new Thread(() -> {
            runnable.tortoiseRace();
        });
        t1.start();
        t2.start();
    }

备注:Thread.join()代表的是当前线程放弃CPU的执行权,等待某个线程的执行直至结束,这就使得线程之间的并行执行变成了同步执行
Thread.join()方法举例如下:

public class Test1 {
   static class JoinTest implements Runnable{
       @Override
       public void run() {
           for (int i = 1; i <= 100; i++) {
               System.out.println(Thread.currentThread().getName()+":"+i);
           }
       }
   }
    public static void main(String[] args) throws InterruptedException {
       JoinTest joinTest = new JoinTest();
        Thread t1 = new Thread(joinTest, "t1");
        Thread t2 = new Thread(joinTest, "t2");
        t2.start();
        t1.start();
        /**
         * join会使得当前线程放弃执行,并返回对应的线程
         *  main方法中调用了t1线程的join(),则main线程会放弃CPU的执行权,
         *  并返回t1线程继续执行直至t1线程执行结束
         */
        t1.join();
        System.out.println("main线程执行结束");
    }
}
    public final void join() throws InterruptedException {
        join(0);
    }
     // 传入参数millis为0 代表某个线程一直等待调被用线程的执行,直至被调用线程执行结束
     // 传入参数millis如果是大于0的数值,代表某个线程会等待被调用线程millis后,这两个线程再并行执行
     // 其执行原理就是wait()/notifyAll()方法:
     // 调用join()方法处,被调用方法的wait()执行;当被调用线程执行完毕后,会自动调用其notifyAll()唤醒调用线程继续执行
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }
案例二 模拟3位教师下放100份作业的实现
public class MyData {
    private int num = 100;

    public void issueHomeWork() {
        while (true) {
            synchronized (MyData.class) {
                if (num >= 1) {
                    System.out.println(Thread.currentThread().getName() + "下发第" + (num--) + "份作业");
                }else {
                    break;
                }
            }
        }
    }
}
public class TestMyData {
    public static void main(String[] args) {
        MyData myData = new MyData();
        for (int i = 0; i < 3; i++) {
            new Thread(()->{
                myData.issueHomeWork();
            },"第"+i+"位教师").start();
        }
    }
}

测试时结果总是启动1个或者2个线程下发全部的作业,较难见到三个线程同时运行的情况,因此采用多线程Semaphore信号量的方式对共享资源进行控制其并发数。

public class MyData {
    private int num = 100;
    // 创建固定数量的信号量:控制并发线程数
    Semaphore semaphore = new Semaphore(3);
    public void issueHomeWork() {
        while (true) {
            try {
              //在当前信号量中获取一个许可.当前线程会一直阻塞直到有一个可用的许可,或被其他线程中断.
                semaphore.acquire();
                if (num >= 1) {
                    System.out.println(Thread.currentThread().getName() + "下发第" + (num--) + "份作业");
                }else {
                    break;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
              // 释放一个许可,让它返回到semaphore信号量中.
                semaphore.release();
            }
        }
    }
}
案例三实现奇偶数的顺序打印
public class OrderPrint {
    private int num = 1;
    private Lock lock;
    // 奇数条件
    private Condition oddLock;
    // 偶数条件
    private Condition evenLock;
    // 最大值
    private int MAX_VALUE = 100;
    //线程停止运行标志位
    private boolean isStop = false;

    public OrderPrint(Lock lock) {
        this.lock = lock;
        oddLock = lock.newCondition();
        evenLock = lock.newCondition();
    }
    // 打印奇数
    public void printOdd() {
        try {
            lock.lock();
            while (num <= MAX_VALUE && !isStop) {
                if (num % 2 != 0) {
                    System.out.println(Thread.currentThread().getName() + "输出:" + num);
                    num++;
                    evenLock.signalAll();
                    oddLock.await();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
     //打印偶数
    public void printEven() {
        try {
            lock.lock();
            while (num <= MAX_VALUE && !isStop) {
                // 如果是偶数
                if (num % 2 == 0) {
                    System.out.println(Thread.currentThread().getName() + "输出:" + num);
                    if (num == MAX_VALUE) {
                        // 到达最大值100,设置线程停止标志位为true
                        isStop = true;
                        // 确保奇数线程唤醒,停止其运行
                        oddLock.signalAll();
                    } else {
                        // num ++ 一定是奇数
                        num++;
                        // 唤醒奇数线程进行消费
                        oddLock.signalAll();
                        // 如果num不是偶数 则进行等待
                        evenLock.await();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

测试多线程启动查看结果是否顺序打印:

public class OrderPrintTest {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        OrderPrint data= new OrderPrint(lock);
        new Thread(()->{
            data.printOdd();
        },"奇数线程").start();
        new Thread(()->{
            data.printEven();
        },"偶数线程").start();
    }
}

测试结果如下:
顺序打印奇偶数

案例四 猜数字游戏

要求:
1 一个线程随机生成一个(1-100)的数,等待另一个线程输入
2 如果输入的数值小于或者大于随机生成的数值,则生成随机数的线程给出提示小了或者大了,然后再次等待线程的输入数值,再按照上面顺序循环
3只有猜的数字和随机生成的数字一样,给出猜对了提示后,停止运行

public class GuessData {
    private int num = -1;
    private int guessNum = 0;
    private ReentrantLock lock;
    private Condition waitInput;
    private Condition nextInput;
    // 中断标志位
    private boolean isStop = false;

    public GuessData(ReentrantLock lock) {
        this.lock = lock;
        this.waitInput = lock.newCondition();
        this.nextInput = lock.newCondition();
    }

    public void generateNum() {
        num = new Random().nextInt(100) + 1;
        System.out.println("生成得随机数为:" + num);
        try {
            // 获取可中断得锁
            //lock.lockInterruptibly();
            lock.lock();
            // 死循环判断
            while (true && !isStop) {
                // 等待线程2输入所猜测得数据
                waitInput.await();
                if (num > guessNum) {
                    System.out.println("猜的数字小了");
                } else if (num < guessNum) {
                    System.out.println("猜的数字大了");
                } else {
                    //如果输入得数字和随机生成得数字相等,则中断当前线程
                    isStop = true;
                    System.out.println("恭喜你,猜对了");
                }
                //唤醒线程2进行下次输入
                nextInput.signalAll();
            }
        } catch (Exception e) {
             e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void guessNum() {
        Scanner scanner = new Scanner(System.in);
        try {
           // lock.lockInterruptibly();获取可中断得锁
            lock.lock();
            while (true && !isStop) {
                System.out.println("请输入你猜的数字");
                guessNum = Integer.parseInt(scanner.next());
                //唤醒线程1进行判断
                waitInput.signalAll();
                // 线程2进行等待下次输入
                nextInput.await();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

测试

public class Test {
    public static void main(String[] args) {
        GuessData guessData = new GuessData(new ReentrantLock());

        new Thread(() -> {
            guessData.generateNum();
        }, "生成数字线程").start();
        new Thread(() -> {
            guessData.guessNum();
        }, "猜数字线程").start();

    }
}

测试结果如下:
猜数字游戏

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值