力扣1115题目:给定类,两个不同的线程分别执行调用同一个FooBar实例,修改程序实现”foobar“被交替打印n次。
给定类如下:
class FooBar {
public void foo() {
for (int i = 0; i < n; i++) {
print("foo");
}
}
public void bar() {
for (int i = 0; i < n; i++) {
print("bar");
}
}
}
两个不同的线程共用一个FooBar
实例:
foo()
方法;bar()
方法。设计修改程序,实现foobar
被循环输出n次。
无法被重写
。临界区:一个访问公用资源,例如公用设备或者公用存储器,的程序片段;这些公用资源无法同时被多个线程访问的特性;当有线程进入临界区段时,其他线程必须wait等待(例如:bounded waiting 等待算法),有一些同步的机制会在临界区的进入点或者离开点实现,保证公用资源是互斥获得使用权,例如:信号量semaphore机制;生活中的例子有,打印机资源的互斥使用。
给出测试代码如下:
package java_thread_run;
public class TestNotifyNotifyAll {
private static Object obj = new Object();
public static void main(String[] args) {
//测试RunnableImplA wait方法
Thread t1 = new Thread(new RunnableImplA(obj));
Thread t2 = new Thread(new RunnableImplA(obj));
t1.start();
t2.start();
// //RunnableImplB notify()
// Thread t3 = new Thread(new RunnableImplB(obj));
// t3.start();
//RunnableImplC notifyAll()
Thread t4 = new Thread(new RunnableImplC(obj));
t4.start();
}
}
class RunnableImplA implements Runnable {
private Object obj;
public RunnableImplA(Object obj) {
this.obj = obj;
}
@Override
public void run() {
System.out.println("run on RunnableImplA");
synchronized (obj) {
System.out.println("obj to wait on RunnableImplA");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("obj continue to run on RunnableImplA");
}
}
}
class RunnableImplB implements Runnable {
private Object obj;
public RunnableImplB(Object obj) {
this.obj = obj;
}
@Override
public void run() {
System.out.println("run on RunnableImplB");
System.out.println("睡眠3秒...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
System.out.println("notify obj on RunnableImplB");
obj.notify();
}
}
}
class RunnableImplC implements Runnable {
private Object obj;
public RunnableImplC(Object obj) {
this.obj = obj;
}
@Override
public void run() {
System.out.println("run on RunnableImplC");
System.out.println("睡眠3秒...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
System.out.println("notifyAll obj on RunnableImplC");
obj.notifyAll();
}
}
}
调用notify方法,则始终有一个线程等待被唤醒,程序不会终止;调用notifyAll方法,则线程t1、t2都会执行完毕。
7. 多线程测试某个条件变化,使用if还是while?
注意,notify唤醒沉睡的县城之后,线程会继续执行上次的步骤,进行条件判定,可以把wait语句忽略不计;但是显然,需要确保程序一定会执行,并且保证程序直至满足一定的条件在执行,则需要while等待,直至满足条件后继续往下执行。
class FooBar {
private int n;
private Object obj = new Object();
private volatile boolean fooExec = true;
public FooBar(int n) {
this.n = n;
}
public void foo(Runnable printFoo) throws InterruptedException {
for (int i = 0; i < n; i++) {
synchronized (obj) {
if (!fooExec) {
//若fooExec为false,则线程等待,true执行下面步骤
obj.wait();
}
printFoo.run();
fooExec = false;
//唤醒其他线程
obj.notifyAll();
}
// printFoo.run() outputs "foo". Do not change or remove this line.
}
}
public void bar(Runnable printBar) throws InterruptedException {
for (int i = 0; i < n; i++) {
// printBar.run() outputs "bar". Do not change or remove this line.
synchronized (obj) {
if (fooExec) {
obj.wait();
}
printBar.run();
fooExec = true;
obj.notifyAll();
}
}
}
}
Java语言提供了一种稍弱的同步机制,也就是volatile 变量,保证变量的更新操作会告知其他线程。
若把某个变量声明为volatile,编译器在运行时都会注意到该变量存在共享行为,不会把该变量的操作和内存其他操作一起重排序。volatile 变量不会被缓存在寄存器或者其他处理器不可见的地方,在读取volatile 类型的变量时总是会返回最新写入的数值。
在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。
对于非volatile 变量读写时,每一个线程会把内存拷贝变量到CPU cache缓存中,若计算机存在多个CPU,每个线程可能会在不同的CPU处理,意味每一个线程都可以拷贝到不同的cache中。
声明为volatile 之后,JVM保证每次变量读取都从内存中,跳过CPU cache这一步。