一、什么是进程,什么是线程
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程:是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程,线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
二、线程调度
分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性)。
Java使用的为抢占式调度。CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,因为不论线程有多少,CPU都只能一个一个的去执行,但多线程程序能够提高程序运行效率,让CPU的使用率更高。
三、同步与异步
同步:同时执行 , 效率高但是数据不安全(可能发生哄抢)。
异步:排队执行 , 效率低但是安全(不会发生哄抢)。
四、并发与并行
并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。
五、JAVA多线程的三种实现方式(面试)
(1)、继承Thread类(无返回值)。通过继承Thread类来创建并启动多线程的一般步骤如下:
1)、定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体。
2)、创建Thread子类的实例,也就是创建了线程对象
3)、启动线程,即调用线程的start()方法
public class MyThread extends Thread {
//run方法就是线程要执行的任务方法
@Override
public void run() {
//这里的代码就是一条新的执行路径
//这个执行路径是触发方式,不是调用run方法,而是通过thread对象的start方法来启动任务
for (int i = 0; i < 10; i++) {
System.out.println("锄禾日当午"+i);
}
}
public class Main {
public static void main(String[] args){
new MyThread().start();//创建并启动线程
}
}
其常用方法如下:
1)、thread.start() 启动线程;
2)、stop() 停止方法一停用,此方法会导致线程不安全(例如可能内部还没有完成清理垃圾,却被外部用stop()结束了)。
3)、sleep(long millis) 休眠millis毫秒;
4)、Thread.currentThread() 获取当前线程;
5)、Thread.currentThread().getName() 获取当前线程名称。
(2)、实现Runnable接口(无返回值)。通过实现Runnable接口创建并启动线程一般步骤如下:
1)、定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体。
2)、创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象。
3)、通过调用线程对象的start()方法来启动线程。代码示例:
public class MyRunnable2 implements Runnable{//实现Runnable接口
@Override
public void run() {
//线程的任务
for (int i = 0; i < 10; i++) {
System.out.println("锄禾日当午"+i);
}
}
public class Main {
public static void main(String[] args){//创建并启动线程
MyThread2 myThread=new MyThread2();
Thread thread=new Thread(myThread);
thread().start();//或者 new Thread(new MyThread2()).start();
}
}
(3)、带返回值的线程Callable。可以主线程与支线程并发执行,也可以等支线程执行后再执行主线程。返回的执行结果需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。Callable使用步骤如下:
1)、 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
@Override
public <T> call() throws Exception {
return T;
}
}
2).、创建FutureTask对象 , 并传入第一步编写的Callable类对象。
FutureTask<Integer> future = new FutureTask<>(callable);
3).、通过Thread,启动线程
new Thread(future).start();
(4)、实现Runnable与继承Thread相比的优势
1)、通过创建任务,然后给线程分配的方式来实现多线程,更适合多个线程同时执行相同任务的情况;
2)、可以避免单继承所带来的局限性;
3)、任务与线程本身是分离的,提高了线程的健壮性;
4)、后续学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的任务。
(5)、Runnable 与 Callable
1)、Runnable 与 Callable的相同点:都是接口;都可以编写多线程程序;都采用Thread.start()启动线程。
2)、Runnable 与 Callable的不同点:Runnable没有返回值;Callable可以返回执行结果;Callable接口的call()允许抛出异常;Runnable的run()不能抛出。
六、线程安全与通信问题
(1)、线程阻塞
所谓线程阻塞就是所有比较消耗时间的操作。
(2)、线程的中断
一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定,而不是外界强行结束。
(3)、如何安全的结束线程
解决方法:给线程加上中断标记(thread.interrupt()加标记),线程发现中断标记后会自动跳到try_catch块中的catch块,所以只需在catch块中加上“return;”,由线程程序自行判断后自杀。代码实例:
package thread;
public class Demo5 {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
t1.start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//给线程t1添加中断标记
t1.interrupt();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//e.printStackTrace();
System.out.println("发现了中断标记,线程自杀");
return;
}
}
}
}
}
运行结果:
(4)、线程安全问题(多个线程同时抢夺一个数据导致不安全)。解决方法如下(使用加锁机制):
1)、同步代码块:格式:synchronized(锁对象){代码块 };
2)、同步方法:把需排队的代码弄成一个方法锁,方法如果是static,这个同步锁是当前类级别的,而非静态用的锁是this。
3)、显示锁 Lock 子类 ReentrantLock。同步代码块和同步方法都是属于隐式锁。
(5)、线程死锁问题
什么叫线程死锁?假如A线程等B线程,B线程也在等A线程,程序就卡在这里了,这就叫线程死锁。如何避免:不要在锁中调用另一个锁,避免套上加套,混乱不清。
(6)、多线程通信问题
Java中Object类中给出了一些解决线程间通信问题的方法。
1)、void wait() 使线程休眠等待唤醒。与sleep()不同,sleep()是休眠多久时间自动唤醒,而wait()是由外部线程唤醒。
2)、void notifyAll() 唤醒所有由wait()方法休眠的线程。
3)、void notify() 随机唤醒一个由wait()休眠的线程。
七、四种线程池 Executors(面试)
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。Java中的四种线程池ExecutorService:
(1)、缓存线程池(长度无限制)
//执行流程:1. 判断线程池是否存在空闲线程;
// 2. 存在则使用;
// 3. 不存在,则创建线程 并放入线程池, 然后使用。
//运行效果(线程顺序不定):
/*pool-1-thread-3锄禾日当午
pool-1-thread-2锄禾日当午
pool-1-thread-1锄禾日当午*/
//创建一个缓冲池对象
ExecutorService service = Executors.newCachedThreadPool();
//向线程池中 加入 新的任务
//线程一
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
//线程二
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
//线程三
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
(2)、定长线程池(长度是指定的数值)
/*执行流程
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程
**/
/*运行结果(线程顺序不定):
1、先出现pool-1-thread-1锄禾日当午
pool-1-thread-2锄禾日当午
2、几秒后出现pool-1-thread-1锄禾日当午
*/
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
(3)、单线程线程池(长度为一的线程池)
/*执行流程
1 判断线程池的那个线程是否空闲
2 空闲则使用
3 不空闲则等待它空闲后再使用
**/
/*
运行结果:
pool-1-thread-1锄禾日当午
pool-1-thread-1锄禾日当午
pool-1-thread-1锄禾日当午
*/
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
});
(4)、周期性任务定长线程池
/*
执行流程:
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程
执行结果:pool-1-thread-1锄禾日当午
*/
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
//定时执行一次线程
//参数1:定时执行的任务(run)
//参数2:时长数字(5)
//参数3:时长数字的时间单位(TimeUnit.SECONDS) Timeunit的常量指定
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
},5, TimeUnit.SECONDS); //5秒钟后执行
/*
周期性任务执行时:定时执行, 当某个时机触发时, 自动执行某任务。
周期性执行任务
参数1:任务
参数2:延迟时长数字(第一次在执行上面时间以后)
参数3:周期时长数字(每隔多久执行一次)
参数4:时长数字的单位
* **/
/* scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
},5,1, TimeUnit.SECONDS);*/
}
注意:线程执行完后程序不会自动停止,它会一直等待停止指令,或者是直到一定的时间后才会自动停止程序。