多线程
synchronized
1、synchronized关键字
-
简介
解决多个线程之间访问资源的同步性。保证被他修饰的方法或者是代码块在任意时刻只能有一个线程执行。
1.6 之前依赖底层的操作系统的来实现的,java线程映射到操作系统的原生线程之上的。原生切换线程,需要从用户态转成内核态要花费挺长的时间。
1.6之后,JVM层进行优化,所得效率也优化了很多。关于锁的实现也引入了大量的优化,降低了锁操作的开销。
-
如何使用?项目什么地方用
-
三种使用方式:
修饰实例方法,作用于当前对象实例,加锁,进入同步代码前要获得当前对象实例的锁
修饰静态方法,作用域当前类对象加锁,计入同步代码前获得当前类对象的锁。
- 访问静态
synchronized
方法,占用的锁是类的锁, - 访问非静态
synchronized
方法占用个的锁是对象的锁, - 这两个不会互斥
- 修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的
-
2.原理
- 同步语句块的情况
public class SynchronizedDemo{
public void method(){
synchronized(this){
System.out.println("Ssynchroized 代码块");
}
}
}
javac SynchronizedDemo.java
产生SynchronizedDemo.class
javap -c -s -v- l SynchronizedDemo.class
public void method();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String Ssynchroized 代码块
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: monitorexit
14: goto 22
17: astore_2
18: aload_1
19: monitorexit
20: aload_2
21: athrow
22: return
- 注意上边的monitorenter 这个就是为了获取monitor,本来计数器是0 然后获取后就会变成1,后续的继要获取的时候就会获取失败,直到另一个线程释放为止。
- 注意上边的monitorexit 释放线程,计数器重新变成0 然后就可以有后续的操作
-
synchrronized修饰方法
public class SynchronizedDemo2{ public synchronized void method(){ System.out.println("synchronized 方法"); } }
操作步骤同上
{
public SynchronizedDemo2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public synchronized void method();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String synchronized 方法
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 3: 0
line 4: 8
}
见第15行,falges多个ACC_SUNCHRONIZED
3、概念
-
对象的锁,
-
如果
Synchronized
明确指定了锁对象,比如Synchronized
(变量名)、
Synchronized(this)
等,说明加解锁对象为该对象。 -
如果没有明确指定:
-
Synchronized
修饰的方法为非静态方法,表示此方法对应的对象为 锁对象 - 若
Synchronized
修饰的方法为静态方法,则表示此方法对应的类对象 为锁对象。
-
-
-
1.6的优化
- 自旋锁:把线程进行阻塞操作之前先让线程自旋等待一段时间
- 三种不同的
Monitor
实现:(也就是不同的Monitor
)- 偏向锁:没有竞争出现时,默认会使用偏向锁。也就是在对象头上的
MarkWord
部分设置线程ID,标示这个对象偏向于当前线程,这个时候没有互斥 - 轻量级锁:如果另一个线程试图锁定被偏斜的对象,
JVM
就撤销偏斜锁,切换到轻量级锁实现。 - 重量级锁:轻量级锁依赖
CAS
操作Mark
Word
来试图获取锁,如果重试成功,
就使用普通的轻量级锁;否则,进一步升级为重量级锁。
- 偏向锁:没有竞争出现时,默认会使用偏向锁。也就是在对象头上的
-
Synchronized 非公平锁:
获取锁的行为上,并不是先到先得,而是任何一个线程都有机会领到锁,所以叫非公平锁。
-
锁消除和锁粗化
- 锁消除:指虚拟机即时编译器在运行时,对一些代码上要求同步,但被
检测到不可能存在共享数据竞争的锁进行消除。 - 锁粗化:就是增大锁的作用域。原则上应该同步块范围尽量的小,但是如果对同一块频繁的由同一个对象频繁的加锁解锁,也是浪费资源。
- 锁消除:指虚拟机即时编译器在运行时,对一些代码上要求同步,但被
-
悲观锁/乐观锁/
CAS
- 悲观锁:并发策略是悲观的:不管是否会被竞争,所有的数据操作都要求被加锁,用户态核心态转换,维护锁计数器,检查是否有被阻塞线程需要被唤醒灯操作。
- 乐观锁:基于冲突检测的乐观并发策略。先进行操作,如没有其他线征用数据就没问题。如果产生了冲突,进行其他补偿措施,也叫做非阻塞同步。
- 乐观锁的核心算法是
CAS
:内存值、预期值、新值。当且仅当预期值和内存值相
等时才将内存值修改为新值。
-
乐观锁的优劣
- 只能保证一个共享变量的原子操作
- 长时间自旋可能导致开销大
-
ABA
问题
-
ReentrantLock
可重入锁/Synchronized
对比- 锁的实现原理基本是为了达到一个目的:让所有的线程都能看到某种标记。
-
Synchronized
:对象头中设置标记 -
ReentrantLock
/所有的基于 Lock 接口的实现类:用一个volitile
修饰的int
型变量,并保证每个线
程都能拥有对该int
的可见性和原子修改 -
ReentrantLock
有比synchorized
多的功能- 等待可中断:长期不释放锁的时候,正在等待的线程可以选择放弃等待
lock.lockInterruptibly()
- 带超时的获取锁尝试:指定的时间范围内获取锁
- 可以判断是否有线程在排队等待获取锁。
- 可以响应中断请求:与
Synchronized
不同,当获取到锁的线程被中 断时,能够响应中断,中断异常将会被抛出,同时锁会被释放。 - 可以实现公平锁。(ReentrantLock 既能够实现公平锁,也能够实现非公平锁,通过构造方里的参数来进行控制的)
- 在竞争不激烈时,
Synchronized
的性能要优于ReetrantLock
;在高竞争情况下,Synchronized
的性能会下降几十倍
- 等待可中断:长期不释放锁的时候,正在等待的线程可以选择放弃等待
4、ReentrantLock使用
- 普通使用
package com.synchronizedDemo;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockTest extends Thread {
public static ReentrantLock lock = new ReentrantLock();
public static int i = 0;
public ReentrantLockTest(String name) {
super.setName(name);
}
@Override
public void run() {
for (int j = 0; j < 1000000; j++) {
lock.lock();
try {
System.out.println(this.getName() + " " + i);
i++;
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockTest test1 = new ReentrantLockTest("thread1");
ReentrantLockTest test2 = new ReentrantLockTest("thread2");
test1.start();
test2.start();
test1.join();
test2.join();
System.out.println(i);
}
}
2、可中断
package com.synchronizedDemo;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.concurrent.locks.ReentrantLock;
public class LockInterrupt extends Thread{
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public LockInterrupt(int lock, String name) {
super(name);
this.lock = lock;
}
@Override
public void run() {
try {
if(lock ==1) {
lock1.lockInterruptibly();
Thread.sleep(500);
lock2.lockInterruptibly();
}else if (lock ==2) {
lock2.lockInterruptibly();
Thread.sleep(500);
lock1.lockInterruptibly();
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(lock1.isHeldByCurrentThread()) {
lock1.unlock();
}
if(lock2.isHeldByCurrentThread()) {
lock2.unlock();
}
System.out.println(Thread.currentThread().getId() + ":线程退出");
}
}
public static void main(String[] args) throws InterruptedException {
LockInterrupt t1 = new LockInterrupt(1, "LockInterrupt1");
LockInterrupt t2 = new LockInterrupt(2, "LockInterrupt2");
t1.start();
t2.start();
Thread.sleep(1000);
DeadlockChecker.check();
}
static class DeadlockChecker{
private final static ThreadMXBean mbean = ManagementFactory
.getThreadMXBean();
public static void check() {
Thread tt = new Thread(()->{
while(true) {
long[] deadlockedThreadIds = mbean.findDeadlockedThreads();
if (deadlockedThreadIds != null) {
ThreadInfo[] threadInfos = mbean.getThreadInfo(deadlockedThreadIds);
for (Thread t : Thread.getAllStackTraces().keySet()) {
for (int i = 0; i < threadInfos.length; i++) {
if (t.getId() == threadInfos[i].getThreadId()) {
System.out.println(t.getName());
t.interrupt();//中断
}
}
}
}
}
});
tt.setDaemon(true);
tt.start();
}
}
}
- 锁定的时候用
lock1.lockInterruptibly();
- 中断时候,找到这个线程,然后直接
t.interrupt();
3、超时中断(代码太多,和上边比较类似,就是锁定的时候显示)
lock.tryLock(5, TimeUnit.SECONDS)
4、公平锁,初始化锁的时候,
public static ReentrantLock fairLock = new ReentrantLock(true);
5、JUC/AQS
-
AQS
框架- 一个用来构建锁和同步器的框架
- 内部定义了一个
volatile int state
变量,表示同步状态;state =0
的时候表示没有线程占用,可以获得锁,同时将state=1
.如过state=1
表示其他锁正在用,加入同步队列 - 通过 Node 内部类构成的一个双向链表结构的同步队列