Java 中每一个对象都有一个锁,当线程访问synchronized 的方法和代码块的时候,会获得这个锁,也可以理解为为这个对象加锁,这个锁在同一时间有且只能被一个线程访问。如果一个线程访问对象锁的时候,锁正在被其他线程访问,那么这个线程就需要等待占用锁的那个线程释放对象锁,陷入阻塞状态。当执行完synchronized方法或者代码块的时候,线程就会释放对象锁。
当线程通过 synchronized 方法或者代码块获得对象锁后,其他线程无法访问对象的任何synchronized方法或者代码块,但是可以访问非synchronized方法或者代码块。
如果对静态方法添加synchronized 或者对静态变量的修改添加synchronized 块,那么这个时候获得的是类对象的锁。(class对象)
当线程休眠的时候,是不会释放对象锁。
可以把对象锁理解为资源,线程使用完这个资源后,会释放这个资源,以便其他线程来使用。
线程中的wait() 与 锁的关系
我们先看一段代码:
/**
* 计算输出其他线程锁计算的数据
*
*/
public class ThreadA {
public static void main(String[] args) throws InterruptedException{
ThreadB b = new ThreadB();
//启动计算线程
b.start(); (原文中是放在这个位置,但是这里会导致b线程run方法快速执行完,所以建议放在下面的synchronized 代码块里)
//线程A拥有b对象上的锁。线程为了调用wait()或notify()方法,该线程必须是那个对象锁的拥有者
synchronized (b) {
System.out.println("等待对象b完成计算。。。");
//当前线程A等待
b.wait();
System.out.println("b对象计算的总和是:" + b.total);
}
}
}
/**
* 计算1+2+3 ... +100的和
*
*/
class ThreadB extends Thread {
int total;
public void run() {
synchronized (this) {
for (int i = 0; i < 101; i++) {
total += i;
}
//(完成计算了)唤醒在此对象监视器上等待的单个线程,在本例中线程A被唤醒
notify();
System.out.println("计算完成");
}
}
}
执行结果:
等待对象b完成计算。。。
计算完成
b对象计算的总和是:5050
如果我们将b.wait()去掉呢?结果如下:
等待对象b完成计算。。。
b对象计算的总和是:0
计算完成
上述的结果表明,当去掉b.wait()时,新启动的线程ThreadB与主线程ThreadA是各自执行的,没有线程等待的现象。
我们想要的效果是,当线程ThreadB完成计算之后,再去取计算后的结果。所以使用了b.wait()来让主线程等待。
那为什么是使用b.wait(),而不是Thread.currentThread.wait(),或者其他的呢?
如果我们将b.wait()替换成Thread.currentThread.wait(),将会得到如下的结果:
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:485)
at pa.com.thread.ThreadA.main(ThreadA.java:18)
等待对象b完成计算。。。
计算完成
替换的代码Thread.currentThread.wait()好像理所当然应该如我们预期的正确啊,让当前线程处于等待状态,让其他线程先执行。
我们忽略了一个很重要的问题:线程与锁是分不开的,线程的同步、等待、唤醒都与对象锁是密不可分的。
线程ThreadA持有对象b的锁,我们要使用这把锁去让线程释放锁,从而让其他的线程能抢到这把锁。
从我们的程序来分析就是:线程ThreadA首先持有锁对象b,然后调用b.wait()将对象锁释放,线程ThreadB争抢到对象锁b,从而执行run()方法中的计算,计算完了之后使用notify()唤醒主线程ThreadA,ThreadA得以继续执行,从而得到了我们预期的效果。
(之所以ThreadB的对象锁也是b,是因为synchronized(this)中的this指向的就是ThreadB的实例b)
Thread.currentThread.wait()调用的是当前线程对象(即主线程ThreadA)的wait()方法,当前线程对象ThreadA是没有被加锁的,它只是获取了对象锁b。我基本没有看到过这样的调用,一般使用的是锁对象的wait(),本例中为b.wait()
顺带讲一下wait()与sleep()的区别。
如果我们将b.wait()换成Thread.sleep(1000),则会出现如下的结果:
等待对象b完成计算。。。
b对象计算的总和是:0
计算完成
从执行结果可以看出,Thread.sleep(1000)只是让主线程ThreadA睡眠了1秒钟,而并没有释放对象锁,所以在主线程ThreadA睡眠的过程中,ThreadB拿不到对象锁,从而不能执行。
所以我们也就得出了如下的结论:
wait()方法是让线程释放对象锁,让其他线程拿到锁之后去优先执行,当其他全程唤醒wait()中的线程 或者 拿到对象锁的线程都执行完释放了对象锁之后,wait()中的线程才会再次拿到对象锁从而执行。
sleep()方法是让线程睡眠,此时并没有释放对象锁,其他想要拿到睡眠线程的对象锁的线程也就拿不到相应的对象锁,从而不能抢在它前面执行。
补:
wait、notify和notifyAll方法是Object类的final native方法。所以这些方法不能被子类重写,Object类是所有类的超类,因此在程序中有以下三种形式调用wait等方法。
wait();//方式1:
this.wait();//方式2:
super.wait();//方式3
void wait()
导致线程进入等待状态,直到它被其他线程通过notify()或者notifyAll唤醒。该方法只能在同步方法中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。
————————————————
版权声明:本文为CSDN博主「程序羊的自我修养」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u012102536/article/details/71108000
本文深入解析Java中线程与锁的关系,包括synchronized关键字的使用,对象锁的获取与释放过程,以及wait()与sleep()的区别。通过具体代码示例,阐述了线程如何通过对象锁进行同步,确保数据的一致性和并发安全性。

2390

被折叠的 条评论
为什么被折叠?



