并发编程之美-1

主要用来记录一些自己觉得重点的知识

不定期更新读书笔记 欢迎关注、点赞 👌

[TOC]

马桶🚽Java 上厕所就能看完的小知识! 欢迎关注、点赞 持续更新!

第一章

synchronized

Java中Synchronized的用法

synchronized是Java中的关键字,是一种同步锁。

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象

2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象
  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象
  4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

一个线程访问一个对象的同步代码块时,别的线程可以访问该对象的非同步代码块而不受阻塞。

synchronized关键字不能继承

在定义接口方法时不能使用synchronized关键字


public class ThreadTest {
    public static void main(String[] args) {
        System.out.println("使用关键字静态synchronized");
        SyncThread syncThread1 = new SyncThread();
        SyncThread syncThread2 = new SyncThread();
        Thread thread1 = new Thread(syncThread1, "SyncThread1");
        Thread thread2 = new Thread(syncThread2, "SyncThread2");
        thread1.start();
        thread2.start();
    }
}
class SyncThread implements Runnable {
    private static int count;

    public SyncThread() {
        count = 0;
    }

    public static synchronized void method() {
        for (int i = 0; i < 5; i ++) {
            try {
                System.out.println(Thread.currentThread().getName() + ":" + (count++));
                Thread.sleep(500);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public synchronized void run() {
        method();
    }
}

线程通知与等待

当一个线程调用一个公共变量的wait()时,该调用的线程会被阻塞挂起。

调用 wait() 方法的线程需要事先获取该对象的监视器锁。否则会抛出异常。

该调用线程会被阻塞挂起时只有通过以下情况才能返回

  1. 线程调用了该共享对象 notify()或者 notifyAll(0方法

  2. 其他线程调用了该线程 interrupt() 方法,该线程抛出 nterruptedException 异常返回。

sleep()调用线程会暂时让出指定时间的**执行权**,也就是在这期间不参与CPU的调度,但是程所拥有的监视器源,比如**锁**还是持有**不让出**的。获取到 CPU 资源后就可以继续运行了。

yield()一般很少使用这个方法,在调试或者测试时这个方法或许可以帮助复现由于并发竞争条件导致的问题,其在设计并发控制时或许会有用途。

sleep方法与yield方法的区别在于,当线程调用sleep方法时调用线程会被阻塞挂起指定的时间,在这期间线程调度器不会去调度该线程。

而调用yield方法时,线程只是让出自己剩余的时间片,并没有被阻塞挂起,而是处于就绪状态,线程调度器下一次调度时就有可能调度到当前线程执行

线程中断

Java 中的线程中断是种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是根据被中断的线程根据中断状态自行处理。

interrupt() : 设置线程中断标志为true

isinterrupted():检测调用线程是否被中断 不会清除中断标志

interrupted(): 检测当前线程是否被中断 会清除中断标志 静态方法

 @Override
    public void run() {
        //如果当前线程被中断则退出循环 则退出线程
        while (!Thread.currentThread().isInterrupted() && count < 10) {
            method();
        }

    }

当一个线程阻塞时间过长时,我们可以通过设置中断强制抛出异常而返回,使线程重新进入激活状态。

例如:休眠3s 但是发现3s内就能满足条件。如果一直等待3s就会浪费时间。调用中断强制激活。

守护线程与用户线程

Java 中的线程分为两类,分别为 daemon 线程(守护线程)和 user 线程(用户线程)。

JVM启动会调用main函数,其所在的钱程就是一个用户线程,其实在 JVM内部同时还启动了好多守护线程,比如垃圾回收线程。

区别:当最后一个非护线程结束时JVM正常退出,而不管当前是否存在守护线程,也就是说守护线程是否结束并不影响 JVM 退出。

当Main线程运行结束后,JVM会启动一个叫做DestroyJava VM的线程,该线程会等待所有用户线程结束后终止JVM进程。

ThreadLocal

ThreadLocal JDK 包提供的,它提供了线程本地变量,也就是如果你创建了ThreadLocal ,那么访问这个变量的每个线程都会有这个变量的一个本地副本,当多个线程操作这个变量时,实际操作的是自己本地内存里面变量,从而避免了线程安全问题。

获取资源模型

实现原理

每次操作的都是线程内部的ThreadLocalMap,每个线程的本地变量不是存放在 ThreadLocal 里面,而是存放在调用线程的threadLocals变量里,Thr adLocal 就是个工具壳。

key 为我们定义的ThreadLocal变量this引用, value则为使用set方法设置的值。

具体实现
    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        // 将当前线程作为 key ,去查找对应的线手呈交量,找到则设置
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
         // 没有第一次创建对应MAP   
            createMap(t, value);
        }
    }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

第二章

什么是多线程并发编程

并发是指同一个时间段内多个任务同时都在执行。

并行是说在单位时间内多个任务同时在执行。

synchronized

synchronized 块是 Java 提供的一种原子性内置锁,当阻塞一个线程时,需要从用户态切换到内核态执行阻塞操作,这是很耗时的操作。会带来线程调度开销。

进入synchronized块的内存语义是把在 synchronized块内使用到的变量从线程的工作内存清除,这样在synchronized块内使用到该变时就不会从线程的工作内存中获取,而是直接从主内存中获取

退出synchronized块的内存语义是把在synchronized块内对共享变量修改刷新到主内存。

volatile

当一个变量被声明为volatile时,线程在写入变量时不会把值缓存在寄存器或者其他地方,而是会把值刷新回主内存当其它线程读取该共享变量,会从主内存重新获取最新值,而不是使用当前线程的工作内存中的值。

什么时候使用?

写入变量不依赖变量的当前值时,因为如果依赖当前值,将是获取、计算、写入三步操作

这三步操作不是原子性的,而 volatile 不保证原子性。例如(count++ 读取count的值到工作内存,计算count值,再将count刷新进主内存。)

Unsafe

jdk提供的不安全的功能,更低层(c语言),因为是通过可以直接调用内存不直接提供给用户使用。可以进行数组边界判断与比较与交换CAS操作进行赋值。官方不推荐使用。

想要使用只能使用反射获取变量

getUnsafe()代码要求必须通过BootStrapClassLoader 加载,而我们使用main函数调用会使用AppClassLoader调用得所以无法获取

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class<?> caller = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(caller.getClassLoader()))
            throw new SecurityException("Unsafe");
        return theUnsafe;
    }

示例

public class UnsafeTest {
    static Unsafe unsafe;
    private static long countOffset;
    private int count = 1;
    static {
        //使用反射获取Unsafe 的成员交量thUnsafe
        try {

            Field field = Unsafe.class.getDeclaredField("theUnsafe");
//           给权限防止禁止反射使用
            field.setAccessible(true);
            unsafe= (Unsafe) field.get(null);
//            设置count参数偏移量
            countOffset =  unsafe.objectFieldOffset(UnsafeTest.class.getDeclaredField("count"));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        UnsafeTest unsafeTest = new UnsafeTest();
        int andAddInt = unsafe.getAndAddInt(unsafeTest, countOffset, 2);
//        原值
        System.out.println(andAddInt);
//        新值
        System.out.println(unsafeTest.count);
    }

}

乐观锁与悲观锁

悲观锁指对数据被外界修改持保守态度,认为数据很容易就会被其他线程修改,所以在数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处于锁定状态。

乐观锁 相对悲观锁来说的,它认为数据在一般情况下不会造成冲突,所以在访问记录前不会加排它锁,而 在进行数据提交更新时,才会正式对数据冲突与否进行检测 。

独占锁与共享锁

独占锁保证任何时候都只有一个线程能得到锁, ReentrantLock 就是以独占方式实现共享锁则可以同时由多个线程持有 ,例如 ReadWriteLock 锁,它允许一个资源可以被多线程同时进行读操作

独占锁是一种悲观锁,由于每次访问资源都先加上互斥锁,这限制了并发性,因为操作并不会影响数据的一致性 ,而独占锁只允许在同一时间由一个线程读取数据,其他线程必须等待当前线程释放锁才能进行读取。

共享锁则是一种乐观锁,它放宽了加锁的条件,允许多个线程同时进行读操作。

自旋锁

当一个线程在获取锁(比如独占锁)失败后,会被切换到内核状态而被挂起。

当该线程获取到锁时又需要将其切换到内核状态而唤醒该线程,而从用户状态切换到内核状态的开销是比较大的,在一定程度上会影响并发性能。

自旋锁则是,当前线程在获取锁时,如果发现锁已经被其他线程占有,它不马上阻塞自己,在不放弃 CPU 使用权的情况下,多次尝试获取默认次数是 10,很有可能在后面几次尝试中其他线程己经释放了锁,如果尝试指定的次数后仍没有获取到锁则当前线程才会被阻塞挂起。由此看来自旋锁是使用CPU 时间换取线程阻塞与调度的开销,但是很有可能这些CPU时间白白浪费。所以选择获取锁释放锁快的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容