需求
现有一个需求,有一个共享变量初始值是0,这个变量分别有一个线程操作该变量递加,另外一个线程进行读取操作,并且每次变量的值变化都必须读取并打印显示。这就是非常常见的读写分离。
下面我们用代码先编写一个读写分离的基本逻辑:
package com.jwb;
public class ThreadDemo4 {
// 多线程的共享变量
public static int count = 0;
public static void main(String[] args) {
Thread inThread = new Thread(new Runnable() {
@Override
public void run() {
while (count <= 10) {
try {
Thread.sleep(1000); // 为了能更好观察结果,此次加了线程等待
} catch (InterruptedException e) {
}
count = count + 1; // 变量递加
}
}
});
Thread outThread = new Thread(new Runnable() {
@Override
public void run() {
while (count <= 10) {
try {
Thread.sleep(1000);// 为了能更好观察结果,此次加了线程等待
} catch (InterruptedException e) {
}
System.out.println(count); // 读取变量并打印
}
}
});
inThread.start();
outThread.start();
}
}
打印结果为:
![](http://jiawenbin.coding.me/images/2019-05-18 pm5.09.22.png)
结果并没有满足要求,按照预期,结果应该是从1到11逐个输出,那么就需要对代码进行修改。
在修改之前,先分析一下整个过程,问题出在哪里:
- 首先一点,对变量操作的读写是分别两个线程进行的,那么久有可能出现读取数据的线程执行2次或者多次后,写入操作的线程才执行。所以导致输出的结果会有多次打印一同个值。
- 同理,当写入线程执行2次或者多次以后,读取线程才执行,就会造成打印的数字不连续。
- 最后,不应该还要输出一个11,但是结果是偶然能输出11,这是线程不安全导致的问题。
找到了问题,就可以针对问题进行解决:
- 先解决线程安全的问题,所以必须要进行线程的同步操作,将读和写都进行同步。
- 还必须满足写入时,读取操作要暂停,同样,读取数据时,写入操作要暂停。
读写逐步,最重要的操作时让线程休眠和唤醒,既是,在读的时候如果发现还没写入完成,则休眠读线程,直到写入完成才唤醒,同理,写入的时候发现读的操作没完成,则休眠写入线程,直到读取完成才唤醒。实现读写逐步进行。
线程同步操作在之前的文章已经提到,那么java中如何实现读写逐步呢。java在Object类中提供了两个重要的成员方法,如下。
wait()、notify()
- wait()方法:如果对象(锁对象)调用了此方法,就会使持有该对象的线程,把对象的控制权交出去(释放锁),然后让调用方法的当前线程处于等待状态。
- notify()方法:如果对象(锁对象)调用了此方法,就会通知某一个正在等待这个对象的控制权的线程,可以继续运行(需要重新获取锁对象)。
- 这两个方法是被定义在Object类中的,但是一定要配合synchronized一起使用,否则会出现异常。
具体的使用案例如下,我们结合上面的需求,在原来的代码上修改,并实现需求:
package com.jwb;
public class ThreadDemo5 {
// 多线程的共享变量
public static int count = 0;
public static boolean flag = false; // 标记读写状态的变量,false标识正在写入
public static void main(String[] args) {
Object object = new Object(); // 同步锁的对象
Thread inThread = new Thread(new Runnable() {
@Override
public void run() {
while (count <= 10) {
synchronized (object) { // 同步代码块
if (flag) {
try {
object.wait(); // 如果还没有读取完成,则线程释放锁,并且休眠
} catch (InterruptedException e) {
}
} else {
count = count + 1;
flag = true; // 改变标记
object.notify(); // 写入完成,则释放锁,并唤醒读取线程
}
}
}
}
});
Thread outThread = new Thread(new Runnable() {
@Override
public void run() {
while (count <= 10) {
synchronized (object) { // 同步代码块
if (flag) {
System.out.println(count);
flag = false;
object.notify(); // 如果读取完成,释放锁,并唤醒写入线程
} else {
try {
object.wait(); // 如果写入未完成,则释放锁,并休眠此线程
} catch (InterruptedException e) {
}
}
}
}
}
});
inThread.start();
outThread.start();
}
}
打印结果:
最后,还补充一下notifyAll()
方法。
notifyAll()方法
notifyAll()
方法的功能和notify()
是一样的,唯一的区别在于,前者是通知所有的等待的线程,而后者是通知某一个正在等待的线程(随机)。此方法也是被定义在Object类中。