class MyThread extends Thread { public MyThread(String name) { super(name); } @Override public void run() { synchronized(this) { try { for (int i = 0; i < 5; i++) { Thread.sleep(100); // 休眠100ms System.out.println(Thread.currentThread().getName() + " loop " + i); } } catch (InterruptedException ie) { } } } } public class Demo1_2 { public static void main(String[] args) {
//2个线程处理同2个任务 Thread t1 = new MyThread("t1"); // 新建“线程t1” Thread t2 = new MyThread("t2"); // 新建“线程t2” t1.start(); // 启动“线程t1” t2.start(); // 启动“线程t2” } }
class MyRunable implements Runnable { @Override public void run() { synchronized(this) { try { for (int i = 0; i < 5; i++) { Thread.sleep(100); // 休眠100ms System.out.println(Thread.currentThread().getName() + " loop " + i); } } catch (InterruptedException ie) { } } } } public class Demo1_1 { public static void main(String[] args) { Runnable demo = new MyRunable(); // 新建“Runnable对象” //2个线程处理同一个任务 Thread t1 = new Thread(demo, "t1"); // 新建“线程t1”, t1是基于demo这个Runnable对象 Thread t2 = new Thread(demo, "t2"); // 新建“线程t2”, t2是基于demo这个Runnable对象 t1.start(); // 启动“线程t1” t2.start(); // 启动“线程t2” } }
class Count { // 含有synchronized同步块的方法 public void synMethod() { synchronized(this) { try { for (int i = 0; i < 5; i++) { Thread.sleep(100); // 休眠100ms System.out.println(Thread.currentThread().getName() + " synMethod loop " + i); } } catch (InterruptedException ie) { } } } // 非同步的方法 public void nonSynMethod() { try { for (int i = 0; i < 5; i++) { Thread.sleep(100); System.out.println(Thread.currentThread().getName() + " nonSynMethod loop " + i); } } catch (InterruptedException ie) { } } } public class Demo2 { public static void main(String[] args) { final Count count = new Count(); // 新建t1, t1会调用“count对象”的synMethod()方法 Thread t1 = new Thread( new Runnable() { @Override public void run() { count.synMethod(); } }, "t1"); // 新建t2, t2会调用“count对象”的nonSynMethod()方法 Thread t2 = new Thread( new Runnable() { @Override public void run() { count.nonSynMethod(); } }, "t2"); t1.start(); // 启动t1 t2.start(); // 启动t2 } }
实例锁 -- 锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。
实例锁对应的就是synchronized关键字。
全局锁 -- 该锁针对的是类,无论实例多少个对象,那么线程都共享该锁。
全局锁对应的就是static synchronized(或者是锁在该类的class或者classloader对象上)。
关于“实例锁”和“全局锁”有一个很形象的例子:
pulbic class Something { public synchronized void isSyncA(){} public synchronized void isSyncB(){} public static synchronized void cSyncA(){} public static synchronized void cSyncB(){} }
假设,Something有两个实例x和y。分析下面4组表达式获取的锁的情况。
(01) x.isSyncA()与x.isSyncB()
(02) x.isSyncA()与y.isSyncA()
(03) x.cSyncA()与y.cSyncB()
(04) x.isSyncA()与Something.cSyncA()
(01) 不能被同时访问。因为isSyncA()和isSyncB()都是访问同一个对象(对象x)的同步锁!
(02) 可以同时被访问。因为访问的不是同一个对象的同步锁,x.isSyncA()访问的是x的同步锁,而y.isSyncA()访问的是y的同步锁。
(03) 不能被同时访问。因为cSyncA()和cSyncB()都是static类型,x.cSyncA()相当于Something.isSyncA(),y.cSyncB()相当于Something.isSyncB(),因此它们共用一个同步锁,不能被同时反问。
(04) 可以被同时访问。因为isSyncA()是实例方法,x.isSyncA()使用的是对象x的锁;而cSyncA()是静态方法,Something.cSyncA()可以理解对使用的是“类的锁”。因此,它们是可以被同时访问的。
在Object.java中,定义了wait(), notify()和notifyAll()等接口。wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。 Object类中关于等待/唤醒的API详细信息如下: notify() -- 唤醒在此对象监视器上等待的单个线程。 notifyAll() -- 唤醒在此对象监视器上等待的所有线程。 wait() -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)。 wait(long timeout) -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。 wait(long timeout, int nanos) -- 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量”,当前线程被唤醒(进入“就绪状态”)。
package com.test; // WaitTest.java的源码 class ThreadA extends Thread{ public ThreadA(String name) { super(name); } public void run() { System.out.println("1111"); synchronized (this) { System.out.println(Thread.currentThread().getName()+" call notify()"); // 唤醒当前的wait线程 notify(); System.out.println("2222"); } } } public class WaitTest { public static void main(String[] args) { ThreadA t1 = new ThreadA("t1"); synchronized(t1) { try { System.out.println(Thread.currentThread().getName()+" start t1"); t1.start(); //main线程和新线程用的是同一个锁t1,start方法调用后,run方法不能进去,因为锁被main线程占用。 System.out.println(Thread.currentThread().getName()+" wait()"); t1.wait(); //锁调用wait方法,让正在使用锁t1的main线程等待,wait方法让当前线程释放锁,run方法进去,notify()唤醒等待的main线程,synchronized执行完之后释放锁,main才可以继续走下去。 System.out.println(Thread.currentThread().getName()+" continue"); } catch (InterruptedException e) { e.printStackTrace(); } } } } /* t1.wait()应该是让“线程t1”等待;但是,为什么却是让“主线程main”等待了呢? 注意:jdk的解释中,说wait()的作用是让“当前线程”等待,而“当前线程”是指正在cpu上运行的线程! 这也意味着,虽然t1.wait()是通过“线程t1”调用的wait()方法,但是调用t1.wait()的地方是在“主线程main”中。而主线程必须是“当前线程”,也就是运行状态,才可以执行t1.wait()。 所以,此时的“当前线程”是“主线程main”!因此,t1.wait()是让“主线程”等待,而不是“线程t1”! */
package com.test; public class WaitNotifyDemo1 { private int num; //输出数字 private int runThreadNum; //当前运行线程编号 public WaitNotifyDemo1(int num, int runThreadNum){ this.num = num; this.runThreadNum = runThreadNum; } /** * 打印线程 */ static class PrintThread extends Thread{ private int threadNum; //当前运行线程编号 private WaitNotifyDemo1 demo; //锁对象 public PrintThread(int threadNum, WaitNotifyDemo1 demo){ this.threadNum = threadNum; this.demo = demo; } @Override public void run() { synchronized (demo) { // 这里要同步,不然后面demo.wait()就会报错 try{ for(int i=1; i<=5; i++){ while(true){ if(threadNum == demo.runThreadNum){ break; } else{ //如果当前线程不是接下来要运行的线程,进入等待池 demo.wait(); } } for(int j=1; j<=5; j++){ System.out.println("线程"+threadNum+":"+(++demo.num)); } demo.runThreadNum = demo.runThreadNum%3 +1; //计算之后运行的线程编号 demo.notifyAll(); //唤醒所有等待池中的线程 } } catch(Exception e){ e.printStackTrace(); } } } } public static void main(String[] args) { WaitNotifyDemo1 demo = new WaitNotifyDemo1(0,1); // 3个线程处理同一个任务 new PrintThread(1,demo).start(); new PrintThread(2,demo).start(); new PrintThread(3,demo).start(); } }
启动3个线程打印递增的数字, 线程1先打印1,2,3,4,5, 然后是线程2打印6,7,8,9,10, 然后是线程3打印11,12,13,14,15. 接着再由线程1打印16,17,18,19,20....以此类推, 直到打印到75.
public class Print { //3个线程处理同一个任务, public static void main(String[] args) { new Thread(new PrintRunnable(1)).start(); new Thread(new PrintRunnable(2)).start(); new Thread(new PrintRunnable(3)).start(); } } class PrintRunnable implements Runnable { private static volatile int printNum = 0; private int threadId; public PrintRunnable(int threadId){ this.threadId = threadId; } @Override public void run() { while(printNum < 75){ synchronized (Print.class){ if (printNum/5%3 + 1 == threadId){ for (int i = 0; i <5; i++) { System.out.println("线程"+threadId+":"+(++printNum)); } Print.class.notifyAll(); }else { try { Print.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
package com.test; public class NotifyAllTest { private static Object obj = new Object(); public static void main(String[] args) { ThreadA t1 = new ThreadA("t1"); ThreadA t2 = new ThreadA("t2"); ThreadA t3 = new ThreadA("t3"); t1.start(); t2.start(); t3.start(); try { System.out.println(Thread.currentThread().getName()+" sleep(3000)"); Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(obj) { // 主线程等待唤醒。 System.out.println(Thread.currentThread().getName()+" notifyAll()"); obj.notifyAll(); //喚醒 } } static class ThreadA extends Thread{ public ThreadA(String name){ super(name); } public void run() { synchronized (obj) { try { // 打印输出结果 System.out.println(Thread.currentThread().getName() + " wait"); // 唤醒当前的wait线程 obj.wait(); //线程要执行,1要被唤醒,2要获取锁,不然仍然处于wait的阻塞状态 // 打印输出结果 System.out.println(Thread.currentThread().getName() + " continue"); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
5. 为什么notify(), wait()等函数定义在Object中,而不是Thread中
Object中的wait(), notify()等函数,和synchronized一样,会对“对象的同步锁”进行操作。
wait()会使“当前线程”等待,因为线程进入等待状态,所以线程应该释放它锁持有的“同步锁”,否则其它线程获取不到该“同步锁”而无法运行!
OK,线程调用wait()之后,会释放它锁持有的“同步锁”;而且,根据前面的介绍,我们知道:等待线程可以被notify()或notifyAll()唤醒。现在,请思考一个问题:notify()是依据什么唤醒等待线程的?或者说,wait()等待线程和notify()之间是通过什么关联起来的?答案是:依据“对象的同步锁”。
负责唤醒等待线程的那个线程(我们称为“唤醒线程”),它只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个),并且调用notify()或notifyAll()方法之后,才能唤醒等待线程。虽然,等待线程被唤醒;但是,它不能立刻执行,因为唤醒线程还持有“该对象的同步锁”。必须等到唤醒线程释放了“对象的同步锁”之后,等待线程才能获取到“对象的同步锁”进而继续运行。
总之,notify(), wait()依赖于“同步锁”,而“同步锁”是对象锁持有,并且每个对象有且仅有一个!这就是为什么notify(), wait()等函数定义在Object类,而不是Thread类中的原因。