Java知识总结

https://www.bilibili.com/video/BV1ys4y1S7Lc

1、Java中线程的实现方式

为什么说本质上只有一种实现线程的方式?实现 Runnable 接口究竟比继承 Thread 类实现线程好在哪里?

  1. 实现 Runnable 接口

public class RunnableThread implements Runnable {

    @Override

    public void run() {

        System.out.println('用实现Runnable接口实现线程');

    }

}

第 1 种方式是通过实现 Runnable 接口实现多线程,如代码所示,首先通过 RunnableThread 类实现 Runnable 接口,然后重写 run() 方法,之后只需要把这个实现了 run() 方法的实例传到 Thread 类中就可以实现多线程。

  1. 继承 Thread 类

public class ExtendsThread extends Thread {

    @Override

    public void run() {

        System.out.println('用Thread类实现线程');

    }

}

第 2 种方式是继承 Thread 类,如代码所示,与第 1 种方式不同的是它没有实现接口,而是继承 Thread 类,并重写了其中的 run() 方法。相信上面这两种方式你一定非常熟悉,并且经常在工作中使用它们。

  1. 线程池创建线程

那么为什么说还有第 3 种或第 4 种方式呢?我们先来看看第 3 种方式:通过线程池创建线程。线程池确实实现了多线程,比如我们给线程池的线程数量设置成 10,那么就会有 10 个子线程来为我们工作,接下来,我们深入解析线程池中的源码,来看看线程池是怎么实现线程的?

static class DefaultThreadFactory implements ThreadFactory {

    DefaultThreadFactory() {

        SecurityManager s = System.getSecurityManager();

        group = (s != null) ? s.getThreadGroup() :

            Thread.currentThread().getThreadGroup();

        namePrefix = "pool-" +

            poolNumber.getAndIncrement() +"-thread-";

    }

    public Thread newThread(Runnable r) {

        Thread t = new Thread(group, r,

                 namePrefix + threadNumber.getAndIncrement(),0);

        if (t.isDaemon())

            t.setDaemon(false);

        if (t.getPriority() != Thread.NORM_PRIORITY)

            t.setPriority(Thread.NORM_PRIORITY);

        return t;

    }

}

对于线程池而言,本质上是通过线程工厂创建线程的,默认采用 DefaultThreadFactory ,它会给线程池创建的线程设置一些默认值,比如:线程的名字、是否是守护线程,以及线程的优先级等。但是无论怎么设置这些属性,最终它还是通过 new Thread() 创建线程的 ,只不过这里的构造函数传入的参数要多一些,由此可以看出通过线程池创建线程并没有脱离最开始的那两种基本的创建方式,因为本质上还是通过 new Thread() 实现的。

  1. 有返回值的 Callable 创建线程

class CallableTask implements Callable<Integer> {

    @Override

    public Integer call() throws Exception {

      return new Random().nextInt();

    }

}

//创建线程池

ExecutorService service = Executors.newFixedThreadPool(10);

//提交任务,并用 Future提交返回结果

Future<Integer> future = service.submit(new CallableTask());

第 4 种线程创建方式是通过有返回值的 Callable 创建线程,Runnable 创建线程是无返回值的,而 Callable 和与之相关的 Future、FutureTask,它们可以把线程执行的结果作为返回值返回,如代码所示,实现了 Callable 接口,并且给它的泛型设置成 Integer,然后它会返回一个随机数。

但是,无论是 Callable 还是 FutureTask,它们首先和 Runnable 一样,都是一个任务,是需要被执行的,而不是说它们本身就是线程。它们可以放到线程池中执行,如代码所示, submit() 方法把任务放到线程池中,并由线程池创建线程,不管用什么方法,最终都是靠线程来执行的,而子线程的创建方式仍脱离不了最开始讲的两种基本方式,也就是实现 Runnable 接口和继承 Thread 类。

实现 Runnable 接口比继承 Thread 类实现线程要好

下面我们来对刚才说的两种实现线程内容的方式进行对比,也就是为什么说实现 Runnable 接口比继承 Thread 类实现线程要好?好在哪里呢?

首先,我们从代码的架构考虑,实际上,Runnable 里只有一个 run() 方法,它定义了需要执行的内容,在这种情况下,实现了 Runnable 与 Thread 类的解耦,Thread 类负责线程启动和属性设置等内容,权责分明。

第二点就是在某些情况下可以提高性能,使用继承 Thread 类方式,每次执行一次任务,都需要新建一个独立的线程,执行完任务后线程走到生命周期的尽头被销毁,如果还想执行这个任务,就必须再新建一个继承了 Thread 类的类,如果此时执行的内容比较少,比如只是在 run() 方法里简单打印一行文字,那么它所带来的开销并不大,相比于整个线程从开始创建到执行完毕被销毁,这一系列的操作比 run() 方法打印文字本身带来的开销要大得多,相当于捡了芝麻丢了西瓜,得不偿失。如果我们使用实现 Runnable 接口的方式,就可以把任务直接传入线程池,使用一些固定的线程来完成任务,不需要每次新建销毁线程,大大降低了性能开销。

第三点好处在于 Java 语言不支持双继承,如果我们的类一旦继承了 Thread 类,那么它后续就没有办法再继承其他的类,这样一来,如果未来这个类需要继承其他类实现一些功能上的拓展,它就没有办法做到了,相当于限制了代码未来的可拓展性。

综上所述,我们应该优先选择通过实现 Runnable 接口的方式来创建线程。

  1. Java中线程的状态

在java中,线程的状态六种,对应着枚举类型 Thread.State 的六个枚举常量:NEW 、BLOCKED、RUNNABLE、WAITING 、TIMED_WAITING、TERMINATED

1. NEW : 新建状态,至今尚未启动的线程的状态。

2. BLOCKED: 阻塞状态,受阻塞并且正在等待监视器锁的某一线程的线程状态。

3. RUNNABLE: 可运行线程的线程状态。这里其实合并了两种状态(RUNNING、RUNABLE)

4. WAITING : 等待状态,表示线程进入状态。进入此状态后,会无限等待,直到其他线程做出一些特定的动作(唤醒通知、中断通知)才会再次运行。

5. TIMED_WAITING : 计时等待状态,此状态与 WAITING 状态有些类似,但它是有时间限制的,即只会等待一段指定的时间,当时间到来前,没有被唤醒或或中断,那么时间到来了,就自动"醒来",进入RUNNABLE状态。

6. TERMINATED : 终止状态,已终止线程的线程状态。

线程在自身的生命周期中,并不是固定地处于某个状态,而是随着代码的执行在不同的状态之间进行切换,java线程状态变迁如下图所示:

注意以下几点:

1、当线程的run方法结束时,该线程就完成。即线程死亡。但注意,此时线程的状态是死亡了,而且是不可以复活的,但是死亡的线程的对象并没有立即消失(因为Thread是一个类,是一个记录、操作线程的类),特别是在别处被引用下,你可以继续调用这个Thread实例上的大部分方法,而对线程操作的方法基本上都会抛异常,如:start()、wait()、notify()不可以再调用;

2、只要线程启动了,也就是调用start()方法,也就永远就不能再次启动;

3、线程执行的顺序与线程启动的顺序无关,start()线程启动,线程首先由新建状态变成就绪状态,并不是直接就是运行状态,即不会马上就运行,何时进入CPU运行,得看调度算法;

4、java 将操作系统当中的就绪 与 运行两个状态合并为运行状态。

5、线程进入synchronize修饰的方法或代码块中,线程的状态变为阻塞状态。但如果线程进入的是Lock接口的代码块中,却是等待状态。这是因为Lock对于阻塞的本质实现是使用了LockSupport类中的相关方法。

6、WAITING 、TIMED_WAITING 两种等待状态都是可以被"中断"打断的,所以那些将线程变为等待状态的方法,如wait()、sleep等都要 捕获 InterruptedException异常。

3、Java中如何停止线程

1)使用退出标志,使线程正常退出,也就是当 run() 方法完成后线程中止。

在 run() 方法执行完毕后,该线程就终止了。但是在某些特殊的情况下,run() 方法会被一直执行;比如在服务端程序中可能会使用 while(true) { … } 这样的循环结构来不断的接收来自客户端的请求。此时就可以用修改标志位的方式来结束 run() 方法。
2)使用 stop() 方法强行终止线程,但是不推荐使用这个方法,该方法已被弃用。

通过查看 JDK 的 API,我们会看到 java.lang.Thread 类型提供了一系列的方法如 start()、stop()、resume()、suspend()、destory()等方法来管理线程。但是除了 start() 之外,其它几个方法都被声名为已过时(deprecated)。

虽然 stop() 方法确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且该方法已被弃用,最好不要使用它。

为什么弃用stop:

调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。

调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。
3)使用 interrupt 方法中断线程。

现在我们知道了使用 stop() 方式停止线程是非常不安全的方式,那么我们应该使用什么方法来停止线程呢?答案就是使用 interrupt() 方法来中断线程。

需要明确的一点的是:interrupt() 方法并不像在 for 循环语句中使用 break 语句那样干脆,马上就停止循环。调用 interrupt() 方法仅仅是在当前线程中打一个停止的标记,并不是真的停止线程。

也就是说,线程中断并不会立即终止线程,而是通知目标线程,有人希望你终止。至于目标线程收到通知后会如何处理,则完全由目标线程自行决定。这一点很重要,如果中断后,线程立即无条件退出,那么我们又会遇到 stop() 方法的老问题。

事实上,如果一个线程不能被 interrupt,那么 stop 方法也不会起作用。

public boolean Thread.isInterrupted() //判断是否被中断

public static boolean Thread.interrupted() //判断是否被中断,并清除当前中断状态

这两个方法使得当前线程能够感知到是否被中断了(通过检查标志位)。

4、sleep和wait的方法区别

5、并发编程的三大特性

6、什么是CAS?有什么优缺点?

7、@Contended注解有什么用?

8、Java中的四种引用类型 强 软 弱 虚

9、threadlocal的内存泄漏问题?

10、Java中锁的分类

11、synchronized在jdk1.6的优化?

12、synchronized的实现原理?

13、什么是AQS?

14、AQS唤醒节点时,为何从后往前找?

15、reentrantLock和synchronized的区别?

16、reentrantReadWriteLock的实现原理?

17、jdk中提供了哪些线程池?

18、线程池的核心参数有什么?

19、线程池的状态?

20、线程池的执行流程?

21、线程池添加工作线程的流程?

22、线程池为何要构建空任务的非核心线程?

23、线程池使用完为何必须shutdown?

24、线程池的核心参数到底如何设置?

25、ConcurrentHashMap的散列算法?

26、ConcurrentHashMap初始化数组的流程?

27、ConcurrentHashMap扩容的流程?

28、ConcurrentHashMap读取数据的流程?

29、ConcurrentHashMap中计数器的实现

30、谈谈对spring的理解

31、spring中应用到的设计模式有哪些?

单利模式、原型模式、模板模式

32、谈谈Autowired和Resource两个注解的区别?

33、谈谈spring常用的注解

34、谈谈对循环依赖的理解

35、spring是如何解决循环依赖的问题的

36、spring是解决构造注入的循环依赖问题的

37、spring的循环依赖为什么需要三级缓存?

38、spring中bean对象的生命周期

39、spring中支持的作用域有几种?

40、spring中事务的隔离级别介绍

41、spring中事务的隔离级别

42、spring中事务的本质

43、谈谈beanfactory和applicationContext的理解

44、谈谈beanfactorypostprocessor的理解

45、谈谈beanpostprocessor的理解

46、谈谈对springmvc框架的理解,spring和springmvc的关系?

47、谈谈delegatingFilterProxy的理解

48、谈谈springboot自动装配原理的理解

49、谈谈对import注解的理解

50、谈谈对deferredimportselector的理解

51、谈谈springboot中的bootstrap.yml文件的作用

52、谈谈indexed注解的作用

53、如何对属性文件中的账户密码进行加密?

54、@Componet。@Controller。@Repository。@Service注解的区别

55、有哪些通知类型(advice)

56、介绍下spring中的依赖注入

57、Spring中的bean单利对象是否线程安全?

58、Redis为什么快?

59、Redis适合的应用场景

  1. Redis6.0之前为什么一直不使用多线程
  2. Redis6.0为什么要引入多线程
  3. Redis有哪些高级功能?
  4. 怎么理解redis中的事务?
  5. Redis过期策略以及内存淘汰机制?
  6. 什么事缓存穿透?如何避免?
  7. 什么事缓存雪崩?如何避免?
  8. 使用redis如何设计分布式锁?
  9. 怎么使用redis实现消息队列?
  10. 什么事bigkey?会有什么影响?
  11. Redis如何解决key冲突?
  12. 怎么提高缓存命中率?
  13. Redis持久化方式有哪些?有什么区别?
  14. Redis为什么需要把所有数据放到内存中?
  15. 如何保证缓存与数据库双写时的数据一致性
  16. Redis集群方案应该怎么做?
  17. Redis集群方案什么情况下会导致整个集群不可用?
  18. 说一说redis哈希槽的概念?
  19. Redis集群会有写操作丢失吗?为什么?
  20. Redis常见性能问题和解决方案有哪些?
  21. Redis热数据和冷数据是什么?
  22. 什么情况下可能导致redis阻塞?
  23. Redis过期策略有哪些?LRU算法你了解吗?
  24. Jvm中的类加载问题
  25. 谈下你对类加载器的理解
  26. 双亲委派机制
  27. 运行时数据区
  28. 栈帧结构与动态链接
  29. 为什么java堆要进行分代设计
  30. 老年代的担保机制
  31. 对象的创建过程
  32. 持久代与元空间以及方法区的关系
  33. CMS垃圾收集器
  34. G1垃圾收集器

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值