Java并发编面试题

57 阅读3分钟

addAll比addAll2方法更优,因为在多线程的环境中addAll你只能拿到0或是最终结果,而addAll2可能会取到for循环的中间值。

不要在for循环等多次调用的场景去赋值成员变量,使用临时变量来赋值最终结果。临时变量的存取速度要比成员变量要快。

  1. 第一种写法不会出现读到中间状态的脏数据
  2. 尽量使用小范围的变量
  3. 尽量少暴露计算过程的中间状态(避免使用成员变量缓存,如idea成员变量使用了红色)
private int reuslt;

public void addAll(List<Integer> list) {
    int sum = 0;
    for (int s : list) {
        sum += s;
    }
    reuslt = sum;
}

public void addAll2(List<Integer> list) {
    for (int s : list) {
        reuslt += s;
    }
}
哲学家吃饭死锁
  • 如果都是右撇子先拿右边锁再拿左边锁就永远都持有右边锁资源等左边的了
  • 按顺序取锁资源,偶数先左后右,奇数先右后左。
有多个任务,一个任务失败了其他任务要取消
  • 当一个任务失败了,要通知boss,boss告诉其他人,要回滚任务并取消写日志,如果回滚失败一定要记下日志人工去处理,如果写日志失败了则要报错;(中间可以加一些重试)
按顺序执行一些任务
  • lockSupport park 相当于wait了阻塞 挂起状态, unpark相当于notify了获取执行权 恢复。并且它具有粘性效果可以先unpark
当有多个线程按顺序执行任务

利用ReentrantLock的Condition来实现。CountDownLatch来控制哪个线程先执行。

//       ReentrantLock出现来优化synchronized
        final ReentrantLock lock = new ReentrantLock();//一把锁有2个队列,生产者在一个队列,消费者在另外一个队列;当消费者空了就叫醒另外一个队列
        final Condition condition1 = lock.newCondition();//每一个condition都是一个队列
        final Condition condition2 = lock.newCondition();//每一个condition都是一个队列
        final Condition condition3 = lock.newCondition();//每一个condition都是一个队列

        final CountDownLatch countDownLatch1 = new CountDownLatch(1);
        final CountDownLatch countDownLatch2 = new CountDownLatch(1);
        final char[] s1 = "123456".toCharArray();
        final char[] s2 = "ABCDEF".toCharArray();
        final char[] s3 = "abcdef".toCharArray();
        thread1 = new Thread("thread1") {
            @Override
            public void run() {
                lock.lock();
                try {
                    for (char c : s1) {
                        System.out.print(c);
                        countDownLatch1.countDown();
                        condition2.signal();
                        condition1.await();
                    }
                    condition2.signal();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    lock.unlock();
                }

            }
        };


        thread2 = new Thread("thread2") {
            @Override
            public void run() {
                try {
                    countDownLatch1.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                lock.lock();
                try {
                    for (char c : s2) {
                        System.out.print(c);
                        countDownLatch2.countDown();
                        condition3.signal();
                        condition2.await();
                    }
                    condition3.signal();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    lock.unlock();
                }

            }
        };
        thread3 = new Thread("thread3") {
            @Override
            public void run() {
                try {
                    countDownLatch2.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                lock.lock();
                try {
                    for (char c : s3) {
                        System.out.print(c);
                        condition1.signal();
                        condition3.await();
                    }
                    condition1.signal();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                } finally {
                    lock.unlock();
                }

            }
        };
        thread1.start();
        thread2.start();
        thread3.start();
synchronized
  • synchronized 关键字确实可以保证代码块的原子性和可见性
  • 具有可重入性:指的是调用一个方法,这个方法有synchronized关键字,你获得了锁的执行权,当你在这个方法里再次调用另外一个方法,这个方法也有synchronized,会直接让你执行并且这个2方法都是使用当前对象的同一把锁。(调用父类方法也一样都是子类锁)
  • 程序中如果出现异常默认锁是会被释放的
  • 锁只能升级不能降级
  • 当资源执行时间长时使用synchronized比自旋锁更好,因为重量级锁会当其他线程等待锁资源时,线程进入等待状态就不会占用CPU了; 当执行时间短并且线程数量少时 使用自旋锁更合适。
  • 锁升级:默认当一个线程来访问锁只会记住线程ID,此时是偏向锁,线程反复调用这个方法会直接执行而不用加锁。如果有另外线程访问此方法升级为自旋锁,拿不到锁执行权则会进行自旋10次等待获得锁权限,如果自旋后没有拿到就会释放CPU资源,升级为重量级锁,进入等待队列。
volatile
  1. 保证线程的可见性(每个线程有自己的内存副本),保证了缓存一致性协议MESI。保证了副本改变同时主内存也会改变。
  2. 禁止进行指令重排