• synchronized


    问题:线程安全问题的主要诱因是什么?

    ---》

    1、存在共享数据(也称临界资源)

    2、存在多条线程共同操作这些共享数据

    解决问题的根本方法:

      同一时刻有且只有一个线程在操作共享数据,其他线程必须等到该线程处理完数据后再对共享数据进行操作(串行)

    互斥锁的特性

      1、互斥性:在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程的协调机制,这样在同一实际只有一个线程对学院同步的代码块(复合操作)进行访问。互斥性也成为操作的原子性。

      2、可见性:必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(则在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作,从而引起不一致

    注意的是:synchronized锁的不是代码,锁的都是对象

    根据获取的锁的分类:获取对象锁获取类锁

      获取对象锁的两种用法:

      1、同步代码块(synchronized(this),synchronized(类实例对象)),锁时小括号中的实例对象

      2、同步非静态方法(synchronized method), 锁时当前对象的实例对象

      获取类锁的两种方法:

      1、同步代码块(synchronized(类.class)),锁是小括号()中的类对象(Class对象)

      2、同步静态方法(synchronized static method) ,锁是当前对象的类对象(Class对象)

    public class SyncThread implements Runnable {
    
        @Override
        public void run() {
            String threadName = Thread.currentThread().getName();
            if (threadName.startsWith("A")) {
                async();
            } else if (threadName.startsWith("B")) {
                syncObjectBlock1();
            } else if (threadName.startsWith("C")) {
                syncObjectMethod1();
            } else if (threadName.startsWith("D")) {
                syncClassBlock1();
            } else if (threadName.startsWith("E")) {
                syncClassMethod1();
            }else if(threadName.startsWith("F")){
                syncObjectMethod2();
            }
    
        }
    
        /**
         * 异步方法
         */
        private void async() {
            try {
                System.out.println(Thread.currentThread().getName() + "_Async_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "_Async_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 方法中有 synchronized(this|object) {} 同步代码块
         */
        private void syncObjectBlock1() {
            System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            synchronized (this) {
                try {
                    System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + "_SyncObjectBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        /**
         * synchronized 修饰非静态方法
         */
        private synchronized void syncObjectMethod1() {
            System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            try {
                System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        private synchronized void syncObjectMethod2() {  // 注意这里的syncObjectMethod2方法和syncObjectMethod1方法锁的对象实例都是同一个!!!
            System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod2: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            try {
                System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod2_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "_SyncObjectMethod2_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        private void syncClassBlock1() {
            System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            synchronized (SyncThread.class) {
                try {
                    System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + "_SyncClassBlock1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private synchronized static void syncClassMethod1() {
            System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            try {
                System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_Start: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + "_SyncClassMethod1_End: " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

      

    public class SyncDemo {
        public static void main(String... args) {
            SyncThread syncThread = new SyncThread();
            Thread A_thread1 = new Thread(syncThread, "A_thread1");
            Thread A_thread2 = new Thread(syncThread, "A_thread2");
            Thread B_thread1 = new Thread(syncThread, "B_thread1");
            Thread B_thread2 = new Thread(syncThread, "B_thread2");
            Thread C_thread1 = new Thread(syncThread, "C_thread1");
            Thread C_thread2 = new Thread(syncThread, "C_thread2");
            Thread D_thread1 = new Thread(syncThread, "D_thread1");
            Thread D_thread2 = new Thread(syncThread, "D_thread2");
            Thread E_thread1 = new Thread(syncThread, "E_thread1");
            Thread E_thread2 = new Thread(syncThread, "E_thread2");
            Thread F_thread1 = new Thread(syncThread, "F_thread1");
            Thread F_thread2 = new Thread(syncThread, "F_thread2");
            A_thread1.start();
            A_thread2.start();
            B_thread1.start();
            B_thread2.start();
            C_thread1.start();
            C_thread2.start();
            D_thread1.start();
            D_thread2.start();
            E_thread1.start();
            E_thread2.start();
            F_thread1.start();
            F_thread2.start();
        }
    }
    

      

    总结:

    1、有线程访问对象的同步代码块时,另外的线程可以访问该对象的非同步代码块

    2、若锁住的是同一个对象,一个线程在访问对象的同步代码块时,另一个访问对象的同步代码块的线程会被阻塞

    3、若锁住的是同一个对象,一个线程在访问对象的同步方法时,另一个访问对象同步方法的线程会被阻塞

    4、若锁住的是同一个对象,一个线程在访问对象的同步代码块时,另一个访问对象同步方法的线程会被阻塞,反之亦然

    5、同一个类的不同对象的对象锁互不干扰

    6、类锁由于也是一种特殊的对象锁,因此表现和上述1,2,3,4一致,而由于一个类只有一把对象锁,所以同一个类的不同对象使用类锁将会是同步的

    7、类锁和对象锁互补干扰

    JVM优化锁的策略有哪一些?

    自旋锁

    1、许多情况下,共享数据的锁定状态持续时间较短,切换线程不值得

    2、通过让线程执行忙循环等待锁的释放,不让出CPU

    3、缺点:若锁被其他线程长时间占用,会带来许多性能上的开销

    自适应自旋锁

    1、自旋的次数不再固定

    2、由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定

    (JVM会越来越聪明,预测的时间越来越精确)

    锁消除

    更彻底的优化

      1、JIT编译时,对运行上下文进行扫描,去除不可能存在竞争的锁

    public class StringBufferWithoutSync {
        public void add(String str1, String str2) {
            //StringBuffer是线程安全,由于sb只会在append方法中使用,不可能被其他线程引用
            //因此sb属于不可能共享的资源,JVM会自动消除内部的锁
            StringBuffer sb = new StringBuffer();
            sb.append(str1).append(str2);
        }
    
        public static void main(String[] args) {
            StringBufferWithoutSync withoutSync = new StringBufferWithoutSync();
            for (int i = 0; i < 1000; i++) {
                withoutSync.add("aaa", "bbb");
            }
        }
    
    }
    

      

    锁粗化

    另一种极端

    1、通过扩大加锁的范围,避免反复加锁和解锁

    public class CoarseSync {
        public static String copyString100Times(String target){
            int i = 0;
            StringBuffer sb = new StringBuffer();
            while (i<100){
                sb.append(target); 
    // 由于append是同步方法,循环调用这个同步方法,
    //会导致锁的不断加锁和释放锁的操作,影响不必要的资源浪费。
    //而这个时候jvm会把锁进行粗化,粗化到整块区域(红色标记) } return sb.toString(); } }

      

    自旋锁,自适应自旋锁,锁消除,锁粗化

    synchronized的四种状态

    1、无锁、偏向锁、轻量级锁、重量级锁

    锁膨胀方向:无锁---》偏向锁---》轻量级锁----》重量级锁

    偏向锁:减少同一线程获取锁的代价

      1、大多数情况下,锁不存在多线程竞争,总是由同一线程多次获得

      核心思想:如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word的结构也变为偏向锁结构,当该线程再次请求锁时,无需再做任何同步操作,则获取锁的过程只需要检查Mark Word的锁标记位为 偏向锁以及当前线程Id等于Mark Word 的ThreadID即可,这样就省去了大量有关锁申请的操作。

      不适合于锁竞争比较激烈的多线程场合

    轻量级锁

      轻量级锁时由偏向锁升级来的,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,偏向锁就会升级为轻量级锁

      适应的场景:线程交替执行同步块

      若存在同一时间访问同一锁的情况,就会到只轻量级锁膨胀为重量级锁

    问题:轻量级锁的加锁过程和减锁过程是怎样的?

    ----》

    锁的内存语义

      当线程释放锁时,Java内存模型会把该线程对应的本地内存中的共享变量刷新到主内存中;

      而当线程获取锁时,Java内存模型会把该线程对应的本地内存设置为无效,从而使得被监控器包含的临界区代码必须从主内存中读取共享变量

    理解上:本地内存A就是指该A线程的栈帧里面的displaced mark word ,由于栈帧是线程私有的,其他线程是无法看到的,因此需要把这个值放到公共的地方,其他线程才能看到,从而做出下一步动作。

    CAS(Compare And Swap )  可以简单地理解为:是一种无锁的算法

  • 相关阅读:
    自动化测试
    Linux分区介绍
    Python 爬虫学习 网页图片下载
    Sublime Text 2 配置
    Python 爬虫学习 urllib2
    Python 爬虫学习 urllib
    Python 学习之 NumPy
    ipython
    2020-2021-1 20209309闫兆森 《Linux内核原理与分析》第二周作业
    2020-2021-1 20209309《Linux内核原理与分析》第一周作业
  • 原文地址:https://www.cnblogs.com/vingLiu/p/10665696.html
Copyright © 2020-2023  润新知