在调用wait方法时,线程必须要持有被调用对象的锁,当调用wait方法后,线程就会释放掉对象的锁(monitor);在调用Thread类的sleep方法时,线程是不会释放掉对象的锁的(阻塞)。
package com.msz;
public class msz_001_test {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
synchronized (object) {
object.wait();
}
}
}
关于wait与notify和notifyAll方法的总结:
①当调用wait时,首先需要确保调用了wait方法的线程已经持有了对象的锁;
②当调用wait后,该线程就会释放掉这个对象的锁,然后进入到等待状态(wait set);
③当线程调用了wait后进入到等待状态时,它就可以等待其他线程调用相同对象的notify或者notifyAll方法来使自己被唤醒;
④一旦这个线程被其他线程唤醒后,该线程就会与其他线程一同开始竞争这个对象的锁(公平竞争),只有当该线程获取到了这个对象的锁后,线程才会继续往下执行;
⑤调用wait方法的代码片段需要放在一个synchronized块或是synchronized方法中,这样才可以确保线程在调用wait方法前已经获取到了对象的锁;
⑥当调用对象的notify方法时,它会随机唤醒该对象等待集合(wait set)中的任意一个线程,当某个线程被唤醒后,它就会与其他线程一同竞争对象的锁;
⑦当调用对象的notifyAll方法时,它会唤醒该对象等待集合(wait set)中的所有线程,这些线程被唤醒后,又开始竞争对象的锁;
⑧在某一时刻,只有唯一 一个线程可以拥有对象的锁。
public class Test {
public synchronized void method_1() {
}
public synchronized void method_2() {
}
}
Test test = new Test();
当某一个线程执行这个Test对象的method_1方法时但是还没有执行完,此时其他线程是不能再执行method_2方法的,因为对于同一个对象来说它的所有synchronized方法的锁的对象或者monitor是一个东西;当一个线程执行了method_1方法获取到对象锁时,其他线程是无论如何也不能执行method_2方法的。
Test test = new Test();
Test test_1 = new Test();
第一个对象执行method_1方法,第二个对象执行method_2方法;是能够行得通的,因为是两个对象,它们是两个不同的锁或者monitor,第一个线程获取第一个对象的锁,第二个线程获取第二个对象的锁,相互之间没有影响的。
public class Test {
public synchronized void method_1() {
}
public static synchronized void method_2() {
}
}
Test test = new Test();
一个线程执行上面这个对象的method_1和method_2方法是行得通的;因为method_1方法synchronized锁的是Test对象,而method_2方法synchronized锁的是Test对象所对应的class对象。一个是当前对象,一个是当前对象对应的class对象,所以锁的对象或者monitor不是一个东西;故而一个线程可以同时执行这两个方法;两个不同的对象拥有自己不同的锁或者monitor。
题目:编写一个多线程程序,实现目标:
①存在一个对象,该对象有一个int类型的成员成员变量counter,该成员变量的初始值为0;
②创建两个线程,其中一个线程对该对象的成员变量counter增1,另一个线程对该对象的成员变量减1;
③输出该对象成员变量counter每次变化后的值;
④最终输出的结果应为:1010101010…
package com.msz;
public class msz_002_MyObject {
private int counter;
public synchronized void increase() {
if (counter != 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counter++;
System.out.println(counter);
notify();
}
public synchronized void decrease() {
if (counter == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counter--;
System.out.println(counter);
notify();
}
}
package com.msz;
public class msz_003_IncreaseThread extends Thread {
private msz_002_MyObject myObject;
public msz_003_IncreaseThread(msz_002_MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
for (int i=0; i<30; ++i) {
try {
Thread.sleep((long)Math.random() * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myObject.increase();
}
}
}
package com.msz;
public class msz_004_DecreaseThread extends Thread{
private msz_002_MyObject myObject;
public msz_004_DecreaseThread(msz_002_MyObject myObject) {
this.myObject = myObject;
}
@Override
public void run() {
for (int i=0; i<30; ++i) {
try {
Thread.sleep((long)Math.random() * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myObject.decrease();
}
}
}
package com.msz;
import sun.misc.InnocuousThread;
public class msz_005_Client {
public static void main(String[] args) {
msz_002_MyObject myObject = new msz_002_MyObject();
Thread increaseThread = new msz_003_IncreaseThread(myObject);
Thread decreaseThread = new msz_004_DecreaseThread(myObject);
increaseThread.start();
decreaseThread.start();
}
}
package com.msz;
import sun.misc.InnocuousThread;
public class msz_005_Client {
public static void main(String[] args) {
msz_002_MyObject myObject = new msz_002_MyObject();
Thread increaseThread = new msz_003_IncreaseThread(myObject);
Thread increaseThread2 = new msz_003_IncreaseThread(myObject);
Thread decreaseThread = new msz_004_DecreaseThread(myObject);
Thread decreaseThread2 = new msz_004_DecreaseThread(myObject);
increaseThread.start();
increaseThread2.start();
decreaseThread.start();
decreaseThread2.start();
}
}
当启动四个线程时程序出现两个问题:
①输出结果不正确;
②程序不退出(挂起状态)
分析原因:当程序启动两个线程情况下,假如一个线程执行一个线程等待,当这个执行线程执行到notify时,唤醒的必然是另外一个等待的线程,以此相互【等待唤醒】【唤醒等待】,故能够完成题目中的需求。当线程启动四个或者更多的时候线程被唤醒时需要再次先判断一下counter然后再执行逻辑,所以使用while,并且在执行过程中可能多个线程处于等待状态,所以唤醒用notifyAll。原程序执行四个线程出现的问题耐心逐步分析便可找到事发原由。下面是正确的程序代码:
package com.msz;
public class msz_002_MyObject {
private int counter;
public synchronized void increase() {
while (counter != 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counter++;
System.out.println(counter);
notifyAll();
}
public synchronized void decrease() {
while (counter == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counter--;
System.out.println(counter);
notifyAll();
}
}
synchronized关键字:
如果一个对象里面有若干个synchronized方法,那么在某一时刻只能有唯一的一个线程进入到这里面的其中一个synchronized方法(那么在某一时刻这些synchronized方法当中只会有唯一的一个synchronized方法被某一个线程访问,而其他的线程即便想要访问另外的synchronized方法,也要去等待);因为当一个对象里面有若干个synchronized方法的时候,线程要执行其中一个方法需要尝试获取“锁”,这个锁是当前对象的锁,而当前对象只有唯一的一把锁。
当一个线程去访问某一个对象的synchronized static这样一个方法的时候它需要尝试获取的锁并不是当前对象的锁,而是当前对象所对应的class对象的锁。本质上来说,一个静态的方法并不属于当前对象,它是属于当前对象所对应的class对象。方法执行完或者抛出异常都会释放锁。
当我们使用synchronized关键字来修饰代码块时,字节码层面上是通过monitorenter与monitorexit指令来实现的锁的获取与释放动作。
当线程进入到monitorenter指令后,线程将会持有Monitor对象,退出monitorenter指令后,线程将会释放Monitor对象。
对于synchronized关键字修饰方法来说,并没有出现monitorentor与moitorexit指令,而是出现了一个ACC_SYNCHRONIZED标志。JVM使用了ACC_SYNCHRONIZED访问标志来区分一个方法是否为同步方法;当方法被调用时,调用指令会检查该方法是否拥有ACC_SYNCHRONIZED标志,如果有,那么执行线程将会先持有方法所在对象的Monitor对象,然后再去执行方法体;在该方法执行期间,其他任何线程均无法再获取到这个Monitor对象,当线程执行完该方法后,它会释放掉这个Monitor对象。
待更新...



