关于多线程之synchronized关键字

对象及变量的并发访问

1.synchronized同步方法

synchronized关键字可用来保障原子性、可见性、和有序性。

我们需要掌握的是:

1)synchronized对象监视器为Object时的使用方法

2)synchronized对象监视器为Class的使用方法

1.1方法内的变量为线程安全

非线程安全问题存在于实例变量中,对于方法内部的私有变量,则不存在线程安全问题。

1.2 实例变量非线程安全问题

如果多个线程共同访问一个对象中的实例变量,则有可能会出现非线程安全问题。

两个线程同时访问同一个对象中的同步方法时一定是线程安全的。

1.3 多个对象多个锁

创建类HasSelfPrivateNum:

public class HasSelfPrivateNum {
    private int num = 0;
    synchronized public void add(String username){
        try {
            if(username.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 (InterruptedException e){
            e.printStackTrace();
        }
    }
}

创建两个线程类:

public class ThreadA extends  Thread {
    private HasSelfPrivateNum numRef;
    public  ThreadA(HasSelfPrivateNum numRef){
        super();
        this.numRef = numRef;
    }
    @Override
    public void run() {
        super.run();
        numRef.add("a");
    }
}
ThreadB:
public class ThreadB extends  Thread {
    private HasSelfPrivateNum numRef;
    public ThreadB(HasSelfPrivateNum numRef){
        super();
        this.numRef = numRef;
    }
    @Override
    public void run() {
        super.run();
        numRef.add("b");
    }
}

最后的运行结果为:

a set over
b set over
bnum=200
anum=100

总结:本示例创建了两个业务对象(numRef1,numRef2),在系统中产生了两个锁,线程和业务对象属于一对一的关系,每个线程执行自己所属业务对象中的同步方法,不存在争抢关系,所以运行结果是异步的,更具体的讲,在上面的这个示例中创建了两个业务对象,所以产生两份实例变量,每个线程访问自己的实例变量,所以加不加synchronized关键字都是线程安全的。

如果运行结果为a set over,anum =100,b set over,bnum = 200,才说明线程是同步进行的。

1.4 关于synchronized同步锁方法的结论
1.4.1 A线程持有Object对象的lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法
1.4.2 A线程持有object对象的lock锁,B线程如果在这时调用object对象中的synchronized类型的方法,则需要等待。
1.4.3 在方法声明处添加synchronized并不是锁方法,而是锁当前类的对象
1.4.4 在Java中只有“将对象作为锁”这种说法,并没有“锁方法”这种说法
1.4.5 在Java中,“锁”就是对象,“对象”可以映射成“锁”,哪个线程拿到这把锁,哪个线程就可以执行这个对象中的synchronized同步方法
1.4.6 如果在X对象中使用了synchronize的关键字声明非静态方法,则X对象就被当成锁。
1.5 脏读

通过synchronized关键字可以解决脏读的问题。

拿一个对象A的setValue()和getValue()为例,其中setValue()被synchronized修饰,而getValue()没有。

现在有两个线程A和线程B

A先执行,setValue(),A线程未执行完,B线程无法调用setValue(),但是getValue()可被B线程调用,此时出现脏读

解决方案:给getValue()也加上synchronized修饰。

1.6 为了解决synchronized同步方法的等待时间过长的方案------->synchronized同步代码块synchronized(this)

synchronized同步代码块的作用:(synchronized同步代码块也是一样)

1.对其他synchronized同步方法或synchronized(this)同步代码块调用呈同步效果

2.同一时间只有一个线程可以执行synchronized同步方法中代码

1.7 synchronized(非this对象)
1.7.1 除了使用synchronized(this)格式来创建同步代码块,其实Java还支持将“任意对象”作为锁来实现同步的功能,这个“任意对象”大多数是实例变量及方法的参数,使用格式为
1.7.2 synchronized(非this对象x)同步代码块的作用:当多个线程争抢相同的“非this对象x”的锁时,同一时间只有一个线程可以执行synchronized(非this 对象x)同步代码块中的代码s
1.7.3 锁非this 对象具有一定的优点:如果一个类中有很多个synchronized方法,则这时虽然能实现同步,但影响运行效率,如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的,因为有两把锁,不与其他锁this同步方法争抢this 锁,可大大提高运行效率。
1.8 多个锁就是异步执行

使用synchronized(非this对象x)同步代码“代码块”操作时,锁必须是同一个,否则就是异步调用,交叉执行。

下面使用个例子证明多个锁时是异步执行的结果:

public class ThreadA extends  Thread{
    private  Service service;
    public ThreadA(Service service){
        this.service = service;
    }

    @Override
    public void run() {
        service.a();
    }
}

ThreadB:
public class ThreadB extends  Thread{
    private  Service service;
    public ThreadB(Service service){
        this.service = service;
    }

    @Override
    public void run() {
        service.b();
    }
}

创建个模拟业务类:Service

public class Service {
    private String anyString  = new String();
    public void a(){
        try {
            synchronized (anyString){
                System.out.println("a begain");
            }
            Thread.sleep(3000);
            System.out.println("a end!");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
    synchronized public void b(){
        System.out.println("b begain");
        System.out.println("b end!");
    }
}

最后运行我们的测试类:

public class RunTest {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("a");
        a.start();
        ThreadB b = new ThreadB(service);
        b.setName("b");
        b.start();
    }
}

最后的运行结果我们看到当A线程还没跑完的时候,B线程也已经在开始了,由于锁不同,所以运行结果是异步调用的;

a begain
b begain
b end!
a end!

和synchronized(非this对象X)和synchronized(this对象或者方法)不同的是,后者多个线程时,得等先拿到锁的那个线程跑完了才能执行其他synchronized的方法而synchronized(非this对象x)可以和其他synchronized方法异步执行。

1.9 细化synchronize(非this对象x)的三个结论:synchronize的(非this对象x)是将x对象本身作为“对象监视器”
1.9.1 当多个线程同时执行synchronized(x){}同步代码块时呈现同步效果
1.9.2 当其他线程执行x对象中synchronized同步方法时呈现同步效果
1.9.3 当其他线程执行x对象方法里面的synchronized(this)代码块时呈现同步效果

需要注意的是,如果其他线程调用不加synchronized关键字的方法,则还是异步调用,而且锁只是类继承的环境。

1.20 静态同步synchronized方法与synchronized(class) 代码块

关键字synchronized应用在static静态方法上时,那是对当前的*.java问对应的Class类对象进行持锁,Class类的对象是单例的。更具体的说,在静态static方法上使用synchronized关键字声明同步方法时,使用当前静态方法所在类对应Class类的单例对象作为锁。

而将synchronized加在非static方法上是将方法所在类的对象作为锁。

下面验证两个不是同一个锁的。

Service.java:

public class Service {
    synchronized public static void printA(){
        try {
            System.out.println("线程的名称为:"+Thread.currentThread().getName()+
                    "在"+System.currentTimeMillis()+"进入了printA");
            Thread.sleep(3000);
            System.out.println("线程的名称为:"+Thread.currentThread().getName()+
                    "在"+System.currentTimeMillis()+"离开了printA");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    synchronized public static void printB(){
        System.out.println("线程的名称为:"+Thread.currentThread().getName()+
                "在"+System.currentTimeMillis()+"进入了printB");
        System.out.println("线程的名称为:"+Thread.currentThread().getName()+
                "在"+System.currentTimeMillis()+"离开了printB");
    }

    synchronized public void printC(){
        System.out.println("线程的名称为:"+Thread.currentThread().getName()+
                "在"+System.currentTimeMillis()+"进入了printC");
        System.out.println("线程的名称为:"+Thread.currentThread().getName()+
                "在"+System.currentTimeMillis()+"离开了printC");
    }

}

创建三个线程:

public class ThreadA extends  Thread{
    private  Service service;
    public ThreadA(Service service){
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.printA();
    }
}
public class ThreadB extends  Thread{
    private  Service service;
    public ThreadB(Service service){
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.printB();
    }
}
public class ThreadC extends  Thread{
    private  Service service;
    public ThreadC(Service service){
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.printC();
    }
}

最后运行测试类:

public class RunTest {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("a");
        a.start();
        ThreadB b = new ThreadB(service);
        b.setName("b");
        b.start();
        ThreadC c = new ThreadC(service);
        c.setName("c");
        c.start();
    }
}

最后的运行结果如图:方法C是异步执行,同步synchronized放在static方法上可以对类的所有对象的实例起作用,比如此时的线程A和B,不同的service对象,但是对printA和printB依然是同步执行

线程的名称为:a在1592551267059进入了printA
线程的名称为:c在1592551267062进入了printC
线程的名称为:c在1592551267062离开了printC
线程的名称为:a在1592551270059离开了printA
线程的名称为:b在1592551270059进入了printB
线程的名称为:b在1592551270060离开了printB
1.21 同步synchronized(A.class)和synchronized A中的static方法是同样作用,可以对类的所有对象的实例起作用,但是非static的synchronized只能对同一个实例有作用
1.22 String常量池特性与同步相关的问题与解决方案

问题:当synchronized(String对象)时,假设两个线程的值都是“AA”,String的特性会判断“AA"=="AA"为true,则此时两个线程的锁相同,B线程将不会被执行。

解决:所以大多数情况下,不适用String作为锁对象,而改用其他,而用new Object()实例化一个新的Object对象,它并不放入缓存池中,或者执行new String ()创建不同的字符串对象,形成不同的锁。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值