本文是一篇练习总结,记录在学习下面这份博客时的问题,并对一些相关问题进行归纳。
参考博文:java后端常见面试题总结
- 这两个方法来自不同的类分别是Thread和Object最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法。wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围)sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常sleep是Thread类的静态方法。sleep的作用是让线程休眠制定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件事恢复线程执行。wait是Object的方法,也就是说可以对任意一个对象调用wait方法,调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify方法才会重新激活调用者。
| 通信方法 | 描述 |
|---|---|
| Thread.sleep(100) | sleep的作用是让线程休眠制定的时间,在时间到达时恢复,也就是说sleep将在接到时间到达事件事恢复线程执行 |
| wait() | 一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。wait是Object的方法,也就是说可以对任意一个对象调用wait方法,调用wait方法将会将调用者的线程挂起,直到其他线程调用同一个对象的notify方法才会重新激活调用者。 |
| notify | 一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程,就唤醒优先级高的线程 |
| notifyAll | 一旦执行此方法,就会唤醒所有被wait()的线程 |
以下为题目思考部分:
这是一个多线程的问题,在学习这个问题时首先我们要对java的多线程有了解,参考博文:多线程的练习。在该博文中使用了两种方法来理解多线程问题:
① 继承Thread类的方法:
这种方法使用wait和sleep函数。
首先,创建User即创建线程,不同线程使用同一个Account。重写User的构造方法和run方法,从而决定线程的操作行为。其次,创建Account,在Account内处理线程同步问题。在Account中需要进行线程同步的方法为cunqian()方法。使用用同步代码块的方式解决线程安全问题,同步监视器为Account.class。关键代码行见下:(从中可以看出sleep和wait的区别)
这里使用notify和wait的作用是保证进程执行顺序按照先“甲”后“乙”的顺序。而synchronized的作用是线程同步,保证线程安全的。如果去掉notify和wait,只是甲乙存款的顺序会乱掉,但是由于synchronized,所以仍然可以保证线程安全。
//存钱方法
public void cunqian(double jine){
if(jine>0){
//用同步代码块的方式解决线程安全问题,同步监视器为Account.class
synchronized (Account.class){
Account.class.notify();
money+=1000;
try{
Thread.sleep(100);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-存款,账户余额为:"+money);
//
try{
Account.class.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
实现Runnable接口:
更多线程问题,可以参考:深入理解线程问题 2. synchronized底层原理是可重入锁吗
重入锁:所谓重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。
synchronized 和 ReentrantLock 都是可重入锁。可重入锁的意义在于防止死锁。实现原理是通过为每个锁关联一个请求计数器和一个占有它的线程。当计数为0时,认为锁是未被占有的;线程请求一个未被占有的锁时,JVM将记录锁的占有者,并且将请求计数器置为1 。如果同一个线程再次请求这个锁,计数将递增;每次占用线程退出同步块,计数器值将递减。直到计数器为0,锁被释放。关于父类和子类的锁的重入:子类覆写了父类的synchonized方法,然后调用父类中的方法,此时如果没有重入的锁,那么这段代码将产生死锁
JVM基于进入和退出Monitor对象来显示方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另外一种方式实现的。monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法的结束处和异常处,JVM保证每个monitorenter必须有对应的monitorexit与之配对。
Synchronize是可重入锁,一个获得锁的线程可以再次进入一个同步块方法
参考链接:可重入锁和不可重入锁。根据链接中的代码例子,我们可以很容易地理解重入锁和不可重入锁。关键在于notify()执行在锁的数量为0时(数量大于0都不notify),还是无锁时(锁的数量只能为1或0)。
3. CAS原理,CAS会有什么问题,怎么解决ABA问题(可以谈Java内存模型)


