多线程锁
乐观锁
无锁编程,更新数据只会判断有没有其他线程更改了这个数据。适合读操作多的场景
悲观锁
适合写操作多的场景,先加锁保证写操作时数据正确
总结
-
一个类中方法被synchronized修饰,整个资源被加上了悲观锁,其他被synchronized修饰的方法也被锁住。多个线程访问同个资源对象的不同方法,不能同时进行,需要第一个线程释放完锁,其他线程才能依次访问,没有被synchronized修饰的,没有加锁可以访问。
-
多个线程访问实例不同资源对象的方法,互不影响,悲观锁只锁定同资源对象。
-
都换成static synchronized修饰锁定的时class模板,无论多少实例对象,都来自一个模板,静态方法都会被锁住,即多线程不能同时访问多个对资源的静态方法, 而没有static修饰的锁的只是实例对象。
-
静态方法锁,类锁;普通方法锁,对象锁;能用对象锁不要用类锁;
-
synchronized可以对代码块枷锁,锁只对括号内代码有效
synchronized底层实现
-
同步代码块
javap -c 进行反编译代码
Object object=new Object(); public void m1(){ synchronized (object){ System.out.println("-----hello synchronized code block"); } }
public void m1(); Code: 0: aload_0 1: getfield #3 // Field object:Ljava/lang/Object; 4: dup 5: astore_1 6: monitorenter 7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 10: ldc #5 // String -----hello synchronized code block 12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 15: aload_1 16: monitorexit 17: goto 25 20: astore_2 21: aload_1 22: monitorexit 23: aload_2 24: athrow 25: return
通过monitorenter和monitorexit进行加锁和退出锁,加锁和退出锁1对2,为了出了异常也能退出锁
-
普通同步方法
javap -v 进行反编译代码 输出附加信息
public synchronized void m2(){ System.out.println("-----hello synchronized code block"); }
public synchronized void m2(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=2, locals=1, args_size=1 0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #5 // String -----hello synchronized code block 5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return
调用指令减去检查方法的ACC_SYNCHRONIZED访问标识是否被设置
如何设置了,执行线程会将先持有monitor锁,然后在执行方法,最后在方法完成时是否monitor
-
静态同步方法
public static synchronized void m3(){ System.out.println("-----hello synchronized code block"); }
public static synchronized void m3(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED Code: stack=2, locals=0, args_size=0 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String -----hello synchronized code block 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return
通过 ACC_STATIC, ACC_SYNCHRONIZED来判断是否是静态同步方法(类锁)
公平锁和非公平锁
Lock lock = new ReentrantLock(true); 表示公平锁,先到先得
Lock lock = new ReentrantLock(false);表示非公平锁,后来的也可能获得锁,默认是非公平锁
非公平锁更能充分利用cpu的时间片,尽量减少cpu空闲状态时间,减少线程的切换
可重入锁(递归锁)
ReentrantLock和Synchronized都是可重入锁,指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁叫可重入锁。
一个线程中的多个流程可以获得同一把锁,持有这把同步锁可以再次进入,自己可以获得自己的内部锁。
static Lock lock= new ReentrantLock();
定义个静态锁
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"\t"+"come in 外层");
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t"+"come in 内层");
lock.lock();
}finally {
lock.unlock();
}
}finally {
lock.unlock();
}
},"t1").start();
输出:
t1 come in 外层
t1 come in 内层
lock()和unlock()要配对,否则会导致其他线程一直等待拿不到锁,造成死锁
死锁及排查
死锁主要原因
- 系统资源不足
- 进程运行推进的顺序不合适
- 资源分配不当
排查方法
final Object objectA=new Object();
final Object objectB=new Object();
new Thread(()->{
synchronized (objectA){
System.out.println(Thread.currentThread().getName()+"\t 自己持有A锁,希望获得B锁");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objectB){
System.out.println(Thread.currentThread().getName()+"\t 成功获得B锁");
}
}
},"A").start();
new Thread(()->{
synchronized (objectB){
System.out.println(Thread.currentThread().getName()+"\t 自己持有B锁,希望获得A锁");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (objectA){
System.out.println(Thread.currentThread().getName()+"\t 成功获得A锁");
}
}
},"B").start();
输出:
A 自己持有A锁,希望获得B锁
B 自己持有B锁,希望获得A锁
相互等待,互相持有锁
使用命令
-
jps -l 查看进程 ,jstack 进程编号
-
jconsole 可以打开图形化页面