Java多线程(一)

并发编程的优缺点

优点:
1.充分利用多核CPU的计算能力:通过并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升。
2.方便进行业务拆分,提升系统并发能力和性能:多线程并发编程是开发高并发系统的基础,利用多线程机制可以大大提高系统整体的并发能力以及性能。
面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能符合这种业务拆分。

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

并发编程的三要素是什么,怎么保证多线程在程序中的运行安全
并发编程三要素(线程的安全性问题体现在):
1.原子性:原子,即一个不可再被分割的颗粒。原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。
2.可见性:一个线程对共享变量的修改,另一个线程能够立即看到。(synchronized,volatile)
3.有序性:程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)

出现线程安全的原因:
1.线程切换带来的原子性问题
2.缓存导致的可见性问题
3.编译优化带来的有序性问题

解决办法
1.JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题
2.synchronized、volatile、LOCK,可以解决可见性问题
3.Happens-Before规则可以解决有序性问题

并行和并发的区别
1.并发:多个任务在同一个CPU核上,按细分的时间片轮流(交替)执行,从逻辑上看那些任务是同时执行。
2.并行:单位时间内,多个处理器或多核处理器同时处理多个任务,真正意义上的同时进行。
3.串行:有n个任务,由一个线程按照顺序执行。由于任务、方法都在一个线程执行所以不存在线程不安全情况,也就不存在临界区的问题。
ex:
并发:两个机器一个人处理,两个机器可能会有交替的问题
并行:两个机器两个人处理,两个人同时处理两台机器
串行:一个机器和一个人,按照顺序流程进行处理

什么是多线程,多线程的优劣
多线程:多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务。

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

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

线程和进程的区别
什么是线程和进程?
进程
一个在内存中运行的应用程序。每个进程都有独立的一块内存空间,一个进程可以有多个线程,一个运行的.exe就是一个进程。
线程
进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可以共享数据。

进程与线程的区别
1.线程具有许多传统进程所具有的特征,称为轻型进程或进程元;
2.把传统的进程称为重型进程,它相当于只有一个线程的任务。
3.在引入线程的操作系统中,通常一个进程都有若干线程。

根本区别:
进程是操作系统资源分配的基本单位
线程是处理器任务调度和执行的基本单位

资源开销:
每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销
线程可以看成轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小

包含关系:
如果一个进程内有多个线程,则执行过程不是一条线,而是多线程共同完成,线程是进程的一部分,所以线程也叫轻权进程或者轻量级进程

内存分配:
同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

影响关系:
一个进程奔溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃后整个进程都会崩溃,所以多进程要比多线程健壮

执行过程:
每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存于应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。

什么是上下文切换
为了让多线程都能够得到有效执行,CPU采取的策略是为每个线程分配时间片并轮转的形式。当一个线程的时间片用完时,就会重新处于就绪状态让给其他线程使用,这个过程就是一次上下文切换

当前任务在执行完CPU时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务状态。
任务从保存到再加载的过程就是一次上下文切换

上下文切换通常是计算密集型的,对系统来说意味着消耗大量的CPU时间,可能是操作系统中时间消耗最大的操作

Linux相比于其他操作系统有很多优点,其中一项是上下文切换模式的切换时间消耗非常少

守护线程和用户线程的区别
1.用户线程(User):运行在前台,执行具体的任务,如程序的主线程、连接网罗的子线程等都是用户线程
2.守护线程(Daemon):运行在后台,为其他前台线程服务。一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作

main函数所在的线程就是一个用户线程,main函数启动的同时在JVM内部同时还启动了很多守护线程,比如垃圾回收线程

区别
用户线程结束,JVM退出,不管是否有没有守护线程运行,而守护线程不会影响JVM的退出。
注意:
1.setDaemon(true)必须在start()方法前执行,否则抛出IllegalThreadStateException异常
2.在守护线程中产生的新线程也是守护线程
3.不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑。
4.守护线程中不能依靠finally块的内容来确保执行关闭或清理资源的逻辑。因为一旦所有用户线程结束运行,守护线程会随JVM一起结束工作,所以守护线程中的finally语句块可能无法被执行

如何在Windows和Linux上查找哪个线程cpu利用率最高?
windows上用任务管理器看,linux可以使用top工具看
1.找出cpu耗用厉害的进程pid,终端执行top命令,然后按下shift+p查找cpu利用最厉害的pid号
2.根据拿到的pid号,top -H -p pid,然后按下shift+p,查找出cpu利用率最厉害的线程号,比如top -H -p 1328
3.将获取到的线程号转换成16进制
4.使用jstack工具将进程信息打印输出,jstack pid号>/tmp/t.dat,如jstack 31365>/tmp/t.dat
5.编辑/tmp/t.dat文件,查找线程号对应的信息

什么是线程死锁
死锁指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力的作用,它们都将无法推进下去。此时系统处于死锁或系统产生了死锁,这些永远在互相等待进程(线程)称为死锁进程(线程)。
多个线程同时被阻塞,它们中的一个或全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

例子:线程A持有资源2,线程B持有资源1,它们同时想申请对方的资源,所以两个线程会互相等待而进入死锁
[线程A] [线程B]
||申请 ||申请

资源2 资源1

线程死锁举例:

public class DeadLockDemo{
	private static Object resource1=new Object();//资源1
	private static Object resource2=new Object();//资源2
	public static void main(String[] args){
		new Thread(()->{
			synchronized(resource1){
				S.o.p(Thread.currentThread()+"get resource1");
				try{
					Thread.sleep(1000);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
				S.o.p(Thread.currentThread()+"waiting get resource2");
				synchronized(resource2){
					S.o.p(Thread.currentThread()+"get resource2");
				}
			}
		},"线程1").start();
		new Thread(()->{
			synchronized(resource2){
				S.o.p(Thread.currentThread()+"get resource2");
				try{
					Thread.sleep(1000);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
				S.o.p(Thread.currentThread()+"waiting get resource1");
				synchronized(resource2){
					S.o.p(Thread.currentThread()+"get resource1");
				}
			}
			
		},"线程2").start();
	}
}

结果:
Thread[线程1,5,main]get resource1
Thread[线程2,5,main]get resource2
Thread[线程1,5,main]waiting get resource2
Thread[线程2,5,main]waiting get resource1

解析:
1.线程A通过synchronized(resource1)获得了resource1的监视器锁。
2.通过Thread.sleep(1000);让线程A休息1s,为了让线程B得到CPU执行权。
3.线程B取得resource2的监视器锁。
4.线程A和线程B休眠结束了都开始企图请求获取对方的资源,然后两个线程就会陷入互相等待的状态,这就产生了死锁,符合产生死锁的四个条件。

形成死锁的四个必要条件
1.互斥条件:线程对于所分配到的资源具有排他性,即一个资源只能被一个线程占用,直到被该线程释放
2.请求与保持条件:一个线程因请求被占用资源而发生阻塞时,对已获得的资源保持不放
3.不剥夺条件:线程已经获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源
4.循环等待条件:当发生死锁时,所等待的线程必定会形成一个环路(类似于死循环),造成永久堵塞

如何避免线程死锁
破坏产生死锁的四个条件中的其中一个就可以避免死锁。

破坏互斥条件
这个条件无法破坏,用锁本来就是让他们互斥(临界资源需要互斥访问)。

破坏请求与保持条件
一次性申请所有的资源

破坏不剥夺条件
占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放占有的资源

破坏循环等待条件
靠按序申请资源来预防,按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件

new Thread(()->{
	synchronized(resource2){
		S.o.p(Thread.currentThread()+"get resource2");
		try{
			Thread.sleep(1000);
		}catch(InterruptedException e){
			e.printStackTrace();
		}
		S.o.p(Thread.currentThread()+"waiting get resource1");
		synchronized(resource2){
			S.o.p(Thread.currentThread()+"get resource1");
		}
	}
			
},"线程2").start();

结果为:
线程1get resource1
线程1waiting get resource2
线程1get resource2
线程2get resource1
线程2waiting get resource2
线程2get resource2
解析:
1.线程1获取resource1的监视器锁
2.Thread.sleep(1000),此时线程2无法获取resource1的监视器锁(线程1占用)
3.线程1等待获取resource2,获取resource2(此时resource2并没有被使用)
4.线程1执行结束释放resource1和resource2
5.线程2获取resource1,resource2完成执行

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值