Java关键字synchronized
一、用途
synchronized可以实现线程同步,保证线程安全。被synchronized修饰的代码块或方法在同一时刻只允许一个线程进入临界区。
二、用法
修饰普通方法,即同步方法
修饰静态方法,即静态同步方法
修饰代码块,即同步代码块
三、例子
1、同步方法
代码中用synchronized修饰了method,启动线程t0和线程t1。我们的预期结果是某一线程进入临界区,另一个线程在等待。进入临界区的线程睡眠2s后退出临界区,另一个线程再进入临界区。
public class Sync {
public synchronized void method() {
System.out.println(Thread.currentThread().getName() + ": in");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": out");
}
public static void main(String[] args) {
final Sync s = new Sync();
Thread t0 = new Thread(new Runnable() {
@Override
public void run() {
s.method();
}
});
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
s.method();
}
});
t0.start();
t1.start();
}
}
运行结果:
可以看到是预期的结果。
现在只有一个方法,下面我们测试一下多个方法。
public class Sync {
public synchronized void method1() {
System.out.println(Thread.currentThread().getName() + ": in method1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": out method1");
}
public void method2() {
System.out.println(Thread.currentThread().getName() + ": in method2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": out method2");
}
public static void main(String[] args) {
final Sync s = new Sync();
Thread t0 = new Thread(new Runnable() {
@Override
public void run() {
s.method1();
}
});
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
s.method2();
}
});
t0.start();
t1.start();
}
}
现在有两个方法分别是method1和method2,method1是由synchronized修饰的,method2则是一个普通方法。
来看一下运行结果:
似乎并不影响其他线程调用对象的非synchronized修饰的方法。
那么如果两个方法都是synchronized修饰的方法呢?
public class Sync {
public synchronized void method1() {
System.out.println(Thread.currentThread().getName() + ": in method1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": out method1");
}
public synchronized void method2() {
System.out.println(Thread.currentThread().getName() + ": in method2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": out method2");
}
public static void main(String[] args) {
final Sync s = new Sync();
Thread t0 = new Thread(new Runnable() {
@Override
public void run() {
s.method1();
}
});
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
s.method2();
}
});
t0.start();
t1.start();
}
}
看到运行结果我们会发现同一个对象的两个被synchronized修饰的不同方法不能同时被调用。
再试试不同对象
public class Sync {
public synchronized void method1() {
System.out.println(Thread.currentThread().getName() + ": in method1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": out method1");
}
public synchronized void method2() {
System.out.println(Thread.currentThread().getName() + ": in method2");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": out method2");
}
public static void main(String[] args) {
final Sync s0 = new Sync();
final Sync s1 = new Sync();
Thread t0 = new Thread(new Runnable() {
@Override
public void run() {
s0.method1();
}
});
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
s1.method1();
}
});
t0.start();
t1.start();
}
}
从运行结果可以看出调用不同对象的method1()方法是可以同时调用的。
那么不同对象的不同名字的方法依然是互不影响的。
那么是不是可以总结得到:synchronized修饰普通方法的时候是对对象进行加锁,即对象锁。
2、静态同步方法
把method1和methd2都改成静态方法,两个线程都去调用method1。
public class Sync {
public static synchronized void method1() {
System.out.println(Thread.currentThread().getName() + ": in method1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": out method1");
}
public static synchronized void method2() {
System.out.println(Thread.currentThread().getName() + ": in method2");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": out method2");
}
public static void main(String[] args) {
Thread t0 = new Thread(new Runnable() {
@Override
public void run() {
Sync.method1();
}
});
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Sync.method1();
}
});
t0.start();
t1.start();
}
}
两个线程调用method1不能同时进入临界区,只有等到一个退出临界区后另一个才能进入。
再试试调用不同静态方法。
public class Sync {
public static synchronized void method1() {
System.out.println(Thread.currentThread().getName() + ": in method1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": out method1");
}
public static synchronized void method2() {
System.out.println(Thread.currentThread().getName() + ": in method2");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": out method2");
}
public static void main(String[] args) {
Thread t0 = new Thread(new Runnable() {
@Override
public void run() {
Sync.method1();
}
});
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Sync.method2();
}
});
t0.start();
t1.start();
}
}
依然是同样的结果,两个进程不能同时进入临界区。
synchronized修饰静态方法是是对类进行加锁,即类锁。
3、同步代码块
同步代码块的都是这样的:
synchronized (...) {
...
}
①对象锁
synchronized (this) {
...
}
像这样的写法是不能写在静态方法里的,效果与同步方法类似。
②类锁
synchronized (XXX.class) {
...
}
XXX指的是类名。
像这样的写法可以写在静态方法和普通方法中。①②两种写法是不能写成构造代码块的,但是能写在构造方法中。
③私有锁
Object lock
synchronized (lock) {
...
}
私有锁的粒度更小,不会与对象锁和类锁竞争。
代码演示一下:
public class Sync {
public Integer a = new Integer(1);
public void method() {
System.out.println(Thread.currentThread().getName() + ": in method");
synchronized (a) {
System.out.println(Thread.currentThread().getName() + ": in synchronized block");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": out synchronized block");
}
System.out.println(Thread.currentThread().getName() + ": out moetod");
}
public static void main(String[] args) {
final Sync s = new Sync();
Thread t0 = new Thread(new Runnable() {
@Override
public void run() {
s.method();
}
});
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
s.method();
}
});
t0.start();
t1.start();
}
}
运行结果可以看到两个线程可以同时进入方法,但是不能同时进入synchronized (a){...}所包含的代码块。
当synchronized (lock){...}中的lock是不同的对象时结果是不一样的。
举个例子:小明和小红两人可以同时走进房间,但是小明(或小红)进入房间后必须走出房间才能再次进入房间。
public class Sync {
class Person {
String name;
Person(String name) {
this.name = name;
}
void enterRoom() {
System.out.println(Thread.currentThread().getName() + "-->" + name + ": 我准备好了!");
synchronized (name) {
System.out.println(Thread.currentThread().getName() + "-->" + name + ": 我进入房间了,并且我要在房间里待2秒!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
exitRoom();
}
}
void exitRoom() {
System.out.println(Thread.currentThread().getName() + "-->" + name + ": 我退出了房间!");
}
}
public static void main(String[] args) {
final Person p1 = new Sync().new Person("小明");
final Person p2 = new Sync().new Person("小明");
Thread t0 = new Thread(new Runnable() {
@Override
public void run() {
p1.enterRoom();
}
});
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
p2.enterRoom();
}
});
t0.start();
t1.start();
}
}
姓名相同,即lock是同一个对象时,两个线程是不能同时进入的。
把p2改名叫小红
final Person p1 = new Sync().new Person("小明");
final Person p2 = new Sync().new Person("小红");
可以看到小明小红几乎同时进入房间,也就是这两个线程同步进行的。