java synchronized 关键字详解

java synchronized 关键字详解

synchronized定义: java语言的关键字、修饰词,当它用来修饰一个方法或者一段代码块的时候,能保证在同一时刻最多只有一个线程执行该代码。

什么是锁:

java的内置锁:每个java对象都可以用作实现一个同步的锁,这些锁成为内置锁。线程进入同步代码块或者方法的时候回自动获取该锁,在退出同步代码块或者方法的时候,释放锁。获得锁的唯一方法就是进这个锁保护的同步代码块或者方法。
java内置锁是一个互斥的锁。这就意味着最多只有一个线程能够获取该锁,当线程A尝试去获取线程B持有的内置锁时,线程A必须等待获取阻塞,直到B释放锁,如果B一直没有释放,A则一直等待。
参考:java 中的锁 – 偏向锁、轻量级锁、自旋锁、重量级锁

synchronized的三种应用方式
synchronized关键字最主要有以下3种应用方式,下面分别介绍
修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁

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

修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
废话不多说,直接上代码:
synchronized同步代码块 :当有两个线程并发访问同一个对象中的synchronized(this)同步代码块时,同一时间只能有一个线程执行,另一个需等待前一个线程执行完成后才能执行:
public class Thread1 implements Runnable {
    @Override
    public void run() {
        synchronized (this){
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "    synchronized loop    " + i);
            }
        }
    }
    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        Thread threadA = new Thread(thread1,"threadA");
        Thread threadB = new Thread(thread1,"threadB");
        threadA.start();
        threadB.start();
    }
}
执行结果:
threadB    synchronized loop    0
threadB    synchronized loop    1
threadB    synchronized loop    2
threadB    synchronized loop    3
threadB    synchronized loop    4
threadA    synchronized loop    0
threadA    synchronized loop    1
threadA    synchronized loop    2
threadA    synchronized loop    3
threadA    synchronized loop    4

被synchronize修饰的代码块同一时间纸杯一个线程访问了,另一个线程在等待前一个线程执行完成后才执行。我们看看不加关键字的执行情况:
threadB    synchronized loop    0
threadA    synchronized loop    0
threadB    synchronized loop    1
threadB    synchronized loop    2
threadA    synchronized loop    1
threadA    synchronized loop    2
threadA    synchronized loop    3
threadA    synchronized loop    4
threadB    synchronized loop    3
threadB    synchronized loop    4
这时看到的就是乱序的执行,两个线程都能随时执行。
当一个线程访问object的一个synchronize(this) 同步代码块时,另一个线程任然可以访问该object中的非synchronize(this)同步代码块。
public class Thread2 {

    public void test1(){
        synchronized (this){
            int i =5;
            while ((i-- > 0)) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public void test2(){
        int i =5;
        while ((i-- > 0)) {
            System.out.println(Thread.currentThread().getName() + ":" + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        final Thread2 thread2 = new Thread2();

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                thread2.test1();
            }
        },"threadA");
        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                thread2.test2();
            }
        },"threadB");

        threadA.start();
        threadB.start();
    }


}
执行结果:
threadA:4
threadB:4
threadA:3
threadB:3
threadB:2
threadA:2
threadB:1
threadA:1
threadB:0
threadA:0

可以看到只有被synchronize修饰的方法才会被同步,而类则不会受影响。
修饰静态方法:当修饰静态方法时,其锁就是当前类的class对象锁。由于静态成员不属于任何一个实例对象,是类的成员,因此通过class对象锁可以同步静态成员的并发操作。需要注意的是,修饰该类中的非静态方法时,不会同步并发操作,因为非静态方法的锁时当前实例对象锁。
public  class  Thread3 implements Runnable{
    private static int i = 0;

    /**
     * 作用于静态方法,锁是当前class对象,也就是
     * AccountingSyncClass类对应的class对象
     */
    public static synchronized void increase(){
        i ++;
        System.out.println(Thread.currentThread().getName()+ ":" + i);
    }
    /**
     * 非静态,访问时锁不一样不会发生互斥
     */
    public synchronized void increase4Obj(){
        i++;
    }

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

    public static void main(String[] args) {
        Thread3 thread3 = new Thread3();
        Thread threadA = new Thread(thread3,"threadA");
        Thread threadB = new Thread(thread3,"threadB");
        threadA.start();
        threadB.start();

    }
}
运行结果:
threadB:1
threadB:2
threadB:3
threadB:4
threadB:5
threadA:6
threadA:7
threadA:8
threadA:9
threadA:10

由于synchronize关键字修饰的是静态的increase()方法,与修饰实例方法不同的是,其对象锁是当前的class对象。注意代码中的increase4Obj()方法是实例方法,其对象锁是当前实例,如果别的线程调用该方法,将不会产生互斥现象,因为用的是不同的对象锁,但是这种情况就是线程安全问题了,他们都共享了静态变量。

  public static void main(String[] args) {
        final Thread3 thread3 = new Thread3();
        Thread threadA = new Thread(thread3,"threadA");
        Thread threadB = new Thread(thread3,"threadB");
        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i1 = 0; i1 < 5; i1++) {
                    thread3.increase4Obj();
                }
            }
        }, "threadC");
        threadA.start();
        threadB.start();
        threadC.start();

    }
运行结果:
threadA:2
threadC:2
threadA:3
threadC:4
threadA:5
threadC:6
threadA:7
threadC:8
threadA:9
threadC:10
threadB:11
threadB:12
threadB:13
threadB:14
threadB:15

关于synchronized 可能需要了解的关键点

1.synchronized的可重入性 从锁的互斥设计上来说,当一个线程试图想操作一个有其它线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁,请求将会成功,在java中synchronize是基于原子性的内部锁机制,是可以重入的,因此在线程调用synchronize方法的同时,在其方法体内部调用该对象另一个synchronize方法,也就是说一个线程得到一个对象锁后再次请求该对象锁,是允许的,这就是synchronize的可重入性。如下:
public class Thread4 implements Runnable {
    static Thread4 thread4 = new Thread4();

    int x = 0;
    int y = 0;

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            x ++;
//            System.out.println(Thread.currentThread().getName()+ "    :  x=" + x);
            increase();
        }


    }

    public synchronized void increase(){
        y++;
        System.out.println(Thread.currentThread().getName() + "    :  y=" + y);
    }

    public static void main(String[] args) {
        Thread threadA = new Thread(thread4,"threadA");
        Thread threadB = new Thread(thread4,"threadB");
        threadA.start();
        threadB.start();


    }

}
运行结果:
threadA    :  y=1
threadB    :  y=2
threadB    :  y=3
threadB    :  y=4
threadB    :  y=5
threadB    :  y=6
threadA    :  y=7
threadA    :  y=8
threadA    :  y=9
threadA    :  y=10

此时实例中的increase()方法虽然都是使用的该实例对象锁,但是运行结果显示该方法没有被同步,而是被重入。
2.线程中断与synchronized :
线程中断:线程正在运行时打断,在java中,提供了三个方法:
//中断线程(实例方法)
public void Thread.interrupt();

//判断线程是否被中断(实例方法)
public boolean Thread.isInterrupted();

//判断是否被中断并清除当前中断状态(静态方法)
public static boolean Thread.interrupted();

当一个线程处于被阻塞状态或者试图执行一个阻塞操作时,使用==Thread.interrupt()== 方法中断该线程,此时将会抛出==InterruptedException==异常,同时中断状态将会被复位(由中断状态改为非中断状态),如下:

public class Thread5 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(){
            @Override
            public void run() {
                while (true) {
                    try {
                        //当前线程处于阻塞状态,异常必须捕捉处理,无法往外抛出
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        System.out.println("Interruted When Sleep");
                        boolean interrupted = this.isInterrupted();
                        System.out.println("interrupted :" + interrupted );
                    }
                }
            }
        };
        thread.start();
        TimeUnit.SECONDS.sleep(2);
        thread.interrupt();
    }
}
运行结果:
Interruted When Sleep
interrupted :false

代码说明:创建一个线程,并在线程的run方法中调用sleep方法进行阻塞,启动后再调用线程的interrupt方法中断线程,抛出InterruptedException异常,此时中断状态也被复位(interrupted :false)。如果在线程未阻塞的情况下调用该方法去中断运行线程时,没有任何影响:

public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(){
            @Override
            public void run() {
                while (true) {
                    System.out.println("线程未中断");
                }
            }
        };
        thread.start();
        TimeUnit.SECONDS.sleep(2);
        thread.interrupt();
    }

此时调用interrupt方法,而线程未被中断,所以我们需要手段检测中断情况,并结束程序,如下:

public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(){
            @Override
            public void run() {
                while (true) {
                    if(this.isInterrupted()){
                        System.out.println("线程中断");
                        break;
                    }
                }
                System.out.println("已跳出循环,线程中断!");
            }
        };
        thread.start();
        TimeUnit.SECONDS.sleep(2);
        thread.interrupt();
    }
运行结果:
线程中断
已跳出循环,线程中断!

我们在代码中使用了实例方法isInterrupted判断线程是否已被中断,如果被中断将跳出循环以此结束线程,注意非阻塞状态调用interrupt()并不会导致中断状态重置。综合所述,可以简单总结一下中断两种情况,一种是当线程处于阻塞状态或者试图执行一个阻塞操作时,我们可以使用实例方法interrupt()进行线程中断,执行中断操作后将会抛出interruptException异常(该异常必须捕捉无法向外抛出)并将中断状态复位,另外一种是当线程处于运行状态时,我们也可调用实例方法interrupt()进行线程中断,但同时必须手动判断中断状态,并编写中断线程的代码(其实就是结束run方法体的代码)。有时我们在编码时可能需要兼顾以上两种情况,那么就可以如下编写:

public void run(){
    try {
    //判断当前线程是否已中断,注意interrupted方法是静态的,执行后会对中断状态进行复位
    while (!Thread.interrupted()) {
        TimeUnit.SECONDS.sleep(2);
    }
    } catch (InterruptedException e) {

    }
}
3.中断与synchronized
事实上线程的中断操作对于正在等待获取的锁对象的synchronized方法或者代码块并不起作用,也就是对于synchronized来说,如果一个线程在等待锁,那么结果只有两种,要么它获得这把锁继续执行,要么它就保存等待,即使调用中断线程的方法,也不会生效。演示代码如下:
public class Thread6 implements Runnable{

    /**
     * 在构造器中创建新线程并启动获取对象锁
     */
    public Thread6() {
        //该线程已持有当前实例锁
        new Thread(){
            @Override
            public void run() {
                y();
            }
        }.start();
    }

    public synchronized void y() {
        System.out.println("Trying to call y()");
        while (true) {
            Thread.yield();
        }
    }

    @Override
    public void run() {
        while (true) {
            if(Thread.interrupted()){
                System.out.println("中断线程");
                break;
            }else {
                y();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread6 thread6 = new Thread6();
        Thread threadA = new Thread(thread6,"threadA");
        //启动后调用y()方法,无法获取当前实例锁处于等待状态
        threadA.start();
        TimeUnit.SECONDS.sleep(1);
        //中断线程,无法生效
        threadA.interrupt();
    }
}

运行后,中断线程失败。当程序执行时,我们先初始化了Thread6类,并且执行了Thread6类的构造方法,构造方法中创建了一个子线程,并且线程调用了y()方法,y方法是被synchronize修饰的非静态方法,所以该子线程持有了thread6类的实例对象锁,然后threadA线程执行时,再调用y方法时,锁已被占用,所以只能等待所释放,才能执行,但并不是阻塞,所以调用interrupt()方法时,不能中断线程。

4.等待唤醒机制与synchronized

所谓等待唤醒机制本篇主要指的是notify/notifyAll和wait方法,在使用这3个方法时,必须处于synchronized代码块或者synchronized方法中,否则就会抛出IllegalMonitorStateException异常,这是因为调用这几个方法前必须拿到当前对象的监视器monitor对象,也就是说notify/notifyAll和wait方法依赖于monitor对象,在前面的分析中,我们知道monitor 存在于对象头的Mark Word 中(存储monitor引用指针),而synchronized关键字可以获取 monitor ,这也就是为什么notify/notifyAll和wait方法必须在synchronized代码块或者synchronized方法调用的原因。

synchronized (obj) {
       obj.wait();
       obj.notify();
       obj.notifyAll();         
 }

需要特别理解的一点是,与sleep方法不同的是wait方法调用完成后,线程将被暂停,但wait方法将会释放当前持有的监视器锁(monitor),直到有线程调用notify/notifyAll方法后方能继续执行,而sleep方法只让线程休眠并不释放锁。同时notify/notifyAll方法调用后,并不会马上释放监视器锁,而是在相应的synchronized(){}/synchronized方法执行结束后才自动释放锁。

水平有限,欢迎批评指正

参考:

深入理解Java并发之synchronized实现原理

java synchronized详解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值