Java提供了一种内置的锁机制来支持原子性:同步代码块(synchronized block),同步代码块包含2个部分:1,作为锁的对象引用;2,该锁所保护的代码块
synchronized(lock){
......
}
public class Status {
private int count = 0;
public int getNum(){
synchronized(this){
++count;
}
return count;
}
}
方法的执行过程为:
0: aload_0
1: dup
2: astore_1
3: monitorenter //获得对象锁
4: aload_0
5: dup
6: getfield #12; //Field count:I
9: iconst_1
10: iadd
11: putfield #12; //Field count:I
14: aload_1
15: monitorexit //释放对象锁
16: goto 22
19: aload_1
20: monitorexit
21: athrow
22: aload_0
23: getfield #12; //Field count:I
26: ireturn
可以看到比起正常的方法,增加了加锁和解锁的字节码指令(为什么会有个goto语句?)
每一个Java对象都可以用作一个实现同步的锁,称为内置锁,线程进入同步代码块之前自动获取到锁,代码块执行完成正常退出或代码块中抛出异常退出时会释放掉锁
内置锁为互斥锁,即线程A获取到锁后,线程B必须等待或阻塞直到线程A退出同步代码块,线程B才能获取到同一个锁,由于同一时刻只能有一个线程进入同步代码块,故同步代码块中的操作可以保证原子性
public class Status {
private int num = 0;
public final int getNum(){
return num;
}
public final void setNum(int num){
this.num = num;
}
}
public class Task implements Runnable {
private Status status = new Status();
public void run() {
synchronized (status) {
int num = status.getNum();
status.setNum(num + 1);
System.out.println(Thread.currentThread().getName()
+ "|" + status.getNum()
+ "|" + status.hashCode());
}
}
public static void main(String[] args) {
Task task = new Task();
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
Thread t3 = new Thread(task);
Thread t4 = new Thread(task);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
运行结果为:
Thread-0|1|22971385
Thread-2|2|22971385
Thread-3|3|22971385
Thread-1|4|22971385
修改一下上面的代码,每个线程运行时new一个新的Status对象,而不是像上面的代码,4个线程共用同一个Status对象:
public class Task implements Runnable {
private Status status;
public void run() {
status = new Status();
synchronized (status) {
int num = status.getNum();
status.setNum(num + 1);
System.out.println(Thread.currentThread().getName()
+ "|" + status.getNum()
+ "|" + status.hashCode());
}
}
......
由于充当锁的对象不一定是同一个对象(hashcode不同),同步失效:
Thread-0|1|27744459
Thread-3|1|28737396
Thread-1|2|28737396
Thread-2|1|6927154
因此同步代码块中充当锁的对象必须为同一个对象
public class Task implements Runnable {
private Status status;
public Task(Status status){
this.status = status;
}
public void run() {
synchronized (status) {
System.out.println("Thread lock");
System.out.println("Thread:" + status.getNum());
System.out.println("Thread over");
}
}
public static void main(String[] args) {
Status status = new Status();
Task task = new Task(status);
Thread t = new Thread(task);
t.start();
//synchronized(status){
System.out.println("Main");
status.setNum(1);
System.out.println("Main:" + status.getNum());
//}
}
}
运行结果为:
Main
Thread lock
Main:1
Thread:1
Thread over
从运行结果可以看出,在Thread线程锁定status对象的时候,Main线程在Thread线程释放锁对象前依然能够修改status对象的num域,说明锁没有生效
Main线程中没有对status对象进行同步,故在Thread线程锁定status对象的时候不需要等待或阻塞,可以直接操作status对象,因此所有使用同步对象的地方都必须进行同步
修改方式为:Task类的main方法中,在操作status对象时进行同步(去掉代码中的注释部分)
用synchronized关键字修饰的方法是一个横跨整个方法体的同步代码块,锁为方法所在的对象,如果该方法为静态方法,则锁为Class类,当然这里的锁也必须为同一个对象
特别需要注意的是所有访问状态变量的方法都必须进行同步:
public class Task implements Runnable {
private int count = 0;
public void run() {
for(int i = 0 ; i < 5 ; i++){
this.reset();
this.add();
}
}
public synchronized void reset(){
if(count == 5){
count = 0;
System.out.println(Thread.currentThread().getName()
+ "[count reset]");
}
}
public synchronized void add(){
count++;
System.out.println(Thread.currentThread().getName()
+ "[count:" + count + "]");
}
public static void main(String[] args){
Task t = new Task();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
t2.start();
}
}
运行结果:
Thread-0[count:1]
Thread-0[count:2]
Thread-0[count:3]
Thread-0[count:4]
Thread-1[count:5]
Thread-1[count reset]
Thread-1[count:1]
Thread-1[count:2]
Thread-1[count:3]
Thread-1[count:4]