java多线程

来源:互联网 发布:公司招人用什么软件 编辑:程序博客网 时间:2024/06/07 00:31

一个synchronized相当于原子操作
synchronized是锁住锁对象
当访问synchronized方法时,需要获取到锁对象
而访问非synchronized方法时,不需要获取锁对象,直接运行

进程是受操作系统管理的基本运行单位
线程是进程中独立运行的子任务
多线程是异步

多线程安全问题
导致的原因
1是否有多线程环境
2是否有共享数据
3是否有多条语句操作共享数据

把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行。
问题是我们不知道怎么包啊?其实我也不知道,但是Java给我们提供了:同步机制。

同步代码块:
* synchronized(对象){
* 需要同步的代码;
* }

A:对象是什么呢?
* 我们可以随便创建一个对象试试。
* B:需要同步的代码是哪些呢?
* 把多条语句操作共享数据的代码的部分给包起来
*
* 注意:
* 同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
* 多个线程必须是同一把锁。
多个线程使用的是同一把锁对象

同步代码块的锁对象是任意对象
而同步方法的锁对象是this
静态方法的锁对象是类的字节码对象

原子操作是对于访问同一个状态的所有操作(包括该操作本身)来说,这个操作是一个以原子方式执行的操作

即使是单核处理器也支持多线程执行代码,CPU通过给每个线程分配CPU时间片来实现

这个机制。时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切
换线程执行,让我们感觉多个线程是同时执行的,时间片一般是几十毫秒(ms)。
CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片后会切换到下一个
任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这
个任务的状态。所以任务从保存到再加载的过程就是一次上下文切换。

减少上下文切换
方法有无锁并发编程 cas算法 使用最少线程和使用协程
无锁并发编程。多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一
些办法来避免使用锁,如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据。
·CAS算法。Java的Atomic包使用CAS算法来更新数据,而不需要加锁。
·使用最少线程。避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这
样会造成大量线程都处于等待状态。
·协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。

要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的和可变的状态的访问
对象的状态是指存储在状态变量 例如实例或者静态域中数据
对象的状态可能包括其他依赖对象的域
在对象的状态中包含了任何可能影响其外部可见行为的数据
共享意味变量可以由多个线程同时访问,而可变则意味着变量的值在其生命周期内可以发生变化
一个对象是否需要是线程安全的,取决于它是否被多个线程访问 指的是程序中访问对象的方式,而不是对象要实现的功能

同步机制有关键字synchronized提供一种独占的加锁方式,但是同步还包括volatile类型的变量 显示锁以及原子变量
多个线程访问同一个可变状态变量没有使用同步 修复方法
1不在线程之间共享该状态变量
2将状态变量修改为不可变的变量
3在访问状态变量时使用同步

正确性:某个类的行为与其规范完全一致 在良好的规范中通常会定义各种不变性条件来约束对象的状态,以及定义各种后验条件来描述对象操作的结果当多个线程访问某个类时,这个类始终都能表现出正确的行为 就称这个类为线程安全无状态:即不包括任何域,也不包含任何对其他类中域的引用竞态条件   当某个计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件最常见的竞态条件类型 先检查后执行操作 即通过一个可能失效的观测结果来决定下一步的动作竞态条件的本质 基于一种可能失效的观察结果来做出判断或者执行某个计算 即先检查后执行 首先观察到某个结果为真 例如文件x不存在 然后根据这个观察结果采用相应的动作 创建文件x 但事实上 在你观察   到这个结果以及开始创建文件之间,观察结果可能变得无效 另一个线程在这期间创建了文件x 从而导致问题

数据竞态
如果在访问共享的非final类型的域时没有采用同步来进行协同,那么就会出现数据竞态。当一个线程写入一个变量而另一个线程接下来读取这个变量,或者读取一个之前由另一个线程写入的变量时,并且在这两个线程之间没有使用同步

要避免竞态条件问题,就必须在某个线程修改该变量时,通过某种方式防止其他线程使用这个变量,从而确保其他线程只能在修改操作完成之前或者之后读取和修改状态,而不是在修改状态的过程中
原子操作是指,对于访问同一个状态的所有操作(包括该操作本身)来说,这个操作是一个以原子方式咨询的操作
Java.util.concurrent.atomic支持在单个变量上解除锁的线程安全编程
atomiclong可以使用原子方式更新的long值
long addAndGet(long delta)
以原子方式将给定值添加到当前值。
boolean compareAndSet(long expect, long update)
如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。
long decrementAndGet()
以原子方式将当前值减 1。
double doubleValue()
以 double 形式返回指定的数值。
float floatValue()
以 float 形式返回指定的数值。
long get()
获取当前值。
long getAndAdd(long delta)
以原子方式将给定值添加到当前值。
long getAndDecrement()
以原子方式将当前值减 1。
long getAndIncrement()
以原子方式将当前值加 1。
long getAndSet(long newValue)
以原子方式设置为给定值,并返回旧值。
long incrementAndGet()
以原子方式将当前值加 1。
int intValue()
以 int 形式返回指定的数值。
void lazySet(long newValue)
最后设置为给定值。
long longValue()
以 long 形式返回指定的数值。
void set(long newValue)
设置为给定值。
String toString()
返回当前值的字符串表示形式。
boolean weakCompareAndSet(long expect, long update)
如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。

在实际情况中,应尽可能的使用现有的线程安全对象来管理类的状态

Java提供内置的锁机制来支持原子性:同步代码块 一个作为锁的对象引用,一个作为由这个锁保护的代码块
其中该同步代码块的锁就是方法调用所在的对象 静态的synchronized方法以class对象作为锁

当某个线程请求一个由其他线程持有的锁,发出请求的线程就会阻塞 但是由于内置锁是可重入的,因此如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功
对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁,在这种情况下,我们称状态变量是由这个锁保护的

常见的加锁约定是;将所有的可变状态都封装在对象内部,并通过对象的内置锁对所有访问可变状态的代码连接进行同步,使得该对象上不会发生并发访问。在许多线程安全类中都使用了这种模式

共享数据的情况为多个线程可以访问同一个变量
线程的构造函数是被main线程调用的,而run方法是被名称为Thread-0的线程调用 run方法是自动调用的方法
public class test {
public static void main(String[] args) {
mythread t=new mythread();
Thread a=new Thread(t,”a”);
a.start();
}
}
class mythread extends Thread{
private int count=5;
public mythread(){
System.out.println(“threadname=”+Thread.currentThread().getName());
System.out.println(“this.getName()”+this.getName());
}
public void run(){
super.run();
System.out.println(“i=”+(count–)+”,threadname :”+this.currentThread().getName());
System.out.println(“this.getName()”+this.getName());
}
}

isAlive()判断当前的线程是否处于活动状态
活动状态是线程已经启动且尚未终止,线程处于正在运行或者准备开始运行的状态
Thread.currentThread()方法返回的是对正在执行的线程对象的引用,this代表的是当前调用它所函数所属的对象的引用
Runnable接口应该由那些打算通过某一线程执行器实例的类来实现。类必须定义一个称为run的无参方法
runnable的目的是希望在活动时执行代码的对象提供一个公共协议
Runnable 为非 Thread 子类的类提供了一种激活方式。通过实例化某个 Thread 实例并将自身作为运行目标,就可以运行实现 Runnable 的类而无需创建 Thread 的子类。大多数情况下,如果只想重写 run() 方法,而不重写其他 Thread 方法,那么应使用 Runnable 接口。、
这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。
Runnabel是线程所运行的代码宿主 在Runnable中不能使用this.getName()方法
sleep方法在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) 这个正在执行的线程是指this.currentThread()返回的线程

停止线程
大多数停止一个线程的操作使用Thread.interrupt()方法 该方法不会终止一个正在运行的线程 还要加入一个频道才可以完成线程的停止
this.interrupted()测试当前线程是否已经中断
this.isInterrupted()测试线程是否已经中断
当前线程是指运行this.interrupted方法的线程

 quartz框架 定时器 new Timer().schedule();

设计思想
/*
* 要用到共同数据(包括同步锁)的若干个方法应该归在同一个类
* 身上,这种设计正好体现了高类聚和程序的健壮性
*
* 两个线程执行的代码片段要实现同步互斥,他们必须用同一个lock锁
* 锁是上在代表要操作的资源的类的内部方法中,而不是线程代码中
*
* 读写锁,分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥
* 这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,
* 可以很多人同时读 但是不能同时写,那就上读锁 如果你的代码修改数据,
* 只能有圆规人在写,且不能同时读取,那就上写锁
*
*/

进程通信
采用将业务逻辑放入同一个类中
要用到共同数据(包括同步锁)的若干个方法应该归在同一个类
然后设置一个boolean变量bushouldsub=true
1当bushouldsub=true时 sub方法执行 main方法等待
当sub执行完后设置bushouldsub=false 通知main醒来
2当bushouldsub=false时 sub方法应该等待 main方法执行
当main方法执行完后设置bushouldsub=true 通知sub方法醒来

public class traditionalThreadCommunication {
private static business b=new business();
public static void main(String[] args) {
new Thread(new Runnable(){

        @Override        public void run() {            for(int i=0;i<50;i++){                try {                    b.sub(i);                } catch (InterruptedException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }            }        }    }).start();    for(int i=0;i<50;i++){        try {            b.main(i);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }}

}
class business
{
private boolean bushouldsub=true;
public synchronized void sub(int i) throws InterruptedException{
while(!bushouldsub)
{
this.wait();
}
for(int j=1;j<=10;j++)
{
System.out.println(“sub thread sequece of”+j+” loop of”+i);
}
bushouldsub=false;
this.notify();
}
public synchronized void main(int i) throws InterruptedException{
while(bushouldsub)
{
this.wait();
}
for(int j=1;j<=10;j++){
System.out.println(“main thread sequece of”+j+” loop of”+i);
}
bushouldsub=true;
this.notify();
}
}

ThreadLocal 实现线程范围内共享变量
每个线程调用全局ThreadLocal对象的set方法,相当于往其内部的map增加一条记录
key分别为是各自的线程,value是各自的set方法传进去的值,在进程结束后可以调用ThreadLocal.clear方法
这样会更快释放内存
ThreadLocal应用场景
订单处理包含一系列操作 减少库存量 增加一条流水台账,修改总账,这几个操作
需要在同一个事务中完成。通常也即同一个线程中进行处理 如果累加公司应收款的操作失败 则应该把前面的操作回滚

停止一个线程的操作使用Thread.interrupt()方法
停止 终止

interrupted测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。 this.interrupted()测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能this.isInterrupted()测试线程thread对象是否已经是中断状态 但不清除状态标志如果在sleep状态下停止某一线程,会进入catch语句 并且清除停止状态,使之变成false

stop释放锁会给数据造成不一致的结果

守护线程
当进程中不存在非守护线程时 守护线程会自动销毁
典型的守护线程为垃圾回收线程 当只有当前JVM实例中存在任何一个非守护线程没有结束 守护线程就在工作 当最后一个非守护线程结束时 守护线程随着JVM一同结束

synchronized同步方法
非线程安全 是指多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是脏读 也就是取到的数据其实是被更改过的
方法内的变量为线程安全
如果多个线程共同访问1个对象中的实例变量 用线程访问的对象中如果有多个实例变量 则运行结果可能出现交叉的情况
class hasSelfPrevateNum{
private int num=0;
public void addI(String username){
try{
if(usernane.equals(“a”)){
num=100;
System.out.println(“a set over”);
Thread.sleep(2000);
else{
num=200;
System.out.println(“b set over”);
}
System.out.println(username+”num=”+num);
}catch(Exception e)
{
e.printStackTrace();
}
}
}

 class thread extenda Thread{        private hasSelfprivateNum num;    public threada(hasSelfprivateNum num){         super();     this.num=num;    }    public void run(){        super.run();    num.addI("a");    }} class threadb extenda Thread{        private hasSelfprivateNum num;    public threadb(hasSelfprivateNum num){         super();     this.num=num;    }    public void run(){        super.run();    num.addI("a");    }}

public class run{
public static void main(String[] args){
hasSelfprivateNum num=new hasSelfprivateNum();
treada a=new threada(num);
a.start();
threadb b=new threadb(num);
b.start();
}
}

在这里会出现非线程安全问题
只要在addI方法前加入关键字synchronized即可

关键字synchronized取到的都是对象锁 而不是把一段代码或者方法函数当做锁,所以在上面的实例中,哪个线程先执行带synchronized关键字的方法 哪个线程就持有该方法所属对象的锁lock 其他线程只能等待状态
调用synchronized声明的方法一定是排队运行的

只有共享资源的读写访问才需要同步化
a线程先持有object对象的lock锁 b线程可以以异步的方式调用object对象中的非synchronized类型的方法
a线程先持有object对象的lock锁 b线程如果在这时调用object对象的synchronized类型的方法则需要等待 也就是同步

synchronized锁重入
使用synchronized时 当一个线程得到一个对象锁后 再次请求此对象锁时是可以再次得到该对象的锁
证明在一个synchronized方法/块内部调用本类的其他synchronized方法/块时,是永远可以得到锁

当一个线程执行的代码出现异常时,其所持有的锁会自动释放
同步不具有继承性

用synchronized声明方法在某些情况下是有弊端的,比如a线程调用同步方法执行一个长时间的任务,那么b线程则必须等待比较长时间,这样可以使用synchronized同步语句块来解决

当两个并发线程访问同一个对象object中synchronized(this)同步代码块时,一段时间内只能有一个线程被执行,另一个线程必须等待当前线程执行完成这个代码块才能执行该代码块
不在synchronized块中就是异步执行 在synchronized块中就是同步执行
在使用同步synchronized(this)代码块时需要注意 当一个线程访问object的一个synchronized(this)同步代码块时 其他线程对同一个object中所有其他synchronized(this)同步代码块的访问将被阻塞 说明synchronized使用的对象监视器是一个

多个线程调用同一个对象中的不同名称的synchronized同步方法或synchronized(this)同步代码块时,调用的效果就是按顺序执行,也就是同步的 阻塞的

采用synchronized(非this对象) 在多个线程持有对象监视器为同一个对象的前提下 同一时间只有一个线程可以执行synchronized(非this对象)同步代码块中代码
如果一个类中有很多synchronized方法,这时虽然能实现同步,但是会受到阻塞 如果使用同步代码块锁非this对象 则synchronized(非this)代码块中程序与同步方法是异步的,不与其他锁this同步方法争抢this锁

静态同步synchronized方法是对当前的*.java文件对应的class类进行持锁

常量池
JVM具有String常量池缓存的功能
string a=”a”
string b=”b”
system.out.println(a==b) true
在大多数的情况下 同步synchronized代码块都不使用String作为锁对象

同步不能继承 在父类使用synchronize时,在子类中还是需要继续需要synchronized
在将任何数据类型作为同步锁,需要注意,是否有多个线程同时持有锁对象,如果同时持有相同的锁对象,则这些线程之间就是同步的,如果分别获得锁对象,线程之间就是异步的

class demoe implements Runnable{
private boolean isContinuePrint=true;
public boolean isContinuePrint(){
return isContinuePrint;
}
public void setContinuePrint(boolean is)
{
this.isContinuePrint=is;
}
public void printStringMethod()
{
try{
System.out.println(“进入run方法”);
while(isContinuePrint==true)
{

        }        System.out.println("线程被停止");    }catch(Exception e)    {        e.printStackTrace();    }}@Overridepublic void run() {    printStringMethod();}

}

关键字volatile
如果不是多继承的情况下,使用继承Thread类和实现Runnable接口在取得程序运行的结果上没有什么太大的区别 如果出现多继承的情况,则用实现Runnable接口的方式来处理多线程的问题很有必要
关键字volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值
当将JVM设置为-server时会出现死循环
在启动RunThread.java线程时,变量private boolean isRunning=true,存在于公共堆栈及线程的私有堆栈中,在JVM被设置为-server模式时为了线程的效率,线程一直在私有堆栈中取得isRunning的值是true 二代码thread.setRunning(false),虽然被执行,更新的却是公共堆栈中的isRunning变量值false,所以一直就是死循环的状态
线程主体
线程写入| 线程读取|
工作内存
写入主内存| 读取该数据|
主内存

关键字volatile是线程同步的轻量级实现 所以volatile性能肯定比synchronized要好,并且volatile只能修饰变量 synchronized可以修饰方法 及代码块
多线程访问volatile不会发生阻塞
volatile能保证数据的可见性,但不能保证原子性 synchronized可以保证原子性,也可以间接保证可见性

关键字volatile主要使用的场合是在多个线程中可以感知实例变量被更改,并且可以获取最新的值使用,也就是用多线程读取共享变量时可以获得最新值使用
volatile提示线程每次从共享内存中读取变量,而不是从私有内存中读取,保证同步数据的可见性
对于用volatile修饰的变量,JVM虚拟机只是保证从主内存加载到线程工作内存的值是最新的

使用原子类进行i++操作
除了在i++操作时使用synchronized关键字外实现同步,还可以使用AtomicInteger原子类进行实现
原子操作是不能分割的整体,没有其他线程能够中断或检查正在原子操作的变量,一个原子类型就是一个原子操作可用的类型
synchronized可以使多个线程访问同一个资源具有同步性,而且还具有将线程工作内存中私有变量与公共内存中的变量同步的功能
synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法或某一个代码块。它包含两个特性:互斥性和可见性。synchronized不仅仅解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程。都看到由同一个锁保护增强所有的修改效果

进程通信
等待/通知机制
之前多个线程之间实现通信是因为多个线程共同访问同一个变量,两个线程完全是主动式读取一个共享变量,在花费读取时间的基础上,读取到的值是不是想要的,并不能完全确定
wait方法作用是使当前执行代码的线程进行等待,wait方法是Object类的方法。该方法用来将当前线程置入预执行队列中。并且在wait方法所在代码处停止执行,直到接到通知或被中断。在调用wait方法之前,线程必须获得该对象的对象级别锁 即只能在同步方法或同步块中调用wait方法,
public class thread1 extends Thread{
private Object lock;
public thread1(Object lock)
{
this.lock=lock;
}
public void run() {
try{
synchronized(lock)
{
if(mylist.size()!=5)
{
System.out.println(“wait begin”+System.currentTimeMillis());
lock.wait();
System.out.println(“wait end “+System.currentTimeMillis());
}
}
}catch(Exception e)
{
e.printStackTrace();
}
}
}
public class thread2 extends Thread{
private Object lock;
public thread2(Object lock)
{
this.lock=lock;
}
public void run() {
try{
synchronized(lock)
{
for(int i=0;i<10;i++)
{
mylist.add();
if(mylist.size()==5)
{
lock.notify();
System.out.println(“已发出通知”);
}
System.out.println(“添加了”+(i+1)+”个元素”);
Thread.sleep(1000);
}
}
}catch(Exception e)
{
e.printStackTrace();
}
}
}
public class run {
public static void main(String[] args) {
try{
Object lock=new Object();
thread1 t1=new thread1(lock);
t1.start();
Thread.sleep(50);
thread2 t2=new thread2(lock);
t2.start();
}catch(Exception e)
{
e.printStackTrace();
}
}
}
执行结果wait begin1494815347526
添加了1个元素
添加了2个元素
添加了3个元素
添加了4个元素
已发出通知
添加了5个元素
添加了6个元素
添加了7个元素
添加了8个元素
添加了9个元素
添加了10个元素
wait end 1494815357738

synchronized可以使任何一个Object对象作为同步对象来看待,而Java为每个Object都实现了wait和notify方法,他们必须用在被synchronized同步的Object的临界区内。通过调用wait方法可以使处于临界区内的线程进入等待状态。同时释放被同步对象的锁
每个锁对象都有两个队列,一个是就绪队列,一个是阻塞对象

wait
执行完同步代码块就会释放对象的锁
在执行同步代码块的过程中,遇到异常而导致线程终止,锁也会释放
在执行同步代码块的过程中,执行了锁所属对象
wait(long)是等待某一时间内是否有线程对锁进行唤醒,如果超过这个时间则自动唤醒

通过管道进行线程间通信 字节流

类ReenntrantLock也可以实现关键字synchronized与wait()和notify()/notifyAll()方法相结合可以实现等待/通知模式,但是对于ReentrantLock来说,需要借助于Condition对象,Condition类是可以实现多路通知功能,也就是在一个Lock对象中可以创建多个Condition(即对象监视器)实例,线程对象
对象可以注册在指定的Condition中,从而可以有选择性进行线程通知,在调度线程上更加灵活
使用ReentrantLock结合Condition类可以实现选择性通知。
synchronized相当于整个Lock对象中只有单一的Condition对象,所有线程都注册在它一个对象上,
Condition将Object监视器方法(wait,notify,notifyAll)分解为截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。
其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用
方法
await() 造成当前线程在接到信号或被中断之前一直处于等待状态
signal() 唤醒一个等待线程

公平与非公平锁
锁Lock分为公平锁和非公平锁。
公平锁为线程获取的属性是按照线程加锁的顺序来分配,即先来先得的FIFO先进先出顺序,
而非公平锁就是一种获取锁的抢占机制,是随机获得锁,和公平锁不一样的就是先来的不一定先得到锁。
int getHoldCount()查询当前线程保持此锁定的个数,也就是调用lock方法的次数
int getQueueLength()返回正在等待获取此锁定的线程估计数
int getWaitQueueLength(Condition condition) 返回等待与此锁定相关的给定条件Condition的线程估计数
boolean hasQueueThread(Thread thread) 查询指定的线程是否正在等待获取此锁定
boolean hasQueueThreads()查询是否有线程正在等待获取此锁定
boolean hasWaiters(Condition condition) 查询是否有线程正在等待与此锁定有关的condition条件
boolean isFair() 判断是不是公平锁
boolean isHeldByCurrentThread()查询当前线程是否保持此锁定
boolean isLocked() 查询此锁定是否由任意线程保持
void lockInterruptibly()如果当前线程未被中断,则获取锁定,如果已经被中断则出现异常
boolean tryLock() 仅在调用锁定未被另一个线程保持的情况下,才获取该锁定
boolean tryLock(Long timeout,TimeUnit unit)如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定

定时器
它在内部使用多线程的方式进行处理 timer负责计划任务的功能,也就是在指定的时间内开始执行某一个任务
timer设置计划任务,timerTask封装任务

java中每一个对象都可以作为锁,这是synchronized实现同步的基础
1 普通同步方法,锁是当前实例对象
2 静态同步方法,锁是当前类的class对象
3 同步方法,锁是括号里面的对象

原创粉丝点击