关于synchronized关键字的认识

很多时候在阅读Android源码时,都会碰到synchronized这个关键字,实际上我一直是知道这个方法的作用是让方法或者代码块的操作具有原子性,从而解决多线程共享资源的问题,但是这个概念一直比较模糊,这里就趁着这篇文章来梳理一下。

1、Java线程生命周期

说好的讲synchronized的呢,怎么开篇就是生命周期问题呢?要讲清楚synchronized必须要搞清楚Java线程生命周期问题。关于Java线程生命周期详细知识可以在参考我的另一篇博客Java线程生命周期各个状态总结。这里只是简单的说下,Java线程生命周期从大的方面划分,可以分为四个状态,即新建、就绪、阻塞和死亡。

新建(new):分配了必须的系统资源,并执行了初始化。
新建状态下的线程表示可以获得CPU时间片,调度器可以将其转化为runnable或者blocked状态。

就绪(runnable): 就绪状态表示线程处于随时可运行的状态,也包括了正在运行的状态
当线程在就绪状态时,只要调度器把时间片分配给线程,线程就能运行。

阻塞(blocked):当线程处于阻塞状态时,调度器将会忽略线程,不会分配给线程任何CPU时间
也就是说阻塞和就绪两状态区别在于,调度器可以给就绪状态线程分配CPU时间片,而无法给阻塞状态线程分配CPU时间片,只有当线程变成了runnable状态,它才有可能被执行。

结束(terminated):处于结束状态的线程是不可调度的,并且再也不会得到CPU时间片
这个结束状态比较好理解,不过需要提的是一般而言,线程是在run方法运行完成后,就进入了结束状态。

这四个状态新建(new)和结束(terminated)没啥好说的,平时在线程运行过程中,我们一般关注的都是就绪(runnable)以及阻塞(blocked)两个状态,而且这两个状态在一定条件下可以发生转化,而本章要说的synchronized关键字和这个转化就有一定关系。

2、wait 和 notify

小伙伴们可能要说了,什么情况还不开始讲synchronized啊,怎么又扯到wait和notify了。小伙伴,别着急,要说清楚synchronized还真的说说wait和notify这个两个方法,这两个方法就是控制线程的就绪(runnable)和阻塞(blockded)两状态的方式之一。

Java的所有对象就继承自Object类,该类中定义了wait和notify方法,而且都是final的native方法,具体怎么实现这里不管,我们知道知道是个类,我们就可以调用这两个方法就行了。

这里写图片描述

要说清wait和notify方法,就要了解线程锁的概念。线程锁是一个很抽象的概念,既然是一把锁总得有一个上锁的对象吧,它上锁的对象就是这里提到的继承自Object的类的实例(这里要注意一个类本身也可以理解为一个特殊的实例,也及时说ClassExtendFromObject.class.wait()这样用也是没啥问题的)。

知道了线程锁上锁的对象了,那么怎么上锁的呢,实际上这个上锁就是通过synchronized这个关键字来完成的,这个上锁的知识点下一小节细讲,这里知道是通过synchronized关键字上锁的便可。

最后,知道给什么上锁和怎么上锁后,小伙伴们肯定一头雾水了,我们为什么要上锁呢?实际上上锁就是为了保证当前由synchronized标注的方法或者代码块的原子性,也就是A线程在操作synchronized标注的方法或者代码块时,表示A线程拥有了对应线程锁,那么B线程没有线程锁便无权操作对应synchronized标注的方法或者代码块,这样就确保了synchronized标注的方法或者代码块的操作原子性。

线程锁说了这么多,这里提到的wait和notify方法和线程锁有什么关系么?当然是有的,线程A在获取线程锁执行synchronized方法或者代码块时,发现条件不成立不方便继续执行的时候,便可以通过wait方法来让出线程锁,也可以通过wait来随机让调度器分配另一个线程来获得线程锁,或者通过notifyAll让所有其它线程有机会来争取线程锁(这个可以参考生产者消费者模式实现这个博客)。不过特别需要说明的是,线程锁有排他性,一旦一个线程获取到了特定对象的线程锁,那么其它线程便无法执行对应synchronized关键字标注的方法或者代码块。

总得来说就是在synchronized方法或者代码块中执行了wait则当前的线程便失去竞争线程锁的可能,只有被notify后,才有机会来重新竞争线程锁,只有竞争到对应对象的线程锁才能执行对应对象的synchronized方法或者代码块。

3、synchronized关键字使用

终于讲到synchronized关键字了,synchronized关键字有三种使用方式

修饰实例方法:作用于当前实例加锁,进入同步方法代码前要获得当前实例的锁

修饰静态方法:作用于当前类对象加锁,进入同步方法代码前要获得当前类对象的锁

修饰代码块:指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁

实际上这三种方式本质上都是一个意思,就是通过synchronized给类对象(类本身也是一个对象)加线程锁,并且制定线程锁的作用范围。那么我们通过下面例子来看看synchronized的具体作用。

public class ShareResource implements Runnable {
    static int i = 0;

    public synchronized void increase() {
        i = i + 1;
    }

    @Override
    public void run() {
        for (int k = 0; k < 100000; k++) {
            increase();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ShareResource instance1 = new ShareResource();
        ShareResource instance2 = new ShareResource();
        Thread treadA = new Thread(instance1);
        Thread treadB = new Thread(instance2);
        treadA.start();
        treadB.start();
        //join含义:当前线程必须等待thread线程终止之后才能从thread.join()返回
        treadA.join();
        treadB.join();
        System.out.println("i = " + i);
    }
}

按照上面讲的synchronized 加了线程锁,那么increase方法具有了原子性,那么A线程和B线程执行后,输出应该是200000了,但是结果好像不太对。
这里写图片描述
经过分析发现,main方法中生命了两个ShareResource对象,而increase方法又是实例方法,所以实际上
threadA和threadB分别获取的是两把锁,那么锁都不一样了,它们操作increase就没有排它性了。

public synchronized void increase()

要怎么做才能让threadA和threadB操作increase方法具有排它性呢?很简单,只需要synchronized关键字修饰静态方法,这样的话线程锁就是加在ShareResouce类上了,那么ShareResource的所有实例都使用的是一把线程锁了,这样便能实现increase方法的排它性

public static synchronized void increase()

再来看看执行结果
这里写图片描述
这样就没有问题了,实际上synchronized关键字修饰代码块也是同样的道理,这样synchronized关键字的使用方法也算是讲清楚了。

4、说说wait和sleep

最后这一小节真是凭空多出来的,只想了解synchronized关键字的小伙伴可以跳过了。列出这一小结也是为了彻底让wait和sleep划清界限,但是上要划清界限实际上也不简单,因为wait和sleep的确有些相似之处。

1、wait和sleep都可以使得当前线程处于阻塞状态,失去CPU时间

但是相似之外最大的还是区别

1、wait是针对线程锁而言的,wait使得当前线程失去线程锁并且没有被notify时,将没有机会再获取线程锁,而sleep没有线程锁的概念,只是单纯的让线程失去CPU时间,一旦sleep时间已过,线程便处于就绪(runnable)状态,随时可以获取CPU时间。实际上这个也是最关键的区别;
2、wait是Object实例方法,而sleep是Thread的静态方法;

所有总得来说wait和sleep的关键区别还是wait和线程锁有关系,而sleep和线程锁没有关系。

本文到这里就结束了,有什么问题还望小伙伴们指出,谢谢!

参考文献

1、深入理解Java并发之synchronized实现原理
2、Java线程生命周期各个状态总结
3、java中的sleep()和wait()的区别
4、线程的四种状态以及wait和sleep的区别
5、生产者消费者模式实现

  • 10
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值