JRE:Java Runtime Environment(java运行时环境)。即java程序的运行时环境,包含了java虚拟机,java基础类库。
JDK:Java Development Kit(java开发工具包)。即java语言编写的程序所需的开发工具包。
JDK包含了JRE,同时还包括java源码的编译器javac、监控工具jconsole、分析工具jvisualvm等。
一、理解”==“的含义
在java中,主要有两个作用。
1、基础数据类型:比较的是他们的值是否相等,比如两个int类型的变量,比较的是变量的值是否一样。
2、引用数据类型:比较的是引用的地址是否相同,比如说新建了两个User对象,比较的是两个User的地址是否一样。
OK。到这就注意了,你会发现,我在举引用的例子的时候,使用的是User对象,而不是String。别着急接着往下看。
从这个源码中你会发现,比较的是当前对象的引用和obj的引用是否相同,也就是说比较的默认就是地址。还记的在上面我们使用的是User而不是String嘛?在这里比较的是引用的地址,equals也是比较的是引用的地址,所以他们的效果在这里是一样的。
现在你会发现好像equals的作用和没什么区别呀,那String类型那些乱七八糟的东西是什么呢?继续往下看马上揭晓。
三、重写equals
1、String中equals方法
看到这个标题相信你已经能找到答案里,Object对象里面的==和equals没有什么区别,这样一看equals方法存在的意义真的不大,不过后来String在Object的基础之上重写了equals,于是功能被大大的改变了。如何重写的呢?我们去String的源码中找寻答案:
二、理解equals的含义
先看看他的源码,equals方法是在Object中就有。注意了这里的源码是Object里面的equals。
public boolean equals(Object anObject) {
if (this == anObject) { //引用比较
return true;
}
if (anObject instanceof String) { //判断是否为String类型
String anotherString = (String)anObject;
int n = value.length;
//anotherString.value.length 相当于 anotherString.length()
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i]) //比较每一个字符是否相等
return false;
i++;
}
return true;
}
}
return false;
}
从上面的源码,我们能够获取到的信息是:String中的equals方法其实比较的是字符串的内容是否一样。也就是说如果像String、Date这些重写equals的类,你可要小心了。使用的时候会和Object的不一样。
2、测试String
看看下面的代码:
在上面的代码中,定义了三个字符串,分别使用==和equals去比较。为什么会出现这样一个结果呢?还需要从内存的角度来解释一下。
3、内存解释
在java中我们一般把对象存放在堆区,把对象的引用放在栈区。因此在上面三个字符串的内存状态应该是下面这样的。
现在明白了吧。
(1)String str1 = "Hello"会在堆区存放一个字符串对象
(2)String str2 = new String(“Hello”)会在堆区再次存放一个字符串对象
(3)String str3 = str2这时候Str3和Str2是两个不同的引用,但是指向同一个对象。
根据这张图再来看上面的比较:
(1)str1 == str2嘛?意思是地址指向的是同一块地方吗?很明显不一样。
(2)str1 == str3嘛?意思是地址指向的是同一块地方吗?很明显不一样。
(3)str2 == str3嘛?意思是地址指向的是同一块地方吗?很明显内容一样,所以为true。
(4)str1.equals(str2)嘛?意思是地址指向的内容一样嘛?一样。
(4)str1.equals(str3)嘛?意思是地址指向的内容一样嘛?一样。
(4)str2.equals(str3)嘛?意思是地址指向的内容一样嘛?一样。
OK。现在不知道你能理解嘛?
4、总结:
(1)、基础类型比较
使用==比较值是否相等。
(2)、引用类型比较
①重写了equals方法,比如String。
第一种情况:使用==比较的是String的引用是否指向了同一块内存
第二种情况:使用equals比较的是String的引用的对象内用是否相等。
②没有重写equals方法,比如User等自定义类
答案:不对
我们先看到Objec类中hashCode()方法源码
该方法是个native方法,因为native方法是由非Java语言实现的,所以这个方法的定义中也没有具体的实现。根据jdk文档,该方法的实现一般是通过将该对象的内部地址转换成一个整数来实现的,这个返回值就作为该对象的哈希码值返回。
再看equals源码,尤其要注意return的说明
hashCode值是从hash表中得来的,hash是一个函数,该函数的实现是一种算法,通过hash算法算出hash值,hash表就是hash值组成的,一共有8个位置。因此,hashCode相同的两个对象不一定equals()也为true。
final作为Java中的关键字可以用于三个地方。用于修饰类、类属性和类方法。
特征:凡是引用final关键字的地方皆不可修改!
Math.round(-1.5)的返回值是-1。四舍五入的原理是在参数上加0.5然后做向下取整。
我们可以通过大量实验看下结果
答:String、StringBuffer、StringBuilder
区别:
抽象类必须有关键字abstract来修饰。
抽象类可以不含有抽象方法
如果一个类包含抽象方法,则该类必须是抽象类
这个题要从流的角度去划分:
按照流的流向分,可以分为输入流和输出流;
按照操作单元划分,可以划分为字节流和字符流;
按照流的角色划分为节点流和处理流。
InputStream/Reader: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流。
什么是IO
什么是NIO
什么是AIO
AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由操作系统先完成了再通知服务器应用去启动线程进行处理,AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用操作系统参与并发操作,编程比较复杂,JDK1.7之后开始支持。.
AIO属于NIO包中的类实现,其实IO主要分为BIO和NIO,AIO只是附加品,解决IO不能异步的实现
在以前很少有Linux系统支持AIO,Windows的IOCP就是该AIO模型。但是现在的服务器一般都是支持AIO操作
BIO和NIO、AIO的区别
Java容器:
数组,String,java.util下的集合容器
数组长度限制为 Integer.Integer.MAX_VALUE;
String的长度限制: 底层是char 数组 长度 Integer.MAX_VALUE 线程安全的
1、java.util.Collection 是一个集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。
List,Set,Queue接口都继承Collection。
直接实现该接口的类只有AbstractCollection类,该类也只是一个抽象类,提供了对集合类操作的一些基本实现。List和Set的具体实现类基本上都直接或间接的继承了该类。
2、java.util.Collections 是一个包装类。它包含有各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等),大多数方法都是用来处理线性表的。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
List(对付顺序的好帮手):List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象
Set(注重独一无二的性质):不允许重复的集合。不会有多个元素引用相同的对象。
Map(用Key来搜索的专家):使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。
HashMap可以接受null键值和值,而Hashtable则不能。
Hashtable是线程安全的,通过synchronized实现线程同步。而HashMap是非线程安全的,但是速度比Hashtable快。
这两个类有许多不同的地方,下面列出了一部分:
我们先看一下HashSet和TreeSet在整个集合框架中的位置。他们都实现了Set接口。他们之间的区别是HashSet不能保证元素的顺序,TreeSet中的元素可以按照某个顺序排列。他们的元素都不能重复。
先来看一下HashSet:
public static void main(String[] args) {
Set<String> set = new HashSet<String>();
set.add("张三");
set.add("李四");
set.add("王五");
System.out.println(set);
System.out.println(set.size());
System.out.println(set.contains("张三"));
}
打印输出的顺序是是: [李四, 张三, 王五]
可以看出和存进去的顺序不一致。
我们先看一下 Set set = new HashSet();
这行代码创建了一个HashSet,构造函数如下:
public HashSet() {
map = new HashMap<>();
}
可以看到实际上是创建了一个HashMap的对象。没错,HashSet底层就是一个HashMap.
再来看一下这行代码:set.add(“张三”);
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
非常的简单,就是调用了一下HashMap的put方法对元素进行插入。
这里的PERSENT是什么呢?继续顺藤摸瓜:
private static final Object PRESENT = new Object();
原来就是一个普通的Object对象前面用static final修饰说明是不可变的。
继续添加:set.add(“李四”);
可以看出来HashMap的key分别为”张三”,”李四”,“王五”,
因为HashSet用不到value,他们的value都是一样的指向同一个地方。
继续往下看:System.out.println(set.size());
public int size() {
return map.size();
}
也是调用的HashMap的size方法。
System.out.println(set.contains(“张三”));
public boolean contains(Object o) {
return map.containsKey(o);
}
同样调用的HashMap的contains方法。
数组转 List ,使用 JDK 中 java.util.Arrays 工具类的 asList 方法
public static void testArray2List() {
String[] strs = new String[] {"aaa", "bbb", "ccc"};
List<String> list = Arrays.asList(strs);
for (String s : list) {
System.out.println(s);
}
}
List 转数组,使用 List 的toArray方法。无参toArray方法返回Object数组,传入初始化长度的数组对象,返回该对象数组
public static void testList2Array() {
List<String> list = Arrays.asList("aaa", "bbb", "ccc");
String[] array = list.toArray(new String[list.size()]);
for (String s : array) {
System.out.println(s);
}
}
首先我们给出标准答案:
定义一个 Array 时,必须指定数组的数据类型及数组长度,即数组中存放的元素个数固定并且类型相同。
ArrayList 是动态数组,长度动态可变,会自动扩容。不使用泛型的时候,可以添加不同类型元素。
队列是一个典型的先进先出(FIFO)的容器。即从容器的一端放入事物,从另一端取出,并且事物放入容器的顺序与取出的顺序是相同的。
队列的两种实现方式:
1、offer()和add()的区别
2、peek()和element()的区别
3、poll()和remove()的区别
java.lang.Iterable 接口被 java.util.Collection 接口继承,java.util.Collection 接口的 iterator() 方法返回一个 Iterator 对象
next() 方法获得集合中的下一个元素
hasNext() 检查集合中是否还有元素
remove() 方法将迭代器新返回的元素删除
forEachRemaining(Consumer super E> action) 方法,遍历所有元素
public class TestIterator {
static List<String> list = new ArrayList<String>();
static {
list.add("111");
list.add("222");
list.add("333");
}
public static void main(String[] args) {
testIteratorNext();
System.out.println();
testForEachRemaining();
System.out.println();
testIteratorRemove();
}
//使用 hasNext 和 next遍历
public static void testIteratorNext() {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
System.out.println(str);
}
}
//使用 Iterator 删除元素
public static void testIteratorRemove() {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String str = iterator.next();
if ("222".equals(str)) {
iterator.remove();
}
}
System.out.println(list);
}
//使用 forEachRemaining 遍历
public static void testForEachRemaining() {
final Iterator<String> iterator = list.iterator();
iterator.forEachRemaining(new Consumer<String>() {
public void accept(String t) {
System.out.println(t);
}
});
}
}
ListIterator 继承 Iterator
ListIterator 比 Iterator多方法
1) add(E e) 将指定的元素插入列表,插入位置为迭代器当前位置之前
2) set(E e) 迭代器返回的最后一个元素替换参数e
3) hasPrevious() 迭代器当前位置,反向遍历集合是否含有元素
4) previous() 迭代器当前位置,反向遍历集合,下一个元素
5) previousIndex() 迭代器当前位置,反向遍历集合,返回下一个元素的下标
6) nextIndex() 迭代器当前位置,返回下一个元素的下标
使用范围不同,Iterator可以迭代所有集合;ListIterator 只能用于List及其子类
ListIterator 有 add 方法,可以向 List 中添加对象;Iterator 不能
ListIterator 有 hasPrevious() 和 previous() 方法,可以实现逆向遍历;Iterator不可以
ListIterator 有 nextIndex() 和previousIndex() 方法,可定位当前索引的位置;Iterator不可以
ListIterator 有 set()方法,可以实现对 List 的修改;Iterator 仅能遍历,不能修改
可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,这样改变集合的任何操作都会抛出 Java.lang.UnsupportedOperationException 异常。
同理:Collections包也提供了对list和set集合的方法。
Collections.unmodifiableList(List)
Collections.unmodifiableSet(Set)
拓展:
我们很容易想到用final关键字进行修饰,我们都知道
final关键字可以修饰类,方法,成员变量,final修饰的类不能被继承,final修饰的方法不能被重写,
final修饰的成员变量必须初始化值,如果这个成员变量是基本数据类型,表示这个变量的值是不可改变的,
如果说这个成员变量是引用类型,则表示这个引用的地址值是不能改变的,
但是这个引用所指向的对象里面的内容还是可以改变的。
三、多线程
35.并行和并发有什么区别?
并发,指的是多个事情,在同一时间段内同时发生了。
并行,指的是多个事情,在同一时间点上同时发生了。
并发的多个任务之间是互相抢占资源的。
并行的多个任务之间是不互相抢占资源的、
只有在多CPU的情况中,才会发生并行。否则,看似同时发生的事情,其实都是并发执行的。
就像上面这张图,只有一个咖啡机的时候,一台咖啡机其实是在并发被使用的。而有多个咖啡机的时候,多个咖啡机之间才是并行被使用的。
守护线程是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。在 Java 中垃圾回收线程就是特殊的守护线程。
专门用于服务其他的线程,如果其他的线程(即用户自定义线程)都执行完毕,连main线程也执行完毕,
那么jvm就会退出(即停止运行),此时连jvm都停止运行了,守护线程当然也就停止执行了。
换一种通俗的说法,如果有用户自定义线程存在的话,jvm就不会退出,
此时守护线程也不能退出,也就是它还要运行,为什么呢,就是为了执行垃圾回收的任务。
1.继承Thread类型重写run 方法
2.实现Runnable接口
3.实现Callable接口
主要区别
Java中线程的状态分为6种。
2.1. 就绪状态
2.2. 运行中状态
3. 阻塞状态
4. 等待
5. 超时等待
6. 终止状态
sleep()方法:
功能
使用场合
使用场合:
package thread;
public class MultiThread {
public static void main(String[] args) throws InterruptedException {
new Thread(new Thread1()).start();
//主动让出CPU,让CPU去执行其他的线程。在sleep指定的时间后,CPU回到这个线程上继续往下执行
Thread.sleep(5000);
new Thread(new Thread2()).start();
}
}
class Thread1 implements Runnable{
@Override
public void run() {
synchronized (MultiThread.class){
System.out.println("进入线程1");
try{
System.out.println("线程1正在等待");
Thread.sleep(5000);
//MultiThread.class.wait();
//wait是指一个已经进入同步锁的线程内(此处指Thread1),让自己暂时让出同步锁,
//以便其他在等待此锁的线程(此处指Thread2)可以得到同步锁并运行。
}catch(Exception e){
System.out.println(e.getMessage());
e.printStackTrace();
}
System.out.println("线程1结束等待,继续执行");
System.out.println("线程1执行结束");
}
}
}
class Thread2 implements Runnable{
@Override
public void run() {
synchronized (MultiThread.class){
System.out.println("进入线程2");
System.out.println("线程2唤醒其他线程");
//Thread2调用了notify()方法,但该方法不会释放对象锁,只是告诉调用wait方法的线程可以去
//参与获得锁的竞争了。但不会马上得到锁,因为锁还在别人手里,别人还没有释放。
//如果notify()后面的代码还有很多,需要执行完这些代码才会释放锁。
MultiThread.class.notify();
try {
Thread.sleep(5000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2继续执行");
System.out.println("线程2执行结束");
}
}
}
锁池:
等待池:
notify和notifyAll的区别:
run()方法:
start()方法:
问题
面试官:请问启动线程是start()还是run()方法,能谈谈吗?
应聘者:start()方法
当用start()开始一个线程后,线程就进入就绪状态,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。但是这并不意味着线程就会立即运行。只有当cpu分配时间片时,这个线程获得时间片时,才开始执行run()方法。start()是方法,它调用run()方法.而run()方法是你必须重写的. run()方法中包含的是线程的主体(真正的逻辑)。
6种
1. newFixedThreadPool
public class TestThreadPool {
//定长线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量,
//这时线程数量不再变化,当线程发生错误结束时,线程池会补充一个新的线程
static ExecutorService fixedExecutor = Executors.newFixedThreadPool(3);
public static void main(String[] args) {
testFixedExecutor();
}
//测试定长线程池,线程池的容量为3,提交6个任务,根据打印结果可以看出先执行前3个任务,
//3个任务结束后再执行后面的任务
private static void testFixedExecutor() {
for (int i = 0; i < 6; i++) {
final int index = i;
fixedExecutor.execute(new Runnable() {
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " index:" + index);
}
});
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4秒后...");
fixedExecutor.shutdown();
}
}
测试代码:
public class TestThreadPool {
//可缓存的线程池,如果线程池的容量超过了任务数,自动回收空闲线程,
//任务增加时可以自动添加新线程,线程池的容量不限制
static ExecutorService cachedExecutor = Executors.newCachedThreadPool();
public static void main(String[] args) {
testCachedExecutor();
}
//测试可缓存线程池
private static void testCachedExecutor() {
for (int i = 0; i < 6; i++) {
final int index = i;
cachedExecutor.execute(new Runnable() {
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " index:" + index);
}
});
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4秒后...");
cachedExecutor.shutdown();
}
}
测试代码:
public class TestThreadPool {
//定长线程池,可执行周期性的任务
static ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(3);
public static void main(String[] args) {
testScheduledExecutor();
}
//测试定长、可周期执行的线程池
private static void testScheduledExecutor() {
for (int i = 0; i < 3; i++) {
final int index = i;
//scheduleWithFixedDelay 固定的延迟时间执行任务; scheduleAtFixedRate 固定的频率执行任务
scheduledExecutor.scheduleWithFixedDelay(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + " index:" + index);
}
}, 0, 3, TimeUnit.SECONDS);
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4秒后...");
scheduledExecutor.shutdown();
}
}
测试代码:
public class TestThreadPool {
//单线程的线程池,线程异常结束,会创建一个新的线程,能确保任务按提交顺序执行
static ExecutorService singleExecutor = Executors.newSingleThreadExecutor();
public static void main(String[] args) {
testSingleExecutor();
}
//测试单线程的线程池
private static void testSingleExecutor() {
for (int i = 0; i < 3; i++) {
final int index = i;
singleExecutor.execute(new Runnable() {
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " index:" + index);
}
});
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4秒后...");
singleExecutor.shutdown();
}
}
5. newSingleThreadScheduledExecutor
测试代码:
public class TestThreadPool {
//单线程可执行周期性任务的线程池
static ScheduledExecutorService singleScheduledExecutor =Executors.newSingleThreadScheduledExecutor();
public static void main(String[] args) {
testSingleScheduledExecutor();
}
//测试单线程可周期执行的线程池
private static void testSingleScheduledExecutor() {
for (int i = 0; i < 3; i++) {
final int index = i;
//scheduleWithFixedDelay 固定的延迟时间执行任务; scheduleAtFixedRate 固定的频率执行任务
singleScheduledExecutor.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName() + " index:" + index);
}
}, 0, 3, TimeUnit.SECONDS);
}
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4秒后...");
singleScheduledExecutor.shutdown();
}
}
测试代码:
public class TestThreadPool {
//任务窃取线程池
static ExecutorService workStealingExecutor = Executors.newWorkStealingPool();
public static void main(String[] args) {
testWorkStealingExecutor();
}
//测试任务窃取线程池
private static void testWorkStealingExecutor() {
for (int i = 0; i < 10; i++) {//本机 CPU 8核,这里创建10个任务进行测试
final int index = i;
workStealingExecutor.execute(new Runnable() {
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " index:" + index);
}
});
}
try {
Thread.sleep(4000);//这里主线程不休眠,不会有打印输出
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4秒后...");
// workStealingExecutor.shutdown();
}
}
1、RUNNING
(1) 状态说明:线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理。
(2) 状态切换:线程池的初始化状态是RUNNING。换句话说,线程池一旦被创建,就处于RUNNING状态,并且线程池中的任务数为0!
2、 SHUTDOWN
(1) 状态说明:线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务。
(2) 状态切换:调用线程池的shutdown()接口时,线程池由RUNNING -> SHUTDOWN。
3、STOP
(1) 状态说明:线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。
(2) 状态切换:调用线程池的shutdownNow()接口时,线程池由(RUNNING or SHUTDOWN ) -> STOP。
4、TIDYING
(1) 状态说明:当所有的任务已终止,ctl记录的任务数量为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
(2) 状态切换:当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。
当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING。
5、 TERMINATED
(1) 状态说明:线程池彻底终止,就变成TERMINATED状态。
(2) 状态切换:线程池处在TIDYING状态时,执行完**terminated()**之后,就会由 TIDYING -> TERMINATED。
区别:
submit(Callable task)、submit(Runnable task, T result)、submit(Runnable task)
归属于ExecutorService接口。
execute(Runnable command)归属于Executor接口。ExecutorService继承了Executor。
submit()有返回值。
execute没有返回值。
public class ThreadPoolTest {
private String taskName;
public ThreadPoolTest(String taskName) {
this.taskName = taskName;
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("execute任务执行中");
}
});
System.out.println("----分界线----");
Future<String> future = executorService.submit(() -> {
System.out.println("submit任务执行中");
return "submit任务完成,这是执行结果";
});
try {
//如果future.get()返回null,任务完成
System.out.println(future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
System.out.println("任务失败原因:" + e.getCause().getMessage());
}
executorService.shutdown();
}
}
//输出:
----分界线----
execute任务执行中
submit任务执行中
submit任务完成,这是执行结果
public class ThreadPoolTest implements Runnable {
private String taskName;
public ThreadPoolTest(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
throw new RuntimeException("此处" + this.taskName + "抛出异常。");
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new ThreadPoolTest("task1"));
System.out.println("----分界线----");
Future<?> future = executorService.submit(new ThreadPoolTest("task2"));
try {
future.get();//如果future.get()返回null,任务完成
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
System.out.println("任务失败原因:" + e.getCause().getMessage());
}
executorService.shutdown();
}
}
锁的级别从低到高:
锁分级别原因:
无锁:
偏向锁:
轻量级锁:
重量级锁:
所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。如下图所示:
产生死锁的四个必要条件(互请不循):
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的【循环等待资源】关系。
ThreadLocal 是线程本地存储,在每个线程中都创建了一个 ThreadLocalMap 对象,每个线程可以访问自己内部 ThreadLocalMap 对象内的 value。
经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关了 B线程正在使用的 Connection; 还有 Session 管理 等问题。
ThreadLocal 使用例子:
public class TestThreadLocal {
//线程本地存储变量
private static final ThreadLocal<Integer> THREAD_LOCAL_NUM = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {//启动三个线程
Thread t = new Thread() {
@Override
public void run() {
add10ByThreadLocal();
}
};
t.start();
}
}
/**
* 线程本地存储变量加 5
*/
private static void add10ByThreadLocal() {
for (int i = 0; i < 5; i++) {
Integer n = THREAD_LOCAL_NUM.get();
n += 1;
THREAD_LOCAL_NUM.set(n);
System.out.println(Thread.currentThread().getName() + " : ThreadLocal num=" + n);
}
}
}
打印结果:启动了 3 个线程,每个线程最后都打印到 “ThreadLocal num=5”,而不是 num 一直在累加直到值等于 15
实现原理:
按照我们第一直觉,感觉 ThreadLocal 内部肯定是有个 Map 结构,key 存了 Thread,value 存了 本地变量 V 的值。每次通过 ThreadLocal 对象的 get() 和 set(T value) 方法获取当前线程里存的本地变量、设置当前线程里的本地变量。
而 JDK 的实现里面这个 Map 是属于 Thread,而非属于 ThreadLocal。ThreadLocal 仅是一个代理工具类,内部并不持有任何与线程相关的数据,所有和线程相关的数据都存储在 Thread 里面。ThreadLocalMap 属于 Thread 也更加合理。
还有一个更加深层次的原因,这样设计不容易产生内存泄露。
ThreadLocal 持有的 Map 会持有 Thread 对象的引用,只要 ThreadLocal 对象存在,那么 Map 中的 Thread 对象就永远不会被回收。ThreadLocal 的生命周期往往比线程要长,所以这种设计方案很容易导致内存泄露。
JDK 的实现中 Thread 持有 ThreadLocalMap,而且 ThreadLocalMap 里对 ThreadLocal 的引用还是弱引用(WeakReference),所以只要 Thread 对象可以被回收,那么 ThreadLocalMap 就能被回收。JDK 的这种实现方案复杂但更安全。
synchronized是jvm实现的一种互斥同步访问方式,底层是基于每个对象的监视器(monitor)来实现的。被synchronized修饰的代码,在被编译器编译后在被修饰的代码前后加上了一组字节指令。
在代码开始加入了monitorenter,在代码后面加入了monitorexit,这两个字节码指令配合完成了synchronized关键字修饰代码的互斥访问。
在虚拟机执行到monitorenter指令的时候,会请求获取对象的monitor锁,基于monitor锁又衍生出一个锁计数器的概念。
Java并发锁
当执行monitorenter时,若对象未被锁定时,或者当前线程已经拥有了此对象的monitor锁,则锁计数器+1,该线程获取该对象锁。
当执行monitorexit时,锁计数器-1,当计数器为0时,此对象锁就被释放了。那么其他阻塞的线程则可以请求获取该monitor锁。
作用:
区别:
package com.cn.test.thread.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
private Lock lock = new ReentrantLock();
/*
* 使用完毕释放后其他线程才能获取锁
*/
public void lockTest(Thread thread) {
lock.lock();//获取锁
try {
System.out.println("线程"+thread.getName() + "获取当前锁"); //打印当前锁的名称
Thread.sleep(2000);//为看出执行效果,是线程此处休眠2秒
} catch (Exception e) {
System.out.println("线程"+thread.getName() + "发生了异常释放锁");
}finally {
System.out.println("线程"+thread.getName() + "执行完毕释放锁");
lock.unlock(); //释放锁
}
}
public static void main(String[] args) {
LockTest lockTest = new LockTest();
//声明一个线程 “线程一”
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.lockTest(Thread.currentThread());
}
}, "thread1");
//声明一个线程 “线程二”
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.lockTest(Thread.currentThread());
}
}, "thread2");
// 启动2个线程
thread2.start();
thread1.start();
}
}
package com.cn.test.thread.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
private Lock lock = new ReentrantLock();
/*
* 尝试获取锁 tryLock() 它表示用来尝试获取锁,如果获取成功,则返回true,
* 如果获取失败(即锁已被其他线程获取),则返回false
*/
public void tryLockTest(Thread thread) {
if(lock.tryLock()) { //尝试获取锁
try {
System.out.println("线程"+thread.getName() + "获取当前锁"); //打印当前锁的名称
Thread.sleep(2000);//为看出执行效果,是线程此处休眠2秒
} catch (Exception e) {
System.out.println("线程"+thread.getName() + "发生了异常释放锁");
}finally {
System.out.println("线程"+thread.getName() + "执行完毕释放锁");
lock.unlock(); //释放锁
}
}else{
System.out.println("我是线程"+Thread.currentThread().getName()+"当前锁被别人占用,我无法获取");
}
}
public static void main(String[] args) {
LockTest lockTest = new LockTest();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.tryLockTest(Thread.currentThread());
}
}, "thread1");
//声明一个线程 “线程二”
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.tryLockTest(Thread.currentThread());
}
}, "thread2");
// 启动2个线程
thread2.start();
thread1.start();
}
}
package com.cn.test.thread.lock;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
private Lock lock = new ReentrantLock();
public void tryLockParamTest(Thread thread) throws InterruptedException {
if(lock.tryLock(3000, TimeUnit.MILLISECONDS)) {
//尝试获取锁 获取不到锁,就等3秒,如果3秒后还是获取不到就返回false
try {
System.out.println("线程"+thread.getName() + "获取当前锁"); //打印当前锁的名称
Thread.sleep(4000);//为看出执行效果,是线程此处休眠2秒
} catch (Exception e) {
System.out.println("线程"+thread.getName() + "发生了异常释放锁");
}finally {
System.out.println("线程"+thread.getName() + "执行完毕释放锁");
lock.unlock(); //释放锁
}
}else{
System.out.println("我是线程"+Thread.currentThread().getName()+"当前锁被别人占用,等待3s后仍无法获取,放弃");
}
}
public static void main(String[] args) {
LockTest lockTest = new LockTest();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
lockTest.tryLockParamTest(Thread.currentThread());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "thread1");
//声明一个线程 “线程二”
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
lockTest.tryLockParamTest(Thread.currentThread());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}, "thread2");
// 启动2个线程
thread2.start();
thread1.start();
}
}
1、原始构成:
2、使用方法:
3、等待是否可中断:
4、加锁是否公平:
5、锁绑定多个条件Condition:
import java.lang.reflect.Method;
public class InvokeTester
{
public int add(int param1, int param2)
{
return param1 + param2;
}
public String echo(String message)
{
return "Hello: " + message;
}
public static void main(String[] args) throws Exception
{
// 以前的常规执行手段
InvokeTester tester = new InvokeTester();
System.out.println(tester.add(1, 2));
System.out.println(tester.echo("Tom"));
System.out.println("---------------------------");
// 通过反射的方式
// 第一步,获取Class对象
// 前面用的方法是:Class.forName()方法获取
// 这里用第二种方法,类名.class
Class<?> classType = InvokeTester.class;
// 生成新的对象:用newInstance()方法
Object invokeTester = classType.newInstance();
System.out.println(invokeTester instanceof InvokeTester); // 输出true
// 通过反射调用方法
// 首先需要获得与该方法对应的Method对象
Method addMethod = classType.getMethod("add", new Class[] { int.class, int.class });
// 第一个参数是方法名,第二个参数是这个方法所需要的参数的Class对象的数组
// 调用目标方法
Object result = addMethod.invoke(invokeTester, new Object[] { 1, 2 });
System.out.println(result); // 此时result是Integer类型
//调用第二个方法
Method echoMethod = classType.getDeclaredMethod("echo", new Class[]{String.class});
Object result2 = echoMethod.invoke(invokeTester, new Object[]{"Tom"});
System.out.println(result2);
}
}
注意事项:
具体使用
package constxiong.interview;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* 测试序列化,反序列化
* @author ConstXiong
* @date 2019-06-17 09:31:22
*/
public class TestSerializable implements Serializable {
private static final long serialVersionUID = 5887391604554532906L;
private int id;
private String name;
public TestSerializable(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "TestSerializable [id=" + id + ", name=" + name + "]";
}
@SuppressWarnings("resource")
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("TestSerializable.obj"));
oos.writeObject("测试序列化");
oos.writeObject(618);
TestSerializable test = new TestSerializable(1, "ConstXiong");
oos.writeObject(test);
//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("TestSerializable.obj"));
System.out.println((String)ois.readObject());
System.out.println((Integer)ois.readObject());
System.out.println((TestSerializable)ois.readObject());
}
}
答:动态代理:在运行时,创建目标类,可以调用和扩展目标类的方法。
应用场景如:
1、JDK实现动态代理
2.CGLIB动态代理
有两种方式:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class MyUtil {
private MyUtil() {
throw new AssertionError();
}
public static <T> T clone(T obj) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();
// 说明:调用ByteArrayInputStream或ByteArrayOutputStream对象的close方法没有任何意义
// 这两个基于内存的流只要垃圾回收器清理对象就能够释放资源,
//这一点不同于对外部资源(如文件流)的释放
}
}
下面是测试代码:
import java.io.Serializable;
/**
* 人类
* @author
*
*/
class Person implements Serializable {
private static final long serialVersionUID = -9102017020286042305L;
private String name; // 姓名
private int age; // 年龄
private Car car; // 座驾
public Person(String name, int age, Car car) {
this.name = name;
this.age = age;
this.car = car;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
}
}
/**
* 小汽车类
* @author
*
*/
class Car implements Serializable {
private static final long serialVersionUID = -5713945027627603702L;
private String brand; // 品牌
private int maxSpeed; // 最高时速
public Car(String brand, int maxSpeed) {
this.brand = brand;
this.maxSpeed = maxSpeed;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
@Override
public String toString() {
return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
}
}
class CloneTest {
public static void main(String[] args) {
try {
Person p1 = new Person("Hao LUO", 33, new Car("Benz", 300));
Person p2 = MyUtil.clone(p1); // 深度克隆
p2.getCar().setBrand("BYD");
// 修改克隆的Person对象p2关联的汽车对象的品牌属性
// 原来的Person对象p1关联的汽车不会受到任何影响
// 因为在克隆Person对象时其关联的汽车对象也被克隆了
System.out.println(p1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
答:复制一个 Java 对象