对象锁:
/**
* 全局锁,对象锁的区别
* @author Z7M-SL7D2
*
*/
class Person{
private static int COUNT = 10;
// 同一时间只允许一个线程吃
public synchronized void eat() {
System.out.print("开始吃 ");
COUNT--;
System.out.print(" 吃东西-" + COUNT);
System.out.print(" 吃完了\n");
}
}
public class TestAll {
public static void main(String[] args) {
Thread thread0 = new Thread(()-> {
for(int i = 0; i < 3; i++) {
Person p = new Person();
p.eat();
}
});
Thread thread1 = new Thread(()-> {
for(int i = 0; i < 3; i++) {
Person p = new Person();
p.eat();
}
});
Thread thread2 = new Thread(()-> {
for(int i = 0; i < 3; i++) {
Person p = new Person();
p.eat();
}
});
thread0.start();
thread1.start();
thread2.start();
}
}
理想状态:
实际结果:出现了多个线程同时吃的情况
以上状态产生的原因是
synchronized对于非静态的同步方法。只是锁住了当前对象的访问权限。意思是使用synchronize同步非静态方法只能锁住一个对象。对于多个对象就没办法了。
要想产生理想的结果有以下两种方法:
1,将方法变为静态方法
2,使用同步代码块同步类(类锁)。
上述两种方法都是使用全局锁的方式。将方法变为静态方法同步的实际上也是将当前类设置为同步锁对对象,称为类锁。
使用方法1,对上述代码Person类的修改:
class Person{
private static int COUNT = 10;
// 同一时间只允许一个线程吃
public static synchronized void eat() {
System.out.print("开始吃 ");
COUNT--;
System.out.print(" 吃东西-" + COUNT);
System.out.print(" 吃完了");
System.out.println();
}
}
运行结果如下:
使用方法2对上述代码的修改:
class Person{
private static int COUNT = 10;
// 同一时间只允许一个线程吃
public void eat() {
synchronized (Person.class) {
System.out.print("开始吃 ");
COUNT--;
System.out.print(" 吃东西-" + COUNT);
System.out.print(" 吃完了");
System.out.println();
}
}
}
运行结果如下:
上述Person.class就是全局锁。
使用Person person对象作为锁的叫对象锁。
线程安全解决方案
synchronized,ReentrantLock,Atomic 使用场景描述
在实际开发过程中如果服务量,请求频繁,就会经常碰见并发,这时候不做处理就会出现很多非法数据。这时候就需要解决线程安全的问题,这时候就可以使用java当中的锁机制。常用有java关键synchronized、可重入锁ReentrantLock,还有并发包下的Atomic 或者Concurrent的安全类型。
synchronized使用场景:
在资源竞争不是很激烈的情况下,偶尔出现并发,需要同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronized,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。可以多对方法进行加锁(同步方法),也可以对对象进行加锁(同步代码快)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
代码可读性强,毕竟是java的关键字,执行优先级高。synchronized关键字一放,就解决线程安全的问题。
但是还有一个问题,当前资源竞争激烈时,对于部分线程迟迟获取不到锁,这时候会出现一个锁升级的过程,且锁升级的过程是不可逆的。当从轻量级锁到偏向锁,再到一个重量级锁。性能会大大的降低。
在资源竞争激烈可以使用其他方式来加锁。
ReentrantLock使用场景:
ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock还能保证正常的性能。
且这个锁可以定义成公平锁还可以定义成非公平锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
我这里以公平锁作为演示对象。ReentrantLock还可以查看锁的状态, 锁是否被锁上了.
可以查看当前有多少线程再等待锁。但是因为ReentrantLock是悲观锁,加锁时会对资源进行加锁,当读取频繁时性能会不如CAS的乐观锁。所以读取频繁使用乐观锁,写入频繁使用悲观锁。
Atomic或者Concurrent使用场景:
和上面的类似,不激烈情况下,性能比synchronized略逊,而激烈的时候,也能维持常态。激烈的时候,Atomic的性能会优于ReentrantLock一倍左右。但是其有一个缺点,就是只能同步一个值,一段代码中只能出现一个Atomic的变量,多于一个同步无效。因为他不能在多个Atomic之间同步。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
对于其他类型的比如和Map和Set可以使用用并发包下的ConcurrentHashMap和ConcurrentHashSet等线程安全的数据类型。
1 2 3 4 5 6 7 8 9 10 11 12 |
|
ConcurrentHashMap内部的实现是CAS的乐观锁,当锁无法取得会开始自旋,直到下一次取得锁。
参考文章: