并发编程一个很棘手的问题就是对临界资源的访问,当大量线程同时对一个不安全的临界资源访问时,为了不出现与我们期望不符的结果,我们在写程序的时候需要考虑做适当的线程同步。其中synchronized关键字就是对线程同步的一种手段,synchronized可以修饰普通方法,静态方法,代码块。
1.1修饰普通方法
在我初次接触到synchronized关键字的时候,当我使用synchronized修饰普通方法时,我有一个疑惑就是synchronized锁住的是方法,还是这个方法所在的对象,也就是说,一个类中有多个被synchronized修饰的方法,如果我有两个线程,不管synchronized锁住的是方法还是整个对象,两个线程是无法同时访问同一个方法的,但是两个线程是否可以同时访问不同的被synchronized修饰的方法呢,我们通过下面的一个例子来解答我最初的疑惑。
package Synchronized;
/**
* Created by Jackie on 2017/8/10.
*
*/
public class Synchronzied {
public synchronized void methodOne(){
System.out.println(Thread.currentThread() + "methodOne");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void methodTwo(){
System.out.println(Thread.currentThread() + "methodTwo");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
Synchronzied synchronzied = new Synchronzied();
new Thread(){
@Override
public void run() {
synchronzied.methodOne();
System.out.println(Thread.currentThread() + " end");
}
}.start();
System.out.println("test");
new Thread(){
@Override
public void run() {
synchronzied.methodTwo();
System.out.println(Thread.currentThread() + " end");
}
}.start();
}
}
程序就是开两个线程同时访问两个被synchronized修饰的方法,每个方法体都会将当前线程休眠五秒,Java中sleep并不会丢失锁,而wait方法会丢失当前线程的锁。最终程序运行结果是当第一个线程访问methodOne时,第二个线程并不能立即执行methodTwo。当第一个线程运行五秒过后,第二个线程就能运行第二个方法了。从而可以分析出线程访问被synchronized修饰的普通方法时,获取的是整个对象锁,而不是方法锁(好像没这个锁),但是synchronized锁的粒度很大,比如,有两个方法操作操作的是不同的临界资源,不同的线程同时执行两个方法时并不会影响程序的最终结果,此时synchronized会造成程序效率的丢失,所以我们在写程序的时候不能滥用synchronized,此外还有一种很优雅的锁同步机制,那就是Lock,后面我可能会写一篇Lock的用法。
1.2修饰静态方法
/**
* Created by Jackie on 2017/8/10.
*
*/
public class Synchronzied {
public synchronized static void methodOne(){
System.out.println(Thread.currentThread() + "methodOne");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void methodTwo(){
System.out.println(Thread.currentThread() + "methodTwo");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
Synchronzied synchronzied = new Synchronzied();
new Thread(){
@Override
public void run() {
synchronzied.methodOne();
System.out.println(Thread.currentThread() + " end");
}
}.start();
System.out.println("test");
new Thread(){
@Override
public void run() {
synchronzied.methodTwo();
System.out.println(Thread.currentThread() + " end");
}
}.start();
}
}
和上面的程序基本上一样,只是在methodOne上添加了一个static,程序运行结果是,两个不同的线程可以同时访问两个方法,原理也很简单,静态方法是属于类,所以锁住的是类,普通方法是属于实例对象,锁住的是对象,这两个是不同的锁,所以不同的线程可以同时访问这两个方法。我们还可以修改上面的代码来验证我们的结论。/**
* Created by Jackie on 2017/8/10.
*
*/
public class Synchronzied {
public synchronized static void methodOne(){
System.out.println(Thread.currentThread() + "methodOne");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public synchronized void methodTwo(){
System.out.println(Thread.currentThread() + "methodTwo");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args){
Synchronzied synchronzied = new Synchronzied();
new Thread(){
@Override
public void run() {
synchronzied.methodOne();
System.out.println(Thread.currentThread() + " end");
}
}.start();
System.out.println("test");
new Thread(){
@Override
public void run() {
synchronzied.methodTwo();
System.out.println(Thread.currentThread() + " end");
}
}.start();
new Thread(){
@Override
public void run() {
synchronzied.methodOne();
System.out.println(Thread.currentThread() + " end");
}
}.start();
}
}
1.3修饰代码块
/**
* Created by Jackie on 2017/8/10.
*
*/
public class Synchronzied {
public void methodOne() {
System.out.println(Thread.currentThread() + "methodOne");
synchronized (this){
System.out.println("synchronized...... methodOne");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void methodTwo() {
System.out.println(Thread.currentThread() + "methodTwo");
synchronized (this){
System.out.println("synchronized...... methodTwo");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Synchronzied synchronzied = new Synchronzied();
new Thread() {
@Override
public void run() {
synchronzied.methodOne();
System.out.println(Thread.currentThread() + " end");
}
}.start();
new Thread() {
@Override
public void run() {
synchronzied.methodTwo();
System.out.println(Thread.currentThread() + " end");
}
}.start();
}
}
synchronized锁住的是同一个对象this,所以不能同时进入不同的被synchronized修饰的代码块,若是synchronized锁住的是不同的对象,那么不同的线程可以同时访问不同的被synchronized修饰的的代码块了,套路都是差不多了。