JAVA基础补完之并发编程

前言

本篇后半部分针对java并发编程领域,需要一定JAVA基础

WHY JAVA并发编程?

为什么要学习Java并发编程的技术呢?我根本用不到啊,我会用IDE写好业务代码就ok啦
好吧,估计不少同学会有上面的想法,也许你现在看不到学习这方面的需求,但视野不能仅停留在每天的日常工作中,作为开发人员,一个手艺人要让自己的手艺跟得上时代的发展,尤其在这个时代。

确实确定要学习什么是一个非常困难又极为重要的问题,我的思路如下所示

  1. 找准自己的定位以及未来的目标,假如你的目标是成为整天一大堆猎头趋之若鹜的站在技术顶尖的人,拿着上百万的年薪,带着一大堆视你为神明的小弟,开发惠及全球一半人口的大型系统……
  2. 分析要达到这一点其他人都经历过那些阶段,具备什么能力
  3. 找到离你最近的那个看看那个阶段的要求是什么,然后看看自己有那些已经具备了,有哪些还没有
  4. 开始升级或改变目标。

目前我首要的目标是达到软件开发业界的先进对平,达到高级开发和架构师层次,薪资水平达到3W+,于是我去各大招聘网站查看并分析了高级开发和架构师职位的要求,同时查找BAT关于高级JAVA开发的面试经验,发现了一个共同点,有相当一部分都提到了JAVA的并发编程技术,并且我对其中涉及到的知识完全没有认识,所以目前锁定了JAVA并发编程这个技术点进行集中攻关。

学习资料与路径

由于在工作中用过多次并发编程的相关技术,但又发现缺乏进一步的了解,因此我直接跳过了基础部分的了解直接去看了看相关的视频教程,即(imooc)深入浅出Java多线程和(imooc)细说多线程之Thread VS Runnable ,看完之后做了几个例程,发现对于其中理论部分还是存在疑问意识又去看了看相关的书籍,于是看了《Java多线程编程核心技术》,这本书相对简单,有很多示例代码,比较适合初级人员学习,最后仔细阅读了《Java并发编程的艺术》,这本书相对与其他资料深度深了不少,从源码和CPU指令排序等角度解开了我之前很多困惑的地方。

学习路径如下所示:
1. (imooc)深入浅出Java多线程
2. (imooc)细说多线程之Thread VS Runnable
3. 《Java多线程编程核心技术》
4. 《Java并发编程的艺术》
我觉得这个顺序效果还不错,推荐给大家。

JAVA并发编程

并发编程的基础

JAVA多线程的创建

Java多线程的创建主要有两种方式对Thread类的继承与对Runnable接口的实现。实现接口的方式比继承类的方式更灵活,也能减少程序之间的耦合度,面向接口编程也是设计模式6大原则的核心。所以建议使用实现Runnable接口方式。

1.Thread类的集成

public class ThreadThread extends Thread{
     public void run() {
         for (int i = 0; i < 1500; i++) {
           System.out.println(Thread.currentThread().getName() +  i);            
          }
      }
     public static void main(String[] args) {
         ThreadThread  ta= new ThreadThread ("A");
         ThreadThread tb = new ThreadThread ("B");
         ta.start();
         tb.start();
     }
}

2.Runnable接口的实现

public class ThreadRunnable implements Runnable {
        public void run() {       
            for (int i = 0; i < 1500; i++) {
                System.out.println(Thread.currentThread().getName() +  i);            
            }
        }
        public static void main(String[] args) {
            ThreadRunnable t1 = new ThreadRunnable();
            Thread ta = new Thread(t1, "A");
            Thread tb = new Thread(t1, "B");
            ta.start();
            tb.start();
        }
    }

锁的概念

锁是用来控制多个线程访问共享资源的方式,一般来说,一个锁能够防止多个线程同时访问共享资源。在Lock接口出现之前,Java程序是靠synchronized关键字实现锁功能的,而在Java SE 5之后并发包中新增了Lock接口和实现类来实现锁的功能。
总的来说锁是个工具,用来避免在多线程中可能出现的问题。

常用关键字

在一般的多线程的开发中,都是使用volatilesynchronzed来实现的。
1.volatile

public class Stage extends Thread {

    public void run(){      
        ArmyRunnable armyTaskOfSuiDynasty = new ArmyRunnable();
        ArmyRunnable armyTaskOfRevolt = new ArmyRunnable();     
        //使用Runnable接口创建线程
        Thread  armyOfSuiDynasty = new Thread(armyTaskOfSuiDynasty,"隋军");
        Thread  armyOfRevolt = new Thread(armyTaskOfRevolt,"农民起义军");        
        //启动线程,让军队开始作战
        armyOfSuiDynasty.start();
        armyOfRevolt.start();       
        //舞台线程休眠,大家专心观看军队厮杀
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }       
        //停止军队作战
        //停止线程的方法
        armyTaskOfSuiDynasty.keepRunning = false;
        armyTaskOfRevolt.keepRunning = false;       
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }       
        System.out.println("谢谢观看隋唐演义,再见!");

    }

    public static void main(String[] args) {
        new Stage().start();

    }

}
public class ArmyRunnable implements Runnable {

    //volatile保证了线程可以正确的读取其他线程写入的值
    volatile boolean keepRunning = true;

    @Override
    public void run() {
        while(keepRunning){
            //发动5连击
            for(int i=0;i<5;i++){
                System.out.println(Thread.currentThread().getName()+"进攻对方["+i+"]");
            }

        }

        System.out.println(Thread.currentThread().getName()+"结束了战斗!");

    }

}

2.synchronized
Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。
使用方式 如下代码所示,若没有synchronized关键字,则for循环会在多个线程之间切换,而使用了该关键字,则保证全部循环完成后才会线程切换。

public class ThreadSync implements Runnable {
    public void run() {
        synchronized(this) {
            for (int i = 0; i < 1500; i++) {
                System.out.println(Thread.currentThread().getName() + " synchronized loop " + i);
            }
        }
    }
    public static void main(String[] args) {
        ThreadSync t1 = new ThreadSync();
        Thread ta = new Thread(t1, "A");
        Thread tb = new Thread(t1, "B");
        ta.start();
        tb.start();
    }
}

守护线程Deamon

Daemon的作用是为其他线程的运行提供服务,比如说GC线程。其实User Thread线程和Daemon Thread守护线程本质上来说去没啥区别的,唯一的区别之处就在虚拟机的离开:如果User Thread全部撤离,那么Daemon Thread也就没啥线程好服务的了,所以虚拟机也就退出了。

并发编程的进阶

线程的生命周期

本图来自网络
线程的生命周期
1.创建状态:
当用new操作符创建一个新的线程对象时,该线程处于创建状态。
处于创建状态的线程只是一个空的线程对象,系统不为它分配资源。
2.可运行状态
执行线程的start()方法将为线程分配必须的系统资源,安排其运行,并调用线程体——run()方法,这样就使得该线程处于可运行状态(Runnable)。
这一状态并不是运行中状态(Running),因为线程也许实际上并未真正运行。    
3.阻塞状态:
线程调用wait()方法等待特定条件的满足;
线程输入/输出阻塞。
(返回可运行状态):
处于睡眠状态的线程在指定的时间过去后
如果线程在等待某一条件,另一个对象必须通过notify()或notifyAll()方法通知等待线程条件的改变;
如果线程是因为输入输出阻塞,等待输入输出完成。
4.消亡状态
当线程的run()方法执行结束后,该线程自然消亡。

多线程通信与控制

通过使用join,yeild,notify和wait等方法实现多线程的控制。
1.join
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。常见的使用方法有两种:
t.join(); //调用join方法,等待线程t执行完毕
t.join(1000); //等待 t 线程,等待时间是1000毫秒。
2.yield
该方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只能让同优先级的线程有执行的机会。
3.notify与wait
通过notify与wait方法可以实现多线程间的任务调度,这也是最为常见的多线程调度方法。使用方法如下所示。
等待线程代码如下

package com.imooc.concurrent.base.notifyandwait;

/**
 * Created by zhaoenwei on 2017/2/19.
 */
public class MyThreadWait extends Thread {
    private Object lock;

    public MyThreadWait(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock){
                System.out.println("Start wait time ="+System.currentTimeMillis());
                lock.wait();
                System.out.println("end wait time ="+System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

通知线程代码如下

package com.imooc.concurrent.base.notifyandwait;

/**
 * Created by zhaoenwei on 2017/2/19.
 */
public class MyThreadNotify extends Thread {
    private Object lock;
    public MyThreadNotify(Object lock) {
        super();
        this.lock = lock;
    }
    @Override
    public void run() {

            synchronized (lock){
                System.out.println("Start notify time ="+System.currentTimeMillis());
                lock.notify();
                System.out.println("end notify time ="+System.currentTimeMillis());
            }

    }
}

测试线程代码如下

package com.imooc.concurrent.base.notifyandwait;

/**
 * Created by zhaoenwei on 2017/2/19.
 */
public class ThreadTest {
    public static void main(String[] args){
        try{
            Object lock=new Object();
            MyThreadWait threadWait=new MyThreadWait(lock);
            threadWait.start();
            Thread.sleep(2000);
            MyThreadNotify threadNotify=new MyThreadNotify(lock);
            threadNotify.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

高级锁Reentrantlock

JDK 1.5中新增了ReentrantLock类也可以实现wait/notify的线程通知作用,同时在其基础上扩展了嗅探锁定,多路分支通知等功能,同时也更易于使用。
下面就使用多路分支通知进行示例。
MyService代码

package com.imooc.concurrent.base.multicondition;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Created by zhaoenwei on 2017/2/19.
 */
public class MyService {
    private Lock lock = new ReentrantLock();
    public Condition conditionA = lock.newCondition();
    public Condition conditionB = lock.newCondition();

    public void awaitA() {
        try {
            lock.lock();
            System.out.println("开始 awaitA时间为" + System.currentTimeMillis() + "NAME=" + Thread.currentThread().getName());
            conditionA.await();
            System.out.println("结束 awaitA时间为" + System.currentTimeMillis() + "NAME=" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void awaitB() {
        try {
            lock.lock();
            System.out.println("开始 awaitB时间为" + System.currentTimeMillis() + "NAME=" + Thread.currentThread().getName());
            conditionB.await();
            System.out.println("结束 awaitB时间为" + System.currentTimeMillis() + "NAME=" + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void singalAll_A() {
        try {
            lock.lock();
            System.out.println("signalAll _A时间为" + System.currentTimeMillis() + "NAME=" + Thread.currentThread().getName());
            conditionA.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void singalAll_B() {
        try {
            lock.lock();
            System.out.println("signalAll _B时间为" + System.currentTimeMillis() + "NAME=" + Thread.currentThread().getName());
            conditionB.signalAll();
        } finally {
            lock.unlock();
        }
    }
}

ThreadA代码

package com.imooc.concurrent.base.multicondition;

/**
 * Created by zhaoenwei on 2017/2/19.
 */
public class ThreadA extends Thread{
    private MyService service;

    public ThreadA(MyService service) {
        this.service = service;
    }
    public void run(){
        service.awaitA();
    }
}

ThreadB代码

package com.imooc.concurrent.base.multicondition;

/**
 * Created by zhaoenwei on 2017/2/19.
 */
public class ThreadB extends Thread {
    private MyService service;

    public ThreadB(MyService service) {
        this.service = service;
    }

    public void run() {
        service.awaitB();
    }
}

主函数代码

package com.imooc.concurrent.base.multicondition;

/**
 * Created by zhaoenwei on 2017/2/19.
 */
public class ThreadRunLock {
    public static void main(String[] args) throws InterruptedException {
        MyService service=new MyService();
        ThreadA a=new ThreadA(service );
        ThreadB b=new ThreadB(service);
        a.setName("A");
        b.setName("B");
        a.start();
        b.start();
        Thread.sleep(1000);
        service.singalAll_A();
    }

}

输出如下所示
开始 awaitA时间为1487492110944NAME=A
开始 awaitB时间为1487492110944NAME=B
signalAll _A时间为1487492111944NAME=main
结束 awaitA时间为1487492111944NAME=A
说明通知只通知到了A而没有通知到B,多路分支通知有效。

并发编程的艺术

并发编程为什么容易出错

代码底层执行不像我们看到的高级语言—-Java程序这么简单,它的执行是Java代码–>字节码–>根据字节码执行对应的C/C++代码–>C/C++代码被编译成汇编语言–>和硬件电路交互,现实中,为了获取更好的性能JVM和CPU可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。

多线程一定会快么

由于CPU是采用切换上下文的形式,切换多线程的任务,而切换上下文也会消耗一定时间,一般CPU美妙能够切换上千次上下文,所以要是为不适合的场景使用多线程编程不但可能无法提高效率,甚至可能形成处理的瓶颈。

如何减少上下文切换呢

  1. 无锁并发编程。多线程竞争锁时,会引起上下文切换,所以多线程处理数据是,可以用一些办法避免使用锁。
  2. CAS算法。使用Atomic包使用CAS算法来更新数据,不需要加锁。
  3. 使用最少线程。避免创建不需要的线程,若任务很少,但创建了很多处理,这样会有大量线程处于等待状态。
  4. 协程:在单线程里实现多任务的调度,在单线程里维持多个任务间的切换。

JAVA 内存模型JMM

在Java中,所有的实例域,静态域和数组元素都存储在对内存中,堆内存在线程之间共享。局部变量,方法定义参数和异常处理器参数不会在线程之间共享,他们不会有内存可见性问题,也不受内存模型的影响。Java线程之间的通讯有Java内存模型控制,JMM决定一个线程对共享变量的写入合适对另一个线程可见。
JMM定义了线程和主内存之间的关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存存储了该线程已读写变量的副本。本地内存是JMM的一个抽象概念并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
若线程A与线程B通信的话,一般包括如下来两个步骤
1.线程A把本地内存A中更新的共享变量刷新到主内存中
2.线程B到主内存中区读取线程A之前已经更新的共享变量。
而这两个步骤之间并不是原子性的,因此可能出现A将数据更新到主内存了,B还没更新自己的本地内存时,主内存被修改,导致B更新的不是A更新到 数据,从而产生数据异常。
因此,JMM通过控制主内存与每个线程的本地内存之间的交互,来为Java程序提供内存可见性的保证。

happens-before原则

一般来讲原则如下

  1. 程序顺序规则:一个线程中的操作,happens-before于该线程中的任意后续操作;
  2. 监视器锁规则:对于一个锁的解锁,happens-before与随后对于这个锁的加锁;
  3. volatile变量规则:对于一个volatile域的写,happens-before与而难以后续这个volatile域的读;
  4. 传递性:若A happens-before B ,且B happens-before C,则A happens-before C;
    happens-before原则通过限制CPU与JVM对指令的重排,保证了单线程执行以及正确同步的多线程程序的执行的正确性。

CAS

CAS,compare and swap的缩写,中文翻译成比较并交换。

我们都知道,在java语言之前,并发就已经广泛存在并在服务器领域得到了大量的应用。所以硬件厂商老早就在芯片中加入了大量直至并发操作的原语,从而在硬件层面提升效率。在intel的CPU中,使用cmpxchg指令。

在Java发展初期,java语言是不能够利用硬件提供的这些便利来提升系统的性能的。而随着java不断的发展,Java本地方法(JNI)的出现,使得java程序越过JVM直接调用本地方法提供了一种便捷的方式,因而java在并发的手段上也多了起来。而在Doug Lea提供的cucurenct包中,CAS理论是它实现整个java包的基石。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前 值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”

通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新 值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。

类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时 修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法 可以对该操作重新计算。
本文就不详细讨论CAS问题,有兴趣的可以搜索一下。资料很多。

总结

要达到能够使用并发编程的程度至少要掌握以下内容

  1. 应了解多线程的创建方法
  2. wait/notify方法
  3. join方法
  4. volatile关键字
  5. synchronized关键字
    要进一步提高并发编程的水平,则应了解以下内容
  6. 线程的生命周期
  7. JAVA内存模型
  8. happens-before原则
  9. CAS
    同时结合日常的大量实践才能做到知其然也知其所以然
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值