并发编程学习总结(七) :java中synchronized关键字使用详解(1)

来源:互联网 发布:mysql 函数 跳出循环 编辑:程序博客网 时间:2024/05/20 14:26

上一篇中学习了显示锁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() {// codesynchronized(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 stubfinal 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 stubfinal ChildrenSynchronized cs = new ChildrenSynchronized();Thread t = new Thread(new Runnable() {public void run() {cs.childrenMethod();}});t.start();}}


子类和父类中都有同步方法,子类方法childrenMethod()中调用父类的parentMethod()同步方法,线程调用childrenMethod()方法 获得当前对象的锁,之后再调用父类的parentMethod()同步方法,由于两个同步方法使用的锁都是当前对象的锁,所有线程可以顺利的调用父类的同步方法。输出结果为如下:

children invokeparent 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 stubfinal 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之间没有同步关系

输出的一种结果如下:











0 0
原创粉丝点击