博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
并发编程
阅读量:7089 次
发布时间:2019-06-28

本文共 10811 字,大约阅读时间需要 36 分钟。

并发简介

为什么要学习并发编程?

  • 方便实际开发

1、什么是并发编程

  • 并发历史
    早期计算机--从头到尾执行一个程序,资源浪费
    操作系统出现--计算机能运行多个程序,不同的程序在不同的单独的进程中运行
    一个进程,有多个线程
    提高资源的利用率,公平
  • 2、串行与并行的区别
    串行:洗茶具、打水、烧水、等水开、冲茶
    并行:打水、烧水同时洗茶具、水开、冲茶
    好处:可以缩短整个流程的时间

  • 并发编程目的
    摩尔定律:当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍,  性能也将提升一倍。这一定律揭示了信息技术进步的速度。
    让程序充分利用计算机资源
    加快程序响应速度(耗时任务、web服务器)
    简化异步事件的处理
  • 什么时候适合使用并发编程
    任务会阻塞线程,导致之后的代码不能执行:比如一边从文件中读取,一边进行大量计算的情况
    任务执行时间过长,可以划分为分工明确的子任务:比如分段下载
    任务间断性执行:日志打印
    任务本身需要协作执行:比如生产者消费者问题

3、并发编程的挑战之频繁的上下文切换

  • cpu为线程分配时间片,时间片非常短(毫秒级别),cpu不停的切换线程执行,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再加载这 个任务的状态,让我们感觉是多个程序同时运行的
  • 上下文的频繁切换,会带来一定的性能开销
  • 如何减少上下文切换的开销?
    无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些办法来避免使用锁,  如将数据的ID按照Hash算法取模分段,不同的线程处理不同段的数据
    CAS:Java的Atomic包使用CAS算法来更新数据,而不需要加锁。
    使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程都处于等待状态。
    协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。

4、并发编程的挑战之死锁

5、并发编程的挑战之线程安全

第二章、线程基础

1、进程与线程的区别

  • 进程:是系统进行分配和管理资源的基本单位
  • 线程:进程的一个执行单元,是进程内调度的实体、是CPU调度和分派的基本单位,是比进程更小的独立运行的基本单位。线程也被称为轻量级进程,线程是程序执行的最小单位。
  • 一个程序至少一个进程,一个进程至少一个线程。

2、线程的状态及其相互转换

  • 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  • 运行(RUNNABLE):处于可运行状态的线程正在JVM中执行,但它可能正在等待来自操作系统的其他资源,例如处理器。
  • 阻塞(BLOCKED):线程阻塞于synchronized锁,等待获取synchronized锁的状态。
  • 等待(WAITING):Object.wait()、join()、 LockSupport.park(),进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
  • 超时等待(TIMED_WAITING):Object.wait(long)、Thread.join()、LockSupport.parkNanos()、LockSupport.parkUntil,该状态不同于WAITING, 它可以在指定的时间内自行返回。
  • 终止(TERMINATED):表示该线程已经执行完毕。

3、创建线程的方式

  • 继承Thread,并重写父类的run方法
public class MyThread extends Thread{    @Override    public void run() {        System.out.println(Thread.currentThread().getName());    }    public static void main(String[] args) {        MyThread myThread=new MyThread();        myThread.setName("线程demo");        myThread.start();    }复制代码
  • 实现Runable接口,并实现run方法
public class MyRunnable implements Runnable {    public void run() {        System.out.println(Thread.currentThread().getName());    }    public static void main(String[] args) {        Thread thread = new Thread(new MyRunnable());        thread.setName("线程demo");        thread.start();    }}//实际开发中,选第2种:java只允许单继承//增加程序的健壮性,代码可以共享,代码跟数据独立复制代码
  • 使用匿名内部类
public class MyThread {    public static void main(String[] args) {        Thread thread=new Thread(new Runnable() {            public void run() {                System.out.println(Thread.currentThread().getName());            }        });        thread.start();    }}复制代码
  • Lambda表达式
public class Lambada {    public static void main(String[] args) {        new Thread(()->{            System.out.println(Thread.currentThread().getName());        }).start();    }}复制代码
  • 线程池
public class ThreadPool {    public static void main(String[] args) {        ExecutorService executorService = Executors.newSingleThreadExecutor();        executorService.execute(()->{            System.out.println(Thread.currentThread().getName());        });    }}复制代码

4、线程的挂起跟恢复

什么是挂起线程?	线程的挂起操作实质上就是使线程进入不可执行状态,在这个状态下CPU不会分给线程时间片,进入这个状态可以用来暂停一个线程的运行。	在线程挂起后,可以通过重新唤醒线程来使之恢复运行为什么要挂起线程?	cpu分配的时间片非常短、同时也非常珍贵。避免资源的浪费。如何挂起线程?	可以使用的方法(以下方法都是Object对象的方法)		wait() 暂停执行、放弃已经获得的锁、进入等待状态		notify() 随机唤醒一个在等待锁的线程		notifyAll() 唤醒所有在等待锁的线程,自行抢占cpu资源什么时候适合使用挂起线程?	我等的船还不来(等待某些未就绪的资源),我等的人还不明白。直到notify方法被调用复制代码
public class WaitDemo implements Runnable{    private static Object waitObj=new Object();    @Override    public void run() {        //持有资源        synchronized (waitObj){            System.out.println(Thread.currentThread().getName()+"占用资源");            try {                waitObj.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        System.out.println(Thread.currentThread().getName()+"释放资源");    }    public static void main(String[] args) {        Thread thread = new Thread(new WaitDemo(),"对比线程");        Thread thread2 = new Thread(new WaitDemo(),"对比线程2");        thread.start();        thread2.start();        try {            Thread.sleep(3000L);            synchronized (waitObj){                waitObj.notify();            }        } catch (InterruptedException e) {            e.printStackTrace();        }    }}复制代码

5、线程的中断操作

Thread.interrupt方法    自行定义一个标志,用来判断是否继续执行复制代码
public class InterruptDemo implements Runnable {    @Override    public void run() {        while(!Thread.currentThread().isInterrupted()){            System.out.println(Thread.currentThread().getName());        }    }    public static void main(String[] args) {        Thread thread=new Thread(new InterruptDemo());        thread.start();        try {            Thread.sleep(1000l);            thread.interrupt();        } catch (InterruptedException e) {            e.printStackTrace();        }    }}//也可以在while处自定义boolean类型的变量true,通过sleep多长时间改变其值为false中断线程复制代码

6、线程的优先级

线程的优先级告诉程序该线程的重要程度有多大。如果有大量线程都被堵塞,都在等候运行,程序会尽可能地先运行优先级高的那个线程。 但是,这并不表示优先级较低的线程不会运行,只是它被准许运行的机会小一些而已。

线程的优先级设置可以为1-10的任一数值,Thread类中定义了三个线程优先级,分别是:    MIN_PRIORITY(1)、NORM_PRIORITY(5)、MAX_PRIORITY(10),一般情况下推荐使用这几个常量,不要自行设置数值。任务:	快速处理:设置高的优先级	慢慢处理:设置低的优先级复制代码
public class PriorityDemo {    public static void main(String[] args) {        Thread thread=new Thread(()->{            while (true) {                System.out.println(Thread.currentThread().getName());            }        },"线程1");                Thread thread2=new Thread(()->{            while(true){                System.out.println(Thread.currentThread().getName());            }        },"线程2");                thread.setPriority(Thread.MIN_PRIORITY);        thread2.setPriority(Thread.MAX_PRIORITY);        thread.start();        thread2.start();    }}复制代码

7、守护线程

线程分类:用户线程、守护线程    守护线程:任何一个守护线程都是整个程序中所有用户线程的守护者,只要有活着的用户线程,守护线程就活着。    当JVM实例中最后一个非守护线程结束时,守护线程也随JVM一起退出    守护线程的用处:jvm垃圾清理线程    建议: 尽量少使用守护线程,因其不可控,不要在守护线程里去进行读写操作、执行计算逻辑复制代码
public class DaemonThreadDemo implements Runnable{    @Override    public void run() {        while (true){            System.out.println(Thread.currentThread().getName());            try {                Thread.sleep(2000l);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    public static void main(String[] args) {        Thread thread=new Thread(new DaemonThreadDemo());        thread.setDaemon(true);        thread.start();        try {            Thread.sleep(2000l);        } catch (InterruptedException e) {            e.printStackTrace();        }    }}复制代码

第二章、线程安全性问题

1、什么是线程安全性?

当多个线程访问某个类,不管运行时环境采用何种调度方式或者这些线程如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类为线程安全的。什么是线程不安全?多线程并发访问时,得不到正确的结果。复制代码

2、产生线程不安全问题的原因

num++ 不是原子性操作,被拆分成好几个步骤,在多线程并发执行的情况下,因为cpu调度,多线程快递切换,有可能两个同一时刻都读取了同一个num值,之后对它进行+1操作,导致线程安全性。复制代码
public class UnSafeThread {    private static int num = 0;    private static CountDownLatch CountDownLatch=new CountDownLatch(10);    public static void inCreate(){num++;}    public static void main(String[] args) {        for(int i=0;i<10;i++){            new Thread(()->{                for(int j=0;j<100;j++){                    inCreate();                    try {                        Thread.sleep(10l);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }                CountDownLatch.countDown();            }).start();        }        while (true){            if(CountDownLatch.getCount()==0){                System.out.println(num);                break;            }        }    }}复制代码

3、什么是原子性操作

一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。通俗点讲:操作要成功一起成功、要失败大家一起失败如何把非原子性操作变成原子性    volatile关键字仅仅保证可见性,并不保证原子性    synchronize关键字,使得操作具有原子性复制代码

4、深入理解synchronized关键字

内置锁    每个java对象都可以用做一个实现同步的锁,这些锁称为内置锁。    线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁。    获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。互斥锁    内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A尝试去获得线程B持有的内置锁时,    线程A必须等待或者阻塞,直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。    修饰普通方法:锁住对象的实例修饰静态方法:锁住整个类修饰代码块: 锁住一个对象 synchronized (lock) 即synchronized后面括号里的内容复制代码
public class SynDemo {    public synchronized void out(){
//普通方法 try { System.out.println(Thread.currentThread().getName()); Thread.sleep(5000l); } catch (Exception e) { e.printStackTrace(); } } public static synchronized void staticOut(){
//静态方法 try { System.out.println(Thread.currentThread().getName()); Thread.sleep(5000l); } catch (Exception e) { e.printStackTrace(); } } private Object lock=new Object(); public void myOut(){ synchronized (lock){
//代码块,确保lock是同一对象,调用时用同一个对象去调用 try { System.out.println(Thread.currentThread().getName()); Thread.sleep(5000l); } catch (Exception e) { e.printStackTrace(); } } } public static void main(String[] args) { SynDemo synDemo = new SynDemo(); //SynDemo synDemo2 = new SynDemo(); new Thread(()->{ synDemo.myOut(); }).start(); new Thread(()->{ synDemo.myOut(); }).start(); }}复制代码

5、volatile关键字及其使用场景

能且仅能修饰变量保证该变量的可见性,volatile关键字仅仅保证可见性,并不保证原子性禁止指令重排序	A、B两个线程同时读取volatile关键字修饰的对象	A读取之后,修改了变量的值	修改后的值,对B线程来说,是可见使用场景	1:作为线程开关	2:单例,修饰对象实例,禁止指令重排序复制代码

6、单例与线程安全

饿汉式--本身线程安全    在类加载的时候,就已经进行实例化,无论之后用不用到。如果该类比较占内存,之后又没用到,就白白浪费了资源。懒汉式 -- 最简单的写法是非线程安全的,在需要的时候再实例化复制代码
public class HungerSingleton {    private static HungerSingleton ourInstance = new HungerSingleton();    public static HungerSingleton getInstance() {        return ourInstance;    }    private HungerSingleton() {}    public static void main(String[] args) {        for(int i=0;i<10;i++){            new Thread(()->{                System.out.println(HungerSingleton.getInstance());            }).start();        }    }}public class LazySingleton {    private static volatile LazySingleton lazySingleton=null;    private LazySingleton(){}    public static LazySingleton getInstance(){        //判断实例是否为空,为空则实例化        if(null==lazySingleton){            try {                //模拟实例化时耗时的操作                Thread.sleep(1000l);            } catch (InterruptedException e) {                e.printStackTrace();            }            synchronized(LazySingleton.class){                if(null==lazySingleton) {                    lazySingleton = new LazySingleton();                }            }        }        //否则直接返回        return lazySingleton;    }    public static void main(String[] args) {        for(int i=0;i<10;i++){            new Thread(()->{                System.out.println(LazySingleton.getInstance());            }).start();        }    }}复制代码

7、如何避免线程安全性问题

线程安全性问题成因    1:多线程环境   	2:多个线程操作同一共享资源   	3:对该共享资源进行了非原子性操作如何避免:打破成因中三点任意一点    1:多线程环境--将多线程改单线程(必要的代码,加锁访问)    2:多个线程操作同一共享资源--不共享资源(ThreadLocal、不共享、操作无状态化、不可变)    3:对该共享资源进行了非原子性操作--将非原子性操作改成原子性操作(加锁、使用JDK自带的原子性操作的类、JUC提供的相应的并发工具类)复制代码

转载于:https://juejin.im/post/5bdc67d751882516bc477140

你可能感兴趣的文章
银行核心业务系统开发项目管理之道-金融项目我们应该关注那些东西
查看>>
SimpleAdapter参数说胆
查看>>
hibernate 延迟加载(转载)
查看>>
养血祛风利湿治毛发脱落案
查看>>
jq 获取页面中checkbox已经选中的checkbox
查看>>
c语言,gdb
查看>>
新手学习Cocoapods教程
查看>>
使用React并做一个简单的to-do-list
查看>>
unity, 使导入的材质名与3dmax中一致
查看>>
SpringMVC简单例子
查看>>
蓝牙音箱连接成功但没有声音还是电脑的声音
查看>>
ng-file-upload结合springMVC使用
查看>>
005 Hadoop的三种模式区别
查看>>
在笛卡尔坐标系上描绘函数 y=4x^2-2/4x-3
查看>>
ubuntu 下无损扩展分区
查看>>
Caused by: org.xml.sax.SAXParseException; lineNumber: 1
查看>>
手机资源共享
查看>>
Mahout-DistanceMeasure (数据点间的距离计算方法)
查看>>
在线研讨会网络视频讲座 - 方案设计利器Autodesk Infrastructure Modeler 2013
查看>>
【转】批量杀进程
查看>>