public class T2 { public String str="abc"; static class R1 implements Runnable{ public String str; public Object o1; public Object o2; public R1(String str,Object ob1,Object ob2) { this.str=str; this.o1=ob1; this.o2=ob2; } @Override public void run() { if (str.contains("a")){ synchronized (o1){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o2){ System.out.println("aaa-bbb"); } } } if (str.contains("b")){ synchronized (o2){ try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o1){ System.out.println("bbb-aaa"); } } } } } public static void main(String[] args){ Object ob1=new Object(); Object ob2=new Object(); R1 r1=new R1("a",ob1,ob2); Thread thread1=new Thread(r1); thread1.start(); R1 r2=new R1("b",ob1,ob2); Thread thread2=new Thread(r2); thread2.start(); } } 复制代码
多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
ReentrantLock
使用重入锁(默认是非公平锁)创建公平锁:
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } final ReentrantLock lock = new ReentrantLock(); final ReentrantLock lock = new ReentrantLock(false); 复制代码
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁
ReentrantLock
而言, 他的名字就可以看出是一个可重入锁,其名字是 Re entrant Lock
重新进入锁。 Synchronized
而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。 synchronized void setA() throws Exception{ Thread.sleep(1000); setB(); } synchronized void setB() throws Exception{ Thread.sleep(1000); } 复制代码
上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁.
对于synchronized块来说,要么获取到锁执行,要么持续等待。而重入锁的中断响应功能就合理地避免了这样的情况。比如,一个正在等待获取锁的线程被“告知”无须继续等待下去,就可以停止工作了。
可以使用 tryLock()或者tryLock(long timeout, TimeUtil unit) 方法进行一次限时的锁等待。
public class TryLockTest implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); @Override public void run() { try { if (lock.tryLock(1, TimeUnit.SECONDS)) { // 等待1秒 Thread.sleep(2000); //休眠2秒 } else { System.err.println(Thread.currentThread().getName() + "获取锁失败!"); } } catch (Exception e) { if (lock.isHeldByCurrentThread()) lock.unlock(); } } public static void main(String[] args) throws InterruptedException { TryLockTest test = new TryLockTest(); Thread t1 = new Thread(test); t1.setName("线程1"); Thread t2 = new Thread(test); t1.setName("线程2"); t1.start();t2.start(); } } 复制代码
ReentrantLock
而言,其是独享锁。但是对于Lock的另一个实现类 ReadWriteLock
,其读锁是共享锁,其写锁是独享锁。 Synchronized
而言,是独享锁。 独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。
ReentrantLock ReadWriteLock
乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。
从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,==像乐观锁适用于写比较少的情况下(多读场景)==,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数。
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。
自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了 AtomicReference
类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用 AtomicReference类把多个共享变量合并成一个共享变量来操作。
分段锁其实是一种锁的设计,并不是具体的一种锁,对于 ConcurrentHashMap
而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作.
ConcurrentHashMap
来说一下分段锁的含义以及设计思想, ConcurrentHashMap
中的分段锁称为 Segment
,它即类似于 HashMap
(JDK7与JDK8中HashMap的实现)的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个 ReentrantLock
(Segment继承了ReentrantLock)。 这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
自旋锁的目的是为了占着CPU的资源不释放,等到获取到锁立即进行处理。但是如何去选择自旋的执行时间呢?如果自旋执行时间太长,会有大量的线程处于自旋状态占用CPU资源,进而会影响整体系统的性能。因此自旋的周期选的额外重要!
public class SpinLock { private AtomicReference<Thread> cas = new AtomicReference<Thread>(); public void lock() { Thread current = Thread.currentThread(); // 利用CAS while (!cas.compareAndSet(null, current)) { // DO nothing } } public void unlock() { Thread current = Thread.currentThread(); cas.compareAndSet(current, null); } } 复制代码
ock()方法利用的CAS,当第一个线程A获取锁的时候,能够成功获取到,不会进入while循环,如果此时线程A没有释放锁,另一个线程B又来获取锁,此时由于不满足CAS,所以就会进入while循环,不断判断是否满足CAS,直到A线程调用unlock方法释放了该锁。
上面代码是不支持重入的,它是不支持重入的,即当一个线程第一次已经获取到了该锁,在锁释放之前又一次重新获取该锁,第二次就不能成功获取到。由于不满足CAS,所以第二次获取会进入while循环等待,而如果是可重入锁,第二次也是应该能够成功获取到的
而且,即使第二次能够成功获取,那么当第一次释放锁的时候,第二次获取到的锁也会被释放,而这是不合理的。 为了实现可重入锁,我们需要引入一个计数器,用来记录获取锁的线程数。
ublic class ReentrantSpinLock { private AtomicReference<Thread> cas = new AtomicReference<Thread>(); private int count; public void lock() { Thread current = Thread.currentThread(); if (current == cas.get()) { // 如果当前线程已经获取到了锁,线程数增加一,然后返回 count++; return; } // 如果没获取到锁,则通过CAS自旋 while (!cas.compareAndSet(null, current)) { // DO nothing } } public void unlock() { Thread cur = Thread.currentThread(); if (cur == cas.get()) { if (count > 0) {// 如果大于0,表示当前线程多次获取了该锁,释放锁通过count减一来模拟 count--; } else {// 如果count==0,可以将锁释放,这样就能保证获取锁的次数与释放锁的次数是一致的了。 cas.compareAndSet(cur, null); } } } } 复制代码