Java中synchronized 和 ReentrantLock 有什么不同
# 相似点: 这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比较高的.
比较项 | ReentrantLock(可重入锁) | synchronized |
原始构成 | 它是JDK 1.5之后提供的API层面的互斥锁类 | 它是java语言的关键字,是原生语法层面的实现,通过jvm实现的一个功能 |
实现 | api层面的加锁解锁,需要手动释放锁 | 通过JVM加锁解锁 |
代码编写 | 而ReentrantLock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。需要lock()和unlock()方法配合try/finally语句块来完成 | 当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用,更安全 |
灵活性 | 灵活性更好,可以跨方法调用加锁和解锁 | 只能应用在一个方法块里面,不能跨方法调用 |
等待可中断 | 可以在调用加锁代码的时候指定等待时长,当超过这个时长后线程就不再等待了。 | 无法实现中断操作,必须等前面的线程释放锁,他才能做事情。 |
公平性 | 可以实现公平锁 | 不能实现公平锁 |
适用情况 | 在高并发情况下比synchronized性能要好 | 在并发量不高的情况下效率更高 |
bio和nio的区别
我们之前用的io流都是bio,bio被称为阻塞式io。
nio是非阻塞式io,他的运行效率会更高一些。chanal,buffer,selector来实现非阻塞式io。
CountDownLatch 有什么作用?
CountDownLatch是jdk1.5提供的一个工具类可以实现线程的通信。
CountDownLatch里面有个计数器,每个线程都可以通过调用CountDownLatch的countDown方法对计算器进行减一操作。一旦CountDownLatch的计数器由正数变为0后,调用了CountDownLatch的await方法的线程就会被唤醒。以此来实现线程间的通信。
线程创建有几种方式?
- 通过继承Thread类。
- 实现Runnable接口。
- 实现Callable接口,线程执行完毕后可以获取到线程运行的结果。
- 通过线程池技术。
为什么要使用线程池(线程池的作用),线程池的初始化参数
线程池可以保存若干个线程对象,不用每次使用线程的时候都去创建,频繁创建和销毁线程需要浪费资源。各个任务执行完毕后把线程归还给线程池不用销毁和创建线程,节约了资源。
线程池的七个参数:
- 核心线程数
- 最大线程数,核心线程数+额外线程数
- 空闲时间
- 空闲时间的单位
- 任务队列
- 线程工厂。可以对创建的线程进行自定义。
- 拒绝策略。线程池给了我们若干个默认策略。我们可以重写接口实现拒绝策略。
一般我们创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列,但种模式下如果出现任务队列已满且线程池创建的线程数达到你设置的最大线程数时,这时就需要你指定ThreadPoolExecutor的RejectedExecutionHandler参数即合理的拒绝策略,来处理线程池"超载"的情况。ThreadPoolExecutor自带的拒绝策略如下:
1、AbortPolicy策略:该策略会直接抛出异常,阻止系统正常工作;
2、CallerRunsPolicy策略:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者线程当中运行;
3、DiscardOledestPolicy策略:该策略会丢弃任务队列中最老的一个任务,也就是当前任务队列中最先被添加进去的,马上要被执行的那个任务,并尝试再次提交;
4、DiscardPolicy策略:该策略会默默丢弃无法处理的任务,不予任何处理。当然使用此策略,业务场景中需允许任务的丢失;
jvm的内存结构。
jvm的内存有五块内容:
- 程序计数器
- java虚拟机栈
- java堆
- 本地方法栈
- 方法区
程序计数器:一个线程有一个程序计数器空间,这块空间用来保存当前线程执行到哪行代码。
虚拟机栈:一个线程对应一个虚拟机栈。虚拟机栈对应的是栈结构,一个栈里面的一个元素我们称之为栈帧。一个栈帧对应一个java方法。一个栈帧包含了很多信息:1.局部变量表,2.操作数栈,3.动态链接,4.方法返回地址。
java堆:存储程序运行过程中产生的对象信息。堆空间被划分成了两大块。1.新生代2.老年代。新生代分了三块区域分别是伊甸区和幸存区1和幸存区2。刚创建的对象放入到新生代,新生代满了,放入第一个幸存区,两个空间都满了开始垃圾回收,回收一次,大概会释放十分之九的空间。留下的对象,年龄加1.留下的对象会被复制到幸存区2里面。然后再往幸存区放对象,满了后,做垃圾回收,把存活对象放入到幸存区1里面。超过16岁的对象会被转移到老年代。老年代做垃圾回收时需要消耗更多的时间,所以我们应该避免老年代的垃圾回收。再做垃圾回收的时候,我们的业务线程会停止下来,供垃圾回收线程工作。这个现象被称为stop the world简称stw。
本地方法栈:跟虚拟机栈结构类似,区别是本地方法栈里面存储的是被native修饰的方法。虚拟机栈里面存储的是一般方法。
方法区:存储一些常量信息,比如类的信息以及字符串常量信息。
常见的cms和g1两种垃圾回收器的区别
cms | g1 | |
使用范围 | cms是老年代的收集器要配合新生代的收集器一块使用 | 可以用在新生代和老年代里面不需要配合其他收集器 |
stw的时间 | 以最小的停顿时间为目标 | 可预测垃圾回收的停顿时间 |
垃圾碎片 | 标记-清除算法,容易产生内存碎片 | 标记-整理算法,降低了内存空间碎片 |
回收过程 | 初始标记-并发标记-重新标记-并发清除 | 初始标记-并发标记-最终标记-筛选回收 |
Java类加载器,什么是双亲委派模型?
类加载器顾名思义,就是做类加载的。虚拟机把描述类的数据从class字节码文件加载到内存,并对数据进行检验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。它在JVM外部,负责将class文件,解析成JVM能识别的Java的类,类加载器ClassLoader中它生命周期包括加载、链接、初始化,链接又分为 验证,准备,解析三个部分。记不住没关系,我们有口诀“家宴准备了西式菜”,即家(加载)宴(验证)准备 (准备)了西(解析)式(初始化)菜。
- Bootstrap ClassLoader启动类加载器:负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录),如rt.jar,所有的java.开头的类均被Bootstrap ClassLoader加载。
- Extension ClassLoader扩展类加载器:它负责加载JDK\jre\lib\ext目录中。
- AppClassLoader应用程序类加载器:它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
- 用户自定义类加载器:用户根据自己业务需求自己定义的加载器。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的 加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。