1、多线程中相关的概念
进程:正在运行的程序
线程:进程中的一个执行单元,一个程序运行后至少有一个进程,一个进程可以有多个线程
单线程:一台电脑同时只能供一个人上网
多线程:一个网吧可以同时供多个人上网
分时调度:所有线程轮流使用 CPU,每个线程平均分配占用 CPU 的时间
抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,Java使用的为抢占式调度
好处:
提高效率,并充分利用CPU资源
多线程提高的是效率,不是时间
弊端:
线程安全问题
增加CPU负担
容易出现死锁(两个人在发生争执,抢优先级)
吞吐量:单位时间处理的数据量
2、Thread类中相关方法
Thread是程序中的执行线程
start:启动该线程
run:该线程要执行的代码,相当于main方法
sleep:在指定的时间内让正在运行的线程休眠
run方法不开启线程,仅是对象调用方法,start方法才是开启线程
3、创建线程有哪几种方式?
①. 继承Thread类创建线程类
步骤:继承Thread类;重写run方法;创建子类对象调用start方法
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName()+":正在执行!"+i);
}
}
}
new MyThread().start();
②. 通过Runnable接口创建线程类
步骤:实现Runnable接口;重写run方法;new Thread(自定义类对象);调用start方法
实现Runnable接口,可以避免继承Thread类的单继承局限性,并且可以解耦,所以优先使用该方式
class TestRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("当前线程为:" + Thread.currentThread());
}
}
}
Thread t1 = new Thread(new TestRunnable());
t1.start();
③. 通过Callable和FutureTask创建线程
实现Callable接口,重写call方法
- 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
- 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
- 使用FutureTask对象作为Thread对象的target创建并启动新线程。
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
public static void main(String[] args) {
CallableDemo callableDemo = new CallableDemo();
try {
FutureTask<Integer> result = new FutureTask<>(callableDemo);
new Thread(result).start();
System.out.println(result.get());
} catch (Exception e) {
e.printStackTrace();
}
}
class CallableDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += i;
System.out.println("当前线程为:" + Thread.currentThread());
}
return sum;
}
}
相关方法:
获取当前线程名称:getName()
获取当前线程对象:currentThread()
返回当前线程对象的优先级:getPriority()
线程的优先级范围是从1 ~ 10,默认为5
精简版创建线程方式:
new Thread(){
public void run() {
};
}.start();
Runnable a = new Runnable() {
@Override
public void run() {
}
};
new Thread(a).start();
4、线程池的好处与创建
好处:避免消耗过多资源,减少线程创建带来的延迟
创建线程池的方式:
1、实现Runnable接口:
创建线程池对象:Executors.newFixedThreadPool(2);
创建Runnable接口对象:创建Runnable接口子类线程;
提交Runnable接口对象:service.submit®;
关闭线程池:service.shutdown();
2、实现Callable接口:
创建线程池对象:Executors.newFixedThreadPool(2);
创建Callable接口对象:创建Callable接口子类线程;
提交Callable接口对象:service.submit®;
关闭线程池:service.shutdown();
5、什么是线程安全及如何解决?
当多个线程对同一全局变量或静态变量进行写操作时,可能会发生数据冲突问题.这就是多线程安全问题. 进行读时不会发生安全问题,常见案例如:多平台对火车票售卖问题
如何解决:使用线程同步或使用锁
具体方法如下:点我点我点我
死锁:当同步中嵌套其他同步,这时容易引发一种现象:程序出现无线等待,这种现象称为死锁:比如过独木桥问题,其他锁还有可重入锁、可中断锁、公平锁和同步锁
死锁的原因:
1.存在两个或两个以上的共享资源
2.存在两个或者两个以上的线程使用这些共享资源
6、一个Java程序在运行时候,最少有几个线程?
2个,一个是main线程,另一个是JVM的垃圾回收机制
7、并行和并发的区别?
- 并行是指在同一时刻两个或者多个事件进行;而并发是指在同一时间间隔两个或多个事件进行,其实并发有点类似于多线程的原理,多线程并非是同时执行多个任务,如果你开两个线程执行,就是在你几乎不可能察觉到的速度不断去切换这两个任务,以达到"同时执行效果",其实并不是的,只是计算机的速度太快,我们无法察觉到而已。
- 并行是在不同实体上的多个事件,并发是在同一实体上的多个事件。
- 并行类似于单核,在一台处理器上“同时”处理多个任务;并发类似于多核,在多台处理器上同时处理多个任务。如hadoop分布式集群。
所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能。
8、什么是守护线程和用户线程?如何区分?
守护线程一般用来执行后台任务,用户线程一般用于执行用户级任务
通常使用isDaemon方法来区分Java中的两种线程,如果返回true,则说明该线程是守护线程,否则是用户线程
9、线程的生命周期
1.当调用new关键字创建线程对象后,处于新建状态,等待调用start方法。
2.当调用start方法后,程序处于就绪状态,等待JVM的调度
3.当就绪状态获得CPU的资源,就可以执行run方法到达运行状态,此时可以进入许多其他的状态
4.比如执行了sleep或suspend(挂起)方法,会进入阻塞状态执行了wait方法,会进入等待阻塞方法
完成任务或者其他原因终止,会进入死亡状态
10、Lock接口中的方法
lock方法:获取锁、unlock方法:释放锁。
Lock ck = new ReentrantLock();//可重入锁
可以使用Lock接口的获取锁和释放锁来代替同步锁。
线程之间的通信:
等待唤醒机制:通过一定的手段使各个线程有效的利用资源。
wait方法:将正在执行的线程释放执行权,并存储到线程池中;
notify方法:唤醒线程池中被wait的线程,一次唤醒一个,唤醒是任意的;
notifyAll方法:唤醒线程池中的所有被wait的线程。
注意:这些方法必须在同步中才有效,并且在使用时要标明所属锁。
为什么这些操作线程的方法定义在Object类中?
因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中
任意一个对象,锁住的代码越少越好
11、什么地方用到了多线程?
1、文件的上传和下载;
2、多线程最多的场景:web服务器本身
最典型的应用比如tomcat,tomcat内部采用的就是多线程,上百个客户端访问同一个web应用,tomcat接入后都是把后续的处理扔给一个新的线程来处理,这个新的线程最后调用到我们的servlet程序,比如doGet或者doPost方法。
3、一个业务逻辑有很多次的循环,每次循环之间没有影响,
比如验证2000个网点信息中某个网点是否存在,正常情况要循环2000次,逐个去验证,这样效率会很低,
假设验证一条需要1分钟,总共就需要2000分钟,有点恐怖。
这时可以用多线程,将2000条数据分成50等份,开50个线程,每个线程只需验证40条,
这样所有的线程执行完是远小于2000分钟的。
转载:https://blog.csdn.net/yang_guang3/article/details/115918185