Android 并发编程synchronized

92 阅读5分钟

原子性

操作可以理解为是不可分割的操作,要么完全执行,要么不执行,不存在中间状态。在并发环境下,如果多个线程同时访问和修改相同的数据,如果没有保证原子性,可能会导致数据损坏、计算错误或其它异常结果。

可见性

表示内存的可见性,即变量在线程中的工作内存是不确定的,需要从主内存中读取

有序性

为了提高执行效率,编译器、CPU处理器在运行时会对代码指令进行重排,重排过程中会遵循as-if-serial语义,即不影响单线程的运行结果

java momory model (java内存模型)

JMM

  • 主内存:所有线程都共享的,类的成员变量,静态的成员变量

  • 工作内存:每一个线程有自己的工作内存,工作内存只存储该线程对共享变量的副本。线程对变量的所有操作(读、取)都必须在工作内存中完成,而不是直接读写主内存中的变量,不同线程之间也不能直接访问对方工作内存中的变量。

Java内存模型的作用

Java内存模型是一套在多线程读写共享数据时,对共享数据的可见性、有序性和原子性的规则和保障

volatile、synchronized

JMM内存模型和CPU硬件内存的关系

image.png

volatile 只保证可见性

  • volatile主要是保证内存的可见性,即变量在寄存器中的工作内存是不确定的,需要从主内存中读取。synchronized主要是解决多个线程访问资源的同步性。
  • volatile作用于变量,synchronized作用于代码块或者方法。
  • volatile仅可以保证数据的可见性,不能保证数据的原子性。synchronized可以保证数据的可见性和原子性。
  • volatile不会造成线程的阻塞,synchronized会造成线程的阻塞。

synchronized 保证可见性、原子性、有序性

内置锁;锁的管理是 jvm 管理的;

不可中断性

一个线程获取到锁之后,另外一个线程处在阻塞等待状态,不可中断;只有第一个线程释放锁,第二个线程才会继续执行;

wait/notify

  • wait 会释放锁、释放cpu

  • nofity 会通知 wait 的线程继续执行 , 并不是立即通知 wait 而是在 synchronized 代码执行完之后才会通知 wait 地方;

  • notifyAll 通知所有 wait 的线程;

修饰方法 synchronized

作用范围是整个方法,所以方法中所有的代码都是同步的:

  • 修饰非静态方法,同一个实例的线程访问会被拦截,非同一实例可以同时访问。 即此时是默认对象锁(this)。
    private synchronized void sync5() {
        Log.d(TAG, Thread.currentThread().getName() + "_Sync5: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        try {
            Log.d(TAG, Thread.currentThread().getName() + "_Sync5_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            Thread.sleep(2000);
            Log.d(TAG, Thread.currentThread().getName() + "_Sync5_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
  • 修饰静态方法,默认类锁,静态的方法的使用只通过类就可以调用;

修饰代码块

image.png

synchronized(this)

HandlerThread 使用对象锁,作用范围是handlerThread 这个对象;

    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }

    /**
     * This method returns the Looper associated with this thread. If this thread not been started
     * or for any reason isAlive() returns false, this method will return null. If this thread
     * has been started, this method will block until the looper has been initialized.
     * @return The looper.
     */
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }

        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

synchronized(Class)

单例模式使用的类锁;

类锁:所有的线程都会拦截进行阻塞;

public static RetrofitManager getInstance() {
    if (instance == null) {
        synchronized (RetrofitManager.class) {
            if (instance == null) {
                instance = new RetrofitManager();
            }
        }
    }
    return instance;
}

monitor 才是真正的锁,

synchronized 代码块的锁对象会关联一个 monitor,monitor 不是我们主动创建,而是JVM执行到同步代码块时,发现没有 monitor 对象就会创建 monitor ,monitor 内部有两个重要的成员变量 owner 拥有这把锁的线程,recursions 会记录线程拥有锁的的次数,

  • monitor owner:拥有锁的对象 recursions:拥有锁的次数

  • monitorenter recursions += 1; owner = 当前对象;

  • monitorexit recursions -= 1; owner = null;

  • 同步方法会隐式加上 monitorenter 和 monitorexit

同步代码块出现异常会立刻释放锁

synchronized 总结

1、对于静态方法,由于此时对象还未生成,所以只能采用类锁;

2、只要采用类锁,就会拦截所有线程,只能让一个线程访问。

3、对于对象锁(this),如果是同一个实例,就会按顺序访问,但是如果是不同实例,就可以同时访问。

4、如果对象锁跟访问的对象没有关系,那么就会都同时访问。

wait notify join sleep 区别

wait 和 join

区别waitjoin
Object类Thread类
目的线程间通信同步作用,使线程之间的执行从“并行”变成“串行”
同步需要synchronized不需要synchronized
相同点暂停当前的线程暂停当前的线程
相同点可通过 Interrupte 中断唤醒可通过 Interrupte 中断唤醒

wait 和 sleep

区别waitsleep
Object类Thread类
目的notify、notifyall 都是Object对象的方法,一起使用的,用于锁机制,所以会释放锁跟锁没关系,不会释放锁
相同点让出cpu资源让出cpu资源