线程安全问题的解决
解决方式一(同步代码块)
Object o = new Object();
synchronized (o){//方式二:synchronized (this) { 方式三:synchronized (当前类名.class) {
//需要被同步的代码(操作共享数据的代码)
}
说明:
1.通过synchronized(锁)来实现线程同步
2.任何一个类的对象都可以充当锁(可以是this,也可以是当前类的对象),在实现Runnable接口创建多线程的方式中,我们可以考虑使用this充当锁。
3.多个线程必须要共用同一把锁。
4.锁的原理:当有线程进入同步代码块之后,利用JVM的计数器会将锁的标记置为1,当别的线程再想进入的时候,发现锁的标记为1, 该线程就会去锁池等待,当第一个线程出来之后,锁的标记会置为0,之后CPU会随机分配一个线程再次进入同步代码块。
解决方式二(同步方法)
//方式一
public synchronized void synchronizedTest(){
//需要被同步的代码(操作共享数据的代码)
}
//方式二
public static synchronized void synchronizedTest1(){
//需要被同步的代码(操作共享数据的代码)
}
说明:
1.将操作共享数据的代码完整的声明在一个含synchronized关键字的方法中来实现线程同步
2.依旧涉及到锁,但是不需要我们显式的声明锁
3.两种同步方法的锁:
非静态的同步方法:this
静态的同步方法:当前类本身
解决方式二(Lock锁)
Lock l = new ReentrantLock();
l.lock();//手动启动锁
//存在线程安全问题的代码
l.unlock();//解锁
说明:
1.在需要被同步的代码前手动创建Lock的子类对象启动lock锁,在其后手动解除锁unlock,使没拿到锁的线程等待,但是这种很容易出现死锁。注意加锁以及解锁的顺序,就可以避免死锁。
对比ReentrantLock和Synchronized
一、代码
采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用,更安全
而ReentrantLock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。需要lock()和unlock()方法配合try-finally语句块来完成
二、灵活性
Synchronized:锁的范围是整个方法或synchronized块部分
ReentrantLock:Lock因为是方法调用,可以跨方法,灵活性更大
三、等待是否可以被中断
Synchronized:不可中断,除非抛出异常
释放锁方式:
1.代码执行完,正常释放锁。
2.抛出异常,由JVM退出等待。
ReentrantLock:持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待。
方法:
1.设置超时方法 tryLock(long timeout,TimeUnit unit),时间过了就放弃等待。
2.locklnterruptibly()放代码块中,调用interrupt()方法可中断,而synchronized不行。
三、是否是公平锁
Synchronized:非公平锁
ReentrantLock:默认公平锁,构造器可以传入boolean值,true为公平锁,false为非公平
四、适用情况
Synchronized:资源竞争不是很激烈的情况下,偶尔会有同步的情形下, synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,可读性也非常好
ReentrantLock:ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步等。在资源竞争不激烈的情形下,性能肖微比synchronized差一点点。但是当同步非常激烈的时候,synchronized的性能会下降。而ReentrantLock还能维持常态。