java 奇偶数顺序打印
Lock和Synchronized之间的区别
- 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层面
-
synchronized 不需要用户手动去释放锁,系统会让线程主动释放对锁的占用;
lock锁必须显示的去释放锁,否则可能会导致死锁,通常会配合try{lock.lock}catch()finally{lock.unlock}
使用 -
synchronized 是不可中断的,需要程序正常执行完方法体或者抛出异常;
lock是可以中断的,其获取锁的方式:
1)tryLock(long timeout,TimeUnit unit)
或lock.tryLock()
尝试获取锁,如果获取不到,就处于等待
2)lockInterruptibly()
可中断的锁,调用intterput()
方法可中断线程的运行 -
synchronized 非公平锁,ReentrantLock默认是非公平锁,可以通过构造函数参数设置是公平锁或者非公平锁(公平或者非公平:首先申请获取锁的线程是否首先去获取锁)
-
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();
}
}
测试结果如下: