通常情况下,一个进程中比较耗时的操作有文件上传下载,长循环,网络资源的获取,这个时候往往会采用多线程的方式来处理。
当进程中有多个并发线程在执行,并且需要处理一个数据代码块是,往往需要考虑线程安全问题,如果线程不安全的话,可能造成数据不一致的问题。
第一种实现线程安全的方式:同步代码块,即用synchronized关键字
第二种方法:同步方法,也是用synchronized关键字,只是这个关键字用在方法上了,把线程共享的数据块抽象成方法,在方法上加了同步锁。
第三种方法:使用Lock锁机制,对线程不安全的代码块采用lock()加锁,使用unlock()解锁。
总结:
由于synchronized是在JVM层面实现的,因此系统可以监控锁的释放与否;而ReentrantLock是使用代码实现的,系统无法自动释放锁,需要在代码中的finally子句中显式释放锁lock.unlock()。
另外,在并发量比较小的情况下,使用synchronized是个不错的选择;但是在并发量比较高的情况下,其性能下降会很严重,此时ReentrantLock是个不错的方案。
在使用synchronized 代码块时,可以与wait()、notify()、nitifyAll()一起使用,从而进一步实现线程的通信。
其中,wait()方法会释放占有的对象锁,当前线程进入等待池,释放cpu,而其他正在等待的线程即可抢占此锁,获得锁的线程即可运行程序;线程的sleep()方法则表示,当前线程会休眠一段时间,休眠期间,会暂时释放cpu,但并不释放对象锁,也就是说,在休眠期间,其他线程依然无法进入被同步保护的代码内部,当前线程休眠结束时,会重新获得cpu执行权,从而执行被同步保护的代码。
wait()和sleep()最大的不同在于wait()会释放对象锁,而sleep()不会释放对象锁。
notify()方法会唤醒因为调用对象的wait()而处于等待状态的线程,从而使得该线程有机会获取对象锁。调用notify()后,当前线程并不会立即释放锁,而是继续执行当前代码,直到synchronized中的代码全部执行完毕,才会释放对象锁。JVM会在等待的线程中调度一个线程去获得对象锁,执行代码。
需要注意的是,wait()和notify()必须在synchronized代码块中调用。
notifyAll()是唤醒所有等待的线程。