1. 线程的安全性问题:
线程安全和非线程安全: 一个类在单线程环境下能够正常运行,并且在多线程环境下,使用方不做特别处理也能运行正常,我们就称其实线程安全的。反之,一个类在单线程环境下运行正常,而在多线程环境下无法正常运行,这个类就是非线程安全的。
线程安全问题体现在:
- 原子性:原子,即一个不可再被分割的颗粒。原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。
- 可见性:一个线程对共享变量的修改,另一个线程能够立刻看到。(synchronized,volatile)
- 有序性:程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)
- 出现线程安全问题的原因:
线程切换带来的原子性问题
缓存导致的可见性问题
编译优化带来的有序性问题
2. 并行和并发有什么区别?
并发:多个任务在同一个 CPU 核上,按时间片轮流(交替)执行,从逻辑上来看那些任务是同时执行。
并行:单位时间内,多个处理器同时处理多个任务,是真正意义上的“同时进行”。
串行:有n个任务,由一个线程按顺序执行。由于任务、方法都在一个线程执行所以不存在线程不安全情况,也就不存在临界区的问题。
做一个形象的比喻:
并发 = 两个队列和一台咖啡机。
并行 = 两个队列和两台咖啡机。
串行 = 一个队列和一台咖啡机。
3. 多线程
多线程:宏观上看,一个程序中可以同时运行多个不同的线程来执行不同的任务。
多线程的好处:
可以提高 CPU 的利用率。在多线程程序中,一个线程必须等待的时候,CPU 可以运行其它的线程而不是等待,这样就大大提高了程序的效率。也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
坏处:
并发编程可能会遇到很多问题,比如:内存泄漏、上下文切换、死锁还有受限于硬件和软件的资源闲置问题。
4. 进程和线程的区别
这里我以java为例讲进程与线程的区别:
- 首先,进程是一个程序运行的实例,是一个资源分配的基本单位,而线程是程序独立运行的最小单位
- 从资源方面看,进程有自己独立的寻址空间,而且一个进程可以有多个线程,多个线程共享进程的堆和方法区(JDK1.8后的元空间),但是每个线程也有自己的程序计数器、虚拟机栈、本地方法栈。
- 从安全性和健壮性方面比较,进程间是相互独立的,一个进程死掉不会影响其他的进程,而在多线程环境下,线程间极有可能相互影响,可能会存在死锁,线程不安全的情况。
- 从系统开销方面看,由于线程资源共享,使用相同的地址空间,因此线程切换的系统开销小,但不利于资源的管理和保护,进程则相反。
- 在通信方面,线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助 IPC。
5. Linux 上查找哪个线程cpu利用率最高?
windows上面用任务管理器看,linux下可以用 top 这个工具看。
- 找出cpu耗用厉害的进程pid, 终端执行top命令,然后按下shift+p 查找出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文件,查找线程号对应的信息
6. 线程死锁
死锁:指两个或两个以上的进程(线程)由于竞争资源而造成的一种阻塞的现象,若无外力作用,将永远在互相等待,互相僵持下去。
比如说,两个线程互相持有对方资源,同时他们都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
- 形成死锁的四个必要条件是什么
互斥条件:线程对于所分配到的资源具有排它性,即一个资源只能被一个线程占用,直到被该线程释放
请求与保持条件:一个线程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
不剥夺条件:已获得的资源在末使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
循环等待条件:当发生死锁时,所等待的线程必定会形成一个环路(类似于死循环),造成永久阻塞 - 如何避免线程死锁
我们只要破坏产生死锁的四个条件中的其中一个就可以了。
-
破坏互斥条件
这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的 -
破坏请求与保持条件
一次性申请所有的资源。 -
破坏不可剥夺条件
占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。 -
破坏循环等待条件
靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。
7. 创建线程有哪几种方式?
- 创建线程有四种方式:
继承 Thread 类;
实现 Runnable 接口;
实现 Callable 接口;
使用 Executors 工具类创建线程池
1) 继承 Thread 类
- 定义一个Thread类的子类,重写run方法,将相关逻辑实现,run()是线程要执行的业务逻辑方法
- 创建自定义的线程子类对象
- 调用子类实例的star()方法来启动线程
2 )实现 Runnable 接口
- 定义Runnable接口实现类MyRunnable,并重写run()方法
- 创建MyRunnable实例myRunnable,以myRunnable作为target创建Thead对象,该Thread对象才是真正的线程对象
- 调用线程对象的start()方法
3)实现 Callable 接口
4. 创建实现Callable接口的类myCallable
5. 以myCallable为参数创建FutureTask对象
6. 将FutureTask作为参数创建Thread对象
7