JAVA中的synchronized
内置锁
每一个Java对象都可以用作一个实现同步的锁,称为内置锁,线程进入同步代码块之前自动获取到锁,代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁
内置锁为互斥锁,即线程A获取到锁后,线程B阻塞直到线程A退出同步代码块,线程B才能获取到同一个锁,由于同一时刻只能有一个线程进入同步代码块,故同步代码块中的操作可以保证原子性
对象锁和类锁
对象锁:用于对象实例方法或一个对象实例上
类锁:用于类的静态方法或一个类的class对象上
类的对象实例可以有很多个,但是每个类只有一个class对象,所以不同对象实例的对象锁之间互不干扰,但每个类只有一个类锁
有一点必须注意的是,类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定实例方法和静态方法的区别的
synchronized
synchronized可以作用于方法或代码块。线程执行synchronized修饰的代码时,流程为:
获取锁->执行synchronized修饰的代码->释放锁
synchronized方法
class Test {
public synchronized void fun1() {
System.out.println("fun1 begin");
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("fun1 is working");
}
System.out.println("fun1 end");
}
public synchronized void fun2() {
System.out.println("fun2 begin");
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("fun2 is working");
}
System.out.println("fun2 end");
}
}
public class Main {
public static void main(String[] args) {
final Test t = new Test();
new Thread() {
@Override
public void run() {
t.fun1();
}
}.start();
new Thread() {
@Override
public void run() {
t.fun2();
}
}.start();
}
}
执行结果为fun1与fun2顺序执行:
fun1 begin
fun1 is working
fun1 is working
fun1 is working
fun1 end
fun2 begin
fun2 is working
fun2 is working
fun2 is working
fun2 end
删除两个方法的synchronized关键字后,执行结果为两个方法交替执行:
fun1 begin
fun2 begin
fun1 is working
fun2 is working
fun1 is working
fun2 is working
fun1 is working
fun1 end
fun2 is working
fun2 end
这是因为在执行synchronized修饰的方法前,线程需要先获取相应的对象锁(这里即为t的对象锁)。当第一个线程在执行t.fun1时会占有对象t的对象锁直到fun1执行结束,在此期间第二个线程由于无法获取t的对象锁,因此在t.fun1()执行完成,释放t的对象锁前无法执行被synchronized修饰的t.fun2()
易知在main函数中建立两个Test对象t1和t2时,在两个线程中分别执行t1.fun1()和t2.fun2()时,由于t1与t2为两个对象,因此两个线程中的分别执行t1.fun1()和t2.fun2()时需要获取的锁不同,因此两个函数会交替执行:
class Test {
public synchronized void fun1() {
System.out.println("fun1 begin");
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("fun1 is working");
}
System.out.println("fun1 end");
}
public synchronized void fun2() {
System.out.println("fun2 begin");
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("fun2 is working");
}
System.out.println("fun2 end");
}
}
public class Main {
public static void main(String[] args) {
final Test t1 = new Test();
final Test t2 = new Test();
new Thread() {
@Override
public void run() {
t1.fun1();
}
}.start();
new Thread() {
@Override
public void run() {
t2.fun2();
}
}.start();
}
}
执行结果:
fun1 begin
fun2 begin
fun1 is working
fun2 is working
fun2 is working
fun1 is working
fun1 is working
fun2 is working
fun2 end
fun1 end
但是需要注意的是,如果将Test中的fun1与fun2均改为static method,那么,这两个method就变成属于class的method了。此时再执行代码,两个线程需要的为Test.class的锁,因此会顺序执行。执行结果为:
fun1 begin
fun1 is working
fun1 is working
fun1 is working
fun1 end
fun2 begin
fun2 is working
fun2 is working
fun2 is working
fun2 end
synchronized代码块
在类Test中新建两个对象o1和o2,在fun1和fun2中分别使用synchronized(o1)和synchronized(o2)获取obj1和obj2的锁,由于两个对象的锁不同,因此使用两条线程分别调用t.fun1和t.fun2时,两个方法是交互运行的:
class Test {
Object obj1 = new Object();
Object obj2 = new Object();
public void fun1() {
synchronized (obj1) {
System.out.println("fun1 begin");
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("fun1 is working");
}
System.out.println("fun1 end");
}
}
public void fun2() {
synchronized (obj2) {
System.out.println("fun2 begin");
for (int i = 0; i < 3; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("fun2 is working");
}
System.out.println("fun2 end");
}
}
}
public class Main {
public static void main(String[] args) {
final Test t = new Test();
new Thread() {
@Override
public void run() {
t.fun1();
}
}.start();
new Thread() {
@Override
public void run() {
t.fun2();
}
}.start();
}
}
运行结果:
fun1 begin
fun2 begin
fun2 is working
fun1 is working
fun2 is working
fun1 is working
fun1 is working
fun2 is working
fun2 end
fun1 end
删除代码中的obj1和obj2,替换为String str1 = “test”,String str2 = “test”,在fun1与fun2的synchronized中将获取锁的对象分别改为str1和str2,会发现两个方法仍然顺序执行:fun1->fun2,这是因为java中的字符串常量存于堆中,看起来str1和str2是两个不同的对象,实际上两者还是指向同一个字符串”test”的地址,因此需等待fun1执行完成后,第二个线程才能获取到字符串”test”的对象锁,进入synchronized代码段