synchronized

概要前提

线程之间 内存资源是共享的, 这里的“内存共享”指多个线程可以访问和操作同一块内存区域中的数据,
这种共享可以是全局变量、静态变量(static),也可以是堆上的对象等

正是因为线程之间 内存资源是共享, 所以多个线程在对同一个内存资源
进行读或者写的是会造成 数据不一致性
,也就是
当一个线程读取一个变量的值时,该变量的值可能在另一个线程中被修改了,从而导致读取到的是一个不一致或旧的值

由于cpu对线程的调度不可预估性,

为了避免问题存在,Java提供了多种同步机制,如synchronized关键字、ReentrantLock锁、volatile关键字

synchronized使用

synchronized 可以保证 多个线程在对同一个共享资源操作的时候 实现
可见性,原子操作,有序性

synchronized 锁的是对象
synchronized关键字可以用在方法上,也可以用在代码块上,
不管怎么变,原则都是 拿一个 对象 当锁

  • 对于修饰方法的synchronized,默认的锁对象就是当前方法的对象。

Object a=new Object();
a.doSomething();
//用a 对象当锁
public synchronized void doSomething() {
    
}
  • 对于修饰静态方法的synchronized,其锁对象就是此方法所对应的类Class
 private static synchronized void method() {
    }
  • 用任何一个对象当锁同步代码块
Object a=new Object();
synchonized(a) {
    //do something here
}

因为对象锁只有一个,那么哪个线程争抢到这个对象锁,哪个线程下的就先执行,synchonized{}内的代码只有一个线程能做,而synchonized{}外的就不是了,当 synchonized{} 执行完后 或者抛出了异常 会自动把该线程持有的锁释放。

wait 和 notify

wait()和 notify() 是每个对象的方法
即Object object= new Object();
object.notifyAll(),object.wait(),object.notify。

当执行 对象的wait(),这个时候当前线程会处于一个挂起等待状态
wait()后面的代码都不会执行,并且,会把该线程持有的锁给释放出去,直到 有对象执行 唤醒 方法,

调用 wait()后 需要有人执行唤醒 notify ,才能继续执行

而notify 就是唤醒,但是 并不是也不百分百 调用了 notify 那些 wait 的地方就立马 继续跑下去 ,
notify 只是让 wait 的线程 从挂起状态转为 就绪等待状态 ,等待cpu调度 当该线程再次争抢到锁,wait()后面的代码才会继续执行。

sleep和wait

调用 sleep 和 wait 都会让当前线程挂起阻塞
sleep 时间到了就唤醒
而 wait 需要 对应 对象锁 执行 唤醒notify 操作才会唤醒
调用 wait 会释放锁,而sleep不会跟对象锁没关系

synchronized 的使用场景

synchronized 的使用场景思路 其实都是 互斥访问

互斥访问就是通过哪个线程先拿到锁,哪个线程先对资源读或者写,而另外的其他线程在调用 对 资源数据 读写的时刻
线程会挂起阻塞在此地
,直到获取到锁才会继续唤醒执行下去

我们希望两个线程在做事情的时候,其中一个线程 先执行,而另外一个线程挂起阻塞等待

当 A B 两个线程开启 由于cpu 调度 我们无法预约 谁先执行,
也就是会出现两种 情况 ,可能A先 ,可能 B 先
首先不管如何, 一方先拿到锁执行,另外则会再锁外线程挂起阻塞等待

我们希望 A 先执行 再执行B, 还没执行A 前,另外的B线程挂起等待

这个时候,可以通过 在 B 中 进行 条件 循环 wait , 由于条件满足是再
A 中实现, 所以 B 会因为 条件无法达成了 wait 等待下去

这个时候 锁就会 给到 A ,在 A中进行条件达成,然后再 notify
也就是 释放锁 , 让B 接着执行

这样就算 线程 B 先执行,它也会因为 条件无法达成 wait 阻塞挂起
而原本因没法抢到锁而挂起等待的A 会拿到锁
只有A 执行条件达成,notify ,B 才会继续执行
而如果 线程 A 先执行,线程B 也会在锁外方法 阻塞等待下去

说白了
其实就是希望慢执行的一方 如果被cpu调用先执行了,调用wait 先等着
等 希望先执行的人执行了 调用 notify 通知你 你再去执行

应用场景如下:

有时候我们 写代码的时候 在主线程开启一个线程去做事情,然后希望上面的代码先执行 ,下面的代码在上面的代码获取结果 后才执行

//在主线程中开启一个线程做事情
val myThread = MyThread()
myThread.start()
 // 但是在这里,你无法保证,线程的中任务先执行,然后下面的获取数据才执行, 很有可能下面的主线程获取资源执行了,它获取的数据是null 
//在这里是可以简单的使用 sleep (1000) 让 主线程 停止等待下,等上面执行完 然后下面才执行,但是还有更好方法
myThread.getResult()



public class MyThread extends Thread {

    String result;

    @Override
    public void run() {
        super.run();

        result = "result";

    }
}


String getResult() {
    return result;
}

}

那就是采用 互斥访问

public class MyThread  extends Thread {

    String result;

    @Override
    public void run() {
        super.run();
        Log.d("MyThread", "run: 1");
        synchronized (this){
            sleep(2000);
            result = "result";
            Log.d("MyThread", "run: 2");
            notifyAll();
        }
    }


    String getResult() throws InterruptedException {
        Log.d("MyThread", "getResult: 1");
         if(!isAlive()){
            return null;
        }
        synchronized(this){
            Log.d("MyThread", "getResult: 2");
            while (result == null){
                Log.d("MyThread", "getResult: 3");
                wait();
            }
        }
        Log.d("MyThread", "getResult: 4");
        return result;
    }

}

val myThread = MyThread()
myThread.start()

myThread.getResult()
Log.d("activity", "获取资源成功")

我们希望的是 线程里面 获取到资源 是最先执行, 然后 主线程获取资源的时候才执行
在这里 通过 两个 synchronized ,进行了两个 synchronized{}块内的互斥访问

先是利用 myThread 这个对象锁 ,对子线程的 synchronized {} 和主线程的 synchronized {} 谁先抢到锁,谁就先执行

有两种情况:

  • 如果是 子线程的 synchronized {} 先拿到锁,那么当 主线程 调用 getResult
    方法的时候
    它会停在锁synchronized {}外 等待获取,这个时候主线程是卡住的你会发现主线程的
    Log.d(“activity”, “获取资源成功”) 这句打印是 得 等到 主线程 myThread.getResult()
    获取锁 之后才会执行完

在这里插入图片描述

  • 如果 是 主线程的 synchronized {} 先拿到锁,那么他会因为没有拿到资源,资源 ==null ,在 wait(),也就是主线程会停在 wait 这里,而这个时候 锁会给到子线程的 synchronized {} 获取资源,然后 ,获取完资源后,调用了notifyAll() 唤醒主线程 wait ,也就是 wait 后面就会继续执行,而由于获取到资源了,所以不再死循环 所以就返回 资源 最终主线程获取成功这一句话才会打印

这样不管cpu 如何调度 谁先获取锁,都能保证 子线程获取到资源 是最先执行, 然后 主线程获取资源的时候才执行

  • 生产者消费者模型

下面我们以一个最经典的生产消费模式来感受下。
我们希望 生成一个数据,再消费一个数据,在没有生产之前,消费等待生产,在生产了一个之后,等到消费再生产

class FactoryBread {

    var count:Int=0


    @Synchronized
    fun make(threadName: String) {
        while (count == 0) {
            count++
            println(threadName + "生产完成面包" + "当前面包数量为:" + count)
            println("生产完成告诉消费者可以消费了")
            (this as Object).notifyAll()
            try {
                println("生产者等待消费者消费")
                (this as Object).wait()
            } catch (e: InterruptedException) {
                throw java.lang.RuntimeException(e)
            }
        }
    }

    @Synchronized
    fun consume(threadName: String) {
        while (true) {
            if(count>=1){
                count--
                println(threadName + "消费完成面包" + "当前面包数量为:" + count)
                (this as Object).notifyAll()
            }
            try {
                println("消费者等待生产中")
                (this as Object).wait()
            } catch (e: InterruptedException) {
                throw java.lang.RuntimeException(e)
            }
        }
    }

}



fun main() {

    val factoryBread = FactoryBread()
    Thread{
        factoryBread.make(Thread.currentThread().name)
    }.start()

    Thread{
        factoryBread.consume(Thread.currentThread().name)
    }.start()
}

在这里插入图片描述

上面是以 factoryBread 这个对象当作对象锁, Synchronized 操作在方法中

当两个线程执行的时候,

消费者线程方法先执行,也会因为 count 没有 >=1 调用 wait 释放 锁 等待唤醒,
然后 生产者 生产完数据后 就会调用 notify 通知消费者可以执行,并且
自己调用 wait 释放锁,并且等待消费者唤醒

消费者因为条件满足了 count ==1 ,那么进行消费,而消费结束了,
调用 notify 通知 生产者不再挂起
自己调用 wait 释放锁,并且等待生产者唤醒

而如果是生产者线程先执行,消费者线程方法会阻塞在 Synchronized方法外无法阻塞等待获取锁才会进入

还有一种是 synchronized放在代码块上的

class FactoryBread {

    var count:Int=0

    //制造一个对象,用于锁
    val lock = Object()

    fun make(threadName: String) {
        synchronized(lock) {
            while (count == 0) {
                count++
                println(threadName + "生产完成面包" + "当前面包数量为:" + count)
                println("生产完成告诉消费者可以消费了")
                lock.notifyAll()
                try {
                    println("生产者等待消费者消费")
                    lock.wait()
                } catch (e: InterruptedException) {
                    throw java.lang.RuntimeException(e)
                }
            }
        }
    }

    //消费面包的工具,参数就是哪个工人消费的。
    fun consume(threadName: String) {
        synchronized(lock) {
            while (count >= 1) {
                count--
                println(threadName + "消费完成面包" + "当前面包数量为:" + count)
                lock.notifyAll()
                try {
                    println("生产者等待消费者消费")
                    lock.wait()
                } catch (e: InterruptedException) {
                    throw java.lang.RuntimeException(e)
                }
            }
        }
    }

}

上面只是写法不同,专门弄个对象锁。

总结:

  • 对于多个线程共享一个资源 在对 数据进行 读或者写 的时候 会造成线程不安全问题
  • 线程不安全可以用对象锁synchronized 让多个线程在读或者写的时候进行互斥访问
  • 互斥访问就是通过哪个线程先拿到锁,哪个线程先对资源读或者写,而另外的其他线程在调用 对 资源数据 读写的时刻
    线程会挂起阻塞在此地,直到获取到锁才会继续唤醒执行下去
  • 通过 一个线程条件不满足就 wait,等另外线程条件达成调用 notify 的方式 实现顺序执行同步
  • 14
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值