java concurrent之前戏synchronized
对于多线程共享资源的情况须要进行同步,以避免一个线程的修改被还有一个线程的修改所覆盖。
最普遍的同步方式就是synchronized。
把代码声明为synchronized。有两个重要后果,一般是指该代码具有 原子性(atomicity)和 可见性(visibility)。
1、原子性强调的是运行。意味着个时刻,仅仅有一个线程可以运行一段代码,这段代码通过一个monitor object保护。从而防止多个线程在更新共享状态时相互冲突。
2、可见性强调的是结果。它要对付内存缓存和编译器优化的各种反常行为。它必须确保释放锁之前对共享数据做出的更改对于随后获得该锁的还有一个线程是可见的。
同步方法
看一个样例就明确了:
import java.util.Random;
public class TestSyncClass {
private int num=0;
private Random random=new Random();
public synchronized void testAdd1(){
System.out.println("testAdd1--->>");
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1-result-->>"+num);
}
public synchronized void testAdd2(){
System.out.println("testAdd2--->>");
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2-result-->>"+num);
}
public void testAdd3(){
System.out.println("testAdd3--->>");
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3-result-->>"+num);
}
public static void main(String[] args) {
final TestSyncClass syncClass=new TestSyncClass();
Thread thread1=new Thread(){
@Override
public void run() {
syncClass.testAdd1();
super.run();
}
};
Thread thread2=new Thread(){
@Override
public void run() {
syncClass.testAdd2();
super.run();
}
};
Thread thread3=new Thread(){
@Override
public void run() {
syncClass.testAdd3();
super.run();
}
};
thread1.start();
thread2.start();
thread3.start();
}
}
代码执行结果:
testAdd1--->>
testAdd3--->>
1-result-->>2
3-result-->>2
testAdd2--->>
2-result-->>3
代码中testAdd1、testAdd2是被synchronized声明的方法。testAdd3没有声明。在执行的时候因为testAdd3没有被声明,所以在紧跟着開始执行testAdd1的时候也执行了testAdd3。结果testAdd1执行的结果被testAdd3的结果覆盖了,打印了同样的值3。这个主要是可见性的问题。因为testAdd2也是被声明过的,所以testAdd2并没有马上执行。而是等testAdd1执行完之后才開始执行。
全部对象都自己主动含有单一的锁(也称为监视器monitor object)。
当在对象上调用其随意synchronized方法的时候,此对象都被加锁。这时该对象上的其它synchronized方法仅仅有等到前一个方法调用完成并释放了锁之后才干被调用。
针对每一个类,也有一个锁(作为类的Class对象的一部分)。所以synchronized static 方法能够在类的范围内防止对static数据的并发訪问。
同步块
不管何种情况,要想锁住代码,必须使用同样的锁。比如把testAdd2改成
private Object object=new Object();
public void testAdd2(){
synchronized(object){
System.out.println("testAdd2--->>");
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2-result-->>"+num);
}
}
则testAdd2和testAdd1就不会相互等待了。结果例如以下:
testAdd2--->>
testAdd3--->>
testAdd1--->>
3-result-->>3
2-result-->>3
1-result-->>3
事实上synchronized(object)是更安全的上锁方式。由于直接声明方法的形式用的是类的锁,而声明代码块的形式用的是私有属性的锁,尤其是在server开发的时候,外面类的锁非常easy被黑客获取。从而获取了攻击server的入口,而私有属性的私有性让黑客难以获取,所以它的锁就相对安全的多。
类同步
上面的main方法的三个线程用的是同一个TestSyncClass syncClass对象。假设每一个线程都各自创建一个对象就不能达到锁定代码的目标了。要想达到同步的目的,代码须要改动成例如以下:
import java.util.Random;
public class TestSyncClass {
private int num = 0;
private static Object object = new Object();
private Random random = new Random();
public void testAdd1() {
synchronized (object) {
System.out.println("testAdd1--->>");
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1-result-->>" + num);
}
}
public void testAdd2() {
synchronized (object) {
System.out.println("testAdd2--->>");
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2-result-->>" + num);
}
}
public void testAdd3() {
System.out.println("testAdd3--->>");
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("3-result-->>" + num);
}
public static void main(String[] args) {
Thread thread1 = new Thread() {
@Override
public void run() {
TestSyncClass syncClass = new TestSyncClass();
syncClass.testAdd1();
super.run();
}
};
Thread thread2 = new Thread() {
@Override
public void run() {
TestSyncClass syncClass = new TestSyncClass();
syncClass.testAdd2();
super.run();
}
};
Thread thread3 = new Thread() {
@Override
public void run() {
TestSyncClass syncClass = new TestSyncClass();
syncClass.testAdd3();
super.run();
}
};
thread1.start();
thread2.start();
thread3.start();
}
}
执行结果:
testAdd1--->>
testAdd3--->>
3-result-->>1
1-result-->>1
testAdd2--->>
2-result-->>1
事实上使用synchronized (TestSyncClass.class)类的锁也能达到类似的效果,可是考虑到私有属性的安全性就直接使用上面代码做实例了。
注意:synchronized是不能继承的,父类中synchronized的声明在子类的继承过程中须要再次声明,否则synchronized将会丢失。
wait(), notify()。notifyAll()
基类不光有锁。还有这三个方法。wait()会让获取锁的线程等待并释放锁,直到notify()或notifyAll()唤醒并又一次获取锁。
先看一个样例:
public class TestSyncClass {
private int num = 0;
private Object object = new Object();
private Object object1 = new Object();
public void testAdd(int index) {
System.out.println("testAdd" + index + "--->>");
synchronized (object) {
num++;
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(index + "-result-->>" + num);
}
}
public void release() {
synchronized (object) {
object.notifyAll();
System.out.println("-release-->>");
}
}
public static void main(String[] args) {
final TestSyncClass syncClass = new TestSyncClass();
Thread thread1 = new Thread() {
@Override
public void run() {
syncClass.testAdd(1);
super.run();
}
};
Thread thread2 = new Thread() {
@Override
public void run() {
syncClass.testAdd(2);
super.run();
}
};
Thread thread3 = new Thread() {
@Override
public void run() {
syncClass.testAdd(3);
super.run();
}
};
thread1.start();
thread2.start();
thread3.start();
Thread thread4 = new Thread() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
syncClass.release();
super.run();
}
};
thread4.start();
}
}
执行结果:
testAdd1--->>
testAdd2--->>
testAdd3--->>
-release-->>
3-result-->>3
2-result-->>3
1-result-->>3
调用object的wait(), notify()。notifyAll()法前,必须获得object锁,也就是这三个方法必须写在synchronized(obj) {…} 代码段内。否则跑出异常java.lang.IllegalMonitorStateException。
调用object.wait()后。线程A就释放了object的锁,否则syncClass.release()无法获得object锁,等待的线程。
当object.wait()方法返回后。各个线程须要再次获得object锁,才干继续运行。
notify()仅仅能唤醒线程,notifyAll()则能所有唤醒,可是个线程须要又一次竞争object的锁。