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:text2
Thread-2:text1
Thread-1:text2
Thread-1:text2
Thread-1:text2
.
.
.
我们看Thread-1和Thread-2都进入了该方法,但是只有Thread-1进入了同步块,Thread-2被阻塞了,所以text3也未打印出。我们再看一个同一个门卫看守两块代码的例子:
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是不能继承的哦,子类想要同步,要在方法上自己添加。