一.Java中的锁
一般在java中所说的锁就是指的内置锁,每个java对象都可以作为一个实现同步的锁,虽然说在java中一切皆对象, 但是锁必须是引用类型的,基本数据类型则不可以 。执行线程进入synchronized代码块会自动获得锁,无论是通过正常语句退出还是执行过程中抛出了异常,线程都会自动释放锁。 获得锁的唯一途径就是进入这个内部锁保护的同步块或方法(也可以给成员变量加锁) 。
每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回或者抛出异常时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态,从而有效避免了类成员变量的访问冲突。
在java中实现锁机制不仅仅限于使用synchronized关键字,还有JDK1.5之后提供的Lock关键字。
由于一个class不论被实例化多少次,其中的静态方法和静态变量在内存中都只有一份。所以,一旦一个静态的方法被申明为synchronized。此类所有的实例化对象在调用此方法,共用同一把锁,我们称之为类锁。
对象锁是用来控制实例方法之间的同步,类锁是用来控制静态方法(或静态变量互斥体)之间的同步。
在使用synchronized关键字进行线程同步时,可以使用同步代码块和同步方法两种,下面介绍一下他们之间使用的区别:
public class SynObj{
public synchronized void showA(){
System.out.println("showA..");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void showB(){
synchronized (this) {
System.out.println("showB..");
}
}
public void showC(){
String s="1";
synchronized (s) {
System.out.println("showC..");
}
}
}
public class Test {
public static void main(String[] args) {
final SynObj sy=new SynObj();
new Thread(new Runnable() {
@Override
public void run() {
sy.showA();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
sy.showB();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
sy.showC();
}
}).start();
}
}
运行结果如下:
这段代码的打印结果是,showA…..showC…..会很快打印出来,showB…..会隔一段时间才打印出来,那么showB为什么不能像showC那样很快被调用呢?
因为在启动第一个线程进行showA()方法的时候,休眠了3秒钟,在启动第一个线程的时候同时启动第二和第三个线程,因为方法C这里用synchronized进行加锁,这里锁的对象是s这个字符串对象,所以方法C没有遇到阻塞。而方法B则不同,是用当前对象this进行加锁,注意到方法A直接在方法上加synchronized,这也是对当前对象进行加锁的一种方法,所以在第二个线程对这个对象的所有synchronized同步代码进行阻塞。可见,用同步方法和同步代码块synchronized(this)这两种加锁机制是对同一个锁对象进行加锁,即当前对象。
在使用synchronized进行线程同步的时候:
一、当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。
public class Thread1 implements Runnable {
public void run() {
synchronized(this) {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
}
}
}
public static void main(String[] args) {
Thread1 t1 = new Thread1();
Thread ta = new Thread(t1, "A");
Thread tb = new Thread(t1, "B");
ta.start();
tb.start();
}
}
结果:
A synchronized loop 0
A synchronized loop 1
A synchronized loop 2
B synchronized loop 0
B synchronized loop 1
B synchronized loop 2
二、然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
public class Thread2 {
public void m4t1() {
synchronized(this) {
int i = 3;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
public void m4t2() {
int i = 3;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
public static void main(String[] args) {
final Thread2 myt2 = new Thread2();
Thread t1 = new Thread( new Runnable() { public void run() { myt2.m4t1(); } }, "t1" );
Thread t2 = new Thread( new Runnable() { public void run() { myt2.m4t2(); } }, "t2" );
t1.start();
t2.start();
}
}
结果:
t1 : 2
t2 : 2
t1 : 1
t2 : 1
t1 : 0
t2 : 0
三、尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
public class Thread2 {
public void m4t1() {
synchronized(this) {
int i = 3;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
public void m4t2() {
synchronized(this) {
int i = 3;
while( i-- > 0) {
System.out.println(Thread.currentThread().getName() + " : " + i);
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
}
}
public static void main(String[] args) {
final Thread2 myt2 = new Thread2();
Thread t1 = new Thread( new Runnable() { public void run() { myt2.m4t1(); } }, "t1" );
Thread t2 = new Thread( new Runnable() { public void run() { myt2.m4t2(); } }, "t2" );
t1.start();
t2.start();
}
}
结果:
t1 : 2
t1 : 1
t1 : 0
t2 : 2
t2 : 1
t2 : 0