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();
    }
}

测试结果如下:
《java 奇偶数顺序打印和猜数字游戏》

案例四 猜数字游戏

要求:
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();

    }
}

测试结果如下:
《java 奇偶数顺序打印和猜数字游戏》

    原文作者:Michael312917
    原文地址: https://blog.csdn.net/Michael312917/article/details/105663711
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞