题目:
三个线程,A线程输出A,B线程输出B,C线程输出C
然后这三个线程运行循环输出ABCABCABCABC......
此题目考察的是线程的同步,多线程的控制
解决方案中主要用到了java中的sychronised, notify, wait
notify和wait主要是为了控制线程,控制程序的执行流程
其中wait方法有三个over load方法:
wait()
wait(long)
wait(long,int)
wait方法通过参数可以指定等待的时长。如果没有指定参数,默认一直等待直到被通知。
这些方法只能在同步方法或同步块内部调用。如果当前线程不是对象所得持有者,该方法抛出一个java.lang.IllegalMonitorStateException 异常
这些方法需要在同步块中使用,并且调用这些方法的对象要是同步代码块中要求的同步对象,也就是monitor
java.lang.IllegalMonitorStateException 异常
违法的监控状态异常。当某个线程试图等待一个自己并不拥有的对象(O)的监控器或者通知其他线程等待该对象(O)的监控器时,抛出该异常。
首先给出解决的代码:
public class Test{
private static String[] flag = {"A"};
static class AThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
int i = 0;
while(i<10){
synchronized (flag) {
if(flag[0].equals("A")){
System.out.print("A");
flag[0] = "B";
i++;
}
flag.notify();
try {
flag.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
static class BThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
int i = 0;
while(i<10){
synchronized (flag) {
if(flag[0].equals("B")){
System.out.print("B");
flag[0] = "C";
i++;
}
flag.notify();
try {
flag.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
static class CThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
int i = 0;
while(i<10){
synchronized (flag) {
if(flag[0].equals("C")){
System.out.println("C");
flag[0] = "A";
i++;
}
flag.notify();
try {
flag.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) throws InterruptedException{
AThread thread1 = new AThread();
BThread thread2 = new BThread();
CThread thread3 = new CThread();
thread1.start();
thread2.start();
thread3.start();
}
}
使用了一个全局变量flag来作为monitor来进行同步
因为在sychronised代码块中对flag进行了更改,所以flag不能用String对象,因为String对象是不可变对象,对其进行了修改,则在下面的代码中的flag.wait()就会抛出异常.因为同步的monitor是没有更改之前的对象,而在内部又对flag重新赋值了,所以导致调用wait函数的对象和前面同步的monitor不一致,抛出了java.lang.IllegalMonitorStateException 异常
因此在此处使用了字符数组来代替之前的String对象
字符数组的说明:
java中字符数组的内存表示如下图所示:
因为字符串对象变化之后栈区的指针会指向其他的常量区地址,而字符串数组中栈区的引用指向的堆区的地址是不变的,而当改变数组元素时变化的是堆区每个元素空间指向的地址.
因此可以用数组来作为一个全局变量来作为monitor来作为同步.
对线程同步的讲解:
首先获得monitor对象,再判断flag的值是不是"A" "B" "C",来进行对应的处置,如果不是正确的顺序的线程获得了monitor的权限,则不会进入if语句,会直接notify和wait,让其他的线程获得monitor自己阻塞等待.
借助了外部的flag全局变量来使ABC按书序输出,但是并没有实现,A线程运行完就运行B线程,B线程运行完就运行C线程,这样实现线程的按照一定的顺序运行.
另外notify和notifyAll并不能notify某个固定的线程,只是唤醒等待的线程中的某一个或所有,并不能指定唤醒哪一个线程.
wait和sleep的区别就是,wait会释放自己获取的锁,而sleep不释放锁,只是阻塞一定的时间而已.
总结:
1,多线程的同步,使用synchronized来进行同步,线程间通信就使用wait() 和 notify() 函数来让线程按照一定的安排获得CPU使用权运行
2,多线程中的java.lang.IllegalMonitorStateException 异常要注意
3,java中的String等有些内置不可变对象尽量不要作为synchronized的monitor, 要使用可变对象作为monitor,这样便可以在同步代码块内部修改monitor
4,java的字符串数组和字符串的区别以及使用