同步
在实际中,我们可能遇到这样一个问题。假设线程 thread1 和 thread2 都对一个变量 i 进行操作,线程 thread1 每次让 i 加一,线程 thread2 每次让 i 减一。假设:
- t1 时刻,thread1 读取 i 的数据为100;
- t2 时刻(thread1 还没来得及加一),thread2 也读到了修改之前的数据 i,其值为100;
- t3 时刻,thread1 执行 i 加一,将101写回到 i;
- t4 时刻,由于之前thread2读到的数据为100,因此减一之后将99写回到 i。这个99被称为脏数据。
这就是典型的同步问题,那么怎么解决呢?在thread1 读取 i 的时候,应该让这个线程对数据 i 上锁,使得其他线程无法读取变量 i。在thread1 运行结束之后,这个同步锁释放掉,其他进程才可以对 i 进行访问。
所以,Java中通过synchronized关键字,实现了同步的概念。
synchronized关键字
看一段 synchronized 关键字的用法:
Object obj =new Object();
synchronized (obj){
//这里的内容必须要得到这个obj对象后才能运行
do_something();
}
这里的 obj 就相当于前面说的互斥锁,哪个进程得到这个对象,就可以独占这个锁,然后尽情的执行synchronized里面的代码;而其他进程需要等待这个obj被释放掉,才可以去抢这个锁。
除了将Object类的对象作为锁之外,所有继承Object的类的对象都可以当作同步锁。
synchronized方法
在一个类中,我们可以把所有方法都定义为synchronized方法,这样的类称为线程安全类。同一时间,只有一个进程可以进入这个类的一个实例进行数据的修改,以保证这个实例中不出现脏读。
看下面一段代码:
public static String now(){
return new SimpleDateFormat("HH:mm:ss").format(new Date());
}
public static class Person {
String name;
int hp;
public synchronized void addHp(Thread t) {
System.out.println(now() + t.getName() + "占有对象obj");
this.hp++;
System.out.println("现在的hp为: " + this.hp);
System.out.println(now() + t.getName() + "释放对象obj");
}
public synchronized void reduceHp(Thread t) {
System.out.println(now() + t.getName() + "占有对象obj");
this.hp--;
System.out.println("现在的hp为: " + this.hp);
System.out.println(now() + t.getName() + "释放对象obj");
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
//final Object obj = new Object();
Person tom = new Person();
tom.name = "tom";
tom.hp = 100;
Thread t1 = new Thread() {
public void run() {
System.out.println(now() + this.getName() + "线程开始运行");
System.out.println(now() + this.getName() + "线程试图占用obj");
tom.addHp(this);
}
};
t1.setName("t1");
t1.start();
Thread t2 = new Thread() {
public void run() {
System.out.println(now() + this.getName() + "线程开始运行");
System.out.println(now() + this.getName() + "线程试图占用obj");
tom.reduceHp(this);
}
};
t2.setName("t2");
t2.start();
}
在Person类中,我们将所有的方法都定义为 synchronized 方法,所以这是一个线程安全类。对于每一个Person类的实例对象,虽然没有显式的写出来,但是这个同步锁相当于它自身,即this。
这样就好理解了,对于一个实例化的对象,如果有多个线程企图修改他的数据(调用他的方法),那么只能有一个线程在某个时刻可以得到这个对象(this,锁),并执行其代码。等到这个线程结束或抛出异常,其他线程才能去抢占这个锁(this)。