1.java应用程序的main方法是一个线程,是jvm启动的时候第一个调用的,线程的名字叫main
2.实现一个线程,必须创建一个Thread实例,override run方法,并且调用 start 方法
3.在jvm启动后,实际上是有多个线程,但是至少有一个是非守护线程;
4.当调用线程的start方法的时候,此时至少有2个线程,一个是自行实现的线程,还有一个是调用start的线程;
5.线程的生命周期分为new,runnable(就绪),running,block,terminated(终止)
6.join方法的意思是积极的获取到cpu,让其他的线程去等待;
7.join方法的原理:没有说到
8.打断方法 interrupt() 是可以打断线程的,跟使用标志位结束线程是一样的逻辑,你需要在方法中去判断的这个属性是true还是false;
9.打断方法你得去判断具体是要打断谁,判断打断谁很重要,比如说我想打断 join 方法,那就要打断调用join方法的线程;
10.可能有的时候会没有办法执行这个判断,就比如说我去连接一个地址,结果连了10分钟都没有连上,那我在考虑到这种情况的时候就需要这样去做:
1:创建线程a,run方法创建线程b,设置b为守护线程,让b去执行连接,a等待10分钟后结束或者b结束后结束,这样无论b是超时还是正常退出这段程序都没有问题
11.线程锁,线程锁我肯定就是锁的内容越少越好,那也就是说如果我只要2个方法不冲突我就可以创建2个object,然后 锁住这个 object 就可以了;但是最好不要锁住当前类,因为这样的话后期扩展就可能跟你的方法有冲突;
12.如何判断我锁的对象是同一个对象啊,那就是锁住这个对象的时候调用它看是马上响应还是之前的执行完了之后再去响应;
13.静态锁,锁的是一个class,具体怎么去验证这个锁那就需要你在类中写一个 静态块 ,在这个静态块 中让线程 sleep 时间越长,后面执行的方法也就跟着越长,由此证明静态锁锁住的是class对象;
14 sleep和wait方法的不同点:(翻译图片上的)
1.sleep方法是线程的方法,但是wait是Object的方法
2.sleep不会释放锁(Lock)但是wait方法会释放锁
3.使用sleep不需要有一个监视器(monitor)锁(Lock),但是wait方法需要
4.sleep方法不需要重新唤醒线程,但是wait方法需要,(wait(10) wait有个重载方法除外)
15.如何给自己的线程注入钩子程序:
一句话解释什么是钩子程序,钩子程序就是监听,就是当某个东西执行了某个操作的时候就执行钩子;
Runtime.getRuntime().addShutdownHook(new Thread(()->{
// do something
}));
16.如何捕获已经结束了的线程的异常?
Thread 类有个 setUncaughtExceptionHandler(Thread t,Exception e);
在这个方法中可以捕获异常,当然要在线程启动之前添加;
17.线程的3个原则:
1、原子性:这里是说语句的原子性 a = 0 就是原子性的。 a++ 1:读取a的值,2:a的值加一,3:赋值,这就不是原子性的。
2、可见性:volatile 关键字保证了可见性,值是存在堆内存中的,cpu一般是先读一二三级缓存,最后才是读内存(堆);
3、有序性:happens-before relationship 翻译过来是就是3.1。 jvm在运行的时候为了效率最优,你先写的语句在不影响结果的前提下不一定先执行。
3.1、编写在前面的发生在编写在后面的
3.2、unlock必须发生在lock之后
3.3、volatile 修饰的变量,对该变量的写操作先于该变量的读操作;
3.4、传递规则,操作A先于B,B先于C,那么A肯定先于C;
3.5、启动规则,start方法肯定先于其他操作;
3.6、中断规则,线程的中断interrupt,必须发生在捕获该动作之前
3.7、对象的销毁规则,一个对象的初始化操作必须发生在 finalize 之前
3.8、线程的终结规则,所有的操作发生在销毁之前
其中 5,6,7,8 都是废话,显而易见的;
volatile 关键字修饰共享变量后则:
1.保证了不同线程之前的可见性
2.禁止对其进行重排序,也就是保证了有序性
3.并未保证其原子性
18.多线程的读写锁如何实现
一般读的线程比写的线程多,这个时候再轮询的时候就要偏爱一下写的线程
具体情况请看b站视频把