1.并发和并行有和区别
并发:多个任务在同一个CPU上,按照细分的时间片轮流交替执行,由于时间很短,看上去好像是同时进行的。
并行:单位时间内,多个处理器或多核处理器同时处理多个任务,是真正意义上的同时进行。
串行:有n个任务,由一个线程按照顺序执行。
2.线程与进程
进程:内存中运行的运用程序,每个进程都有自己独立的内存空间,一个进程可以由多个线程,例如在Windows系统中,xxx.exe就是一个进程。
线程:进程中的一个控制单元,负责当前进程中的程序执行,一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可以共享数据。
3.死锁
死锁是指两个或两个以上进程(线程)在执行过程中,由于竞争资源或由于彼此通信造成的一种堵塞的现象,若无外力的作用下,都将无法推进,此时的系统处于死锁状态。
四个必要条件
1.互斥条件:线程(进程)对所分配的资源具有排它性,即一个资源只能被一个进程占用,直到该进程被释放。
2.请求与保持条件:一个进程(线程)因请求被占有资源而发生堵塞时,对已获取的资源保持不放。
3.不剥夺条件:线程(进程)已获取的资源在未使用完之前不能被其他线程强行剥夺,只有等自己使用完才释放资源。
4.循环等待条件:当发生死锁时,所等待的线程(进程)必定形成一个环路,死循环造成永久堵塞。
如何避免死锁
我们只需破坏形参死锁的四个必要条件之一即可。
1.破坏互斥条件:无法破坏,我们的🔒本身就是来个线程(进程)来产生互斥
2.破坏请求与保持条件:一次申请所有资源
3.破坏不剥夺条件:占有部分资源的线程尝试申请其它资源,如果申请不到,可以主动释放它占有的资源。
4.破坏循环等待条件:按序来申请资源。
3.创建线程的四种方式
1.继承Thread类
public class MyThread extends Thread{//继承Thread类
@Override
public void run(){
}
}
public class Main {
public static void main(String[] args){
new MyThread().start();//创建并启动线程
}
}
朗姆塔表达式写法
Thread t1 = new Thread(){
@Override
public void run() {
System.out.println("第1种方式:new Thread 1");
}
};
t1.start();
2.实现Runnable接口
public class MyThread implements Runnable{//继承Thread类
@Override
public void run(){
}
}
public class Main {
public static void main(String[] args){
MyThread myThread=new MyThread();
new Thread(myThread).start();//创建并启动线程
}
}
朗姆塔表达式写法
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("第2种方式:new Thread 2");
}
});
t2.start();
3.实现Callable接口
call()方法可以有返回值
call()方法可以声明抛出异常
public static class MyThread3 implements Callable{
@Override
public Object call() throws Exception {
return 5;
}
}
public class Main {
public static void main(String[] args){
MyThread3 th=new MyThread3();
//使用FutureTask类来包装Callable对象
FutureTask<Integer> future=new FutureTask<Integer>(
(Callable<Integer>)()->{undefined
return 5;
}
);
new Thread(future,"有返回值的线程").start();//实质上还是以Callable对象来创建并启动线程
try{
System.out.println("子线程的返回值:"+future.get());//get()方法会阻塞,直到子线程执行结束才返回
}catch(Exception e){undefined
ex.printStackTrace();
}
}
}
朗姆塔表达式写法
FutureTask<String> ft = new FutureTask<>(new Callable<String>() {
@Override
public String call() throws Exception {
String result = "第3种方式:new Thread 3";
return result;
}
});
Thread t3 = new Thread(ft);
t3.start();
4.Executors工具类创建线程池
朗姆塔表达式写法
ExecutorService pool=Executors.newCachedThreadPool();
//可以用继承Thread类或者实现Runnable接口 只是启动方式不同
Thread t4 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("第2种方式:new Thread 2");
}
});
pool.execute(t4);
4.线程声明周期的6种状态
新创建:又称初始化状态,这个时候Thread才刚刚被new出来,还没有被启动。
可运行状态:表示已经调用Thread的start方法启动了,随时等待CPU的调度,此状态又被称为就绪状态。
被终止:死亡状态,表示已经正常执行完线程体run()中的方法了或者因为没有捕获的异常而终止run()方法了。
计时状态:调用sleep(参数)或wait(参数)后线程进入计时状态,睡眠时间到了或wait时间到了,再或者其它线程调用notify并获取到锁之后开始进入可运行状态。另一种情况,其它线程调用notify没有获取到锁或者wait时间到没有获取到锁时,进入堵塞状态。
无线等待状态:获取锁对象后,调用wait()方法,释放锁进入无线等待状态
锁堵塞状态:wait(参数)时间到或者其它线程调用notify后没有获取到锁对象都会进入堵塞状态,只要一获取到锁对象就会进入可运行状态。
5.Java线程同步和线程调度的相关方法
wait():调用后线程进入无限等待状态,并释放所持对象的锁
sleep():使一个线程进入休眠状态(堵塞状态),带有对象锁,是一个静态方法,需要处理InterruptException异常。
notify():唤醒一个处于等待状态的线程(无线等待或计时等待),如果多个线程在等待,并不能确切的唤醒一个线程,与JVM确定唤醒那个线程,与其优先级有关。
notityAll():唤醒所有处于等待状态的线程,但是并不是将对象的锁给所有的线程,而是让它们去竞争,谁先获取到锁,谁先进入就绪状态。
6.sleep()和wait()有什么区别
类不同:sleep()是Thread下的静态方法,wait()是Object类下的方法
是否释放锁:sleep()不释放锁,wait()释放锁
用处不同:wait()常用于线程间的通信,sleep()常用于暂停执行。
用法不同:wait()用完后,线程不会自动执行,必须调用notify()或notifyAll()方法才能执行,sleep()方法调用后,线程经过过一定时间会自动苏醒,wait(参数)也可以传参数使其苏醒。它们苏醒后还有所区别,因为wait()会释放锁,所以苏醒后没有获取到锁就进入堵塞状态,获取到锁就进入就绪状态,而sleep苏醒后之间进入就绪状态,但是如果cpu不空闲,则进入的是就绪状态的堵塞队列中。
7. 如何停止一个正在运行的线程?
使用stop方法终止,但是这个方法已经过期,不被推荐使用。
使用interrupt方法终止线程
run方法执行结束,正常退出
8.线程安全问题
线程安全是指某个方法在多线程的环境下被调用时,能够正确处理多线程之间的共享变量,能程序能够正确完成。
Java中是如何保证多线程安全的?
使用安全类,比如 java.util.concurrent 下的类,使用原子类AtomicInteger
使用自动锁,synchronized锁
Lock lock = new ReentrantLock(),使用手动锁lock .lock(),lock.unlock()方法
9. 你对线程优先级有什么理解?
每个线程都具有优先级的,一般来说,高优先级的在线程调度时会具有优先被调用权。我们可以自定义线程的优先级,但这并不能保证高优先级又在低优先级前被调用,只是说概率有点大。
线程优先级是1-10,1代表最低,10代表最高。
Java的线程优先级调度会委托操作系统来完成,所以与具体的操作系统优先级也有关,所以如非特别需要,一般不去修改优先级。
10.谈谈你对乐观锁和悲观锁的理解?
乐观锁:每个去拿数据的时候都认为别人不会修改,所以不会都不会上锁,但是在更新的时候会判断一下在此期间有没有去更新这个数据。所以乐观锁使用了多读的场合,这样可以提高吞吐量,像数据库提供的类似write_condition机制,都是用的乐观锁,还有那个原子变量类,在java.util.concurrent.atomic包下
悲观锁:总是假设最坏的情况,每次去拿数据的时候都会认为有人会修改,所以每次在拿数据的时候都会上锁。这样别的对象想拿到数据,那就必须堵塞,直到拿到锁。传统的关系型数据库用到了很多这种锁机制,比如读锁,写锁,在操作之前都会先上锁,再比如Java的同步代码块synchronized/方法用的也是悲观锁。