多线程笔记

1、多线程应用场景

多线程处理后台任务,例如定时向100万用户发送邮件
异步处理,例如:发微博、记录日志等
多线程分布式计算
要想“降低延迟,提高吞吐量”,对应的方法呢,基本上有两个方向,一个方向是优化算法,另一个方向是将硬件的性能发挥到极致。前者属于算法范畴,后者则是和并发编程息息相关了。那计算机主要有哪些硬件呢?主要是两类:一个是 I/O,一个是 CPU。简言之,在并发编程领域,提升性能本质上就是提升硬件的利用率,再具体点来说,就是提升 I/O 的利用率和 CPU 的利用率。
​
估计这个时候你会有个疑问,操作系统不是已经解决了硬件的利用率问题了吗?的确是这样,例如操作系统已经解决了磁盘和网卡的利用率问题,利用中断机制还能避免 CPU 轮询 I/O 状态,也提升了 CPU 的利用率。但是操作系统解决硬件利用率问题的对象往往是单一的硬件设备,而我们的并发程序,往往需要 CPU 和 I/O 设备相互配合工作,也就是说,我们需要解决 CPU 和 I/O 设备综合利用率的问题。关于这个综合利用率的问题,操作系统虽然没有办法完美解决,但是却给我们提供了方案,那就是:多线程。
​
在单核时代,多线程主要就是用来平衡 CPU 和 I/O 设备的。如果程序只有 CPU 计算,而没有 I/O 操作的话,多线程不但不会提升性能,还会使性能变得更差,原因是增加了线程切换的成本。但是在多核时代,这种纯计算型的程序也可以利用多线程来提升性能。为什么呢?因为利用多核可以降低响应时间。
​
为便于你理解,这里我举个简单的例子说明一下:计算 1+2+… … +100 亿的值,如果在 4 核的 CPU 上利用 4 个线程执行,线程 A 计算[1,25 亿),线程 B 计算[25 亿,50 亿),线程 C 计算[50,75 亿),线程 D 计算[75 亿,100 亿],之后汇总,那么理论上应该比一个线程计算[1,100 亿]快将近 4 倍,响应时间能够降到 25%。一个线程,对于 4 核的 CPU,CPU 的利用率只有 25%,而 4 个线程,则能够将 CPU 的利用率提高到 100%。
​
​

2、并发编程有什么缺点?

并发编程的目的就是为了能提高程序的执行效率,提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、上下文切换、线程安全、死锁等问题。

3、并发编程三个必要因素是什么?

原子性:原子,即一个不可再被分割的颗粒。原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。
可见性:一个线程对共享变量的修改,另一个线程能够立刻看到。
有序性:程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)

4、在 Java 程序中怎么保证多线程的运行安全?

出现线程安全问题的原因一般都是三个原因:
​
线程切换带来的原子性问题
解决办法:使用多线程之间同步synchronized或使用锁(lock)。
​
CPU缓存导致的可见性问题
解决办法:synchronized、volatile、LOCK,可以解决可见性问题
​
编译优化带来的有序性问题
解决办法:Happens-Before 规则可以解决有序性问题

5、多线程的好处

可以提高 CPU 的利用率。在多线程程序中,一个线程必须等待的时候,CPU 可以运行其它的线程而不是等待,这样就大大提高了程序的效率。也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

6、多线程的劣势

线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
​
多线程需要协调和管理,所以需要 CPU 时间跟踪线程;
​
线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题。

7、多线程的劣势

线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
​
多线程需要协调和管理,所以需要 CPU 时间跟踪线程;
​
线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题。

8、什么是上下文切换?

多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
​
概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。
​
上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
​
Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
​
减少上下文切换的方法:
①无锁并发编程。多线程竞争时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,如将数据的ID按照Hash取模分段,不同的线程处理不同段的数据。
②CAS算法。Java的Atomic包使用CAS算法来更新数据,而不需要加锁。
③使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态
④协程。在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换

9、守护线程和用户线程有什么区别呢?

用户 (User) 线程:用户线程和守护线程唯一的区别就是Daemon(Thread.getDaemon())为false;
守护 (Daemon) 线程:为程序提供后端服务的线程成为守护线程,非守护线程运行结束时守护线程也一并结束;
​
public static void main(String[] args) {
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
​
            try {
                System.out.println("守护线程开始执行...");
                while(true){
                    System.out.println("守护线程休眠中..");
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
​
        }
    });
    thread.setDaemon(true);
    thread.start();
    try {
        System.out.println(Thread.currentThread().getName() + "线程已休眠..");
        Thread.sleep(5000);
        System.out.println(Thread.currentThread().getName() + "线程休眠结束..");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
​
​
执行结果:
​
main线程已休眠..
守护线程开始执行...
守护线程休眠中..
守护线程休眠中..
守护线程休眠中..
守护线程休眠中..
守护线程休眠中..
守护线程休眠中..
线程休眠结束..
​
从打印信息上可以看出我们设置的守护线程为while(true)每隔一秒打印一次信息,main线程休眠5秒后结束,main线程结束时jvm直接停止了;

10、如何在 Windows 和 Linux 上查找哪个线程cpu利用率最高?

windows上面用任务管理器看,linux下可以用 top 这个工具看。
找出cpu耗用厉害的进程pid, 终端执行top命令,然后按下shift+p (shift+m是找出消耗内存最高)查找出cpu利用最厉害的pid号
根据上面第一步拿到的pid号,top -H -p pid 。然后按下shift+p,查找出cpu利用率最厉害的线程号,比如top -H -p 1328
将获取到的线程号转换成16进制,去百度转换一下就行
使用jstack工具将进程信息打印输出,jstack pid号 > /tmp/t.dat,比如jstack 31365 > /tmp/t.dat
编辑/tmp/t.dat文件,查找线程号对应的信息
或者直接使用JDK自带的工具查看“jconsole” 、“visualVm”,这都是JDK自带的,可以直接在JDK的bin目录下找到直接使用

11、形成死锁的四个必要条件是什么?

互斥条件:在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,就只能等待,直至占有资源的进程用毕释放。
占有且等待条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
不可抢占条件:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。(比如一个进程集合,A在等B,B在等C,C在等A)

12、如何避免线程死锁?

避免一个线程同时获得多个锁
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制

13、创建线程有哪几种方式?

(1)继承Thread类创建线程类
​
定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
​
创建Thread子类的实例,即创建了线程对象。
​
调用线程对象的start()方法来启动该线程。
​
(2)通过Runnable接口创建线程类
​
定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
​
创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
​
调用线程对象的start()方法来启动该线程。
​
(3)通过Callable和Future创建线程
​
创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
​
创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
​
使用FutureTask对象作为Thread对象的target创建并启动新线程。
​
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

14、说一下 runnable 和 callable 有什么区别

Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
​
Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。

15、线程有哪些状态?

Java 语言中线程共有六种状态,分别是:
NEW(初始化状态)
RUNNABLE(可运行 / 运行状态)
BLOCKED(阻塞状态)
WAITING(无时限等待)
TIMED_WAITING(有时限等待)
TERMINATED(终止状态)

16、sleep() 和 wait() 有什么区别?

sleep():方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。
​
wait():wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程
​

17、notify()和 notifyAll()有什么区别?

如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
​
当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争。
​
优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值