并发编程基础
Posted jianqiang111
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了并发编程基础相关的知识,希望对你有一定的参考价值。
- 修饰实例⽅法: 作⽤于当前对象实例加锁,进⼊同步代码前要获得当前对象实例的锁
1 /** 2 * synchronized 修饰实例⽅法 3 */ 4 public synchronized void increase() { 5 i++; 6 }
- 修饰静态⽅法: 也就是给当前类加锁,会作⽤于类的所有对象实例,因为静态成员不属于任何⼀个实例对象,是类成员( static 表明这是该类的⼀个静态资源,不管new了多少个对象,只有⼀份)。
1 public class Demo21 { 2 public static int i; 3 private synchronized static void method() { 4 System.out.println("我是类锁的第⼀种形式:static形式,我叫" + Thread.currentThread().getName()); 5 try { 6 Thread.sleep(1000); 7 for (int j = 0; j < 100000; j++) { 8 i++; 9 } 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 System.out.println(Thread.currentThread().getName() + "1=" + i); 14 } 15 }
- 修饰代码块: 指定加锁对象,对给定对象加锁,进⼊同步代码库前要获得给定对象的锁。
1 private void method2() { 2 synchronized (Demo21.class) { 3 // synchronized (this) { 4 System.out.println("我是类锁的第⼀种形式:static形式,我叫" + 5 Thread.currentThread().getName()); 6 try { 7 Thread.sleep(1000); 8 for (int j = 0; j < 100000; j++) { 9 i++; 10 } 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 System.out.println(Thread.currentThread().getName() + "1=" + i); 15 } 16 }
- 同步代码块的情况
⾸先切换到类的对应⽬录执⾏ javac XXX.java 命令⽣成编译后的.class ⽂件,然后执⾏ javap -c -s -v -l XXX.class。
synchronized 同步语句块的实现使⽤的是 monitorenter 和 monitorexit 指令,其中monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置。 当执⾏ monitorenter 指令时,线程试图获取锁也就是获取 monitor(monitor对象存在于 每个Java对象的对象头中,synchronized 锁便是通过这种⽅式获取锁的,也是为什么Java中任意对象可以作为锁的原因) 的持有权。 当计数器为0则可以成功获取,获取后将锁计数器设为1也就是加1。相应的在执⾏monitorexit 指令后,将锁计数器设为0,表明锁被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到锁被另外⼀个线程释放为⽌。
- 同步方法的情况
⾸先切换到类的对应⽬录执⾏ javac XXX.java 命令⽣成编译后的 .class ⽂件,然后执⾏ javap -c -s -v -l XXX.class。
synchronized 修饰的⽅法并没有 monitorenter 指令和 monitorexit 指令,取得代之的却是ACC_SYNCHRONIZED 标识,该标识指明了该⽅法是⼀个同步⽅法,JVM 通过该ACC_SYNCHRONIZED 访问标志来辨别⼀个⽅法是否声明为同步⽅法,从⽽执⾏相应的同
- 偏向锁
引⼊偏向锁的⽬的和引⼊轻量级锁的⽬的很像,他们都是为了没有多线程竞争的前提下,减少传统的重量级锁使⽤操作系统互斥量产⽣的性能消耗。但是不同是:轻量级锁在⽆竞争的情况下使⽤CAS 操作去代替使⽤互斥量。⽽偏向锁在⽆竞争的情况下会把整个同步都消除掉。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程ID,以后该线程在进入和退出同步块时不需要花费CAS操作来加锁和解锁,而只需简单的测试一下对象头的Mark Word里是否存储着指向当前线程的偏向锁,如果测试成功,表示线程已经获得了锁,如果测试失败,则需要再测试下Mark Word中偏向锁的标识是否设置成1(表示当前是偏向锁),如果没有设置,则使用CAS竞争锁,竞争成果后为轻量级锁如果设置了,则尝试使用CAS将对象头的偏向锁指向当前线程。
- 轻量级锁
- 自旋锁和自适应自旋
轻量级锁失败后,虚拟机为了避免线程真实地在操作系统层⾯挂起,还会进⾏⼀项称为⾃旋锁的优化⼿段。
互斥同步对性能最⼤的影响就是阻塞的实现,因为挂起线程/恢复线程的操作都需要转⼊内核态中完成(⽤户态转换到内核态会耗费时间)。
- 锁消除
锁消除理解起来很简单,它指的就是虚拟机即使编译器在运⾏时,如果检测到那些共享数据不可能存在竞争,那么就执⾏锁消除。锁消除可以节省毫⽆意义的请求锁的时间。
- 锁粗化
原则上,我们在编写代码的时候,总是推荐将同步块的作⽤范围限制得尽量⼩,——直在共享数据的实际作⽤域才进⾏同步,这样是为了使得需要同步的操作数量尽可能变⼩,如果存在锁竞争,那等待线程也能尽快拿到锁。
- 两者都是可重⼊锁
- synchronized 依赖于 JVM ⽽ ReenTrantLock 依赖于 API
- ReenTrantLock ⽐ synchronized 增加了⼀些⾼级功能
-
- ReenTrantLock提供了⼀种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
- ReenTrantLock可以指定是公平锁还是⾮公平锁。⽽synchronized只能是⾮公平锁。所谓的公平锁就是先等待的线程先获得锁。 ReenTrantLock默认情况是⾮公平的,可以通过ReenTrantLock类的 ReentrantLock(boolean fair) 构造⽅法来制定是否是公平的。
- synchronized关键字与wait()和notify/notifyAll()⽅法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接⼝与newCondition() ⽅法。Condition是JDK1.5之后才有的,它具有很好的灵活性,⽐如可以实现多路通知功能也就是在⼀个Lock对象中可以创建多个Condition实例(即对象监视器),线程对象可以注册在指定的Condition中,从⽽可以有选择性的进⾏线程通知,在调度线程上更加灵活。 在使⽤notify/notifyAll()⽅法进⾏通知时,被通知的线程是由 JVM 选择的,⽤ReentrantLock类结合Condition实例可以实现“选择性通知” ,这个功能⾮常重要,⽽且是Condition接⼝默认提供的。⽽synchronized关键字就相当于整个Lock对象中只有⼀个Condition实例,所有的线程都注册在它⼀个身上。如果执⾏notifyAll()⽅法的话就会通知所有处于等待状态的线程这样会造成很⼤的效率问题,⽽Condition实例的signalAll()⽅法 只会唤醒注册在该Condition实例中的所有等待线程。
不可变对象
- 所有成员变量必须是private
- 最好同时⽤final修饰(⾮必须)
- 不提供能够修改原有对象状态的⽅法
-
最常⻅的⽅式是不提供setter⽅法
-
如果提供修改⽅法,需要新创建⼀个对象,并在新创建的对象上进⾏修改
-
-
getter⽅法不能对外泄露this引⽤以及成员变量的引⽤
- java.util.Collections#unmodifiableCollection
- java.util.Collections#unmodifiableSet
- java.util.Collections#unmodifiableSortedSet
- java.util.Collections#unmodifiableNavigableSet
- java.util.Collections#unmodifiableList
- java.util.Collections#unmodifiableMap
- java.util.Collections#unmodifiableSortedMap
- java.util.Collections#unmodifiableNavigableMap
- 修饰类(禁⽌继承)
- 修饰⽅法(禁⽌⼦类覆盖,注意现在已经不需要因为效率把⽅法设置为final了)
- final修饰的变量称为常量
- final修饰的引⽤类型变量 是引⽤不可变,⾮对象不可变
线程不安全类 | 线程安全类 |
StringBuilder | StringBuffer |
SimpleDateFormat | JodaTime |
ArrayList | CopyOnWriteArrayList |
HashSet,TreeSet | CopyOnWriteArraySet,ConcurrentSkipListSet |
HashMap,TreeMap | ConcurrentHashMap,ConcurrentSkipListMap |
。。。 | 。。。 |
-
CopyOnWriteArrayList
实现原理:CopyOnWriterArrayList 允许并发的读,读操作是⽆锁的,性能较⾼。写操作的话,⽐如向容器增加⼀个元素,则⾸先将当前容器复制⼀份,然后在新副本上执⾏写操作,结束之后再将原容器的引⽤指向新容器。
以上是关于并发编程基础的主要内容,如果未能解决你的问题,请参考以下文章