理解Java中的synchronized关键字

本文深入解析Java中的synchronized关键字,探讨其在对象级和类级上的使用差异,以及如何通过锁机制实现线程间的同步,同时对比了sleep()与wait()的不同之处。

理解Java中的synchronized [’sɪŋkrənaɪzd]

问题

有如下一个类a

class A {
    public synchronized void a() {
    }

    public synchronized void b() {
    }
}

然后创建两个对象

A a1 = new A();
A a2 = new A();

然后在两个线程中并发访问如下代码:

Thread1       a1.a();        

Thread2       a2.a();

请问二者能否构成线程同步?

如果A的定义是下面这种呢?

class A {
    public static synchronized void a() {
    }

    public static synchronized void b() {
    }
}

要点

  1. synchronized采用锁同步的机制实现线程同步
  2. 线程在等待锁释放的过程中处于阻塞状态
  3. 一把锁可以同步所有对应的代码

使用:

synchronized修饰代码时会表明对应的锁,所有表明这把锁的代码都会同步。

Synchronized主修修饰对象为以下三种:

  1. 修饰普通方法 一个对象中的加锁方法只允许一个线程访问。但要注意这种情况下锁的是访问该方法的实例对象, 如果多个线程不同对象访问该方法,则无法保证同步。

  2. 修饰静态方法 由于静态方法是类方法, 所以这种情况下锁的是包含这个方法的类,也就是类对象;这样如果多个线程不同对象访问该静态方法,也是可以保证同步的。

  3. 修饰代码块 其中普通代码块 如Synchronized(obj) 这里的obj 可以为类中的一个属性、也可以是当前的对象,它的同步效果和修饰普通方法一样;Synchronized方法 (obj.class)静态代码块它的同步效果和修饰静态方法类似。

Class A{
    //锁是this
    public synchronized void a(){};
    public synchronized void b(){};
    //锁是this.class
    public synchronized static void x(){};
    public synchronized static void y(){};
    public void z(){
        //指定锁,该处指定为this
        synchronized(this){
        }
    }
}

this 锁对应object,this.class 锁对应 clss
所以第一种a1,a2两个锁是不同的锁会并发,第二种是相同的锁A.class不会并发;

另外,修饰代码块时指定的锁就与所有这把锁对应的代码同步。

运行机制

简单的说是这样synchronized在实现同步功能时会声明一把锁,表明必须持有这把锁的线程才能访问执行,在一个线程需要执行这块代码时,会先尝试持有这把指定的锁,如果这个锁已被别的线程持有,那么该线程就会开始等待,直到这把锁被释放后才会继续执行,并且在执行时会一直持有这把锁,以防止其他线程进入这把锁控制的所有代码,直到该线程执行完才会释放这把锁。

上面说的很模糊,这个锁是怎么回事呢,看了这个synchronized底层语义原理之后大概意思就是说synchronized的对象锁,锁的指针指向的是monitor对象,每个对象有都存在着一个 monitor(管程) 与之关联,在 java 虚拟机 hotSpot 中monitor于对象的关联关系是由ObjectMonitor实现。
这里写图片描述
其中有_EntryList 和_WaitSet 两个对象列表,多线程访问synchronized修饰的代码段时会先进入_EntryList,线程获取到monitor之后进入 Owner 区域,monitor中的 count 会加一,线程调用 wait 方法之后会释放 monitor,count–,线程进入 waitset等待唤醒(被其他线程调用notify/notifyAll),线程执行完毕也要是释放monitor并复位。我注意到这个地方讲到wait和锁的关系,又想到以前面试被问过sleep() 和wait()的区别

sleep() 和wait()的区别

我当初提前看过一些面试题,就是这样答的:

sleep()方法属于Thread[θrɛd]类中的。而wait()方法是属于Object类中的。sleep()是线程安全的,wait()线程不安全。
真正理解之后的回答:
sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他一直在持有monitor(未进入 Waitset中),当指定的时间到了又会自动恢复运行状态。这就跟上面连到一起了

而当调用wait()方法的时候,线程会进入Waitset,释放 monitor,进入等待此对象的Waitset(等待锁定池),只有针对此对象调用notify/notifyAll方法后本线程才进入对象锁定池准备。

notify和notifyall的区别

notify只会通知一个在等待的对象,而notifyAll会通知所有在等待的对象,并且所有对象都会继续运行

wait此方法导致当前线程(称之为 T)将其自身放置在对象的等待集中Waitset(等待锁定池),然后放弃此对象上的所有同步要求。

出于线程调度目的,在发生以下四种情况之一前,线程 T 被禁用,且处于休眠状态:

  1. 其他某个线程调用此对象的 notify 方法,并且线程 T 碰巧被任选为被唤醒的线程。
  2. 其他某个线程调用此对象的 notifyAll 方法。
  3. 其他某个线程中断线程 T。大约已经到达指定的实际时间。但是,如果 timeout 为零,则不考虑实际时间,在获得通知前该线程将一直等待。

然后,从对象的等待集Waitset中删除线程T,并重新进行线程调度(进入_EntryList)。然后,该线程以常规方式与其他线程竞争,以获得在该对象上同步的权利;一旦获得对该对象的控制权,该对象上的所有其同步声明都将被恢复到以前的状态。

这就是调用 wait方法时的情况。然后,线程 T 从 wait 方法的调用中返回。所以,从 wait 方法返回时,该对象和线程 T 的同步状态与调用 wait 方法时的情况完全相同。

就是说多个线程等待的时候notifyall所有的等待线程都被通知了,这些线程进行竞争之后其中一个会得到锁(也是要等待正在执行的线程先释放锁才行)

这是转的Hongten的一个例子

/**
 * 
 */
package com.b510.test;

/**
 * java中的sleep()和wait()的区别
 * @author Hongten
 * @date 2013-12-10
 */
public class TestD {

    public static void main(String[] args) {
        new Thread(new Thread1()).start();
        try {
            Thread.sleep(5000);
        } catch (Exception e) {
            e.printStackTrace();
        }
        new Thread(new Thread2()).start();
    }

    private static class Thread1 implements Runnable{
        @Override
        public void run(){
            synchronized (TestD.class) {
            System.out.println("enter thread1...");    
            System.out.println("thread1 is waiting...");
            try {
                //调用wait()方法,线程会放弃对象锁,进入等待此对象的等待锁定池
                TestD.class.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println("thread1 is going on ....");
            System.out.println("thread1 is over!!!");
            }
        }
    }

    private static class Thread2 implements Runnable{
        @Override
        public void run(){
            synchronized (TestD.class) {
                System.out.println("enter thread2....");
                System.out.println("thread2 is sleep....");
                //只有针对此对象调用notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。
                TestD.class.notify();
                //==================
                //区别
                //如果我们把代码:TestD.class.notify();给注释掉,即TestD.class调用了wait()方法,但是没有调用notify()
                //方法,则线程永远处于挂起状态。
                try {
                    //sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,
                    //但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。
                    //在调用sleep()方法的过程中,线程不会释放对象锁。
                    Thread.sleep(5000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("thread2 is going on....");
                System.out.println("thread2 is over!!!");
            }
        }
    }
}
enter thread1...
thread1 is waiting...
enter thread2....
thread2 is sleep....
thread2 is going on....
thread2 is over!!!
thread1 is going on ....
thread1 is over!!!

注释TestD.class.notify();

enter thread1...
thread1 is waiting...
enter thread2....
thread2 is sleep....
thread2 is going on....
thread2 is over!!!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值