Java的锁机制--synchronsized关键字

引言

高并发环境下,多线程可能需要同时访问一个资源,并交替执行非原子性的操作,很容易出现最终结果与期望值相违背的情况,或者直接引发程序错误。

举个简单示例,存在一个初始静态变量count=0,两个线程分别对count进行100000次加1操作,期望的结果是200000,实际是这样的吗?写个程序跑下看看:

1234567891011121314151617181920212223242526272829303132333435363738
public class CountWithoutSyn {

    private volatile static int count = 0;

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(2);

        // 启动线程A        new Thread(new Runnable() {            @Override            public void run() {                for(int i=0; i<100000; i++){                    count++;                }                countDownLatch.countDown();            }        }).start();

        // 启动线程B        new Thread(new Runnable() {            @Override            public void run() {                for(int i=0; i<100000; i++){                    count++;                }                countDownLatch.countDown();            }        }).start();

        // main线程等待线程A和B计算完毕        countDownLatch.await();

        // main线程打印结果        System.out.println("count: " + CountWithoutSyn.count);    }

}

多次运行上述程序,会发现最终结果可能出现不是200000的情况,如:

123
count: 150218

Process finished with exit code 0

之所以出现这种情况的原因是,count++不是一个原子性的操作,所谓原子性,说的就是操作不可分割。

count++分为3个步骤:

  • 从内存读取count的值;
  • 对count值执行+1操作;
  • 将count的值写回内存;

比如当前count累加到了101,此时,线程A和B同时拿到了count的值为101,线程A对count加1后将102写回内存,同时线程B也对count加1后将102写回内存,而实际结果应该为103,所以丢失了1次更新。

故高并发环境下,多线程同时对共享变量执行非原子的操作,很容易出现丢失更新的问题。

解决办法很简单,将整个非原子的操作加锁,从而变成原子性的操作就可以了。

Java加锁的方式主要有2种,synchronnized关键字和Lock接口。

下面分别阐述这两种方式,本文先讲解synchronnized。

synchronized

在Java中,每一个对象都有一个锁标记(monitor),也称之为监视器,多线程同时访问某个对象时,线程只有获取了该对象的锁才能访问。

该锁属于典型的互斥锁,即一旦一个线程获取到锁之后,其他线程只能等待。

synchronize关键字可以标记方法或者代码块,当某个线程调用该对象的synchronize方法或者访问synchronize代码块时,该线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,该线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

对引言中的程序通过synchronized来进行改造:

1234567891011121314151617181920212223242526272829303132333435363738394041424344
public class CountWithSyn {

    private volatile static int count = 0;

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(2);

        Object lock = new Object();

        // 启动线程A        new Thread(new Runnable() {            @Override            public void run() {                synchronized (lock){                    for(int i=0; i<100000; i++){                        count++;                    }                }                countDownLatch.countDown();            }        }).start();

        // 启动线程B        new Thread(new Runnable() {            @Override            public void run() {                synchronized (lock){                    for(int i=0; i<100000; i++){                        count++;                    }                }                countDownLatch.countDown();            }        }).start();

大专栏  Java的锁机制--synchronsized关键字ne">        // main线程等待线程A和B计算完毕        countDownLatch.await();

        // main线程打印结果        System.out.println("count: " + CountWithSyn.count);    }

}

多次运行该程序,其结果均是:

123
count: 200000

Process finished with exit code 0

synchronized代码块使用起来比synchronized方法要灵活得多。因为也许一个方法中只有一部分代码只需要同步,如果此时对整个方法用synchronized进行同步,会影响程序执行效率。而使用synchronized代码块就可以避免这个问题,synchronized代码块可以实现只对需要同步的地方进行同步。

因为上述程序的非原子操作仅是count++,所以synchronized仅修饰count++即可实现线程安全。

1234567891011121314151617181920212223242526272829303132333435363738394041424344
public class CountWithSyn {

    private volatile static int count = 0;

    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(2);

        Object lock = new Object();

        // 启动线程A        new Thread(new Runnable() {            @Override            public void run() {                for(int i=0; i<100000; i++){                    synchronized (lock){                        count++;                    }

                }                countDownLatch.countDown();            }        }).start();

        // 启动线程B        new Thread(new Runnable() {            @Override            public void run() {                for(int i=0; i<100000; i++){                    synchronized (lock){                        count++;                    }                }                countDownLatch.countDown();            }        }).start();

        // main线程等待线程A和B计算完毕        countDownLatch.await();

        // main线程打印结果        System.out.println("count: " + CountWithSyn.count);    }}

需要注意的是:

  1. 当一个线程正在访问一个对象的synchronized方法,那么其他线程不能访问该对象的其他synchronized方法。这个原因很简单,因为一个对象只有一把锁,当一个线程获取了该对象的锁之后,其他线程无法获取该对象的锁,所以无法访问该对象的其他synchronized方法。
  2. 当一个线程正在访问一个对象的synchronized方法,那么其他线程能访问该对象的非synchronized方法。这个原因很简单,访问非synchronized方法不需要获得该对象的锁,假如一个方法没用synchronized关键字修饰,说明它不会使用到临界资源,那么其他线程是可以访问这个方法的,
  3. 如果一个线程A需要访问对象object1的synchronized方法fun1,另外一个线程B需要访问对象object2的synchronized方法fun1,即使object1和object2是同一类型),也不会产生线程安全问题,因为他们访问的是不同的对象,所以不存在互斥问题。

那么,synchronized关键字底层是如何实现的呢?反编译它的字节码看一下,如下述代码的字节码为:

123456789101112131415161718
public class SynCode {

    private Object lock = new Object();

    public void method1(){        synchronized (lock){

        }    }

    public synchronized void method2(){

    }

    public void method3(){

    }}

从反编译获得的字节码可以看出,synchronized代码块实际上多了monitorenter和monitorexit两条指令。monitorenter指令执行时会让对象的锁计数加1,而monitorexit指令执行时会让对象的锁计数减1,其实这个与操作系统里面的PV操作很像,操作系统里面的PV操作就是用来控制多个线程对临界资源的访问。

对于synchronized方法,执行中的线程识别该方法的method_info结构是否有ACC_SYNCHRONIZED标记设置,然后它自动获取对象的锁,调用方法,最后释放锁。如果有异常发生,线程自动释放锁。

对于synchronized方法或者synchronized代码块,当出现异常时,JVM会自动释放当前线程占用的锁,因此不会由于异常导致出现死锁现象。

参考文献:

欢迎您扫一扫上面的二维码,关注我的微信公众号!

原文地址:https://www.cnblogs.com/lijianming180/p/12099650.html

时间: 2024-10-08 05:23:57

Java的锁机制--synchronsized关键字的相关文章

java的锁机制

一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个线程)就只能等了(线程阻塞在锁池等待队列中). 取到锁后,他就开始执行同步代码(被synchronized修饰的代码):线程执行完同步代码后马上就把锁还给同步对象,其他在锁池中等待的某个线程就可以拿到锁执行同步代码了.这样就保证了同步代码在统一时刻只有一个线程在执行. 众所周知,在Java多线程编程中

[置顶] 深入探析Java线程锁机制

今天在iteye上提了一个关于++操作和线程安全的问题,一位朋友的回答一言点醒梦中人,至此我对Java线程锁有了更加深刻的认识.在这里也做个总结供大家参考. 先看几段代码吧! 代码一: [java] view plaincopy public class TestMultiThread2 implements Runnable{ private static Object o = new Object(); private static Integer si = 0; private stati

java的锁机制——synchronized

转自:http://blog.csdn.net/yangzhijun_cau/article/details/6432216 一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个线程)就只能等了(线程阻塞在锁池等待队列中). 取到锁后,他就开始执行同步代码(被synchronized修饰的代码):线程执行完同步代码后马上就把锁还给同步对象,其他在锁池中

java中锁机制

一段synchronized的代码被一个线程执行之前,他要先拿到执行这段代码的权限,在java里边就是拿到某个同步对象的锁(一个对象只有一把锁): 如果这个时候同步对象的锁被其他线程拿走了,他(这个线程)就只能等了(线程阻塞在锁池等待队列中). 取到锁后,他就开始执行同步代码(被synchronized修饰的代码):线程执行完同步代码后马上就把锁还给同步对象,其他在锁池中等待的某个线程就可以拿到锁执行同步代码了.这样就保证了同步代码在统一时刻只有一个线程在执行.   众所周知,在Java多线程编

[java多线程] - 锁机制&amp;同步代码块&amp;信号量

在美眉图片下载demo中,我们可以看到多个线程在公用一些变量,这个时候难免会发生冲突.冲突并不可怕,可怕的是当多线程的情况下,你没法控制冲突.按照我的理解在java中实现同步的方式分为三种,分别是:同步代码块机制,锁机制,信号量机制. 一.同步代码块 在java的多线程并发开发过程中,我们最常用的方式就是使用同步代码关键字(synchronized).这种方式的使用不是特别复杂,需要注意的只是你需要明确到底同步的是那个对象,只有当同步的对象一致的情况下,才能够控制互斥的操作.一般情况下,我们会同

Java基础-锁机制

Java中的锁机制 Lock不是Java语言内置的, synchronized是Java语言中的关键字, Lock是一个接口,通过这个接口可以实现同步访问. Lock允许更灵活的结构, 并可以支持多个相关类的条件对象.优势在于: 使锁更公平, 使线程在等待锁的时候可以响应中断; 可以让线程尝试获取锁, 并在无法获取锁的时候立即返回或等待一段时间. public interface Lock { void lock(); void lockInterruptibly() throws Interr

深入浅出 Java Concurrency 锁机制 : AQS

转载:http://www.blogjava.net/xylz/archive/2010/07/06/325390.html 在理解J.U.C原理以及锁机制之前,我们来介绍J.U.C框架最核心也是最复杂的一个基础类:java.util.concurrent.locks.AbstractQueuedSynchronizer. AQS AbstractQueuedSynchronizer,简称AQS,是J.U.C最复杂的一个类,导致绝大多数讲解并发原理或者实战的时候都不会提到此类.但是虚心的作者愿意

Java各种锁机制简述

线程安全是多线程领域的问题,线程安全可以简单理解为一个方法或者一个实例可以在多线程环境中使用而不会出现问题. 在 Java 多线程编程当中,提供了多种实现 Java 线程安全的方式: 最简单的方式,使用 Synchronization 关键字 使用 java.util.concurrent.atomic 包中的原子类,例如 AtomicInteger 使用 java.util.concurrent.locks 包中的锁 使用线程安全的集合 ConcurrentHashMap 使用 volatil

Java 线程锁机制 -Synchronized Lock 互斥锁 读写锁

synchronized 是互斥锁: lock 更广泛,包含了读写锁 读写锁特点: 1)多个读者可以同时进行读2)写者必须互斥(只允许一个写者写,也不能读者写者同时进行)3)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者) 互斥锁特点: 一次只能一个线程拥有互斥锁,其他线程只有等待 所谓互斥锁, 指的是一次最多只能有一个线程持有的锁. 在jdk1.5之前, 我们通常使用synchronized机制控制多个线程对共享资源的访问. 而现在, Lock提供了比synchronize