00:00
下边咱们来谈一个CL in这个线程安全性的问题,那我们知道呢,这个类的初始化呢,诶主要呢,它这个相关的代码的操作呢,都是在咱们CL的这个方法当中去实现的,对吧?那么这个方法的话呢,在被调用的时候,如果涉及到多个线程,那我们就需要保证这个方法调用的一个线程安全性问题。啊,因为这里边儿还涉及到个什么场景呢,就是我们说这个一个类的一个加载,对吧,经历过叫loading link和initialization这样几个环节,我们一个类啊,在内存当中呢,它只会被加载一次。只会被加载一次,就是你在使用的这个阶段,就是内存中如果已经有了,你就不能再去加载一个同样的类的加载器,然后加载同样的一个类了,对吧?这是不行的啊,那么既然如此的话呢,那我们就需要去保证在多线程的这个环境下,如果呢,有多个线程都希望呢,去加载当前这个类,那如果涉及到同样的这个CR这个方法呢,多个线程如果都去执行,那不可以。
01:00
对吧,诶,所以这里边儿我们就涉及到关于这个方法的一个线程安全性的问题。这呢,就我们这里边儿要强调的这个点,那这里边儿我们说呢,CR这个方法呢,在多线程的环境下呢,被正确的加锁和同步了。那如果一个线程呢,呃,已经这个开始去初始化我们这个类了,它呢,比如说已经开始在调用我们的CR的这个方法,那么其他的线程呢,就都得进入这个阻塞等待的这个状态。都得进入这个阻塞等待的状态,那这样的话呢,就能保证我们这个相应的这个呃方法或者叫函数呢,是带锁的,是线程安全的,那就这样个场景,那么我们来看一下这个C,比如针对于咱们当前这个initialization的TEST2,对吧,针对它的话呢,我们看一下这个cell in,诶我们说呢,第一章讲class文件结构的时候,提到每一个方法的话呢,都有一个叫access flag对吧,就是访问标识,那我们看到呢,关于这个C的它的访问标识呢,只有一个static。
02:01
就表明了它是一个静态的随着类的这个加载而执行的是吧,就是我们在初始化环节呢,它就相当于做了一个执行,那么它并没有去加上这个S的这样的一个标识。你看是没有的,那平时呢,咱们说的一个同步方法,那这个时候呢,都会有个西言外G呢,就是我们此时这个方法呢,它的这个锁呀,是一个隐示的一个锁,并不是我们使用这个S的显示来进行控制的,这个大家要注意。那么如果我们一个线程呢,正在访问或者正在调用这个CL的这个方法,那么此时的话呢,我们说其他的线程呢,就得阻塞,那如果其中一个线程呢,它在执行的时候呢,耗时很长,乃至于时候呢,没有确定的终止时间,这样的话呢,就会导致我们其他的多个线程就进入阻塞啊,引发这个死锁的这个问题。对吧,引发这个词索问题,而我们这个词索的场景呢,还很难被发现,为什么呢?因为我们这个平时呢,你说这个出现这个词索了,通常我们都是使用SNE来标识了,很容易的发现,如果你一个程序中有多个锁,那多个线程去调用,就可能会交叉出现死锁问题。
03:09
而我们这里边呢,CR并没有显示的去调用这个CL这个sized,所以这个锁呢很不容易被发现,而且呢,在咱们使用比如说像这个呃,Visual vm等等这样工具的时候呢,我们里边也看不到关于这个词组的这样的一个场景,或者这个相应的这个信息,所以这呢大家一定要小心。哎,这一定要小心啊,那下边呢,就提到了,说如果呢,我们其中一个线程呢,加载一个类调用这个方法呢,执行完了以后呢,其他线程呢,就不用再去调用它了,直接呢去调用我们已经加载好的这样一个类的信息就可以了啊这正是我们说的这个问题,那么关于上边这个死组的场景呢,这里呢,咱们举了一个例子,大家看一下我这里边写的这样一个题目。这个题目呢是这样子的,上面呢有两个类,一个呢叫static a,一个叫static b,就是我们要初始化的两个类。然后再下面呢,是我们去继承于thread的这样一个子类,这个子类的话呢,我们去创建它的两个线程,让两个线程呢,去分别调用这个大的方法执行咱们的这个run方法,那他们俩呢,各有一个参数叫AB,是因为我们在这个各自的run方法里边去配了一下我们相应的这个static a和static b啊,就相应的让我们两个线程呢,Load a和load b呢,分别去加载static a和static b,就是这样的一道题目。
04:27
好,那么这个题目里边呢,有什么问题呢?我们主要就看上边了,大家你会发现呢,我们在如果你去加载这个study a的时候呢,我们就会涉及到这个初始化这样一个呃方法的执行,那初始化方法呢,你看我们这加了一个static个同步代码块,呃,在这个静态代码块,它呢就会在咱们这个。嗯,这个C2的这个方法里边出现,那当我们做初始化的时候呢,就会执行这个同步,就执行这个静态代码块了,对吧,那CRB呢,有同样的道理,然后在这个A里边。我们会看到呢,首先有一个阻塞,阻塞完以后呢,在这个初始化A的时候呢,我们又会去加载一下B,那同样的B里边呢,再去初始化它的时候呢,它又会去加载A,那这呢,是不是就出现了一个交叉行为,比如这是一个线程,这是一个线程,咱们这个叫load a是吧?然后这个呢,叫load b这两个线程,然后在这个load a线程里边呢,它首先呢需要去加载咱们这个叫static a,那我就这样简写了,在加载它的环境里边有一个初始化,初始化的时候呢,里边就需要呢去加载一下这个在这个B。
05:32
对吧,那对于我们这个。LB线程是吧,这个load b线程来讲呢,它呢是本来呢是要加载并初始化我们这个这个B,但是在这个初始化的过程当中呢,它又需要去加载static个A,那就出现这样个场景,那加上咱们这有个阻塞,所以它呢,在这个初始化这个static a的时候呢,那这个static这个sta b呢,相当于就被我们这个线程呢,就先去加载了,那你再去等着B,然后B这块呢,它又去等着A,他们这样呢,不就交叉行为嘛,导致呢,我们就出现了事实上的这个死索的情况,对吧。
06:07
那这个大家要注意一下。啊这呢,一写这个总感觉看着怪怪的是吧,啊行这呢,大家就能理解一下,我们这里边为什么会出现这样的一个思索行为,那我们执行一下,大家也会看到当前这个程序呢,就僵持在这儿。你看执行不下去了,那我们这里边儿相应的这个输出语句的话呢,都没有执行,相当于呢,这个程序呢,就进入一个阻塞状态,这个呢就是构成事实上的一个思索。啊,是这样的一个思索,好,那么大家呢,在这个编写相关这个类的加载的时候呢,一定要小心啊,当你通过一个类加载另外的类,这个加载呢,都有哪些行为啊啊,那咱们下一个问题呢,就讲主动的类的一个初始化,还有被动的一个初始化啊,被动的使用呢,其实就不会出现这个初始化行为了,那么这个。那在使用的过程当中呢,大家呢,一定要小心一下,那交叉的这样的去加载的话呢,就可能会出现这样的一个思索问题,好这呢,大家要小心一下这个事儿。
我来说两句