上一篇中学习了显示锁ReentrantLock和其条件对象Condition的使用,下面小小的总结一下:
(1) 锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码片段
(2) 锁可以管理试图进入被保护代码片段的线程
(3) 锁可以拥有一个或多个相关的条件对象
(4) 每个条件对象管理那些已经获得了锁,但还需要满足额外条件才能运行的线程
Lock和Condition接口为程序设计人员提供了高度的锁控制。实际上大多数情况下我们可以使用java的另一种锁机制。从jdk1.0开始,java 中每一个对象都有一个内部锁,每一个类都有一个类锁,如果一个方法用synchronized关键字声明,那么该方法称为“同步方法”,那么进入到该方法的线程将获得这个对象的锁,在该线程调用“同步方法”结束之前,其他试图调用该“同步方法”的线程将会阻塞。
同步方法的使用结构一般如下:
public synchronized void methodName() {
// method body
}
这种结构等价于使用显示锁的如下结构:
private ReentrantLock lock = new ReentrantLock();
public void methodName() {
lock.lock();
try {
// method body
} finally {
lock.unlock();
}
}
可以看到 使用synchronized关键字比使用内部锁ReentrantLock 要简洁的多,但是要理解同步方法的使用,你必须了解java的每一个对象都有一个内部锁,并且这个内部锁只用一个相关条件。由锁来管理那些试图进入同步方法的线程,由条件对象管理那些调用了条件对象wait() 方法的线程。
实际上synchronized关键字修饰的方法在多线程中有时会大大影响执行效率,比如当用synchronized关键字修饰Thread的run()方法时,此线程就无法调用本类中同样被synchronized关键字修饰的方法。此时我们应该减小加锁的粒度。
解决以上问题我们可以采用同步代码块的方式来使用synchronized关键字。使用结构如下:
public void methodName() {
// code
synchronized(this) {
// code
}
// code
}
(1)synchronized关键字加锁
public class SynchronizedTest1 {
// 使用synchronized关键字修饰的同步方法
public synchronized void printNum() {
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
final SynchronizedTest1 test = new SynchronizedTest1();
Thread t1 = new Thread(new Runnable() {
public void run() {
test.printNum();
}
},"A");
Thread t2 = new Thread(new Runnable() {
public void run() {
test.printNum();
}
},"B");
t1.start();
t2.start();
}
}
上述代码中线程A和线程B同时运行printNum()方法,若线程A首先进入同步方法,则线程A获得当前对象的锁,当线程A结束之前时间片到时,此时线程B开始调用同步方法,由于线程A未释放获得的锁,那么线程B即进入阻塞状态。这样就保证了线程A和线程B同步输出。
(2)synchronized关键字获得的锁也是可重入锁
public class ParentSynchronized {
//父类同步方法
public synchronized void parentMethod() {
System.out.println("parent invoke");
}
}
public class ChildrenSynchronized extends ParentSynchronized{
// 子类同步方法
public synchronized void childrenMethod() {
System.out.println("children invoke");
parentMethod();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
final ChildrenSynchronized cs = new ChildrenSynchronized();
Thread t = new Thread(new Runnable() {
public void run() {
cs.childrenMethod();
}
});
t.start();
}
}
children invoke
parent invoke
(3)synchronized关键字修饰类的静态方法
public class SynchronizedTest2 {
// synchronized关键字修饰静态的方法 同步方法
public synchronized static void printNum1(){
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
// synchronized关键字使用类锁 同步代码块
public static void printNum2(){
synchronized(SynchronizedTest2.class) {
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
// synchronized关键字修饰 同步方法 这里使用的是对象锁
public synchronized void printNum3(){
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
final SynchronizedTest2 test = new SynchronizedTest2();
Thread t1 = new Thread(new Runnable() {
public void run() {
SynchronizedTest2.printNum1();
}
},"A");
Thread t2 = new Thread(new Runnable() {
public void run() {
SynchronizedTest2.printNum2();
}
},"B");
Thread t3 = new Thread(new Runnable() {
public void run() {
test.printNum3();
}
},"C");
t1.start();
t2.start();
t3.start();
}
}
以上代码中 静态方法printNum1() 和 printNum2() 使用的都是类锁,方法printNum3()使用的是当前对象的锁。
我们开了三个线程A,B,C分别运行printNum1(),printNum2(),printNum3()方法 从结果中我们可以看到 线程A和线程B是始终同步的,线程C和线程A,B之间没有同步关系
输出的一种结果如下: