当多个线程访问同一个资源时,很有可能会出现线程安全的问题。比如,多个线程对一个数据进行修改时,会导致某着线程对数据的修改丢失。而同步机制就是为了解决这种问题。
JAVA中,有三种实现同步机制的方法:
1、synchronized关键字:JAVA中,每个对象都有一个对象锁预知相关联,该锁表明对象在任何时候只允许被一个线程拥有,当一个线程调用对象的一段synchronized代码时,需要先获取这个锁,然后去执行相应的代码,执行结束后,释放锁。因为锁是与对象(同步监视器)相联系的,获取哪个对象的锁,就表明在释放锁之前,这个对象只能被一个线程操作。synchronized关键字的两种用法(synchronized块和synchronized方法):a、synchronized方法(同步方法)。在方法的声明之前加synchronized关键字。无需显式指定同步监视器,方法的同步监视器是this,就是对象本身。即这个方法是在哪个对象里,在执行方法时,这个监视器就是这个对象。b、synchronized块(同步代码块),这种方法需要显示的指定同步监视器,即我需要对那个对象进行监视,哪个对象可能被多个线程同时访问或修改,我就把它监视起来,保证任何时候这个对象只能被一个线程拥有。语法格式:synchronized(需要被监视的对象){同步代码块 }
2、Lock:Java5新增加了一种更加强大的线程同步机制—通过显式定义同步锁对象来实现同步,这种机制下,同步锁使用Lock对象来进行充当。Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock实现允许更灵活的结构,可以具有差别很大的属性,并提供多个相关的Condition对象。Lock是控制多个线程对共享资源进行访问的工具。而Lock是一个借口,我们一般比较常用的是ReentrantLock(可重入锁),使用ReentrantLock的代码格式如下:
public class X {
private final ReentrantLock lock=new ReentrantLock();
//需要保证线程安全的方法
public void m(){
lock.lock();//加锁
try{
//需要保证线程安全的代码
}
finally{ //使用finally来保证释放锁
lock.unlock();
}
}
}
其中一些方法用于实现多线程的同步:lock():以阻塞的方式获取锁,即如果获取到了锁,立即返回;如果别的线程持有锁,当前线程等待,直到获取锁后返回。 tryLock():以非阻塞的方式获取锁,只是尝试性的去获取一下锁,成功返回true,否则返回false。tryLock(long timeout,TimeUnit unit)。如果获取了锁,返回true,否则会等待参数给定的时间单元,在等待的过程中,如果获取了锁,立即返回,如果等待超时,返回false。 lockInterruptibly():如果获取了锁,立即返回,如果没有获取锁,当前线程处于休眠状态,直到获得锁,或者当前线程被别的线程中断。该方法与lock的区别是lock获取不到会一直处于阻塞状态。
3、wait()方法和notify()方法:当使用synchronized来修饰某个共享资源时,如果线程A在执行synchronized代码,另外另一个线程B也要同时执行同一对象的synchronized代码时,线程B将要等到A执行完成后才能继续执行。这种情况下可以使用wait方法和notify方法。在synchronized代码被执行期间,线程可以调用对象的wait方法,释放对象锁,进入等待状态,并且可以调用notify ()方法或notifyAll()方法通知其他正在等待的线程。notify仅唤醒等待队列的第一个线程,并允许它获得锁,notifyAll方法唤醒所有等待这个对象的线程并允许它们去竞争获得锁。wait()、notify()、notifyAll()这三个方法不属于Thread类,而是属于Object类,但是这三个方法必须由同步监视器对象来调用,这可以分为两种情况:a、对于synchronized修饰的同步方法、因为该类的默认实例this就是同步监视器,所以可以在同步方法中直接调用这三个方法。b、对于synchronized修饰的同步代码块,同步监视器是synchronized后括号的对象,所以必须使用该对象调用这三个方法。