对象锁
- 在java中每个对象都有一把锁,可以用来处理多线程的调度问题
对象锁相应的关键字是
synchronized
(反引号真难找),既可以修饰方法,也可以修饰类,也可以修饰 一段代码,作用是:使当前调用synchronized
修饰的东西 的对象上锁。这个锁的作用是:只有先用synchronized
的线程可以使用这个对象。举个栗子:package 多线程练习; import java.lang.*; public class ThreadTest implements Runnable { public static void main(String[] args) { // TODO Auto-generated method stub ThreadTest ta = new ThreadTest(); ThreadTest tb = new ThreadTest(); Thread Tha = new Thread(ta, "线程1"); Thread Thb = new Thread(tb, "线程2"); Tha.start(); Thb.start(); } public void run() { // TODO Auto-generated method stub int i = 0; while(i < 10) { System.out.println(Thread.currentThread().getName()+ "正在执行"); i = i+1; } } }
上面,分别建立了ta和tb,然后创建了Tha和Thb用来执行线程。这时程序的输出为:
线程2正在执行
线程1正在执行
线程2正在执行
线程1正在执行
线程1正在执行
线程1正在执行
线程1正在执行
线程1正在执行
线程1正在执行
线程1正在执行
线程1正在执行
线程1正在执行
线程2正在执行
线程2正在执行
线程2正在执行
线程2正在执行
线程2正在执行
线程2正在执行
线程2正在执行
线程2正在执行可见一般情况下,线程的执行并没有什么顺序。这里面创建了两个不同的对象ta和tb,所以虽然类相同,但是两者之间并没有多大联系
我们来看一下线程的资源共享
下面修改一下代码import java.lang.*; public class ThreadTest implements Runnable { public static void main(String[] args) { // TODO Auto-generated method stub ThreadTest ta = new ThreadTest(); Thread Tha = new Thread(ta, "线程1"); Thread Thb = new Thread(ta, "线程2"); Tha.start(); Thb.start(); } int i = 0; @Override public void run() { // TODO Auto-generated method stub // synchronized (this) { while(i < 10) { System.out.println(Thread.currentThread().getName()+ "正在执行"); i = i+1; } } }
运行结果为:
线程2正在执行
线程1正在执行
线程1正在执行
线程1正在执行
线程1正在执行
线程1正在执行
线程1正在执行
线程1正在执行
线程1正在执行
线程2正在执行
线程1正在执行可见此时,线程1和线程2共享同一个对象中的资源,互相争夺
下面终于加入了锁机制,通过
synchronized
关键字,可以让需要的线程获得需要的资源,其他线程只能靠边站。只有执行完毕之后,其他线程才能获得相应的资源- 首先来讲一下object.wait()和object.notify()。每个对象都有一个对象锁,线程执行到synchronized关键字的时候即可获取对象的锁(此处的对象为执行对应代码的那个对象)。拿到对象锁的线程即可执行对应的代码段。wait和notify方法只可用于synchronized(obj){……}代码段中,因为这两个方法和对象锁相关。
wait()方法可以使线程进入休眠,同时释放对应的对象锁;而调用对象的notify则可以唤醒相应的线程,在synchronized(obj){……}代码段结束后运行对应的线程。
注意,此处的wait和notify是根据对应的类来休眠和唤醒线程。
object AAA = new object(); object BBB = new object(); ......... synchronized(AAA) { AAA.wait(); }
注意,此时假设有线程X执行了以上代码,即取得了AAA的对象锁,然后进行了wait()操作。那么接下去只能通过别的线程实现
AAA.nojity();
把线程X唤醒。
也就是说,进行线程调度的时候,一定要有相应的对象来执行Obj.wait()和Obj.notify()。
下面通过一个经典的三线程打印ABC的问题来具体说明wait和notify的使用方法
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(100); //确保按顺序A、B、C执行
new Thread(pb).start();
Thread.sleep(100);
new Thread(pc).start();
Thread.sleep(100);
}
}
程序刚开始:
线程pa执行,获取了对象c和对象a的对象锁,成功打印“A”,然后唤醒对象a上面休眠的其他线程(此时没有,所以没反应),然后让线程pa在对象c上休眠(说法不对,不过可以这么理解)。此时,对象abc的对象锁都被释放。(另外,在
synchronized
对应的代码段执行结束后,自动释放对应的对象锁)100毫秒后,线程pb执行。获取了对象a和对象b的对象锁。同上,最后让线程pb咋对象a上休眠。
此时,调用
c.notify();
可以唤醒线程pa,调用a.notify();
可以唤醒线程pb。100毫秒后,线程pc开始执行,获取了对象bc的对象锁,成功打印“C”。然后线程pc执行
self.notify();
这条语句唤醒了对象C上面休眠的线程pa,线程pa进入可执行状态。此时空闲的对象锁为a和c(c刚刚被释放),然后线程pa就会开始执行。线程pc继续休眠在对象b上。线程pa取得对象锁ac开始执行,打印“A”,然后唤醒对象a上休眠的线程pb。以后就按这个顺序依次执行,直到循环结束。