Java中的每一个对象都有一个内部锁。如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。获得内部锁的唯一途径就是进入这个锁保护的同步代码块或方法。
synchronized关键字主要有修饰代码块和修饰方法两种用法:
1、synchronized关键字修饰一般方法和代码块(使用的是Java对象锁)
public class SynchronizedDemo
{
//synchronized代码块,锁为this,this代表调用该代码块的当前对象
public void test1()
{
synchronized (this)
{
int i = 5;
while (i-- > 0)
{
System.out.println(Thread.currentThread().getName() + " : " + i);
try
{
Thread.sleep(500);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
//synchronized修饰方法,锁为调用该方法的对象
public synchronized void test2()
{
int i = 5;
while (i-- > 0)
{
System.out.println(Thread.currentThread().getName() + " : " + i);
try
{
Thread.sleep(500);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args)
{
final SynchronizedDemo demo= new SynchronizedDemo();
new Thread(new Runnable()
{
public void run()
{
demo.test1();
}
}).start();
new Thread(new Runnable()
{
public void run()
{
demo.test2();
}
}).start();
}
}
运行结果为:
Thread-0 : 4
Thread-0 : 3
Thread-0 : 2
Thread-0 : 1
Thread-0 : 0
Thread-1 : 4
Thread-1 : 3
Thread-1 : 2
Thread-1 : 1
Thread-1 : 0
test1()方法里,synchronized修饰代码块,锁为this,this代表调用该代码块的对象。test2()方法上,synchronized修饰方法,锁为调用该方法的对象。所以两个同步代码所需要获得的对象锁都是同一个对象锁。main函数里,两个线程使用的是同一个对象demo来分别调用test1(),test2(),两个线程都需要获得该对象锁,另一个线程必须等待,进入阻塞状态。所以一个线程得到对象锁执行完以后,才会释放锁,然后另一个线程拿到对象锁,开始运行。
那如果main函数里的代码改成下面这样,分别new一个新对象来调用test1()、test2()方法,其他代码不变:
public static void main(String[] args)
{
//final SynchronizedDemo demo= new SynchronizedDemo();
new Thread(new Runnable()
{
public void run()
{
new SynchronizedDemo().test1();
}
}).start();
new Thread(new Runnable()
{
public void run()
{
new SynchronizedDemo().test2();
}
}).start();
}
}
运行结果如下:
Thread-0 : 4
Thread-1 : 4
Thread-0 : 3
Thread-1 : 3
Thread-0 : 2
Thread-1 : 2
Thread-0 : 1
Thread-1 : 1
Thread-0 : 0
Thread-1 : 0
可以看到,两个线程交替运行,这是因为调用两个方法的对象并不是同一个对象,所以两个方法得到的对象锁也不是同一个锁。synchronized是用于多个线程来抢夺同一个对象锁的情况,来进行同步。
一个类的对象锁和另一个类的对象锁是没有关联的,当一个线程获得A类的对象锁时,它同时也可以获得B类的对象锁。
2、synchronized关键字修饰静态方法和代码块(使用的是Java类锁)
public class SynchronizedDemo
{
public void test1()
{
synchronized (SynchronizedDemo.class)
{
int i = 5;
while (i-- > 0)
{
System.out.println(Thread.currentThread().getName() + " : " + i);
try
{
Thread.sleep(500);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
public static synchronized void test2()
{
int i = 5;
while (i-- > 0)
{
System.out.println(Thread.currentThread().getName() + " : " + i);
try
{
Thread.sleep(500);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args)
{
new Thread(new Runnable()
{
public void run()
{
new SynchronizedDemo().test1();
}
}).start();
new Thread(new Runnable()
{
public void run()
{
SynchronizedDemo.test2();
}
}).start();
}
}
运行结果:
Thread-0 : 4
Thread-0 : 3
Thread-0 : 2
Thread-0 : 1
Thread-0 : 0
Thread-1 : 4
Thread-1 : 3
Thread-1 : 2
Thread-1 : 1
Thread-1 : 0
类锁修饰方法和代码块和对象锁是一样的,因为静态方法是所有对象实例共用的,所以对应着synchronized修饰的静态方法的锁也是唯一的,所以是一个类锁,它的锁对象是本类的class文件,即SynchronizedDemo.class字节码文件,该字节码文件只有一份,被所有的类共用。
那如果synchronized同时修饰静态和非静态方法:
public class SynchronizedDemo
{
public synchronized void test1()
{
int i = 5;
while (i-- > 0)
{
System.out.println(Thread.currentThread().getName() + " : " + i);
try
{
Thread.sleep(500);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public static synchronized void test2()
{
int i = 5;
while (i-- > 0)
{
System.out.println(Thread.currentThread().getName() + " : " + i);
try
{
Thread.sleep(500);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args)
{
new Thread(new Runnable()
{
public void run()
{
new SynchronizedDemo().test1();
}
}).start();
new Thread(new Runnable()
{
public void run()
{
SynchronizedDemo.test2();
}
}).start();
}
}
运行结果:
Thread-0 : 4
Thread-1 : 4
Thread-1 : 3
Thread-0 : 3
Thread-1 : 2
Thread-0 : 2
Thread-1 : 1
Thread-0 : 1
Thread-1 : 0
Thread-0 : 0
上面代码synchronized同时修饰静态方法和实例方法,但是运行结果是交替进行的,这证明了类锁和对象锁是两个不一样的锁,控制着不同的区域,它们是互不干扰的。同样,线程获得对象锁的同时,也可以获得该类锁,即同时获得两个锁,这是允许的。
总结:所以总的来说,synchronized修饰一般方法时,使用的是调用该方法的对象锁,如果new出来不同的对象来调用该方法,那使用的锁是不一样的,也就没有什么同步可言了。synchronized修饰静态(static)方法时,使用的是该方法所在类的字节码文件,整个内存中只有一份,起到同步的作用,虽然也可以使用new来新建对象调用该静态方法,那其实使用的还是同一个锁对象,但是一般是直接使用类名来调用static方法。