并发-并发编程的挑战

同步VS异步

同步和异步用来形容一次方法调用。

同步:同步方法调用一开始,调用者必须等待被调用的方法结束后,调用者后面的代码才能执行。

异步:异步调用指的是,调用者不管被调用的方法是否完成,都会继续执行后面的代码,当被调用的方法完成后会通知调用者。

并发VS并行

并发:指多个任务交替执行

并行:真正意义上的“同时运行”。

实际上,系统内只有一个CPU,而使用多线程时,那么真实系统环境下不能并行,只能通过切换时间片的方式交替进行,而成为并发执行任务。真正的并行也只能出现在拥有多个CPU的系统中。

阻塞VS非阻塞

阻塞和非阻塞通常用来形容多线程间的相互影响。

阻塞:一个线程占有了临界区资源,那么其他线程需要这个资源就必须进行等待该资源的释放,会导致等待的线程挂起。

非阻塞:没有一个线程可以阻塞其他线程,所有的线程都会尝试地前行。

临界区

一种公共资源或者说是共享数据,可以被多个线程使用。但是每个线程使用时,一旦临界区资源被一个线程占有,那么其他线程必须等待。

并发编程

优点:

1、并发编程的形式可以将多核CPU的计算能力发挥到极致,性能得到提升。

2、面对复杂业务模型,并行程序会比串行程序更适应业务需求,而并发编程更能吻合这种业务拆分。

缺点:并发编程的目的是为了让程序运行的更快,但是也会造成一些问题:上下文切换,线程安全

上下文切换

即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现这个机制。因为时间片非常短,所以CPU通过不断地切换线程执行,让我们感觉多个线程是同时执行的。

CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。

多线程一定快吗?

不一定,因为线程有创建和上下文切换的开销。

如何减少上下文切换?

1、无锁并发编程。可以参考concurrentHashMap锁分段的思想,不同线程处理不同段的数据,这样在多线程竞争条件下,可以减少上下文切换的时间。

2、CAS算法。利用Atomic下使用CAS算法来更新数据,使用了乐观锁,可以有效减少一部分不必要的锁竞争带来的上下文切换。

3、使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多的线程,这样会造成大量的线程都处于等待状态。

4、协程。在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。

死锁

一旦产生死锁,就会造成系统功能的不可用。

模拟死锁的代码:

public class DeadLockDemo {
​
    private static String A = "A";
    private static String B = "B";
​
    public static void main(String[] args){
        new DeadLockDemo().deadLock();
    }
​
    private void deadLock(){
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                //对A加锁
                synchronized(A){
                    System.out.println("t1 get resource A");
                    try {
                        //睡眠一段时间,让t2先获取到B
                        Thread.sleep(2000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    //尝试获取B,对B加锁,因为t2一直在尝试获取A,一直不释放B,所以t1获取不到B
                    synchronized(B){
                        System.out.println("t1 get resource B");
                        System.out.println("1");
                    }
                }
            }
        });
        Thread t2 =new Thread(new Runnable() {
            @Override
            public void run() {
                //对B加锁
                synchronized (B){
                    System.out.println("t2 get resource B");
                    //获取A,对A加锁,因为t1一直在尝试获取B,没有获取到一直未执行结束,不释放A,所以t2不会获取到A
                    synchronized(A){
                        System.out.println("t2 get resource A");
                    }
                }
            }
        });
        t1.start();
        t2.start();
    }
}

避免死锁常用的方法:

1、避免一个线程同时获取多个锁。

2、避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。

3、尝试使用定时锁,使用lock.tryLock(timeout)来代替使用内部锁机制。

4、对数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值