线程同步是为了防止多个线程访问同一个数据对象,对数据造成破坏
一、线程的并行执行造成的问题:
public class FanXing { private int i = 0; public static void main(String[] args) throws InterruptedException { FanXing fanXing = new FanXing(); int k = 0; Thread t1 = new Thread(){ @Override public void run() { for (int i=0;i<100000;i++){ fanXing.f(); } } }; Thread t2 = new Thread(){ @Override public void run() { for (int i=0;i<100000;i++){ fanXing.f(); } } }; t1.start(); t2.start(); Thread.sleep(5000); System.out.println("最后的结果是:"+fanXing.getI()); } public int getI() { return i; } public void f(){ i++; } }
上面代码有两个线程同时对一个对象属性做自增操作,但最后得到的结果总是小于200000
这是问什么呢,因为自增操作不是原子性的,分为三个操作,取值,自增,赋值。试想一下如果第一个线程取到值为100并执行到自增这一步,另外一个线程取值并自增完成操作(目前值为101),而第一个线程在自增赋值,写回101,而第二个线程也将101写回内存。可见,数据已经出错了。
二、线程的同步与对象锁
Java中每个对象都有一个内置锁。当程序运行非静态的synchronized方法时,自动获取与正在执行的类的对象锁。也称为获得对象锁。
一个对象只有一个锁。所以一个线程获得这个线程锁时,其他线程要想获得锁,就必须等待获得锁的线程释放该锁。
关于锁:
1、只能同步方法,而不能同步变量或者类
2、同步方法时,判断该方法是否需要同步,不需要同步的方法不必加锁,因为锁也是有开销的
3、如果一个线程获得这个对象的对象锁时,其他线程只能执行该对象的非同步方法
4、线程可以获得多个锁,比如在一个同步方法中调用另一个对象的同步方法
5、线程睡眠时,不会释放所持有的对象锁
对于上述代码,我们可以对方法加锁,这样可以使其同步,得到正确的结果
public synchronized void f(){ i++; }
只需要在方法上加synchronized关键字即可
这样便得到了正确的结果
三、静态方法的同步
因为静态方法属于类本身,而不是属于一个对象(虽然你可以用对象去调用它),对于静态方法,你需要锁住这个类,也就这个类的Class对象
public static void f(){ i++; }
所以对于静态方法,我们需要对该类进行加锁,也就是在synchronized关键字中锁住这个类的Class对象,像这样
public static void f(){ synchronized (FanXing.class) { i++; } }
四、线程同步小结
1、线程同步就是为了避免多个线程对同一个资源的访问而可能造成的数据破坏
2、线程同步是根据对象锁实现的,每个对象只有一个锁,所以当一个线程得到了该对象的锁,其他线程想访问该对象的同步方法时,只能等待获得锁的线程对该锁的释放
3、对于非静态方法,只要锁住对象即可,而对于静态方法,则需要锁住该类
4、当多个线程等待一个对象锁时,没有获得到锁的对象将阻塞