少年,思无邪,最最动人。
1.Java中有哪几种创建线程的方式
1.1 继承Thread类
代码示例
class HelloWorld01 extends Thread{
@Override
public void run() {
System.out.println("这是继承 Thread 类方式实现多线程!");
}
}
public class CreateThread01 {
public static void main(String[] args) {
new HelloWorld01().start();
System.out.println("这是主线程的业务逻辑!");
}
}
运行结果
这是主线程的业务逻辑!
这是继承 Thread 类方式实现多线程!
缺点:类是单继承的。
1.2 实现Runnable接口
代码示例
class HelloWorld02 implements Runnable{
@Override
public void run() {
System.out.println("这是实现 Runnable 接口方式实现多线程!");
}
}
public class CreateThread02 {
public static void main(String[] args) {
new Thread(new HelloWorld02()).start();
System.out.println("这是主线程的业务逻辑!");
}
}
运行结果
这是主线程的业务逻辑!
这是实现 Runnable 接口方式实现多线程!
优缺点:解决单继承问题,但是没有回调。
1.3 实现Callable接口
代码示例
class HelloWorld03 implements Callable {
@Override
public Object call() throws Exception {
return "这是实现 Runnable 接口方式实现多线程!";
}
}
public class CreateThread03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask futureTask = new FutureTask<>(new HelloWorld03());
new Thread(futureTask).start();
System.out.println(futureTask.get());
System.out.println("这是主线程的业务逻辑!");
}
}
运行结果
这是实现 Runnable 接口方式实现多线程!
这是主线程的业务逻辑!
1.4 创建线程池
代码示例
class HelloWorld04 extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class CreateThread04 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
executorService.execute(new HelloWorld04());
}
executorService.shutdownNow();
}
}
运行结果
pool-1-thread-1
pool-1-thread-3
pool-1-thread-4
pool-1-thread-5
pool-1-thread-2
2.为什么不建议使用Executors来创建线程池
查看 JDK 源码
可以看到,创建的队列为 LinkedBlockingQueue ,这是一个无界的阻塞队列。使用该线程池执行任务,如果任务过多就会不断地添加到这个队列中,任务越多占用的内存就越多,最终可能耗尽内存,导致OOM。
除此之外,使用这种方式创建线程池不能自定义线程的名字,不利于排查问题。
3.线程池有哪几种状态?每种状态分别表示什么
3.1 RUNNING
Accept new tasks and process queued tasks
表示线程池正常运行,既能接受新的任务,也会正常处理队列中的任务。一切正常状态。
3.2 SHUTDOWN
Don't accept new tasks, but process queued tasks
当调用线程池的 shutdown()方法时,线程池就进入 SHUTDOWN 状态,表示线程池处于正在关闭状态,此状态下线程池不会接受新任务,但是会继续把队列中的任务处理完。
3.3 STOP
Don't accept new tasks, don't process queued tasks, and interrupt in-process tasks
当调用线程池的 shutdownnow()方法时,线程池马上停止,此状态下线程池既不会接受新任务了,也不会处理队列中的任务,并且正在运行的线程也会被中断。
3.4 TIDYING
All tasks have terminated, workerCount is zero, the thread transitioning to state TIDYING will run the terminated() hook method
线程池中没有线程在运行后,线程池的状态。
3.5 TERMINATED
当调用线程池的 terminated()方法时,线程池的状态。
4.Sychronized 和 ReentrantLock 有哪些不同点
Sychronized | ReentrantLock |
---|---|
Java中的一个关键字 | JDK提供的一个类 |
自动加锁与释放锁 | 需要手动加锁与释放锁 |
JVM层面的锁 | API层面的锁 |
非公平锁 | 公平锁或非公平锁 |
锁的是对象,锁信息保存在对象头中 | int类型的state标识来标识锁的状态 |
底层有锁升级的过程 | 没有锁升级的过程 |
5.ThreadLocal有哪些应用场景,它的底层是如何实现的
ThreadLocal是Java中提供的线程本地缓存机制,可以利用该机制将数据缓存在某个线程内部,该线程可以在任意时刻、任意方法中获取缓存的数据。
ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对象)中都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值。
代码示例
public class ThreadLocalDemo {
// 定义一个ThreadLocal,底层是Map实现
private static ThreadLocal<String> local = new ThreadLocal<>();
public static void a() {
local.set("路明非");
}
public static void b() {
String s = local.get();
System.out.println(s);
// 使用完了之后,移除,防止内存泄漏
local.remove();
}
public static void main(String[] args) {
a();
b();
}
}
注意:如果在线程池中使用ThreadLocal会造成内存泄漏,因为当 ThreadLocal 对象使用完之后,应该要把设置的key,value也就是Entry对象进行回收,但线程池中的线程不会回收,而线程对象是通过强引用指向ThreadLocalMap,ThreadLocalMap也是通过强引用指向Entry对象,线程不被回收,Entry对象也就不会被回收,从而出现内存泄漏,解决方法是,手动调用remove方法,手动清除Entry对象。
ThreadLocal经典的应用场景就是连接管理。