Java语言和C/C++语言不一样,JAVA语言中提供了同步关键字synchronized来支持线程键的同步操作。
同步关键字synchronized
synchronized关键字最常见的用法是保护一段代码,如下:
public class Foo implements Runnable {
private String mLock;
public void lockedMethod() {
......
synchronized (mLock) {
......
}
......
}
@Override
public void run() {
// TODO Auto-generated method stub
}
}
synchronized还可以用在类的方法前面,如下:
public class FooA implements Runnable {
public synchronized void lockedMethod() {......}
public synchronized void unlockedMethod() {......}
@Override
public void run() {
// TODO Auto-generated method stub
}
}
synchronized的第三种用法是修饰一个静态方法,如下:
public class FooB implements Runnable {
public synchronized static void lockedMethod() {......}
@Override
public void run() {
// TODO Auto-generated method stub
}
}
这三种使用方法的含义有很大区别。首先要理解,synchronized锁住的是对象,而不是一段代码。在java中,每个对象都可以是一把锁,这意味着每个java对象都有类似native层的Mutex对象的功能;而synchronized关键字则和native层的Autolock类似,起到的是自动加锁和解锁的作用。
对于上面介绍的第一种使用方法:使用synchronized保护一段代码。假设类Foo有两个实例对象foo1和foo2,如果在两个线程中分别调用foo1.lockedMethod()方法,在同一时间,只有一个线程能执行被synchronized扩起来的代码,也就起到了互斥作用。但是如果一个线程调用的是foo1.lockedMethod(),另一个线程调用的是foo2.lockedMethod(),这样是不会互斥的。因为前者上锁的对象时foo1.mLock,后者上锁的对象是foo2.mLock,它们其实是两把完全不同的锁。
synchronized关键字的第二种用法是:用synchronized修饰一个非静态方法。语法上等价于下面的写法:
public synchronized (this) { ...... }
它表明上锁的对象是类的实例对象本身,而第一种用法中上锁的对象是实例对象的成员变量,锁的范围扩大了。这样导致的结果是两个线程对于同一个实例对象的任何访问都是互斥的。具体而言:对于方法二中的例子,假如类FooA有一个实例对象foo_a,在两个线程中都访问foo_a.lockedMethod(),一定是互斥的;一个线程访问foo_a.lockedMethod(),另一个线程访问foo_a.unlockedMethod(),同样是互斥的。当然如果是两个实例对象foo_a和foo_b,分别访问他们的方法是不会互斥的。
synchronized关键字的第三种用法是:用synchronized修饰一个静态方法。语法上等价于下面的写法:
public synchronized (FooB.class) { ...... }
它表明上锁的对象是类本身,这样锁的作用范围进一步扩大了。因此,导致的结果是一旦有一个线程调用的FooB类的任何实例对象的lockedMethod()方法,另一个线程对于FooB类的任何对象的lockedMethod()方法都将会产生互斥。这种用法导致的结果是最严厉的。
Object类在同步中的作用
synchronized关键字只能提供互斥作用的同步机制,如果需要完成一些更复杂的同步操作,就需要Object类来配合完成。Object类是所有java对象的基类,在Object类中提供改了一些与同步相关的方法,如下:
public final native void notify();
public final native void notifyAll();
public final native void wait() throws InterruptedException;
public final void wait(long millis);
public final native void wait(long millis, int nanos) throws InterruptedException;
同步相关的接口分为notify和wait两种类型,notify类型的函数用来唤醒挂起的线程,wait类型的函数用来挂起调用者所在的线程。
要注意的是,这些方法并不是随时随地都可以调用的,对它们的调用必须是在本线程取得对“Object对象”的控制权以后才能进行。更准确地说,应该在synchronized的作用域内调用。举例说明:
public class ObjectSync implements Runnable {
private static final String TAG = "ObjectSync";
private String name;
private Object lock;
public ObjectSync(String name, Object lock) {
this.name = name;
this.lock = lock;
}
@Override
public void run() {
int count = 10;
while (count > 0) {
synchronized (lock) {
Log.d(TAG, name);
lock.notify();
try {
if (--count > 0) {
lock.wait();
}
} catch (Exception e) {
Log.e(TAG, e.getMessage());
}
}
}
}
private static void output() {
Object lock = new Object();
Thread threadA = new Thread(new ObjectSync("A", lock));
Thread threadB = new Thread(new ObjectSync("B", lock));
threadA.start();
threadB.start();
try {
threadA.join();
threadB.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
上面例子中notify()和wait()都是在对lock对象进行操作,因此,他们必须在synchronized(lock)的代码块内调用,否则运行时会报异常。线程的join()方法的作用是让调用该方法的主线程执行run()时暂时卡住,等run()执行完成后,主线程在调用执行join()后面的代码。
output()方法中创建了Object对象lock作为锁,创建的两个线程是同一个线程类,用参数加以区分,并通过参数把lock对象传递给两个线程,这样两个线程中将使用同一个对象锁。输出结果是“ABABABAB...”。
转载请注明原文地址: https://ju.6miu.com/read-950318.html