马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
死锁
死锁是指两个或两个以上的线程在执行过程中,由于竞争同步锁而产生的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁。
死锁的案例 : 同步代码块的嵌套
创建锁对象:- public class Lock {
- public static final Lock lockA = new Lock();
- public static final Lock lockB = new Lock();
- }
复制代码 测试类:- public class DeadLockTest {
- public static void main(String[] args) {
- while(true){
- new Thread(new Runnable() {
- @Override
- public void run() {
- synchronized (Lock.lockA){
- System.out.println("getlockA...");
- synchronized (Lock.lockB){
- System.out.println("getlockB...");
- }
- }
- }
- }).start();
- new Thread(new Runnable() {
- @Override
- public void run() {
- synchronized (Lock.lockB){
- System.out.println("getlockB...");
- synchronized (Lock.lockA){
- System.out.println("getlockA...");
- }
- }
- }
- }).start();
- }
- }
- }
复制代码 生产者与消费者
创建2个线程,一个线程表示生产者,另一个线程表示消费者- import java.util.ArrayList;
- import java.util.List;
- public class Demo {
- public static void main(String[] args) {
- List<String> list = new ArrayList<String>();
- Object o = new Object();
- // 生产者
- new Thread(new Runnable() {
- @Override
- public void run() {
- while (true) {
- synchronized (o) {
- if (list.size() > 0) {
- try {
- o.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- list.add("aaaa");
- System.out.println(list);
- // 唤醒消费线程
- o.notify();
- }
- }
- }
- }).start();
- // 消费者
- new Thread(new Runnable() {
- @Override
- public void run() {
- while (true) {
- synchronized (o) {
- if (list.size() == 0) {
- try {
- o.wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- list.remove(0);
- System.out.println(list);
- o.notify();
- }
- }
- }
- }).start();
- }
- }
复制代码 线程方法sleep和wait的区别
- sleep()是Thread类静态方法,不需要对象锁。
- wait()方法是Object类的方法,被锁对象调用,而且只能出现在同步中。
- 执行sleep()方法的线程不会释放同步锁。
- 执行wait()方法的线程要释放同步锁,被唤醒后还需获取锁才能执行。
案例性能问题
wait()方法和notify()方法, 本地方法调用OS的功能,和操作系统交互,JVM找OS,把线程停止. 频繁等待与唤醒,导致JVM和OS交互的次数过多.
Condition接口
java.util.concurrent.locks.Condition 是一个接口类, 因此要使用其实现类创建对象
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象
以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)
其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用- // Condition常用方法:
- public void await() // 线程等待
- public void signal() // 唤醒一个等待的线程
- public void singalAll() // 唤醒所有等待的线程
- // 使用其实现类 ReentrantLock 的 newCondition方法获取 Condition
- public Condition newCondition()
复制代码- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- public class Demo {
- public static void main(String[] args) throws InterruptedException {
- // 创建Lock
- Lock l = new ReentrantLock();
- // 获取 Condition 对象
- Condition con = l.newCondition();
- new Thread(new Runnable() {
- @Override
- public void run() {
- l.lock();
- System.out.println("开始等待");
- try {
- con.await();
- } catch (InterruptedException e) {
- e.printStackTrace();
- } finally {
- l.unlock();
- }
- }
- }).start();
- Thread.sleep(2000);
- System.out.println("准备唤醒");
- l.lock();
- con.signal();
- l.unlock();
- }
- }
复制代码 Condition接口方法和Object类方法比较
- Condition可以和任意的Lock组合,也就是实现了线程的分组管理。
- 一个线程的案例中,可以使用多个Lock锁,每个Lock锁上可以结合Condition对象
- synchronized同步中做不到线程分组管理
- Object类wait()和notify()都要和操作系统交互,并通知CPU挂起线程,唤醒线程,效率低。
- Condition接口方法await()不和操作系统交互,而是让线程释放锁,并存放到线程队列容器中,当被signal()唤醒后,从队列中出来,从新获取锁后在执行。
- 因此使用Lock和Condition的效率比Object要快很多
生产者和消费者案例改进
- import java.util.ArrayList;
- import java.util.List;
- import java.util.concurrent.locks.Condition;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
- public class OverWriteWakeUpWaiting {
- public static void main(String[] args) {
- List<String> list = new ArrayList<>();
- Lock l = new ReentrantLock();
- // 对线程进行分组管理
- Condition con1 = l.newCondition(); // 生产线程, 对象监视器
- Condition con2 = l.newCondition(); // 消费线程, 对象监视器
- new Thread(new Runnable() {
- @Override
- public void run() {
- while (true) {
- l.lock();
- if (list.size() > 0) {
- try {
- con1.await();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- list.add("abc");
- System.out.println(list);
- con2.signal();
- l.unlock();
- }
- }
- }).start();
- new Thread(new Runnable() {
- @Override
- public void run() {
- while (true) {
- l.lock();
- if (list.size() == 0) {
- try {
- con2.await();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- list.remove(0);
- System.out.println(list);
- con1.signal();
- l.unlock();
- }
- }
- }).start();
- }
- }
复制代码 java并发编程的三大特性
原子性
原子性,即一个操作或多个操作,要么全部执行并且在执行的过程中不被打断,要么全部不执行
下面具有原子性的操作有?- x = 1;
- // x = 1,是一个单纯的赋值操作,满足原子性。
- y=x;
- // 实际是两个操作,分别是 读取x变量 ,将x赋值给y,这两个操作分别来看都是原子性的,但是合起来就不是了
- x++;
- // 实际是三个操作 ,先读取变量 ,在进行+1操作 ,再赋值给x,不满足原子性
- x=x+1;
- // 同上,不满足原子性
复制代码 JAVA提供了原子性的技术保障有如下:
1、synchronized (互斥锁)
2、Lock(互斥锁)
3、原子类(CAS)
synchronized 和 Lock 都是通过互斥锁实现,即同一时刻只允许一个线程操作该变量,保障了原子性
原子类AtomicInteger
- /*
- java.util.concurrent.atomic.AtomicInteger
- 构造方法
- public AtomicInteger()创建具有初始值 0 的新 AtomicInteger。
- public AtomicInteger(int initialValue) 创建具有给定初始值的新 AtomicInteger。
- 方法
- int incrementAndGet() 以原子方式将当前值加 1。
- int getAndIncrement() 以原子方式将当前值加 1。
- int decrementAndGet() 以原子方式将当前值减 1。
- int getAndIncrement() 以原子方式将当前值减 1。
- int getAndAdd(int delta) 以原子方式将给定值与当前值相加。
- int addAndGet(int delta) 以原子方式将给定值与当前值相加。
- int get() 获取当前值。
- */
- public class Test02 {
- public static void main(String[] args) {
- AtomicInteger ai = new AtomicInteger(1);
- ai.incrementAndGet(); //++ai 2
- ai.getAndIncrement(); //ai++ 3
- System.out.println(ai.get());// 3
- System.out.println(ai.getAndIncrement()); // 3
- System.out.println(ai.get()); // 4
- System.out.println(ai.incrementAndGet()); // 5
- System.out.println(ai.get()); //5
- }
- }
复制代码 CAS无锁机制
CAS是英文单词Compare And Swap的缩写,翻译过来就是比较并替换。当多条线程尝试使用CAS同时更新同一个变量时,只有其中一条线程能更新变量的值,而其他线程都失败,失败的线程并不会被挂起,而是告知这次竞争失败,并可以再次尝试.
CAS的缺点:
CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。
- 循环时间长开销很大。
- CAS 通常是配合无限循环一起使用的,如果 CAS 失败,会一直进行尝试。如果 CAS 长时间一直不成功,可能会给 CPU 带来很大的开销。
复制代码 - 只能保证一个变量的原子操作。
- 当对一个变量执行操作时,我们可以使用循环 CAS 的方式来保证原子操作,但是对多个变量操作时,CAS 目前无法直接保证操作的原子性。
复制代码 - ABA问题。
- 第一条线程获取到V位置的值 假设是 1
- 第二条线程获取到V位置的值 也是1
- 第一条线程cas成功 将值改为 0
- 第一条线程又cas成功 将值改回 1
- 这时第二条线程cas 发现值没变 还是1 cas成功
- 实际上当第二条线程cas时 V位置的值已经从 1-0-1
- 这就是ABA问题
- 如何解决 每次获取V位置的值时,带上一个版本号.这样就可以避免ABA问题 java中AtomicStampedReference这个类在cas时就是通过版本号来解决的
复制代码 可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程应该能够立即看得到修改的值- public class Test {
- public static boolean flag = true;
- public static void main(String[] args) throws InterruptedException {
- new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println("1号线程启动....执行while循环");
- long num = 0;
- while(flag){
- num++;
- }
- System.out.println(num);
- }
- }).start();
- Thread.sleep(2000);
- new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println("2号线程启动....修改flag的值为false,停止循环");
- flag = false;
- }
- }).start();
- }
- }
复制代码通过如上案例 发现修改flag 的值并没有使循环结束
1.加锁,比如使用synchronized.- JMM关于synchronized的两条规定:
- 1)线程解锁前,必须把共享变量的最新值刷新到主内存中
- 2)线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新获取最新的值
复制代码- public class Test {
- //使用同步方法获取flag的值
- public static synchronized boolean getFlag(){
- return flag;
- }
- public static boolean flag = true;
- public static void main(String[] args) throws InterruptedException {
- new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println("1号线程启动....执行while循环");
- long num = 0;
- /*
- 线程调用getFlag方法时 先获取锁 也就是加锁
- 这时会先清空本地内存中共享副本的值,那么在使用值就需要从
- 主内存中重新获取 ,线程释放锁时,也就是解锁,会把共享变量flag
- 的值重新更新到主内存中
- */
- while(getFlag()){
- num++;
- }
- System.out.println(num);
- }
- }).start();
- Thread.sleep(2000);
- new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println("2号线程启动....修改flag的值为false,停止循环");
- flag = false;
- }
- }).start();
- }
- }
复制代码 2.使用volatile关键字保证可见性- public class Test {
- public static volatile boolean flag = true;
- public static void main(String[] args) throws InterruptedException {
- new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println("1号线程启动....执行while循环");
- long num = 0;
- while(flag){
- num++;
- }
- System.out.println(num);
- }
- }).start();
- Thread.sleep(2000);
- new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println("2号线程启动....修改flag的值为false,停止循环");
- flag = false;
- }
- }).start();
- }
- }
复制代码 volatile缓存可见性实现原理
底层实现主要是通过汇编lock前缀指令,会锁住这块区域的缓存,并写回主内存.
1.会将当前处理器缓存的行数据立即写回系统内存
2.这个写回内存的操作导致CPU的缓存该内存地址的数值失效(MESI协议)
volatile只能保证可见性,但是不能保证原子性,如果要保证原子性,请使用锁
有序性
一般来说,程序的执行顺序按照代码的先后顺序执行.但是处理器为了提高程序的效率,可能会对代码的执行顺序进行优化,它不保证程序中各个语句的执行先后顺序一致,但是保证程序的最终结果和代码顺序执行的结果一致.- int a = 10; //语句1
- int b = 20; //语句2
- int c = 20; //语句3
- c= a + b; //语句4
复制代码 CPU可能会对没有依赖关系的语句进行重排,比如 2134,3124 但是不会对有依赖关系的数据进行重排比如 3和4 改为4和3 这样就会对结果造成影响.这种重排对单线程是没有任何影响的,但是如果是多线程就可能会出现问题.
验证CPU是否会进行指令重排:- public class Test {
- public static void main(String[] args) throws InterruptedException {
- for (int i = 0; i < 500000; i++) {
- Test.State state = new Test.State();
- ThreadA t1 = new ThreadA(state);
- ThreadB t2 = new ThreadB(state);
- t1.start();
- t2.start();
- }
- }
- static class ThreadA extends Thread{
- private final Test.State state;
- ThreadA(Test.State state){
- this.state =state;
- }
- public void run(){
- state.a=1;
- state.b=1;
- state.c=1;
- state.d=1;
- }
- }
- static class ThreadB extends Thread{
- private final Test.State state;
- ThreadB(Test.State state){
- this.state =state;
- }
- public void run(){
- if( state.b== 1 && state.a ==0){
- System.out.println("b= " + state.b);
- }
- if(state.c == 1 &&(state.b==0|| state.a ==0)){
- System.out.println("c = " + state.c);
- }
- if(state.d==1 &&(state.a==0||state.b==0||state.c==0)){
- System.out.println("d " + state.d);
- }
- }
- }
- static class State{
- int a = 0;
- int b = 0;
- int c = 0;
- int d = 0;
- }
- }
- /*
- c = 1
- 说明,CPU进行了重排,让c在b或者a前面进行了赋值.
- 改变顺序可能导致执行结果不同,因此需要禁止重排序。
- */
复制代码 使用volatile关键字后 就不会出现刚才的情况- static class State{
- volatile int a = 0;
- volatile int b = 0;
- volatile int c = 0;
- volatile int d = 0;
- }
复制代码 由此可见:volatile关键字有两个作用1.保证可见性.2禁止重排序
单例设计模式
设计模式 : 不是技术,是以前的人开发人员,为了解决某些问题实现的写代码的经验.
Java的设计模式有23种,分为3个类别,创建型,行为型,功能型
单例代表单个实例,保证一个类的对象永远只有一个!
饿汉式
优点: 简单 多线程下没有任何问题
缺点:
- 当类加载时 对象就会被直接创建
- 若不被使用 对象就白创建了
- public class danliDemo1 {
- public static void main(String[] args) {
- for (int i = 0; i < 100; i++) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println(Single1.getInstance());
- }
- }).start();
- }
- }
- }
- class Single1 {
- private static Single1 s = new Single1();
- public Single1() {
- }
- public static Single1 getInstance(){
- return s;
- }
- }
复制代码 懒汉式
优点: 延迟加载 什么时候调用方法 什么时候创建对象
缺点: 多线程时 代码有问题- public class danliDemo2 {
- public static void main(String[] args) {
- for (int i = 0; i < 100; i++) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- System.out.println(Single2.getInstance());
- }
- }).start();
- }
- }
- }
- class Single2 {
- private static Single2 s;
- public Single2() { }
- public static Single2 getInstance(){
- if (s == null) {
- s = new Single2();
- }
- return s;
- }
- }
复制代码 安全问题
一个线程判断完变量 s=null,还没有执行new对象,被另一个线程抢到CPU资源,同时有2个线程都进行判断变量,对象创建多次
性能问题
第一个线程获取锁,创建对象,返回对象. 第二个线程调用方法的时候,变量s已经有对象了,根本就不需要在进同步,不要在判断空,直接return才是最高效的.
双重的if判断,提高效率 Double Check Lock(DCL)
DCL双检查锁机制单例,效率高,线程安全,多线程操作原子性。- class Single2 {
- private static Single2 s;
- public Single2() { }
- public static Single2 getInstance(){
- if (s == null) {
- synchronized (Single2DCL.class) {
- if (s == null) {
- s = new Single2DCL();
- }
- }
- }
- return s;
- }
- }
复制代码 面试题
DCL单例是否需要使用volatile关键字?
需要,单例的模式, 不使用volatile关键字,可能线程会拿到一个尚未初始化完成的对象(半初始化)
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |