java多线程面试相关的一些问题

  1. java中System.out.println()会影响到内存得可见性。
    答:因为 println这个语句 ,看源码得知,这个玩意外面包了一层synchronized
    1.获得同步锁
      2.清空工作内存
      3.从主内存中拷贝对象副本到本地内存
      4.执行代码(打印语句或加加操作)
      5.刷新主内存数据
      6.释放同步锁
    这也就是System.out.println()为何会影响内存可见性的原因了。
  2. volatile这个关键字是用来使得变量强制从内存中读取。只能修饰变量,他是用来解决变量在多个线程之间得可见性。而synchronized关键字解决得是多个线程之间访问公共资源得同步性。
  3. cas是什么?
    答:cas是一种思想。他得思路是,有一个公共变量,多个线程去操作。每个线程先把这个值拿到,放到本地中,然后进行一番操作,操作结束后,此时再比对,最开始拿到得这个变量和内存得公共变量进行比对,如果相同,则进行更新。如果不相同,则撤销刚才得操作,重新执行代码。
package test1;

public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        Test2 test2 = new Test2();
        for (int i = 0; i < 100;  i++) {
            new Thread(()->{
                for (int j = 0; j < 100; j++) {
                    test2.bidaxiao();
                }
            }).start();
        }
        Thread.sleep(3000);
        System.out.printf(""+test2.getValue());
    }
}
class Test2{


    public long getValue() {
        return value;
    }
    private synchronized boolean bianhua(long oldValue,long newValue){
        if(oldValue==value){
            value=newValue;
            return true;
        }
        return false;
    }
    private  volatile long value;
    public long bidaxiao(){
        long oldValue;
        long newValue;
        do{
            oldValue=value;
            newValue=oldValue+1;
        }while (!bianhua(oldValue,newValue));
        return newValue;
    }
}

  1. cas中会产生ABA问题,那什么是ABA问题,这种问题该如何规避呢?
    答:首先说ABA问题,假设有单位给员工发福利,给每个员工存200块钱,此时假设判断依据就是所有员工账号内为0元,那么就给该员工存200,如果为账户余额为200,那么就代表存过了,不是200就代表没存过,但是会有这么一种情况,如果该员工刚好把这200块钱花了,但是此时线程判断依据是不是200就代表没存过,那么就又给这个账号存了200,此时就是说给这个账号存了400块钱,就会产生错误。这就是ABA问题,ABA问题也是可以解决的,就是引入辅助参数,就相当于给这个账号再增加一个辅助参数,如果存了,那就改边这个参数为true(已经存了),那么下次其他线程进来一看,哎已经存了,就不会给这个账号存钱了。这也是乐观锁的解决办法。
  2. 基于cas这种思想java中提供了很多这样的原子类,
    5.1 原子更新基本类型类
    AtomicBoolean:原子更新布尔类型
    AtomivInteger:原子更新整型
    AtomicLong:原子更新长整型
    5.2 原子更新数组
    AtomicIntegerArray:原子更新整型数组里的元素
    AtomicLongArray:原子更新长整型数组里的元素
    AtomicReferenceArray:原子更新引用类型数组中的元素
    5.3 原子更新引用类型
    AtomicReference:原子更新引用类型
    AtomicReferenceField:原子更新引用类型的字段
    AtomicMarkableReference:原子更新带有标记位的引用类型
    5.4 原子更新字段类
    AtomicIntegerFieldUpdate:原子更新整型字段的更新器
    AtomicLongFieldUpdate:原子更新长整型字段的更新器
    AtomicStampedReference:原子更新带有版本号的引用类型
// AtomicLong 代码示例
public class AutomicL {
    private AutomicL() {}
    private static  AutomicL automicL=new AutomicL();
    public static AutomicL getAutomicL() {
        return automicL;
    }
    private final AtomicLong count=new AtomicLong(0);
    private final AtomicLong success=new AtomicLong(0);
    private final AtomicLong fail=new AtomicLong(0);
    public void countAdd(){
        //相当于增加1
        count.incrementAndGet();
    }
    public void successAdd(){
        success.incrementAndGet();
    }
    public void failAdd(){
        fail.incrementAndGet();
    }

    public long getCount() {
        return count.get();
    }

    public long getSuccess() {
        return success.get();
    }

    public long getFail() {
        return fail.get();
    }
}
//测试类
public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        AutomicL automicL = AutomicL.getAutomicL();
        for (int i = 0; i < 1000; i++) {
            new Thread(()->{

                automicL.countAdd();
                Random random=new Random();
                if(random.nextInt(10)%2==0){
                    automicL.successAdd();
                }else {
                    automicL.failAdd();
                }
            }).start();
        }
        Thread.sleep(3000);
        System.out.println(""+automicL.getCount());
        System.out.println(""+automicL.getSuccess());
        System.out.println(""+automicL.getFail());
    }
}

  1. wait()方法只能在同步代码块中由锁对象调用,调用wait方法,当前线程会释放锁,notify可以唤醒线程,该方法也必须在同步代码块中由锁对象调用,如果有多个等待线程,notify方法只能唤醒一个,而且必须是当前代码块执行完毕,才会唤醒wait()线程,notify不会立刻释放锁对象。
  2. await()和signal()这是干什么得?
    答:await是用来等待得。会释放掉锁,他是Condition里面得方法,还有一个类似得叫wait方法。他是obeject得方法。同样是等待释放锁。sinnal是唤醒锁。await和signal它两有个特别得地方就是得需要先获取锁对象。
  3. 读写锁的互斥性?
    答:读写锁允许读读共享,读写互斥,写写互斥。
  4. 线程池的应用
    线程池一个比较顶级的接口就是ExecutorService,下面是关于Excutors这个工具类的一些应用,而最顶级的线程池接口是Excutor接口。我们常用到的就是Excutors生成出来的一些线程池。这个工具类生成的实现类就是ExcutorService。
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
//        scheduledExecutorService.schedule(()->{
//            System.out.println(Thread.currentThread().getName()+Thread.currentThread().getId()+System.currentTimeMillis());
//        },3, TimeUnit.SECONDS);
        //这个玩意相当于是个定时执行的家伙
        /**
         * 下面说的是在3秒后开始执行改任务,以后每隔2秒再执行一次,如果线程运行时长超过间隔时长,那么就会运行完毕后,立马投入下个线程运行
         */
//        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
//            @Override
//            public void run() {
//                System.out.println("现在的执行时间是"+System.currentTimeMillis());
//            }
//        },3,2,TimeUnit.SECONDS);
        /**
         * 下面这个方法是说,运行完线程后,在间隔后继续执行线程
         */
        scheduledExecutorService.scheduleWithFixedDelay(()->{
            System.out.println("当前线程为:"+Thread.currentThread().getId()+"时间为:"+System.currentTimeMillis());
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },3,2,TimeUnit.SECONDS);
  1. 线程池底层原理代码怎么说?
    答:Excutors工具类中返回线程池的方法底层都返回的是ThreadPoolExcutor线程池,其中大部分方法都是ThreadPoolExcutor进行封装的。
  2. ThreadPoolExcutor线程池的构造方法参数讲解。
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        ......
    }

corePoolSize 这个是说线程池中核心线程的数量
maximumPoolSize 这个是说线程池中最大的线程数量
keepAliveTime 当线程池线程的数量超过corePoolSize时,多余的空闲线程的存活时长,即空余线程多长时间会销毁。
unit 指keepAliveTime时长的单位
workQueue 任务队列,把任务提交到该任务队列中等待执行
threadFactory 线程工厂,用于创建线程
handler 拒绝策略,当任务太多的时候,如何拒绝
说明 workQueue工作队列是指提交执行的任务队列,他是BlockingQueue接口的对象,仅用于存储Runnable任务,根据队列功能分类,在ThreadPoolExcutor构造方法中可以使用以下几种阻塞队列:
11.1. 直接提交队列, 由SynchronousQueue对象提供,该队列没有容量,提交给线程池的任务不会被真实的保存,总是将新的任务提交给线程执行,如果没有空余线程,则尝试创建新的线程,如果线程数量已经达到maximumPoolSize就执行线程拒绝策略。
11.2. 有界任务队列,由ArrayBlockingQueue实现在创建这个对象的时候,可以指定一个容量,当有新任务执行的时候,如果我们线程池中的线程数小于核心线程数,如果大于核心线程数,则加入队列。如果队列已满,则无法加入,在线程数小于maximumPoolSize 指定的最大线程数前提下会创建新的线程来执行,如果线程数大于maximumPoolSize 最大线程数则执行拒绝策略。
11.3. 无界任务队列,由LinkedBlokingQueue对象实现,与有界队列相比,除非系统资源耗尽,否则不存在任务入队失败的情况,在系统线程数小于核心线程数,则会创建新的线程来执行任务;当线程池线程数量大于核心线程数则把任务加入阻塞队列中。
11.4. 优先任务队列,是由PriorityBlockingQueue实现的。是带有任务优先级的队列,是一个特殊的无界队列,上面ArrayBlockingQueue还是LinkedBlockingQueue,都是按照先进先出的原则来处理任务的。

  1. 线程池是如何新建和进入队列得?
    在这里插入图片描述

  2. ThreadPoolExcutor中的拒绝策略介绍
    答:RejectedExecutionHandler是拒绝策略的顶级接口,
    AbortPolicy这个类是线程池默认的拒绝策略,如果任务无法处理,那么就抛出异常。
    CallerRunsPolicy这个类的作用是说只要线程池关闭,会调用线程被丢弃的任务。
    DiscardOldestPolicy 将线程池中最老的任务丢弃掉,尝试继续提交新的线程。
    DiscardPolicy 直接丢弃这个无法处理的线程。

  3. ThreadPoolExcutor中存在吃异常得现象。
    答:ThreadPoolExcutor中得submit提交线程得方法。如果线程中有异常产生,他会把线程异常吃了。而如果使用excute提交线程,则会把线程得异常进行一个抛出。当然了我们也可以重写它的方法,我们继承ThreadPoolExcutor这个线程类,然后重写这个类的方法submit和这个excutor方法。就可以重新定义这个方法。进行方法异常的捕获处理。

  4. 栈、堆、元空间。
    答:堆空间和元空间是线程可以共享的空间,即实例变量和静态变量是线程可以共享的。可能存在线程安全问题,栈空间是线程私有的存储空间,局部变量存储在栈空间中,局部变量是固定的。

  5. 锁的优化。
    答:
    1. 减少锁的持有时间
    2. 减少锁的粒度,一个锁保护的共享数据的大小,称为锁的粒度。如果一个锁保护共享数据的数据量大,称为锁的粒度粗。反之称为锁的粒度细。
    3. 使用读写分离锁来代理独占锁。使用readwriteLock读写分离锁可以提高系统性能。
    4. 锁分离,比如说LinkedBlockQueue这个类从头部取,从尾部插入

  6. 偏向锁的概念。
    答:锁偏向,是说如果一个线程获得了锁,那么锁就进入了偏向模式,当这个线程在请求锁的时候,无需任何同步操作,这样可以节省有关锁申请的时间,提高程序的性能。
    锁偏向在没有竞争的时候,可以有较好的效果,对于锁竞争比较激烈的场景效果不佳,锁竞争比较激烈的时候,可能是每次请求的锁都不一样,这是偏向模式失效。

  7. 锁升级的概念。
    答:如果锁偏向失败,jvm并不会立即挂起线程,还会使用一种称为轻量锁的优化手段,会将共享对象(可以理解为就是synchronized锁的对象)的头部作为指针,指向持有锁的堆栈内部,来判断一个线程是否持有对象锁,如果线程获得轻量锁成功,那么就进入临界区,如果获得轻量锁失败,表示其他线程抢到了该锁,那么当前线程的锁的请求就升级为重量级锁,当前线程就转到阻塞队列中,变为阻塞状态。
    偏向锁和轻量锁两个都是乐观锁,重量锁是悲观锁。
    一个对象刚开始实例化时,没有任何线程访问它,它是可偏向的,即 它认为只可能有一个线程来访问它,所以当第一个线程来访问它的时候,它会偏向这个线程.偏向第一个线程,这个线程在修改对象头成为 偏向锁时使用0X5操作,将对象头中油宫姻改成自己的16之后再访 问这个对象时,只需要对比ID即可.一旦有第二个线程访问该对象,因 为偏向锁不会主动释放,所以第二个线程可以查看对象的偏向状态,当 第二个线程访问对象时,表示在这个对象上已经存在竞争了,检查原来 持有对象锁的线程是否存活,如果挂了则将对象变为无锁状态,然后重新偏向新的线程;如果原来的线程依然存活,则马上执行原来线程的栈,检查该对象的使用情况,如果仍需要偏向锁,则偏向锁升级为轻量锁。
    轻量锁认为竞争存在,但是竞争的程度很轻,一般两个线程对同一个锁的操作会错开,或者是稍微等待一下(这个稍微等待一下叫自旋)另外一个线程就会释放锁,当自旋超过一定的次数,或者一个线程持有锁,一个线程在自旋,又来第三个线程方法时,轻量级锁会膨胀为重量锁,重量锁除了持有锁的线程外,其他的线程都阻塞

  8. 线程不同步的代码。

public class ThreadLo {
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public static void main(String[] args) {
        ThreadLo threadLo = new ThreadLo();
        for (int i = 0; i < 5;   i++) {
            new Thread(()->{
                threadLo.setContent(Thread.currentThread().getName()+"的数据");
                System.out.println("------------");
                System.out.println(Thread.currentThread().getName()+"线程的内容为"+threadLo.getContent());
            }).start();
        }
    }
}

使用ThreadLocal版的时候

public class ThreadLo {
    private ThreadLocal<String> threadLocal=new ThreadLocal<>();
    private String content;

    public String getContent() {
        return threadLocal.get();
    }

    public void setContent(String content) {
        this.threadLocal.set(content);
    }

    public static void main(String[] args) {
        ThreadLo threadLo = new ThreadLo();
        for (int i = 0; i < 5;   i++) {
            new Thread(()->{
                threadLo.setContent(Thread.currentThread().getName()+"的数据");
                System.out.println("------------");
                System.out.println(Thread.currentThread().getName()+"线程的内容为"+threadLo.getContent());
            }).start();
        }
    }
}

当然了synchronized 也是可以解决问题的。但是此时就造成了线程不能并发了。

public class ThreadLo {
    private String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public static void main(String[] args) {
        ThreadLo threadLo = new ThreadLo();
        for (int i = 0; i < 5;   i++) {
            new Thread(()->{
                synchronized (threadLo){
                    threadLo.setContent(Thread.currentThread().getName()+"的数据");
                    System.out.println("------------");
                    System.out.println(Thread.currentThread().getName()+"线程的内容为"+threadLo.getContent());
                }

            }).start();
        }
    }
}

  1. ThreadLocal的讲解。
    答:在很早以前,threadLocal确实是在内部维持了一个map结构,这个mapkey放的是线程id,而value放的是要保存的值。从而做隔离。在java8以后翻了个个,就是在thread中维护这个threadLocalmap.相当于自己的线程,存自己的变量。也就是说由线程本身去维护这个变量。可以看ThreadLocal中的set方法。他会在set的同时,给当前线程存一份ThreadLocalMap
    在这里插入图片描述

  2. String 、Stringbuffer、Stringbuilder有什么区别?
    答:
      Java9改进了字符串(包括String、StringBuffer、StringBuilder)的实现。在Java9以前字符串采用char[]数组来保存字符,因此字符串的每个字符占2字节;而Java9的字符串采用byte[]数组再加一个encoding-flag字段来保存字符,因此字符串的每个字符只占1字节。所以Java9的字符串更加节省空间,字符串的功能方法也没有受到影响。
    StringBuffer中它里面得方法都是由synchronized实现得。所以呢StringBuffer是线程安全得。而StringBuilder非线程安全得。

  3. jvm内存模型详解。
    答:jvm内存是由五个部分构成得。栈、本地方法栈、程序计数器、堆、方法区。
    栈、本地方法栈、程序计数器这三个部分都是线程独占的。堆、方法区是线程共享得。

    栈:也叫方法栈,是线程私有的,线程在执行每个方法时都会同时创建一个栈帧,用来存储局部变量表、操作栈、动态链接、方法出口等信息。调用方法时执行入栈,方法返回时执行出栈。
    本地方法栈:本地方法栈与栈类似,也是用来保存线程执行方法时的信息,不同的是,执行 Java 方法使用栈,而执行 native 方法使用本地方法栈。这个native方法也就是相当于java调用c++、c方法。
    程序计数器:程序计数器保存着当前线程所执行的字节码位置,每个线程工作时都有一个独立的计数器。程序计数器为执行 Java 方法服务,执行 native 方法时,程序计数器为空。
    堆:堆是JVM管理的内存中最大的一块,堆被所有线程共享,目的是为了存放对象实例,几乎所有的对象实例都在这里分配。当堆内存没有可用的空间时,会抛出OOM异常。根据对象存活的周期不同,JVM把堆内存…
    方法区:方法区也是各个线程共享的内存区域,又叫非堆区。用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,JDK1.7中的永久代和JDK1.8中的Metaspace都是…

  4. 堆的细分。
    答:堆如果再细分一下又可以细化分为3个部分:年轻代(Young)、年老代(Tenured)、永存区(Perm),其中年轻代又分为伊甸园(Eden space),幸存者0区(Survivor 0 space)和幸存者1区(Survivor 1 space)。
    Young(年轻代)
    Young 区被划分为三部分,Eden(伊甸园) 区和两个大小相同的 Survivor(幸存者)区,其中 Survivor 区间中,某一时刻只有其中一个是被使用的,另外一个留做垃圾收集时复制对象用(后面垃圾回收策略中会说明)。
    在 Young 区间变满的时候,minor GC 就会将存活的对象移到空闲的 Survivor 区间中,根据 JVM 的策略,在经过几次垃圾收集后,仍然存活于 Survivor 的对象将被移动到 Tenured 区间。
    Tenured(年老代)
    Tenured 区主要保存生命周期长的对象,一般是一些老的对象,当一些对象在 Young 复制转移一定的次数以后,对象就会被转移到 Tenured 区,如果系统中用了 Application 级别的缓存,缓存中的对象往往会被转移到这一区间。
    Perm(永久区)
    Perm 代主要保存 class(类,包括接口等),method(方法),filed(属性) 等元数据,这部分的空间一般不会溢出,除非一次性加载了很多的类,不过在涉及到热部署的应用服务器的时候,有时候会遇到 java.lang.OutOfMemoryError : PermGen space 的错误,造成这个错误的很大原因就有可能是每次都重新部署,但是重新部署后,类的 class 没有被卸载掉,这样就造成了大量的 class 对象保存在了 perm 中,这种情况下,一般重新启动应用服务器可以解决问题。

  5. jvm是如何保证内存分配中线程安全的。
    答:1,堆是JVM中所有线程共享的,因此在其上进行对象内存的分配均需要进行加锁,这也导致了new对象的开销是比较大的
    2、JVM为了提升对象内存分配的效率,对于所创建的线程都会分配一块独立的空间TLAB(Thread Local Allocation Buffer), 其大 小由JVM根据运行的情况计算而得,在TLAB上分配对象时不需要加锁,因此JVM在给线程的对象分配内存时会尽量的在TLAB上分配,
    在这种情况下JVM中分配对象内存的性能和C基本是一样高效的,但如果对象过大的话则仍然是直接使用堆空间分配
    3、TLAB仅作用于新生代的Eden Space,因此在编写Java程序时,通常多个小的对象比大的对象分配起来更加高效。
    4 、所有新创建的Object 都将会存储在新生代Yong Generation中。如果Young Generation的数据在一次或多次GC后存活下来,那么将被转移到OldGeneration。 新的Object总是创建在Eden Space。
    说人话就是说,除了在new的时候分配到堆中的操作加锁之外,但是这样频繁的创建对象就会造成性能的降低。其次就是tlab,就是对于方法中新建的对象,此时的对象会随着方法的开始而创建,随着方法的结束而结束,此时的对象会可能会建立在栈中,这样就可以保证线程安全也保证了性能的提高。也可以这么说就是new对象的时候不全是放在堆中,也有可能放在栈中。

  6. java中锁的不同种维度的分类。
    可重入锁

    	指在同一个线程在外层方法获取锁的时候,进入内层方法会自动获取锁。JDK 中基本都是可重入锁,避免死锁的发生。上面提到的常见的锁都是可重入锁。
    	 
    	
    	公平锁 / 非公平锁
    	
    	公平锁,指多个线程按照申请锁的顺序来获取锁。如 java.util.concurrent.lock.ReentrantLock.FairSync
    	非公平锁,指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程先获得锁。如 synchronized、java.util.concurrent.lock.ReentrantLock.NonfairSync
    	        
    	独享锁 / 共享锁
    	
    	独享锁,指锁一次只能被一个线程所持有。synchronized、java.util.concurrent.locks.ReentrantLock 都是独享锁
    	共享锁,指锁可被多个线程所持有。ReadWriteLock 返回的 ReadLock 就是共享锁
    	          
    	悲观锁 / 乐观锁
    	
    	悲观锁,一律会对代码块进行加锁,如 synchronized、java.util.concurrent.locks.ReentrantLock
    	乐观锁,默认不会进行并发修改,通常采用 CAS 算法不断尝试更新
    	悲观锁适合写操作较多的场景,乐观锁适合读操作较多的场景
    	        
    	粗粒度锁 / 细粒度锁
    	
    	粗粒度锁,就是把执行的代码块都锁定
    	细粒度锁,就是锁住尽可能小的代码块,java.util.concurrent.ConcurrentHashMap 中的分段锁就是一种细粒度锁
    	粗粒度锁和细粒度锁是相对的,没有什么标准
    	        
    	偏向锁 / 轻量级锁 / 重量级锁
    	
    	JDK 1.5 之后新增锁的升级机制,提升性能。
    	通过 synchronized 加锁后,一段同步代码一直被同一个线程所访问,那么该线程获取的就是偏向锁
    	偏向锁被一个其他线程访问时,Java 对象的偏向锁就会升级为轻量级锁
    	再有其他线程会以自旋的形式尝试获取锁,不会阻塞,自旋一定次数仍然未获取到锁,就会膨胀为重量级锁        
    	        
    	自旋锁
    	
    	自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环占有、浪费 CPU 资源
    
  7. 详细说明一下什么是可重入锁和什么是不可重入锁。
    首先先看一段代码
    这段代码讲的是方法中方法A的执行需要方法B,但是如果此时方法B也要被运行,那么如果该锁是不可重入锁就会发生死锁的现象,这个就是不可重入锁。

public class Test{
     Lock lock = new Lock();
     public void methodA(){
         lock.lock();
         ...........;
         methodB();
         ...........;
         lock.unlock();
     }
     public void methodB(){
         lock.lock();
         ...........;
         lock.unlock();
     }
}

那我们就来看看这个不可重入锁的一种实现方式。这段代码说明如果同一线程执行上面那个方法的时候,首先会在A方法开始执行阶段拿到锁,然后继续执行B方法,但是B方法的开始也需要拿到锁。所以会造成死锁的现象。

public class Lock{
     private boolean isLocked = false;
     public synchronized void lock() throws InterruptedException{
         while(isLocked){    
             wait();
         }
         isLocked = true;
     }
     public synchronized void unlock(){
         isLocked = false;
         notify();
    }
}

那我们在来看看可重入锁的情况。是如何避免死锁的发生的。

public class Lock{
    boolean isLocked = false;
    Thread  lockedBy = null;
    int lockedCount = 0;
    public synchronized void lock()
            throws InterruptedException{
        Thread thread = Thread.currentThread();
        while(isLocked && lockedBy != thread){
            wait();
        }
        isLocked = true;
        lockedCount++;
        lockedBy = thread;
    }
    public synchronized void unlock(){
        if(Thread.currentThread() == this.lockedBy){
            lockedCount--;
            if(lockedCount == 0){
                isLocked = false;
                notify();
            }
        }
    }
}

其中上面的变量lockedCount为0的时候是获取释放锁的一个标志。细细品。这个代码完美的阐述了什么是可重入锁,和不可重入锁的情况。

  1. 集合初始容量以及扩容系数
    arraylist 10 0.5 16 非安全

vector 10 1 20 安全的

hashset 16 1 32 非安全

hashmap 16 1 32 非安全

  1. 讲一下单例模式中得安全问题,这里用得是双重检验锁方式实现单例模式
public class Test2 {
    private Test2(){}
    private volatile static Test2 test2;
    public static Test2 getTest2(){
        if(test2==null){
            synchronized (Test2.class){
                if(test2==null){
                    return new Test2();
                }
            }
        }
        return test2;
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值