《java多线程编程核心技术》 第2章 对象及变量的并发访问

本章知识点:

  • synchronized对象监视器为Object时的使用。
  • synchronized对象监视器为Class时的使用。
  • 非线程安全是如何出现的。
  • 关键字volatile的主要作用。
  • 关键字volatile与synchronized的区别及使用情况。

2.1 synchronized同步方法

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

方法内的变量不存在线程安全问题,永远都是线程安全的。

2.1.2 实例变量非线程安全

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

2.1.3 多个对象多个锁

public class MainTest02 {

    public static void main(String[] args) {

        HasSelfPrivateNum ha01 = new HasSelfPrivateNum();
        HasSelfPrivateNum ha02 = new HasSelfPrivateNum();
        ThreadA threadA = new ThreadA(ha01);
        threadA.start();
        ThreadB threadB = new ThreadB(ha02);
        threadB.start();
    }
}

关键字synchronized取得的锁都是对象锁,而不是把一段代码或者方法当做锁,所以哪个线程先执行带synchronized关键字的方法,哪个线程就持有该方法所属的对象锁Lock,那么其他线程只能呈现等待状态,前提是多个线程访问的是同一个对象。

2.1.4 synchronized方法与锁对象

1)A线程先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法。
2)A线程先持有object对象的Lock锁,B线程如果在这个时候调用object对象中的synchronized类型的方法,则需要等待,也是同步。

2.1.5 synchronized锁重入

关键字synchronized拥有锁重入的功能,就是在使用synchronized的时候,当一个线程得到一个对象锁后,再一次请求此对象锁的时候是可以再次得到该对象的锁的,也证明在一个synchronized方法内部调用本类的其他synchronized方法时候,是永远可以得到锁的。
可重入锁也支持在父子类继承的环境中。也就是说当存在父子类继承关系时候,子类是完全可以通过可重入锁调用父类的同步方法的

2.1.6 出现异常,锁自动释放

当一个线程执行一段代码出现异常时候,其所持有的锁会自动释放。

2.1.7 同步不具有继承性

同步不可以继承。也就是说子类重写父类synchronized方法时候,要实现同步,还得要在子类方法前面加上synchronized关键字。

2.2 synchronized同步语句块

synchronized方法是对当前对象进行加锁,而synchronized代码块是对某一个对象进行加锁。

2.2.1 synchronized方法的弊端

直接对一个方法进行加锁,其实在时间消耗上会有很大的问题。

2.2.2 synchronized同步代码块的使用

当两个并发线程访问同一个对象object中的synchronized(this) 同步代码块时,一段时间内只能有一个线程被执行,另一个线程处于等待状态。

package demo03;

public class ObjectService {

    public void serviceMethod(){
        try {
            synchronized (this){

                System.out.println(Thread.currentThread().getName()+" begin time"+System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getName()+" end time"+System.currentTimeMillis());
            }

        }catch (Exception ex){

            ex.printStackTrace();
        }
    }
}

package demo03;

public class ThreadA extends Thread{

    private ObjectService objectService;

    public ThreadA(ObjectService objectService){
        this.objectService=objectService;
    }

    @Override
    public void run() {
        super.run();
        objectService.serviceMethod();
    }
}

package demo03;

public class ThreadB extends Thread{

    private ObjectService objectService;

    public ThreadB(ObjectService objectService){
        this.objectService=objectService;
    }

    @Override
    public void run() {
        super.run();
        objectService.serviceMethod();
    }
}

package demo03;

public class Run {

    public static void main(String[] args) {

        ObjectService objectService = new ObjectService();
        ThreadA a= new ThreadA(objectService);
        a.setName("A");
        a.start();
        ThreadB b = new ThreadB(objectService);
        b.setName("B");
        b.start();
    }
}

在这里插入图片描述

2.2.3 一半异步,一半同步

不在synchronized块中就是异步执行,在synchronized块中就是同步执行。

2.2.4 synchronized代码块间的同步性

在使用同步synchronized(this)代码块时候要注意的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对同一个object中所有其他的synchronized(this)同步代码块的访问将被阻塞,这说明synchronized使用的对象监视器是一个。
要注意:
和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。

2.2.5 将任意对象作为对象监视器

这个任意对象大多数是实例变量及方法的参数,使用格式为synchronized(非this对象)。
锁非this对象有一定的优点:
如果在一个类中有多个synchronized方法,这个时候虽然能实现同步,但是方法之间会受到阻塞,影响效率,但是如果使用同步代码块锁非this对象,则synchronized(非this)代码块中的程序与同步方法是异步的。不与其他锁this同步方法争抢this锁,则可以大大提高运行效率。
但是要注意:
使用synchronized(非this对象X)同步代码块格式进行操作时,对象监视器必须是同一个对象,如果不是的话,运行的效果就是异步了。

2.2.6 细化验证三个结论

(1)当多个线程同时执行synchronized(X){}同步代码块时呈同步效果。
(2)当其他线程执行x对象中synchronized同步方法时呈同步效果。
(3)当其他线程执行x对象方法里面的synchronized(this)代码块也呈现同步效果。

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

关键字synchronized还可以应用在静态方法上,表示对当前的java文件对应的Class类进行持锁。

package demo04;

public class Service {

    synchronized public static void printA(){

        System.out.println("线程名称A"+Thread.currentThread().getName()+"时间="+System.currentTimeMillis());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程名称A"+Thread.currentThread().getName()+"时间="+System.currentTimeMillis());
    }

    synchronized public static void printB(){

        System.out.println("线程名称B"+Thread.currentThread().getName()+"时间="+System.currentTimeMillis());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程名称B"+Thread.currentThread().getName()+"时间="+System.currentTimeMillis());
    }
}

package demo04;

public class ThreadA extends Thread{

    @Override
    public void run() {
        super.run();
        Service.printA();
    }

}

package demo04;

public class ThreadB extends Thread{

    @Override
    public void run() {
        super.run();
        Service.printB();
    }
}

package demo04;

public class Run {

    public static void main(String[] args) {

        ThreadA threadA = new ThreadA();
        threadA.setName("A");
        threadA.start();

        ThreadB threadB = new ThreadB();
        threadB.setName("B");
        threadB.start();


    }
}

打印结果

线程名称AA时间=1577800514282
线程名称AA时间=1577800517282
线程名称BB时间=1577800517282
线程名称BB时间=1577800520282

主要是看在两个线程方法中都是直接通过类执行静态方法,并非是创建了对象执行,如果打印是同步的,那就证明确实是对当前的Class类进行持锁。

要注意:
对象锁只是对当前对象进行持锁,但是Class锁是对类的所有对象实例起作用。
示例如下:

package demo05;

public class Service05 {

    public void print() {

        synchronized (Service05.class){
            System.out.println("线程"+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

package demo05;

public class ThreadA extends Thread{

    private Service05 service05;

    public  ThreadA(Service05 service05){
        this.service05=service05;
    }

    @Override
    public void run() {
        super.run();
        service05.print();
    }
}

package demo05;

public class ThreadB extends Thread{

    private Service05 service05;

    public  ThreadB(Service05 service05){
        this.service05=service05;
    }

    @Override
    public void run() {
        super.run();
        service05.print();
    }
}

package demo05;

public class Run05 {

    public static void main(String[] args) {

        Service05 service05 = new Service05();
        ThreadA threadA = new ThreadA(service05);
        threadA.setName("A");
        threadA.start();
        ThreadB threadB = new ThreadB(service05);
        threadB.setName("B");
        threadB.start();
    }
}

线程A
线程B

2.2.8 数据类型String的常量池特性

在JVM中具有String常量缓存池的功能。因此在大多数情况下,同步synchronized 代码块都不使用String作为锁对象。

2.2.9 多线程的死锁

在设计程序的时候一定要避免双方相互持有对方的锁,只要出现互相等待对方释放锁就有可能出现死锁。

2.2.10 锁对象的改变

只要锁对象不变,即使对象的属性被改变,运行的结果还是同步。

2.3 volatile关键字

volatile的作用是使变量在多个线程间可见,是因为当线程访问变量的时候,强制性从公共堆栈中进行取值。

package demo06;

public class Service06 extends Thread{

    volatile private boolean isRunning = true;
    
    public boolean isRunning() {
        return isRunning;
    }

    public void setRunning(boolean running) {
        isRunning = running;
    }

    @Override
    public void run() {
        System.out.println("线程被开启");
        while(isRunning){

        }
        System.out.println("线程被停止了");
    }
}

package demo06;

public class Run06 {

    public static void main(String[] args) {

        Service06 service06 = new Service06();
        service06.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        service06.setRunning(false);
        System.out.println("已经赋值false");
    }
}

打印结果:


线程被开启
已经赋值false
线程被停止了

注意:

  1. volatile最致命的问题是不支持原子性。
  2. synchronized和volatile的比较:
    (1) 关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰变量,而synchronized可以修饰方法,以及代码块。
    (2)多线程访问volatile不会发生阻塞,而synchronized会出现阻塞。
    (3)volatile能保证数据的可见性,但是不能保证原子性,而synchronized可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公有内存中的数据做同步。
    (4)关键字volatile解决的是变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性。

在这里插入图片描述
关键字synchronized可以保证在同一时刻,只有一个线程可以执行某一段代码或者方法,它包含两个特征:互斥性和可见性。同步synchronized不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者代码块的每个线程,都看到由同一个锁保护之前所有的修改效果。

发布了157 篇原创文章 · 获赞 77 · 访问量 15万+

猜你喜欢

转载自blog.csdn.net/wu2374633583/article/details/103573382