多线程-synchronized
引言
synchronized是Java线程同步中的一个重要的概念,synchronized是独占锁(互斥锁),同时也是可重入锁(可重入锁一定程度上避免了死锁的问题,内部是关联一个计数器,加一次锁计数器值加一,为零时释放锁),也是一种重量级锁。
synchronized是Java中的关键字,是一种同步锁,修饰的情形有以下几种:
(1)修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个方法的对象;
(2)修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。
(3)修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象。
(4)修饰一个类,其作用的范围是synchronized后面大括号{}括起来的部分,作用的对象是这个类的所有对象。
修饰一个代码块
(1)一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。
package com.huawei.thread; class SyncThread implements Runnable { private static int count; public SyncThread() { count = 0 ; } @Override public void run() { synchronized ( this ) { for ( int i = 0 ; i < 5 ; i++) { try { System.out.println(Thread.currentThread().getName() + ": " + (count++)); Thread.sleep( 100 ); } catch (InterruptedException ie) { ie.printStackTrace(); } } } } } public class Test38 { public static void main(String[] args) { SyncThread sync = new SyncThread(); Thread t1 = new Thread(sync, "A" ); Thread t2 = new Thread(sync, "B" ); t1.start(); t2.start(); } }
运行截图:
当两个并发线程t1,t2访问同一个对象sync中的synchronized代码块时,在同一个时刻只能有一个线程得到执行,另一个线程受阻塞。t1和t2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。
main函数做简单修改:
public static void main(String[] args) { Thread t1 = new Thread( new SyncThread(), "A" ); Thread t2 = new Thread( new SyncThread(), "B" ); t1.start(); t2.start(); }
synchronized只锁定对象,每个对象只有一个锁与之相关联。t1和t2分别创建了SyncThread对象与之关联,因此t1和t2锁定的对象是不同的,互不干扰,不形成互斥,所有两个线程可以同时执行。
(2)当一个线程访问对象的一个synchronized同步块时,另一个线程仍然可以访问该对象中的非synchronized不同代码块。
package com.huawei.thread; class Counter implements Runnable { private int count; public Counter() { count = 0 ; } public void addCount() { synchronized ( this ) { for ( int i = 0 ; i < 5 ; i++) { try { System.out.println(Thread.currentThread().getName() + ": " + (count++)); Thread.sleep( 100 ); } catch (Exception e) { e.printStackTrace(); } } } } public void printCount() { for ( int i = 0 ; i < 5 ; i++) { try { System.out.println(Thread.currentThread().getName() + " count: " + count); Thread.sleep( 100 ); } catch (Exception e) { e.printStackTrace(); } } } @Override public void run() { String tName = Thread.currentThread().getName(); if (tName.equals( "A" )) { addCount(); } else if (tName.equals( "B" )) { printCount(); } } } public class Test39 { public static void main(String[] args) { Counter counter = new Counter(); Thread t1 = new Thread(counter, "A" ); Thread t2 = new Thread(counter, "B" ); t1.start(); t2.start(); } }
运行截图:
addCount是一个synchronized方法,printCount是非synchronized。从上面的结果中可以看出一个线程访问一个对象的synchronized代码块时,其他线程可以访问对象的非synchronized代码块而不受阻塞。
(3)指定要给某个对象加锁
package com.huawei.thread; class Account { private String name; private float amount; public Account(String name, float amount) { this .name = name; this .amount = amount; } public void deposit( float amt) { amount += amt; try { Thread.sleep( 100 ); } catch (Exception e) { e.printStackTrace(); } } public void withdraw( float amt) { amount -= amt; try { Thread.sleep( 100 ); } catch (Exception e) { e.printStackTrace(); } } public float getAmount() { return amount; } @Override public String toString() { return "Account [name=" + name + ", amount=" + amount + "]" ; } } class AccountOperator implements Runnable { private Account account; public AccountOperator(Account account) { this .account = account; } @Override public void run() { synchronized (account) { account.deposit( 500 ); account.withdraw( 500 ); System.out.println(Thread.currentThread().getName() + ": " + account.getAmount()); } } } public class Test40 { static final int THREAD_NUM = 5 ; public static void main(String[] args) { Account account = new Account( "zhang san" , 1000 .0f); AccountOperator ao = new AccountOperator(account); Thread[] threads = new Thread[THREAD_NUM]; for ( int i = 0 ; i < THREAD_NUM; i++) { threads[i] = new Thread(ao, "Thread" + i); threads[i].start(); } } }
synchronized给account对象加了锁。这时,当一个线程访问account对象时,其他试图访问account对象的线程将会阻塞。
当有一个明确的对象作为锁时,就可以用类似下面这样的方式写程序:
public void method3(SomeObject obj) { //obj 锁定的对象 synchronized (obj) { // #TODO } }
当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的对象来充当锁:
class Test implements Runnable { private byte [] lock = new byte [ 0 ]; // 特殊的instance变量 public void method() { synchronized (lock) { // #TODO 同步代码块 } } public void run() { } }
注:零长度的byte数组对象创建起来比任何对象都经济。
synchronized不能锁住基本的类型如:int,long,float等非对象类型
修饰一个方法
synchronized修饰方法和修饰一个代码块类似,只是作用范围不一样,修饰代码块时大括号括起来的范围,而修饰方法范围是整个函数。
(1)synchronized关键字不能继承
虽然可以用synchronized来定义方法,但是synchronized不属于方法定义的一部分。因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖这个方法,则在子类中这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。
在子类方法中加上synchronized关键字:
class Parent { public synchronized void method() { } } class Child extends Parent { public synchronized void method() { } }
在子类方法中调用父类的同步方法:
class Parent { public synchronized void method() { } } class Child extends Parent { public void method() { super .method(); } }
(2)在定义接口方法时不能使用synchronized关键字
(3)构造方法不能使用synchronized关键字,但可以使用synchronized代码块来进行同步
修饰一个静态方法
(1)静态方法属于类而不属于对象。synchronized修饰的静态方法锁定的是这个类的所有对象。
package com.huawei.thread; public class Test42 { static class SyncThread implements Runnable { private static int count; public SyncThread() { count = 0 ; } public synchronized static void m() { for ( int i = 0 ; i < 5 ; i++) { try { System.out.println(Thread.currentThread().getName() + ": " + (count++)); } catch (Exception e) { e.printStackTrace(); } } } @Override public synchronized void run() { m(); } } public static void main(String[] args) { Thread t1 = new Thread( new SyncThread(), "A" ); Thread t2 = new Thread( new SyncThread(), "B" ); t1.start(); t2.start(); } }
只要修饰了静态方法,相对于整个类及其产生的对象只有一把锁。
(2)Java类中可以有静态代码块static{},可以在其中设置同步代码块
public class Test40 { static final Integer THREAD_NUM = 5 ; static { synchronized (THREAD_NUM) { // #TODO } } }
其中synchronized修饰的必须是static变量或者类
(3)如果一个类中定义了一个synchronized 的 static 函数A,也定义了一个 synchronized 的 instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。B方法的锁是Obj这个对象,而A的锁是Obj所属的那个Class。
package com.huawei.thread; public class Test43 { public static void main(String[] args) { My my = new My(); Thread t1 = new Thread(my, "A" ); Thread t2 = new Thread(my, "B" ); t1.start(); t2.start(); } } class My implements Runnable { @Override public void run() { String name = Thread.currentThread().getName(); if (name.equals( "A" )) { test1(); } else if (name.equals( "B" )) { test2(); } } synchronized void test2() { try { for ( int i = 0 ; i < 5 ; i++) { Thread.sleep( 100 ); System.out.println( "test2" ); } } catch (Exception e) { e.printStackTrace(); } } synchronized static void test1() { try { for ( int i = 0 ; i < 5 ; i++) { Thread.sleep( 100 ); System.out.println( "test1" ); } } catch (Exception e) { e.printStackTrace(); } } }
修饰一个类
class ClassName { public void method() { synchronized (ClassName. class ) { // todo } } }
效果与修饰一个静态方法类似。
小结
(1)无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则它取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。
(2)每个对象只有一个锁(lock)与之相关联,谁拿到这个锁谁就可以运行它所控制的那段代码。
(3)实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
参考资料
http://blog.csdn.net/luoweifu/article/details/46613015
http://www.cnblogs.com/beiyetengqing/p/6213437.html