同步代码块和方法wait()、notify()的深入解析

不管是同步代码块还是同步方法、亦或是同步锁都是在执行操作之前先要获得对对象的锁定。也就是对于同一个对象而言的

同步锁:Lock相当同步代码块和同步方法,亦即对当前对象加锁,二Condition相当同步监听器,Condition中的await()、signal()、signalAll()等方法相当于线程中的wait()、notify()、notifyAll().Condition实例被绑定在一个Lock上,要获得Lock实例的Condition实例,调用Lock对象的newCondition()方法即可。

BlockQueue控制线程间的通信:BlockQueue b=new ArrayBlockingQueue<>(1);创建容量为1的队列b.put("hello");当b队列满时线程阻塞,b.take();当b队列为空时,线程阻塞。

BlockingQueue用法举例如下:

package thread;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class BlockingQueueTest extends Thread
{
 String str[]={"good","mood","world"};
 static BlockingQueueq;
 @Override
 public void run()
 {
 test();
 }
public static void main(String[] args)
{
 q=new ArrayBlockingQueue(1);
 BlockingQueueTest b=new BlockingQueueTest();
 b.start();
 try
 {
  Thread.sleep(100);
 } catch (InterruptedException e)
 {
  // TODO Auto-generated catch block
  e.printStackTrace();
 }
 b.test1();
}
public void test()
{
 for(int i=0;i<10;i++)
 {
  System.out.println("存入");
  try
  {
   q.put(str[i%3]);
  } catch (InterruptedException e)
  {
   
  }
  System.out.println(q.size()+"hello world");
 }
}
public void test1()
{
 for(int i=0;i<10;i++)
 {
  System.out.println("取出");
  try
  {
   q.take();
  } catch (InterruptedException e)
  {
   
  }
  System.out.println(q.size()+"my god");
 }
}
}

输出结果如下:有输出结果可知,当队列满时q.put(str[i%3]);发生阻塞、在此等待,此时 由于队列不为空,所以q.take();可以执行,当队列为空时,q.take();发生阻塞,此时原来发生阻塞的q.put(str[i%3]);可以继续执行,注意此时不是从q.put(str[i%3]);的后面开始执行,而是从q.put(str[i%3]);开始执行,也就是先加入队列,再执行后面的操作。


存入
1hello world
存入
取出
0my god
取出
1hello world
存入
0my god
取出
1hello world
存入
0my god
取出
1hello world
存入
0my god
取出
1hello world
存入
0my god
取出
1hello world
存入
0my god
取出
1hello world
存入
0my god
取出
1hello world
存入
0my god
取出
1hello world
存入
0my god
取出
1hello world
0my god

同步代码块:

synchronized(Object b)//任意对象都可以作为同步监听器

{

}

synchronized后面括号中的b是同步监听器,线程在执行该synchronized代码块之前必须获得对b的锁定,在该线程执行该代码块期间,其他线程不能获得对该对象b的锁定 

如在执行(1)和(2)之前先要获得对s的锁定,所以在执行(1)的过程中不能执行(2),只有(1)执行完之后才可以执行(2)

public class synchronizedBlock extends Thread
{
 static synchronizedBlock s;
 @Override
  public void run()
  {
   test();
  }
   public static void main(String[] args)
  {
    s=new synchronizedBlock();
    s.start();
    s.test1();
  
  }
   public void test()
 {
    synchronized(s)//(1)
    {
  for(int i=0;i<100;i++)
     System.out.println(i+"ggggggggggggg"+"  "+Thread.currentThread().getName());
    }
 }
 public  void test1()
 {
   synchronized(s)//(2)
     {
  for(int i=0;i<100;i++)
   System.out.println(i+"搜索但vc发"+"  "+Thread.currentThread().getName());

     }
 }
}

 

用synchronized修饰的方法称为同步方法,同步方法无需显示指定同步监听器,同步监听器是this,也就是该对象本身,即对应同一个对象,在任意时刻只能调用一个同步方法:比如Mytest类有两个同步方法:则同一个Mytest对象mytest同一时刻只能调用其中的一个同步方法,即不能同时调用test()和test1()方法

public synchronized void test()

{

}

public synchronized void test1()

{

}

 

同步监听器的释放:

(1)当前线程的同步方法、同步代码块执行完毕,当前线程即可释放同步监听器

(2)当遇到break、return时终止代码块、方法的执行、当前线程即可释放同步监听器

(3)当遇到Error或Exception时,当前线程即可释放同步监听器

(4)执行了wait()方法

不能释放同步监听器的方式:

(1)线程同步块或方法调用sleep(),yield()方法

(2)线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放同步监听器

Object 类中的wait(),notify(),nofityAll()方法的使用,一定要用同步监听器来调用:

在synchronized方法中可以直接调用以上的三个方法,因为synchronized方法中的同步监听器是this,由于Object是任何类的直接或者间接父类,所以任意类都继承了Object类以上的三个方法,所以synchronized方法中调用以上的三个方法,实质上是用this同步监听器来调用以上的三个方法。

在synchronized代码块中调用以上三个方法时一定要用synchronized后面括号中的同步监听器来调用。

wait()和notify()的用法:线程之间的通信

wait()导致当前线程等待(只是等待、暂停,不是中断,也就是说当其他线程调用了该同步监听器notify()方法或者notify()方法来唤醒该线程后,该线程是继续执行(接着执行),而不是重写、重头执行),直到其他线程调用了该同步监听器notify()方法或者notify()方法来唤醒该线程

notify():唤醒在此同步监听器上等待的单个线程,如果所有都在此同步监听器上等待,则会选择唤醒其中任意一个线程,注意该方法只是唤醒线程,但是唤醒的线程还不可以执行只有当前线程执行了wait()方法后被唤醒的线程才可以执行。

notifyAll():和notify()作用相同,只是唤醒所有在该同步监听器上等待的线程

wait()和notify()的用法举例

package thread;
public class MyThreadPrinter2 implements Runnable {    
   
   private String name;    
  private Object prev;    
   private Object self;    
 
 private MyThreadPrinter2(String name, Object prev, Object self) {    
       this.name = name;    
        this.prev = prev;    
       this.self = self;    
   }    
  
   @Override   
    public void run() {    
        int count = 10;    
    
   while (count > 0) {    
            synchronized (prev) {    
               synchronized (self) {    
                  System.out.print(name);    
                  count--;   
                   self.notify();    
               }    
               try {    
                    prev.wait();    
              } catch (InterruptedException e) {    
                   e.printStackTrace();    
               }    
            }    
  
        }    
    }    
  
    public static void main(String[] args) throws Exception {    
      Object a = new Object();    
       Object b = new Object();    
       Object c = new Object();    
        MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a);    
       MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b);    
        MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c);    
            
            
       new Thread(pa).start(); 
        Thread.sleep(10); 
        new Thread(pb).start(); 
       Thread.sleep(10); 
        new Thread(pc).start(); 
        Thread.sleep(10); 
    }    
}    

输出结果是ABCABCABCABCABCABCABCABCABCABC
  通过对上面例子的分析来深入理解wait()和notify()

上面的执行的过程如下:

先执行  new Thread(pa).start();线程,  通过下面的代码段可知, 第一个线程先后获得了同步监听器c和a的锁定 , 由于main主线程中用了Thread.sleep(10),所以此时第二个线程还在等待,在第二个线程等待的时间内第一个线程完成的事情是:a同步监听器调用了notify()方法,即唤醒其他在该同步监听器a上等待的线程(此时还没有在同步监听器a上等待的线程),但此时在该同步监听器a上等待的线程还不能获得对a的锁定,即还不能得到执行的机会,要等到当前线程调用wait()方法后,在同步监听器a上等待的线程才获得执行的机会。接着第一个线程的同步监听器c调用了wait()方法,即让当前线程处于等待执行(暂停执行),等到其他线程调用了同步监听器c上的notify()方法时,第一个线程就可以重新获得执行的机会此时在同步监听器a上等待的线程或得了执行的机会,接着过了10毫秒之后,即第一个  Thread.sleep(10);  的时间过了之后,第二个线程   new Thread(pb).start();得到执行首先是获得同步监听器a的锁定(由于第一个线程的同步监听器a调用了notify()方法并且该线程的同步监听器c调用了wait()方法),所以第二个线程不用等待就可以直接获得对同步监听器a的锁定了,接着获得同步监听器b的锁定,执行操作,然后同步监听器b调用了notify()方法,即唤醒其他在该同步监听器a上等待的线程(此时还没有在同步监听器b上等待的线程),接着该线程同步监听器a调用了wait()方法使该线程等待(暂停执行),接着过了10毫秒之后,即第二个  Thread.sleep(10);  的时间过了之后,第三个  new Thread(pc).start();  线程得到执行(由于第二个线程同步监听器b调用了notify()方法释放了监听器的锁定,并且第二个线程同步监听器a调用了wait()方法处第二个线程等待,所以第三个线程可以获得对同步监听器b的锁定,由于第一个线程同步监听器c调用了wait()方法释放了该同步监听器的锁定,所以第三个线程也可以获得了对同步监听器c的锁定,即可以先后获得了对b和c的锁定,也即第三个线程可以获得执行),接着三个线程同步监听器c调用了notify()方法,即唤醒了第一个线程,但第一个线程此时还不能执行,因为第三个线程此时还还有调用同步监听器的wait()方法,接着第三个线程同步监听器b调用wait()方法了,此时第三个线程处于等待,注意第一个线程可以获得执行了(因为第三个线程同步监听器c调用了notify()方法唤醒了第一个线程(因为此前第一个线程在同步监听器c上等待),并且第三个线程同步监听器b调用了wait()方法,所以第一个线程获得了对同步监听器c的锁定,由于第二个线程执行时同步监听器a调用了wait()方法,释放了同步监听器a的锁定,所以第一个线程也可以获得了对a的锁定,即此时第三个线程获得了对c和a的锁定,所以第一个线程可以执行了),接着就是这样循环下去了的。

@Override   
    public void run() {    
        int count = 10;    
    
   while (count > 0) {    
            synchronized (prev) {    
               synchronized (self) {    
                  System.out.print(name);    
                  count--;   
                   self.notify();    
               }    
               try {    
                    prev.wait();    
              } catch (InterruptedException e) {    
                   e.printStackTrace();    
               }    
            }    
  
        }    
    }    

你可能感兴趣的:(Java)