五种,分别是
1.使用Thread创建
2.使用Runnable接口实现run方法再使用Thread开启线程
3.使用Callable接口实现call方法将它放入FutureTask中实现然后开启线程
4.因为Runnable只有一个方法声明,所以它为函数式接口,可以用Lambda表达式写。
5.使用线程池开启线程。
因为看下面两张图
它的底层都是用的LinkedBlockingQueue都是无界的(?)所以容易使得内存溢出(oom),而且它没有办法为线程命名不利于排查问题,所以不推荐使用。
表示此线程池正在工作,能过接受新任务的派送并处理
表示线程池不接受新任务,但是可以继续处理旧任务。
表示线程池不接受新任务,并且久任务也会停下处理。
表示线程池所有的线程下的任务都已经停下了,然后回调用TERMINATED()的方法。我们可以重写TERMINATED()方法来处理业务逻辑。
表示线程池完全关闭。
看上图我们可以看到,线程类下有个对象属性叫做ThreadLocal,所以我们这个线程需要往里面存啥就往里放啥。
那为啥网上经常说ThreadLocal每次放完不用的时候需要将它remove呢?
看上面这张图,对于这个map中的Entry每个都继承了弱引用,所以不需要担心key值的泄露问题,但是它的value是一个强引用,有人就会明白了,你如果每次使用完 ThreadLocal将它设置为空不就得了,因为这样其value的也会因为强引用的消失而被gc回收掉,没错正常情况是这样的,remove一定就是这么做的,但是我们看一下源码真的是这么做的嘛?
我们发现会比我们想的复杂的多。因为其实我们没有考虑到一种情况,那就是如果我们这条线程迟迟没有结束,但是我们这个里面的变量其实早就不用了,为了不占用内存,其实我们需要在不需要它的时候手动的将Entary所对应的value删掉。
如果是公平锁:那么每个线程都是乖乖排队去取锁。
如果是非公平锁:那么每个线程就都会去插队
ps:这里我们要注意,非公平和公平只体现在争锁的阶段,但是不体现在唤醒锁的阶段,唤醒锁是先唤醒排在第一个的锁上的。
偏向锁:此时所只有一个线程在使用,所以对于这个线程来讲这个只为他所用的锁就是偏向锁。
轻量级锁:此时有两个线程来竞争这个锁,锁于是就升级了,变为轻量级锁,先进入的线程工作中。外面的那个线程会一直自旋(这里也称为自旋锁),也就是说没要到锁的哪个线程会一直来问这个锁,里面是否为空。
重量级锁:但是如果外面的那个锁一直问不到,锁就会升级到重量级锁,此时外面那个线程就会进入睡面状态,等锁来唤醒他。
ps:注意线程从睡眠到运行这是要消耗资源的(需要操作系统去实现,比较消耗时间),如果在短时间内,线程自旋可以拿到锁,那么这个代价是要比线程睡眠然后再将它唤醒是划算的多的,但是如果时间长了,自旋消耗的资源也会超过睡眠然后唤醒所消耗的资源。
可重入的意思就是偏向锁。ReentrantLock无法锁升级。
看下面的图,就明白为啥了。
每个应用中都有一个类,那么如果这两个应用开启之后,用的又都是同一个类加载器,那么就会出现问题,因为你原先搞出来的类可能作用不同,但是因为现在同名,所以你声明的两个类可能就只会生出来一个,此时你的另一个类就没有意义了。
jdk就是说我们程序员需要用的类库还有将Java代码编译为Java字节码的编译器,还有就是Java运行的环境。
jre就是我们客户拿到我们的Java程序需要的,只要有它就可以运行编译完成的Java字节码文件。包括jvm,还有运行jvm运行需要的类库。
jvm是Java可以跨平台的主要原因,因为Java字节码并不能直接给操作系统来运行需要将Java字节码准换为机器指令才能让操作系统执行,但是同时jvm就需要为不同的操作系统准备不同的版本,因为每个操作系统上的机器操作指令不同。
hashcode只是将类的信息hash后做一次比较,但是会出现重复的hash值。所以我们一般都是先用hash判断之后,
1.如果两个对象是hash值不相等一定不是相同的对象
2.如果两个对象hash值都相等不一定是相同的对象
3.如果是同一个对象,那么他们的hash一定相同。