【并发编程】-多线程存在的风险

前言

多线程可以提高程序响应速度,提高系统的吞吐量,发挥多处理器的强大能力,正式由于多线程的这些优势,所以我们常常会看到并发编程,异步调用这样的需求。

上篇博客讲到了创建多线程的7种方式,这篇博客,总结一下多线程带来的风险,我们只有规避了存在了风险,才能真正的提升系统效率。

线程安全性问题

什么是线程安全性问题?

线程安全问题指的是多个线程同时访问一个共享资源,并且这个资源是非原子性操作,那么结果就无法保证程序的正确性,也就是结果偏离我们的预期。

示例:

public class Sequence {
	
	private int value;
	
	public int getNext() {
		return value ++;
	}
	
	public static void main(String[] args) {	
		Sequence s = new Sequence();	
		
		new Thread(new Runnable() {	
			@Override
			public void run() {
				while(true) {
					System.out.println(Thread.currentThread().getName() + " " + s.getNext());
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}).start();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				while(true) {
					System.out.println(Thread.currentThread().getName() + " " + s.getNext());
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}).start();
}

打印结果
在这里插入图片描述我们的期望是按照getNext()方法,实现自增;但是在运行结果中我们可以看到两个60,两个65,这就违反了我们的预期,没有正确的执行程序。这就是线程安全性问题。

出现线程安全性问题的条件

1、多线程环境
2、存在共享资源
3、对共享资源进行非原子性操作

如何解决多线程安全性问题

1、对于共享资源,使用synchronized。synchronized属于重量级锁,而重量级锁就和性能有关联了,下边总结性能的时候再说。
2、共享资源如果是原子性操作,可以使用Volatile关键字修饰。Volatile属于轻量级锁,可以保证可见性,但是不能保证操作的原子性。可见性就是一个线程修改变量的值,其他线程 能够读到这个修改的值。
3、使用Lock接口,使用读锁和写锁分别锁定读操作和写操作。

活跃性问题

活跃性指的是一个并发应用程序能及时执行的能力。活跃性问题有三种情况,分别是死锁,活锁,饥饿。

死锁:

两个或多个线程永久阻塞,互相等待对方释放资源。

可以使用重入锁使线程中断或限时等待来解决死锁问题。

活锁:

活锁指的是任务或执行者没有被阻塞,但由于某些条件没有满足,导致一直重复尝试-失败-尝试-失败。

就像一个小河上有两座桥,河两岸分别有一个人想要过河,首先两人上了同一座桥,碰到后都比较谦让,都退了回去。然后又都很默契的上了另外一个桥,又都回退,又都上了另外一座桥,就这样,两人一直碰面,一直谦让,处于一直活动的状态。

活锁和死锁的区别就是,活锁处于活动的状态,可以自行解开,而死锁处于等待的状态,不能自行解开。

饥饿

饥饿指的是当前线程因为优先级太低,或者其他线程一直不释放资源,而获取不到资源,无法执行。

与死锁相比,饥饿有可以获取到资源的可能,只是可能性比较低,而死锁则是没有可能。

可以设置合适的优先级或者使用锁来替代synchronized,锁是自己设置的,也就是自己控制的,而synchronized是由程序分配的。

性能问题

1、线程的生命周期开销是非常大的,一个线程的创建到销毁都会占用大量的内存。如果创建的线程的大于实际需要的线程,那么多余的闲置的线程将会占用大量的内存。

2、上边说到synchronized是重量级锁,重量级锁是相当耗性能的。当系统检查到时重量级锁后,会把等待想要获得锁的线程进行阻塞,在阻塞或唤醒一个线程时,伴随着会发生cpu时间片的转换,在时间片转换前后,需要保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态。而这个转换以及状态的保存,也就是上下文切换,是相当耗性能的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木子松的猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值