Java多线程基础知识,面试足以。
一、线程状态:
1.new 初始
2.runnable 运行
3.blocked 阻塞
4.waiting 等待
5.time_waiting 超时等待
6.terminated 终止
二、创建线程方式:
1.继承Thread,重写父类run方法。
2.实现Runable接口,并实现run方法。
3.线程池Executors.newSingleThreadExecutor();
通过线程工厂newThread方法新建线程,实际为new Thread();
三、线程挂起、唤醒:
1.thread.suspend() -----旧版本废弃
thread.resume() -----旧版本废弃
会造成线程挂起,不会释放锁,造成死锁
2.wait() 暂停执行、放弃以获取的锁进入等待状态
notify() 随机唤醒一个在等待的线程
notifyAll() 唤醒所有在等待的线程。
四、线程中断
1.thread.stop(); 线程立刻终止,线程不安全。
2.thread.interrupt(); 自定义线程中断状态,不会立刻中断线程,设置该线程的中断状态位,即设置中断状态为true。
使用Thread.currentThread().isInterrupted()判断线程是否被中断
判断某个线程是否已被发送过中断请求,使用Thread.currentThread().isInterrupted()方法(因为它将线程中断标示位设置为true后,不会立刻清除中断标示位,即不会将中断标设置为false),而不要使用thread.interrupted()(该方法调用后会将中断标示位清除,即重新设置为false)方法来判断,下面是线程在循环中时的中断方式:
while(!Thread.currentThread().isInterrupted() && more work to do){
do more work
}
使用thread.interrupt(); 如何中断线程
如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait、1.5中的condition.await、以及可中断的通道上的 I/O 操作方法后可进入阻塞状态),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法(sleep、join、wait、1.5中的condition.await及可中断的通道上的 I/O 操作方法)调用处抛出InterruptedException异常,并且在抛出异常后立即将线程的中断标示位清除,即重新设置为false。抛出异常是为了线程从阻塞状态醒过来,并决定线程是否真的需要中断。
public class ThreadIntepurt extends Thread{
public static void main(String args[]) throws Exception {
ThreadIntepurt thread = new ThreadIntepurt();
System.out.println("Starting thread...");
thread.start();
Thread.sleep(3000);
System.out.println("Asking thread to stop...");
thread.interrupt();// 等中断信号量设置后再调用
Thread.sleep(3000);
System.out.println("Stopping application...");
}
public void run() {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("Thread running...");
try {
/*
* 如果线程阻塞,将不会去检查中断信号量stop变量,所 以thread.interrupt()
* 会使阻塞线程从阻塞的地方抛出异常,让阻塞线程从阻塞状态逃离出来,并
* 进行异常块进行 相应的处理
*/
Thread.sleep(1000);// 线程阻塞,如果线程收到中断操作信号将抛出异常
} catch (InterruptedException e) {
System.out.println("Thread interrupted...");
/*
* 如果线程在调用 Object.wait()方法,或者该类的 join() 、sleep()方法
* 过程中受阻,则其中断状态将被清除
*/
System.out.println(this.isInterrupted());// false
//中不中断由自己决定,如果需要真真中断线程,则需要重新设置中断位,如果
//不需要,则不用调用
// Thread.currentThread().interrupt();
}
}
System.out.println("Thread exiting under request...");
}
}
五、线程优先级、守护线程
1. 线程优先级
java定义线程的优先级为1-10,默认值为5,最大值为10。“高优先级线程”被CPU分配的概率高于“低优先级线程”,但是无论是是级别相同还是不同,线程调用都不会绝对按照优先级执行,每次执行结果都不一样,调度算法无规律可循,所以线程之间不能有先后依赖关系。
// 新建t1
Thread t1=new Thread(()->{
System.out.println("thread1");
},"thread1");
// 新建t2
Thread t2=new Thread(()->{
System.out.println("thread2");
},"thread2");
t1.setPriority(Thread.MIN_PRIORITY); //设置最小优先级
t2.setPriority(Thread.MAX_PRIORITY);//设置最打优先级
2. 守护线程
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 。
当JVM中只要有非守护线程运行,守护线程就回持续工作,只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Thread daemonTread = new Thread();
daemonThread.setDaemon(true);//设置为守护线程
JVM中守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
六、线程安全性问题
成因:
1)多线程环境
2)多个线程操作同一共享资源
3)对该共享资源进行非原子性操作
如何避免:
打破成因成任意一点即可避免。
1:多线程环境改为单线程(必要的代码加锁访问);
2:多个线程访问同一资源- -不共享资源(ThreadLocal、不共享、操作 无状态化、共享资源不可变);
3:对共享资源进行非原子性操作- - 将非原子性操作改成原子性操作(加锁、使用JDK自带的原子性操作的类、JUC提供的相应的并发工具类)。
七、原子性操作
一个操作或者多个操作,要么全部执行并且执行过程不会被任何元素打断,要么就不执行。
八.深入理解synchronized
1)内置锁
每个java对象都可以做一个实现同步锁,这些所被称内置锁。线程进入代码块或者方法的时候会自动获取该锁,在退出同步代码块或者方法的时候会释放该锁。
获得内置锁的唯一途径就是进入这个锁保护的同步代码块或方法。
2)互斥锁
内置锁是一个互斥锁,这就是意味这最多只有一个线程能够获得该锁,当线程A尝试去获取线程B持有的内置锁时,线程A必须等待或者阻塞,直到线程B释放这个锁,如果线程B不释放这个锁,线程A将一直等待下去。
3)修饰普通方法
锁住类的实例。
4)修饰静态方法
锁住类。
5)修饰代码块
锁住一个对象,synchronized(lock) 括号内的内容。
九、深入理解lock接口
lock是一个接口,内部提供6中方法。
- lock():lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
- tryLock():有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
- tryLock(long time, TimeUnit unit):和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
- lockInterruptibly():当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
- unlock():释放锁,一般需要加锁的资源(临界区)需要放到try中,而unlock()要放到finally中。
- newCondition():获取新的条件,可精准控制线程,取代Object监视器方法( wait , notify和notifyAll )。
十、synchronized和Lock锁区别
- synchronized是java关键字,lock是一个对象。
- synchronized无法判断是否获取锁的状态,Lock可以判断是否能获取到锁(tryLock() 方法)。
- synchronized是自动释放锁(a线程在执行完毕后释放锁,b线程执行过程中发生异常也会自动释放锁),lock需要放在finally中手动释放锁,否则容易造成死锁。
- synchronized无法中断等待线程(a 线程如果阻塞了,b线程需要一直等待),lock可以自定义控制b线程是否需要等待。
- synchronized的锁可重入,非公平,不可中断,Lock锁可重入,可判断是否公平,可中断。
- Lock锁适合大量同步的代码的同步问题,synchronized锁适合代码少量的同步问题。
-----------------------------------------2021.3.15 11点更新至此-----------------------------