Java:并发问题及加锁

什么是并发和并行

并发:单个CPU在做多个线程的任务。简单理解:1位服务员(CPU),同一时间只能服务1位客人(线程),但可以服务完这位后,去服务下一位,服务多位客人即多个任务。
并行:多个CPU在做多个线程的任务。简单理解:多位服务员(CPU),同一时间可以服务多位客人(线程)

Java多线程

Java是支持多线程的,而我通常喜欢把线程比喻成工人。一个进程的执行,允许有多个工人在工作,比如进程是建一座大厦,有的工人搬砖,有的搬水泥,有的打地基等等...

小知识:

  1. JVM中程序计数器,虚拟机栈是线程独有,各一份。而方法区和堆则是线程共享
  2. 单核CPU:同一时间只能执行一个线程的任务。即同一时间,只会有一个工人在工作,其他工人都是歇着的
    多核CPU:同一时间可以执行多个线程的任务(如4核CPU,每一核同一时间处理1个线程,加起来同一时间最多能处理4个线程)。即同一时间,可以有多个工人在工作,这些工人分散在多个进程里,1个进程可能有1个工人在工作,也可能有多个工人在做(进程和线程并不是包含关系,并不是说一个进程就固定几个线程一成不变)
  3. 以单核CPU为例,如果只有1个线程完成N个任务所消耗的时间,一般要比多线程完成N个任务所消耗的时间要短。因为:线程的切换(可以理解为工人轮换)的时间频繁且耗时
  4. 平时开发好像没有创建和调用过线程,是不是代表应用的运行就没有线程去执行呢?错,框架一般封装了线程池,不然应用怎么可能正常运行。只是说你有额外的异步需求,并且想自己搞个线程去执行,就可以创建新的线程实例去执行

线程安全问题

  1. 同一资源被消费了两次。举例:
现在账户有100块。线程A和线程B同时做取钱的任务,且都是取60块钱。同一时间进入判断:
if(取款金额 <= 账户现有金额){ //60<100成立,两个线程都进入判断
	//取60
	....
}
导致账户变成-20,这不符合业务逻辑了

PS:并发问题 = 线程安全问题。并发一般都是多线程,多线程是导致线程安全问题的前提。并发越高,出现线程安全问题的概率会越大

并发导致的根本原因

线程1在执行A任务的时候,线程2也参与了A任务。且A任务中包含共享资源

解决思路

线程1在执行A任务的时候,其他线程不要参与进来!

解决方法

加锁!

synchronized同步锁

同步代码块

synchronized (锁对象){
    //同步代码      
}
  1. 代码块一般写在方法内
  2. 锁对象可以是任意的类对象。但是!在这里要保证所有线程共享同一把锁,即同一个对象。
  3. 大家经常可以看到synchronized (this)的写法,是由于this指代当前调用的对象。而在spring中,组件一般是单例的,所以可以使用this。有一种写法可以保证锁是唯一的,即取当前类的类对象,如synchronized(User.class){}

同步方法

普通同步方法
public synchronized void A() {}

其实同步方法很好理解,可以理解成同步代码块的代码抽成1个方法出来,方法再声明synchronized就是同步方法了。作用是一样的,同一时间只能有1个线程能够访问方法
值得讨论的是,锁并没有声明。实际上普通同步方法的锁默认是this,大家在实际开发中,要确定普通同步方法的类会有几个实例,如果是单例则没问题,是多例就有问题了

静态同步方法
public static synchronized void A() {

说结论,静态同步方法锁是当前class对象,只有1个,因此锁一定是唯一的。但有个小问题,静态方法内部是不允许去调非静态数据的

lock同步锁

lock是JDK1.5出的一个接口,作用和synchronized一模一样,没有区别。但是用法不同,先上例子:

private final static ReentrantLock lock = new ReentrantLock();//ReentrantLock为Lock的实现类,也是常用的实现类

public static String GetRandom() {
            try {
               lock.lock();//加锁
			....同步代码
            } catch (Exception e) {
            } finally {
                lock.unlock();//释放锁
            }
    }

lock和synchronized的区别

  1. synchronized同步代码块范围明确,可读性强,锁是自动释放;但需要考虑锁对象,考虑不慎可能易导致线程安全问题。
  2. lock无需考虑锁对象。但可读性会比较差,且需要手动去释放锁。

结论:虽然个人还是比较倾向于synchronized,但是实际开发中lock用的更多点。建议的话:优先考虑lock,再是synchronized

其他重要知识

  1. 线程通信不用理解那么高大上,其实就是多个线程共同完成同一件事。比如要打印1-100,由线程A,B共同完成,线程A打印偶数,线程B打印奇数,这就是线程通信。
posted @ 2022-10-28 15:17  爱编程DE文兄  阅读(635)  评论(0编辑  收藏  举报