java并发

并发核心理论

  • 1.共享性: 数据共享是线程安全问题的主要问题之一

  • 2.互斥性: 资源互斥指同一时间内值允许一个访问者访问.通常情况修改数据是互斥性,读数据不要求.因此我们有共享锁和互斥锁.也叫读锁和写锁.

  • 3.原子性:指操作是一个独立,不可分割的整体.操作不会中断,数据不会执行一半的时候被其他操作修改.例如一条指令就是最基本的原子.但是 i++,就分了好几次操作:

    读取i=1值 --> 2.i=1+1 --->3.存储i=2

  • 4.可见性:


    image.png

    每个线程都有自己的工作内存,对于共享变量,先拿到变量的副本,对副本进行操作,在某一个时间在把副本的值同步到共享变量.这样导致,有可能线程1修改了共享变量的值,某一时间线程2可能拿不到最新的值.

  • 5.有序性:为了提高性能,编译器和处理器可能会对指令进行重排.

synchronized及其实现原理

synchronized 作用:

  • 1.确保线程互斥的访问同步代码
  • 2.保证共享变量能及时可见
  • 3.解决重排序问题

synchronize 的一般用法,对象锁,类锁.(synchronized Object,synchronized 方法)
对class文件反编译后的汇编语言如下:


image.png

image.png

说明synchronized 是在对象前面加了 monitor .
Monitorenter : 对象都享有一个monitor
1.线程一旦进入monitor,monitor值1,线程为monitor拥有者.
2.其他线程到了这里,会进行阻塞,直到monitor的值=0.
Monitorexit:monitor 变回0.说明其他线程可以拥有.

对方法加synchronized

image.png

image.png

Synchronized底层优化(偏向锁、轻量级锁)
优点 缺点 适用场景
偏向锁 加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。 如果线程间存在锁竞争,会带来额外的锁撤销的消耗。 适用于只有一个线程访问同步块场景。
轻量级锁 竞争的线程不会阻塞,提高了程序的响应速度。 如果始终得不到锁竞争的线程使用自旋会消耗CPU。 追求响应时间。同步块执行速度非常快。
重量级锁 线程竞争不使用自旋,不会消耗CPU。 线程阻塞,响应时间缓慢。 追求吞吐量。同步块执行时间较长。

锁的状态: 无锁状态,偏向锁 ,轻量级锁,重量级锁
随着锁的竞争,锁的状态可以升级,但是是单向的.从低到高,不会出现锁的降级.
偏向锁<轻量级锁<重量级锁


image.png

轻量级锁:本意使用操作系统互斥量解决传统的重量级锁的性能问题.
使用场景:线程交替执行同步块.如果同一时刻访问同一锁的情况,轻量级锁会升级重量级锁.
偏向锁:用于一个线程执行同步块是提高性能.一旦出现多线程竞争,升级为轻量级锁.
总结 :JDk中采用轻量级锁和偏向锁等对Synchronized的优化,但是这两种锁也不是完全没缺点的,比如竞争比较激烈的时候,不但无法提升效率,反而会降低效率,因为多了一个锁升级的过程,这个时候就需要通过-XX:-UseBiasedLocking来禁用偏向锁。下面是这几种锁的对比:

优点 缺点 适用场景
偏向锁 加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。 如果线程间存在锁竞争,会带来额外的锁撤销的消耗。 适用于只有一个线程访问同步块场景。
轻量级锁 竞争的线程不会阻塞,提高了程序的响应速度。 如果始终得不到锁竞争的线程使用自旋会消耗CPU。 追求响应时间。同步块执行速度非常快。
重量级锁 线程竞争不使用自旋,不会消耗CPU。 线程阻塞,响应时间缓慢。 追求吞吐量。同步块执行时间较长。

Java并发编程:线程间的协作(wait/notify/sleep/yield/join)

线程有5中状态:
新建(New)-->准备状态(Runnable)-->运行状态(Running)-->阻塞(Blocking)-->死亡(Dead)

new :创建 new Thread();
Runnable:调用start(),进入就绪状态,等待cpu分配资源,有系统运行时线程来调度
Running:开始执行Run方法
Blocking: 阻塞
Dead
Wait/notify/notifyAll()
wait() 当前线程挂起,直到有其他线程调用notify(), notifyAll().
wait(long timeout) 当前线程挂起,直到有其他线程调用notify(), notifyAll();或者等到timeout
wait(long timeout,int nanos)
notify() 唤醒指定线程 , notifyAll() 唤醒所有的线程
注意:wait 必须在synchronized 块之内.
Thread.sleep(long sleeptime)
当前线程暂停指定的时间(毫秒),而更深层次的区别在于sleep方法只是暂时让出CPU的执行权,并不释放锁.而wait方法则需要释放锁,(意味其他线程可以拿到锁,进行操作)

package jni.test.leon.javatest;
/**
 * Created by leon on 17-12-11.
 */
public class SleepTest {
    public synchronized void sleepMethod() {
        System.out.println("Sleep start-----");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Sleep end-----");
    }
    public  void waitMethod() {
        System.out.println("Wait start-----");
        synchronized (this) {
            try {
                wait(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("Wait end-----");
    }
    public static void main(String[] args) {
        final SleepTest test1 = new SleepTest();
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test1.sleepMethod();
                }
            }).start();
        }
        try {
            Thread.sleep(10000);//暂停十秒,等上面程序执行完成
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("-----分割线-----");
        final SleepTest test2 = new SleepTest();
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test2.waitMethod();
                }
            }).start();
        }
    }
}
//output-----
Connected to the target VM, address: '127.0.0.1:38691', transport: 'socket'
Sleep start-----
Sleep end-----
Sleep start-----
Sleep end-----
Sleep start-----
Sleep end-----
-----分割线-----
Wait start-----
Wait start-----
Wait start-----
Wait end-----
Disconnected from the target VM, address: '127.0.0.1:38691', transport: 'socket'
Wait end-----
Wait end-----

Process finished with exit code 0

Thread.yeild()
当前线程暂停,让其他线程有机会执行.(用的场景比较少,主要是调试)

package jni.test.leon.javatest;
/**
 * Created by leon on 17-12-11.
 */
public class YieldTest implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            Thread.yield();
        }
    }
    public static void main(String[] args) {
        YieldTest runn = new YieldTest();
        Thread t1 = new Thread(runn, "FirstThread");
        Thread t2 = new Thread(runn, "SecondThread");
        t1.start();
        t2.start();
    }
}
//--output
FirstThread: 0
FirstThread: 1
SecondThread: 0
FirstThread: 2
SecondThread: 1
FirstThread: 3
SecondThread: 2
FirstThread: 4
SecondThread: 3
SecondThread: 4

Thread.join()/Thread.join(long waittime)/Thread.join(long waittime,int nano)
作用:父线程等待子线程执行完之后在执行,可以达到异步子线程,最后的同步.

package jni.test.leon.javatest;
/**
 * Created by leon on 17-12-11.
 */
public class JoinTest implements Runnable {
    @Override
    public void run() {
        try {
            Thread.sleep(10);
            System.out.println(Thread.currentThread().getName() + " start-----");
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " end------");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        Thread test = null;
        for (int i = 0; i < 5; i++) {
            test = new Thread(new JoinTest());
            test.start();
        }
        //这里是阻塞
        try {
            test.join(); //调用join方法
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Finished~~~");
    }
}
//output------
Thread-0 start-----
Thread-1 start-----
Thread-2 start-----
Thread-3 start-----
Thread-4 start-----
Thread-0 end------
Thread-1 end------
Thread-2 end------
Thread-3 end------
Thread-4 end------
Finished~~~

总结:
因为wait() /notify () 是对象monitor,所以当wait的时候就monitorEnter,必须等待有monitorExit 才能释放对象的拥有权.所以,一旦wait 了,其他的线程是无法获得拥有权,也就不能进入.

而sleep, yield这些是Thread级别的方法,只是让出cpu执行权join,调用的是wait 方法,所以是会进行对象级别的monitor.

volatile的使用及其原理

我们知道 可见性,有序性,原子性 的问题.synchronized 就是用来解决这些问题的,但是synchronized是比较重量级,volatile是一个轻量级的解决有序性,可见性,原子性的方案.
原理:
1.对有序性:
在解释这个问题前,我们先来了解一下Java中的happen-before规则,JSR 133中对Happen-before的定义如下:

Two actions can be ordered by a happens-before relationship.If one action happens before another, then the first is visible to and ordered before the second.

通俗一点说就是如果a happen-before b,则a所做的任何操作对b是可见的。(这一点大家务必记住,因为happen-before这个词容易被误解为是时间的前后)。我们再来看看JSR 133中定义了哪些happen-before规则:

  • Each action in a thread happens before every subsequent action in that thread.
  • An unlock on a monitor happens before every subsequent lock on that monitor.
  • A write to a volatile field happens before every subsequent read of that volatile.
  • A call to start() on a thread happens before any actions in the started thread.
  • All actions in a thread happen before any other thread successfully returns from a join() on that thread.
  • If an action a happens before an action b, and b happens before an action c, then a happens before c.

翻译过来为:

  • 同一个线程中的,前面的操作 happen-before 后续的操作。(即单线程内按代码顺序执行。但是,在不影响在单线程环境执行结果的前提下,编译器和处理器可以进行重排序,这是合法的。换句话说,这一是规则无法保证编译重排和指令重排)。
  • 监视器上的解锁操作 happen-before 其后续的加锁操作.(Synchronized 规则)
  • 对volatile变量的写操作 happen-before 后续的读操作。(volatile 规则)
  • 线程的start() 方法 happen-before 该线程所有的后续操作。(线程启动规则)
  • 线程所有的操作 happen-before 其他线程在该线程上调用 join 返回成功后的操作。
  • 如果 a happen-before b,b happen-before c,则a happen-before c(传递性)。
    这里我们主要看下第三条:volatile变量的保证有序性的规则<<Java并发编程:核心理论>>

2.可见性
使用 Volatile 关键字
1.修改时会强制修改主内存的数据
2.修改变量后导致其他线程工作中内存的值失效,所以需要重新读取主内存的数据
volatile 的使用场景比较有限:
1.该变量写操作不依赖当前值
2.该变量没有包含在具有其他变量的不变式中(这个变量只有原子性操作,简单说来就是 进行赋值)

常用:
1.状态标记量

A:
volatile boolean flag = false;
 
while(!flag){
    doSomething();
}
 
public void setFlag() {
    flag = true;
}

B:
volatile boolean inited = false;
//线程1:
context = loadContext();  
inited = true;            
 
//线程2:
while(!inited ){
sleep()
}
doSomethingwithconfig(context);

2.double check

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

推荐阅读更多精彩内容