理解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!!!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值