synchronized
先看一段代码:
public class Account { private int money; public Account(int money) { this.money = money; } public void deposit(int my) { int tmp = this.money; tmp += my; try { Thread.sleep((int)(Math.random() * 1000)); } catch (InterruptedException e) { e.printStackTrace(); } this.money = tmp; } public void withdraw(int my) { int tmp = this.money; tmp -= my; try { Thread.sleep((int)(Math.random() * 1000)); } catch (InterruptedException e) { e.printStackTrace(); } this.money = tmp; } public int getMoney() { return money; } private static final int THREAD_AMOUNT = 100; private static Thread[] threads = new Thread[THREAD_AMOUNT]; public static void main(String[] args) { final Account account = new Account(1000); for (int i = 0; i < THREAD_AMOUNT; i++) { threads[i] = new Thread(new Runnable() { @Override public void run() { account.deposit(100); account.withdraw(100); } }); threads[i].start(); } for (int i = 0; i < THREAD_AMOUNT; i++) { try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("money is " + account.getMoney()); } }
我们用Account模拟一个账户。然后启动1000个线程,对账户中的1000元进行存取。我们先存100元,再取100元,结果应该是1000元不变。但是事与愿违,我们的结果是1000、1100等随机结果。原因是当一个线程存了100元,还没有被取走时,另一个线程的结果把当前的值覆盖掉了。这就需要我们对方法进行同步。
public synchronized void deposit(int my) { } public synchronized void withdraw(int my) { }
这样就能得到正确结果了,但是运行时间貌似增加了~~囧。
下面我们来介绍一下synchronized:
synchronized可以作为方法的修饰符,也可以出现在代码中,如:
synchronized (o) { //your code }
o是一个Object类型
1.当synchronized修饰方法的时候,就相当于类实例的该方法加了一把锁(是每个类实例都拥有各自的锁,而不是所有类实例共享一把锁)。
public class ThreadTest { public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { PrintClass pc = new PrintClass(); pc.printText("thread-1"); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { PrintClass pc = new PrintClass(); pc.printText("thread-2"); } }); t1.start(); t2.start(); } } class PrintClass { public synchronized void printText(String text) { while (true) { System.out.println(text); } } }
上面的demo,这两个线程都会打印,因为每个线程中都持有一个实例,这两个实例的synchronized方法锁并不互斥。但如果改为下面这样:
public class ThreadTest { public static void main(String[] args) { final PrintClass pc = new PrintClass(); Thread t1 = new Thread(new Runnable() { @Override public void run() { pc.printText("thread-1"); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { pc.printText("thread-2"); } }); t1.start(); t2.start(); } } class PrintClass { public synchronized void printText(String text) { while (true) { System.out.println(text); } } }
只有一个线程会打印出来,因为他们持有的同一个实例。要是synchronized修饰的是静态方法又会怎样呢:
public class ThreadTest { public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { PrintClass.printText("thread-1"); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { PrintClass.printText("thread-2"); } }); t1.start(); t2.start(); } } class PrintClass { public synchronized static void printText(String text) { while (true) { System.out.println(text); } } }
还是只有一个线程会打印。这是因为在静态方法上使用synchronized,是在这个类的静态方法上加了把锁,同一时刻只能有一个线程访问该静态方法。
2.当synchronized修饰代码块的时候,在谁的身上加锁取决于synchronized(o) {}中的对象o。在这里你可以把o看成一个门卫,进入synchronized标注的代码块必须取得门卫的同意,门卫每次只放一个线程进入。一个门卫可以看管多个代码块,同一个门卫只允许同一个线程进入。
我们最常见到的门卫是this:
public class ThreadTest { public static void main(String[] args) { final PrintClass pc = new PrintClass(); Thread t1 = new Thread(new Runnable() { @Override public void run() { pc.printText("Thread-1:text1", "Thread-1:text2", "Thread-1:text3"); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { pc.printText("Thread-2:text1", "Thread-2:text2", "Thread-2:text3"); } }); t1.start(); t2.start(); } } class PrintClass { public void printText(String text1, String text2, String text3) { System.out.println(text1); synchronized (this) { for (int i=0; i < 1000; i++) { System.out.println(text2); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println(text3); } }
运行结果是:
Thread-1:text1
Thread-1:text2
Thread-2:text1
Thread-1:text2
Thread-1:text2
Thread-1:text2
.
.
.
我们看Thread-1和Thread-2都进入了该方法,但是只有Thread-1进入了同步块,Thread-2被阻塞了,所以text3也未打印出,只能等到Thread-1完成Thread-2才可以进入。我们再看一个同一个门卫看守两块代码的例子:
public class ThreadTest { public static void main(String[] args) { final PrintClass pc = new PrintClass(); Thread t1 = new Thread(new Runnable() { @Override public void run() { pc.printText("text"); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { pc.printVal(1111); } }); t1.start(); t2.start(); } } class PrintClass { public void printText(String text) { synchronized (this) { while (true) { System.out.println(text); } } } public void printVal(int val) { synchronized (this) { while (true) { System.out.println(val); } } } }
这段代码只能打印出一种结果,原因是这个两个方法都是门卫this来看管的,他只让一个线程通过,即使是不同的代码块。其实由this看管的代码块相当于在类的方法上加synchronized(不包括静态方法哦!),是在类实例上加锁的。如果是新的实例,那么this就不是同一个了。不同的门卫this就可以让不同的线程进入了,这里就不在举例了,那我们在看看其他的门卫:
public class ThreadTest { public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { PrintClass pc = new PrintClass(); pc.printText("text"); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { PrintClass pc = new PrintClass(); pc.printVal(1111); } }); t1.start(); t2.start(); } } class PrintClass { public void printText(String text) { synchronized (PrintClass.class) { while (true) { System.out.println(text); } } } public void printVal(int val) { synchronized (PrintClass.class) { while (true) { System.out.println(val); } } } }
这次是这个类来作门卫了,虽然我们使用了2个类实例(在run方法里new了两个实例),但这次还是只打印出一种结果,这就是类同步。就相当于在静态方法上加synchronized,这个代码块同一时间只允许一个线程访问。你可以试试将PrintClass.class换成this,这样两个方法都可以打印出结果的。其实添加一个静态成员变量,用他做门卫,也可以达到上面demo的效果:
public class ThreadTest { public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { PrintClass pc = new PrintClass(); pc.printText("text"); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { PrintClass pc = new PrintClass(); pc.printVal(1111); } }); t1.start(); t2.start(); } } class PrintClass { private static Object lock = new Object(); public void printText(String text) { synchronized (lock) { while (true) { System.out.println(text); } } } public void printVal(int val) { synchronized (lock) { while (true) { System.out.println(val); } } } }
因为静态成员变量是类持有的,每个实例共享的,所以这样加锁与给类本身加锁效果相同。但要是只是普通的成员变量呢,在这里我们用String这个对象来举例:
public class ThreadTest { public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { PrintClass pc = new PrintClass(); pc.printText("text"); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { PrintClass pc = new PrintClass(); pc.printVal(1111); } }); t1.start(); t2.start(); } } class PrintClass { private String str = new String(); public void printText(String text) { synchronized (str) { while (true) { System.out.println(text); } } } public void printVal(int val) { synchronized (str) { while (true) { System.out.println(val); } } } }
打印出了2种结果,证明这String对象是被类实例持有的,所以只针对类实例加锁。但是,试一试将定义改为private String str = “”。呵呵,只打印出一种结果吧。原因可能是JVM虚拟机优化了String,使之在赋值之后不可变了,所以这个门卫始终就是他一人了。
最后,还要注意一点,方法上的synchronized是不能继承的哦,子类想要同步,要在方法上自己添加。
好文章!666,学习了