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的使用率更高。html
ReentrantLock
而言,经过构造函数指定该锁是不是公平锁,默认是非公平锁。非公平锁的优势在于吞吐量比公平锁大。 对于Synchronized而言,也是一种非公平锁。因为其并不像ReentrantLock是经过AQS的来实现线程调度,因此并无任何办法使其变成公平锁。使用重入锁(默认是非公平锁)建立公平锁:java
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
final ReentrantLock lock = new ReentrantLock();
final ReentrantLock lock = new ReentrantLock(false);
复制代码
可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁android
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);
}
}
}
}
复制代码