synchronized&volatile (一)

简介: synchronized&volatile (一)

synchronized(JVM实现的锁)#



通过这两个关键字,我们可以很容易的实现同步多个任务的行为,可以实现同一时刻,只能有一条线程去访问共享资源


一: 修饰普通方法#



多个线程,共同去竞争访问,方法内部的变量,永远是线程安全的!!!#


public class HasSelfPrivateNum{
      public void  add(String name){
        try{
            int num=0;  //num在方法内部,永远是线程安全的!!!
             if(name.equals("a"){
                num =100;
            }else{
                num =200;
            }
        System.out.println("num=="+num);    
        }catch(InterruptedException e){
            e.printStackTrace();
        }
      }
    }


多个线程,共同去竞争访问对象中的实例变量,可能导致非线程安全#


public class HasSelfPrivateNum{
   private int num=0;  //num是本类的实例变量,可能出现非线程安全问题!!!
      public void  add(String name){
        try{
             if(name.equals("a"){
                num =100;
            }else{
                num =200;
            }
        System.out.println("num=="+num);    
        }catch(InterruptedException e){
            e.printStackTrace();
        }
      }
    }


结论:#


只有共享的资源才可能出现非线程安全性问题,需要同步化...就像方法内部的变量,根本不可能共享,所以说没必要同步化


解决非线程安全问解决#


这一问题引入了Synchronized关键字,让出现非线程安全问题的方法,保持前后的同步,让线程拿到对象锁(this对象锁),当某一个线程进去后,其他线程只能等它释放对象锁之后,获取到对象锁在进入同步方法(Synchronized可以保证它修饰的方法实现原子性的操作)

拓展:A线程持有object对象的锁,B线程可以异步的调用object对象中的非Synchronized类型的方法


public class HasSelfPrivateNum{
   private int num=0;  //num是本类的实例变量,可能出现非线程安全问题!!!
     Synchronized public void  add(String name){
        try{
             if(name.equals("a"){
                num =100;
            }else{
                num =200;
            }
        System.out.println("num=="+num);    
        }catch(InterruptedException e){
            e.printStackTrace();
        }
      }
    }


多个对象多个锁#


比如说,分别给两个线程分别去访问同一个类两个不同不对象同名同步方法,结果不是顺序执行,而是异步执行的...


结论:#


关键字Synchronized获取到的锁全部是对象锁,而不是把一段代码或者一个方法当作锁!真出现了多个线程访问多个对象,那么JVM就会创建出,多把对象锁...


内置锁#


java中每一个对象都可被当作同步的锁,这些锁就叫做内置锁


互斥锁#


一个线程进来之后,另一个线程不能进来, Synchronized就是典型的互斥锁


二: 修饰代码块#


Synchronized的弊端:


声明方法在某些情况下是有弊端的,比如说A线程抢到了cpu的执行权,在调用同步方法执行某一个比较长的任务的,A还没来得及释放这个对象锁,紧接着B线程抢到了cpu的执行权,他也想去访问A线程访问的方法,然而B线程拿不到这个对象锁,所以被拒绝访问,因此B线程必须等待较长的时间


同步代码块语法:


synchronized(Object){
        //同步执行的任务
    }



sychronized代码块里面的方法是同步的,另一个线程只有等待当前线程执行完这个代码块之后,才能执行此代码块


使用同步代码块解决同步方法的弊端,提升效率 一半同步一半异步!-->


public task{
        //类的实例属性  --> 共享资源
        private int a;
        private int b;
        try{
            ....  // 非共享资源
            ....  // 非共享资源
            synchronized(this){
                a++;
                b++;
            }
        }catch(InterruptedExceotion e){
            e.printStackTrace();
        }
    }


可以看到,我们只是把可能出现非线程的共享资源放在同步代码块中...

当一个线程访问object的一个Synchronized(this){}同步代码块里面的内容时,另一个线程可以访问此object对象中的非synchronized代码块!!!

也就是一半同步,一半异步,不在同步代码块中的代码,就是异步执行,在同步代码块中的代码,就是同步执行!


Synchronized代码块的本身具有同步性,而且,Synchronized代码块之间也具有同步性!#


当一个线程访问object对象中的Synchronized代码块的时候,其他的任何线程对object的其他任何Synchronized同步代码块的访问也是阻塞的,这也说明Synchronized同步代码块和Synchronized修饰的方法一样,使用的对象监视器是一个


将任意对象,作为对象监视器:#


Synchronized(非this对象){} 同步代码块可以是任意对象,这个非this对象大多数是,方法的参数,类的实例变量


Synchronized(非this对象){}优点#


如果一个类中有很多的Synchronized同步方法,虽然可以实现同步,但是,会发生阻塞而降低效率,因为所有的Synchronized同步方法,他们拥有的都是this锁,而Synchronized(非this对象){}同步代码块可以使用的是非this锁,拥有不同的锁,因此他们两者之间是异步执行的,对他们自己来说又是同步执行,提升了效率


注意点:#


同步代码块放在非同步Synchronized方法中进行声明,不能保证调用方法的线程执行同步,因为线程调用方法的顺序是无序的,虽然在同步代码块中执行的顺序依然有序,但如果有分支逻辑判断(逻辑判断没有在同步代码块中),就可能会出现脏读


//创建一个只能存储一个元素的集合...
    List list = new ArrayList();
    public void unsafe(){
    try{
        if(list.size()<0){
             Thread.sleep(2000);
            synchronized(){ 
            list.add("a");
         }
    }catch(InterruptedException e){
        e.printStackTrace();
    }
  }
  //此段代码就会出现脏读...
  //解决方法: 同步化
    List list = new ArrayList();
   synchronized public void unsafe(){
    try{
        if(list.size()<0){
             Thread.sleep(2000);
            synchronized(){ 
            list.add("a");
         }
    }catch(InterruptedException e){
        e.printStackTrace();
    }
  }


Synchronized(非this对象){}总结:#


  1. 在多个线程持有"对象监视器"为同一个对象的前提下,同一时间,只有一个线程可以执行synchronized(非this对象){}同步代码块中的代码.
  2. 同一时间,线程想去执行synchronized(非this对象){}同步代码块里面的代码,它必须拿到这个非this对象
  3. 其他线程调用Synchronized(非this##### 象X){}中的X的同步代码块,同步方法,依然是线程安全的.
  4. 在多线程访问的情况下,拥有不同对象监视器的Synchronized(){}同步代码块之间是异步执行的


三: 修饰静态XX#


Synchronized public static void ...... 静态同步锁#


Synchronized方法同样可以修饰静态方法,运行的结果证明它同样可以实现线程安全,顺序执行,但是Synchronized修饰普通方法Synchronized修饰静态方法是有本质上的区别的,Synchronized修饰静态方法实际上是给Class类上锁 而前者是给this对象上锁.

换言之,两个自身顺序执行,同时出现则异步执行,(锁不同)


四 修饰同步代码块#



Synchronized(类名.class){...}

作用和其修饰静态方法一样


五 . synchronized锁重入#


synchronized关键字具有锁重入的功能,也就是说,当一个线程拿到锁对象之后,当它在本synchronized方法中访问本类对象的其他synchronized方法时,它是可以重复拿到锁的!


六. 出现异常锁自动释放#


在synchronized修饰的同步方法中,若在运行时,出现了异常,对象锁会自动释放,意味着其他线程可以直接在此获取到对象锁


七. 数据类型String的常量池特性#


  • JVM中具有String常量池缓存的功能,如下一段代码返回true


public static void mainString[] avgs(){
        String a ="a";
        String b = "a";
        System.out.println(a==b);
    }


这也就是意味着,假如在两个线程给一个Synchronized同步代码块中传递进同样的字符串,也就意味着,他们要去竞争同一把锁,难免会出现阻塞的情况.所以绝大多数情况下,我们是不使用String字符串,来当作锁对象的


八. 同步Synchronized方法的无限循环的等待和解决#


同步方法synchronized同步方法,容易造成死循环,如下代码,method永远不可能得到执行


public class Service{
        synchronized public void methodA(){
            System.out.println("methodA Begin");
            boolean tag = true;
            while(tag){}
            System.out.println("methodA end");
        }
        synchronized public void methodB(){
            System.out.println("methodB Begin");
            System.out.println("methodB end");
        }
    }


使用同步代码块解决无限循环问题


public class Service{
         public void methodA(){
             Object o1 = new Object();
             synchronized(o1){
                 System.out.println("methodA Begin");
                 boolean tag = true;
                 while(tag){}
                 System.out.println("methodA end");
             }
        }
        synchronized public void methodB(){
            Object o2 = new Object();
            synchronized(o2){
            System.out.println("methodB Begin");
            System.out.println("methodB end");
        }
        }
    }


九 多线程的死锁#


  • 多线程的死锁就是说,已经准备就绪的线程们在等待一个根本不可能被释放的锁,从而导致所有的线程任务都无法继续完成,导致了线程的假死,死锁是必须要避免的问题

如下代码死锁现象


public class demoSiSuo implements Runnable{
public class demoSiSuo implements Runnable{
    private String username;
    private  Object lock1 = new Object();
    private  Object lock2 = new Object();
    public void set(String username) {
        this.username = username;
    }
    @Override
    public void run() {
        if (username.equals("a")){
            synchronized (lock1){
                try {
                    System.out.println("a");
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2){
                    //do something
                    System.out.println("接着lock1 lock2的代码执行了");
                }
            }
        }
        if (username.equals("b")){
            synchronized (lock2){
                System.out.println("b");
                //do something
                synchronized (lock1){
                    //do something
                    System.out.println("接着lock2 lock1的代码执行了");
                }
            }
        }
    }
    public static void main(String[] args) {
        demoSiSuo demoSiSuo = new demoSiSuo();
        new Thread(()->{
            demoSiSuo.set("a");
            demoSiSuo.run();
        }).start();
        new Thread(()->{
            demoSiSuo.set("b");
            demoSiSuo.run();
        }).start();
    }
}


使用JDK自带的检查死锁的工具

在cmd窗口切换到bin目录,输入指令jps 找到正在运行的实例id, 输入 jstack -l [id] 可查看到


Found 1 deadlock.

示例如下:


C:\Users\acer>jps
335844 ThreadPool
370676 DeadLock
366976 ThreadPool
163348 Jps
297244
256296 RemoteMavenServer
397720 Launcher
C:\Users\acer>jstack 370676
    ...
    ===================================================
"Thread-1":
        at com.changwu.MultiThread.lock.HoldLock.run(DeadLock.java:22)
        - waiting to lock <0x000000076b6f7f58> (a java.lang.String)
        - locked <0x000000076b6f7f90> (a java.lang.String)
        at java.lang.Thread.run(Thread.java:748)
"Thread-0":
        at com.changwu.MultiThread.lock.HoldLock.run(DeadLock.java:22)
        - waiting to lock <0x000000076b6f7f90> (a java.lang.String)
        - locked <0x000000076b6f7f58> (a java.lang.String)
        at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.



相关文章
|
6月前
|
缓存 Java 编译器
volatile与synchronized
volatile与synchronized
60 0
|
缓存 Java 编译器
|
Java
浅谈volatile
浅谈volatile
70 0
|
缓存 安全 Java
Java并发编程中的四个关键字:ThreadLocal、Volatile、Synchronized和Atomic
Java并发编程中的四个关键字:ThreadLocal、Volatile、Synchronized和Atomic
266 0
java多线程关键字volatile、lock、synchronized
volatile写和volatile读的内存语义: 1. 线程A写一个volatile变量,实质上是线程A向接下来将要读这个volatile变量的某个线程发出了(其对共享变量所在修改的)消息。 2. 线程B读一个volatile变量,实质上是线程B接收了之前某个线程发出的(在写这个volatile变量之前对共享变量所做修改的)消息。 3. 线程A写一个volatile变量,随后线程B读这个volatile变量,这个过程实质上是线程A通过主内存向线程B发送消息。
287 0
|
存储 缓存 算法
volatile synchronized cas
之前写了《熔断》,以及其中使用的《计数器算法》;本来是要接着再写不通过定时器清理计数环的计数器算法,看了下我司亿级网关的计数器,百行的代码,但却是满满bug。不得穿插一下并发的基础知识 处理并发,最基本的元件就这三样 1. synchronized 这个关键字不必讲,从开始多线程,它就进入你的视线 2. volatile 在jdk5之后大放异彩 3. cas 在J.U.C中大量使用,他与volatile组合是J.U.C的基石
148 0
volatile synchronized cas
|
存储 缓存 安全
synchronized&volatile (二)
synchronized&volatile (二)
203 0
|
SQL 存储 算法
volatile详解
在单线程环境中,我们几乎用不到这个关键词,但是多线程环境中,这个关键词随处可见。而且也是面试的常客。总的来说,volatile有以下三个特性: 保证可见性; 不保证原子性; 禁止指令重排。 下面就来详细的说说这三个特性。
volatile详解
|
Java 编译器
volatile与synchronized 区别
volatile与synchronized 区别
130 0
|
缓存 安全 Java
volatile(二)
volatile 这个关键字大家都不陌生,这个关键字一般通常用于并发编程中,是 Java 虚拟机提供的轻量化同步机制,你可能知道 volatile 是干啥的,但是你未必能够清晰明了的知道 volatile 的实现机制,以及 volatile 解决了什么问题,这篇文章我就来带大家解析一波。
volatile(二)