Synchroized

synchroized

代码示例:

package juc_1;/*
 *@author wupeng
 *@time 2021/7/14-14:02
synchronized即保证了可见性有保证了原子性
 */

public class T4 implements Runnable{
    private  int count = 100;

    @Override
    public synchronized  void run() {
        count--;
        System.out.println(Thread.currentThread().getName()+"---- count + "+count);
    }

    public static void main(String[] args) {
        T4 t = new T4();
        for (int i = 0; i < 100; i++) {
            new Thread(t, " Thread  " + i).start();
        }
    }
}

验证同步非同步方法发之间的调用:

package juc_1;/*
 *@author wupeng
 *@time 2021/7/14-14:09
验证同步方法和非同步方法相互之间是否可以调用。
实验结果:可以相互调用
 */

public class T5 {
    public synchronized void m1() {
        System.out.println(Thread.currentThread().getName()+"m1 start...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"m1 end");
    }
    public void m2() {
        System.out.println(Thread.currentThread().getName()+"m2 start");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"m2");
    }

    public static void main(String[] args) {
        T5 t = new T5();

        new Thread(t::m2,"t2   ").start();
        new Thread(t::m1,"t1  ").start();
    }
}

银行账户,解决脏读问题:

package juc_1;/*
 *@author wupeng
 *@time 2021/7/14-14:16
模拟银行账户
对写进行加锁
对读不加锁
容易产生脏读问题
如果对读加上synchronized锁,就会解决脏读问题。
 */

import java.util.concurrent.TimeUnit;

public class T6 {
    String name;
    double balance;

    public synchronized void set(String name,double balance) {
        this.name = name;
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.balance = balance;
    }
    public /*synchronized*/ double getBalance(String name) {
        return this.balance;
    }

    public static void main(String[] args) {
        T6 t = new T6();
        new Thread(()->t.set("zhangsan",100.0)).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(t.getBalance("zhangsan"));
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(t.getBalance("zhangsan"));
    }

}

锁的可重入现象:

package juc_1;/*
 *@author wupeng
 *@time 2021/7/14-14:26
一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候任然会得到该对象的锁,这既是锁的可重入

 */

import java.util.concurrent.TimeUnit;

public class T7 {
    synchronized void m1() {
        System.out.println(Thread.currentThread().getName()+"m1 start");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        m2();
        System.out.println("m1 end");
    }
    synchronized void m2() {
        System.out.println("m2 start");
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m2 end");
    }

    public static void main(String[] args) {
        new T7().m1();
    }
}

父子可重入锁:

package juc_1;/*
 *@author wupeng
 *@time 2021/7/14-14:34
父类synchronized,子类调用super的时候的时候必须是可重入锁。否则会出现问题。
 */

import java.util.concurrent.TimeUnit;

public class T8 {
    synchronized void m() {
        System.out.println("m start");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m end");
    }

    public static void main(String[] args) {
        new TT8().m();
    }
}
class TT8 extends T8 {
    @Override
    synchronized void m() {
        System.out.println("child m start");
        super.m();
        System.out.println("child m end");
    }
}

异常锁:

package juc_1;/*
 *@author wupeng
 *@time 2021/7/14-14:38
产生异常,就会被原来准备拿到这把锁的程序乱入进来
程序在执行过程中,出现异常默认锁被释放

 */

import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class T9 {
    int count = 0;
    synchronized void m() {
        System.out.println(Thread.currentThread().getName()+"start");
        while (true) {
            count++;
            System.out.println(Thread.currentThread().getName()+"count = " +count);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (count == 5) {
                int i = 1/0;    //此处出先异常,锁被释放。如果不想锁被释放,可以catch
                System.out.println(i);
            }
        }
    }

    public static void main(String[] args) {
        T9 t = new T9();
        Runnable r = new Runnable() {
            @Override
            public void run() {
                t.m();
            }
        };
        new Thread(r,"t1").start();
        new Thread(r,"t2").start();
    }
}

Sybchroized的具体表现形式:

  • 对于普通方法,Sybchroized锁的是当前实例对象
  • 对于静态同步方法,Sybchroized锁的是当前类的Class对象
  • 对于同步方法快,锁的是Sybchroized括号里配置的对象

JVM实现原理:

在JVM中,是基于进入和退出Monitor对象来实现方法同步和代码块的同步,但是两者的实现方式不一样,

同步代码快是使用monitorenter和monitorexit指令实现的,而方法的同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明。

monitorenter指令,是在编译后插入到同步代码快的开始位置,而monitorexit是插入到方法的结束和异常处,JVM要保证每个monitorenter必须有对应的monitoorexit与之匹配。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的moniter的所有权,即尝试获得对象的锁。

synchroized底层实现:

加锁:访问某一段代码的临界资源得到时候需要有一把锁的锁住,

JDK早起的实现是重量级的,synchronized需要找操作系统申请锁,造成Synchronized效率低下。

后来的改进:

锁升级的概念

1.5之后对synchronized进行了改进,

sync(Object) 先在Object的头上markword 记录这个线程的ID (偏向锁)

如果有线程竞争使用的话,升级为自旋锁(默认旋10次),

如果10次之后还拿不到锁,升级为重量级锁。从操作系统拿OS锁(其实不占CPU的)。

锁只能升级,不能降级

执行时间长的用系统锁,实行时间短的,线程数比较少用自旋锁。

升级过程:

无状态锁->偏向锁升->自旋锁(轻量级锁)->升级为重量级锁

文章说明:https://www.jianshu.com/p/b43b7bf5e052

https://www.jianshu.com/p/16c8b3707436

posted @ 2021-07-14 15:57  IT小五  阅读(69)  评论(0)    收藏  举报