与线程相关的知识点

与线程相关的知识点

文章参考1: https://blog.csdn.net/boker_han/article/details/82918881
文章参考2: https://blog.csdn.net/sinat_33087001/article/category/9270706
文章参考3: https://mp.weixin.qq.com/s/feZocJfW4CRoODISxlBaCQ
https://mp.weixin.qq.com/s/rkl916p8RIErGn58DNcihw
公众号的老哥:java进阶架构师

多线程

并发编程的知识比较零散,相关度很低,总是给人一种感觉,“学习了很多相关知识,可还是没搞懂是怎么回事”。那么,接下来就由我跟你一起来理一理Java并发编程的脉络。
1.并发理论:并发编程要解决的三大问题;介绍可见性与有序性问题的根源重排序;学习Java内存模型(JMM),理解JMM如何解决这些问题以实现并发编程的。
2.并发关键字:深入volatile、synchronized、final关键字的作用,都解决了什么问题,以及其实现原理。
3.并发基础:并发编程中用到的一些基本概念,如:死锁、饥饿与公平等;线程的创建、运行、调度。
4.CAS原子操作:并发编程的基础与核心CAS的实现原理,以及Java中的CAS原子操作。
5.Lock体系:JDK的Lock对于synchronized有哪些优势;Lock如何通过AQS与LockSupport工具实现的;Lock的使用。
6.并发工具:介绍java.util.concurrent包下提供的并发工具倒计时器CountDownLatch、循环栅栏CyclicBarrier、资源访问控制Semaphore、数据交换Exchanger的用法,并深入分析实现原理。
7.并发容器:介绍java.util.concurrent包下提供的并发容器ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentLinkedQueue、ThreadLocal、及阻塞队列的用法,并深入分析实现原理。
8.线程池(Executor体系):介绍线程池ThreadPoolExecutor和ScheduledThreadPoolExecutor的用法,分析线程池执行流程和原理。

说明

多线程不是为了提高执行速度,而是提高应用程序的使用率。
线程和线程共享 堆内存和方法区内存”,栈内存是独立的,一个线程一个栈
可以给现实世界中的人一种错觉:感觉多个线程在同时并发执行。
多线程:指的是这个程序(一个进程)运行时产生了不止一个线程

一.常见的概念

并行与并发:
  1. 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
  2. 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。
线程安全:

经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果.

共享和可变

要编写线程安全的代码,其核心在于对共享的和可变的状态进行访问。

“共享”就意味着变量可以被多个线程同时访问。我们知道系统中的资源是有限的,不同的线程对资源都是具有着同等的使用权。有限、公平就意味着竞争,竞争就有可能会引发线程问题。

“可变”是指变量的值在其生命周期内是可以发生改变的。“可变”对应的是“不可变”。我们知道不可变的对象一定是线程安全的,并且永远也不需要额外的同步(因为一个不可变的对象只要构建正确,其外部可见状态永远都不会发生改变)。所以“可变”意味着存在线程不安全的风险。

线程与进程的区别归纳

a.地址空间和其它资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
b.通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
c.调度和切换:线程上下文切换比进程上下文切换要快得多。
d.在多线程OS中,进程不是一个可执行的实体。

信号量与锁

信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在semtake的时候,就阻塞在 哪里)。而互斥锁是用在多线程多任务互斥的,一个线程占用了某一个资源,那么别的线程就无法访问,直到这个线程unlock,其他的线程才开始可以利用这 个资源。比如对全局变量的访问,有时要加锁,操作完了,在解锁。有的时候锁和信号量会同时使用的

也就是说,信号量不一定是锁定某一个资源,而是流程上的概念,比如:**有A,B两个线程,B线程要等A线程完成某一任务以后再进行自己下面的步骤,这个任务 并不一定是锁定某一资源,还可以是进行一些计算或者数据处理之类。**而线程互斥量则是“锁住某一资源”的概念,在锁定期间内,其他线程无法对被保护的数据进 行操作。在有些情况下两者可以互换。

线程的基本性质

原子性

原子是世界上最小的单位,具有不可分割性。在我们编程的世界里,某个操作如果不可分割我们就称之为该操作具有原子性。例如:i = 0,这个操作是不可分割的,所以该操作具有原子性。如果某个操作可以分割,那么该操作就不具备原子性,例如i++。非原子操作都存在线程安全问题,这个时候我们需要使用同步机制来保证这些操作变成原子操作,来确保线程安全。
为什么会有原子性问题?

线程是CPU调度的基本单位。CPU会根据不同的调度算法进行线程调度,将时间片分派给线程。当一个线程获得时间片之后开始执行,在时间片耗尽之后,就会失去CPU使用权。多线程场景下,由于时间片在线程间轮换,就会发生原子性问题。
如:对于一段代码,一个线程还没执行完这段代码但是时间片耗尽,在等待CPU分配时间片,此时其他线程可以获取执行这段代码的时间片来执行这段代码,导致多个线程同时执行同一段代码,也就是原子性问题。
线程切换带来原子性问题。
在这里插入图片描述
可见性

线程可见性是指线程之间的可见性**,即一个线程对状态的修改对另一个线程是可见的**,也就是一个线程修改的结果,另外一个线程立马就知道了。比如volitile修饰的变量,就具备可见性。
为什么会有可见性问题?

对于如今的多核处理器,每颗CPU都有自己的缓存,而缓存仅仅对它所在的处理器可见,CPU缓存与内存的数据不容易保证一致。
为了避免处理器停顿下来等待向内存写入数据而产生的延迟,处理器使用写缓冲区来临时保存向内存写入的数据。写缓冲区合并对同一内存地址的多次写,并以批处理的方式刷新,也就是说写缓冲区不会即时将数据刷新到主内存中。
缓存不能及时刷新导致了可见性问题。
在这里插入图片描述
补充一下多核CPU

多核CPU即多个CPU组成,这些CPU集成在一个芯片里,可以通过内部总线来交互数据,共享数据,这些CPU中分配出一个独立的核执行操作系统,每个核都有自己的寄存器,alu运算单元等(这些都是封装在cpu内部的),但是一级二级缓存是共享的,这些CPU通过总线来交互数据,并且工作是并行的,资源分配是由操作系统来完成的,操作系统来决定程序cpu的控制权分配,所以一个多核cpu的工作效率大多体现在操作系统的分配上,因为一个CPU基本上可以执行很多个程序,通过PCB进程控制块的方式存储当前代码段,然后来回跳转,所以当你的CPU核过多时,操作系统在分配时可能会导致部分CPU闲置!

有序性

有序性指的是数据不相关的变量在并发的情况下,实际执行的结果和单线程的执行结果是一样的,不会因为重排序的问题导致结果不可预知。volatile, final, synchronized,显式锁都可以保证有序性。
volatile内存屏障插入策略:
在每个volatile写操作的前面插入一个StoreStore屏障。
在每个volatile写操作的后面插入一个StoreLoad屏障。
在每个volatile读操作的后面插入一个LoadLoad屏障。
在每个volatile读操作的后面插入一个LoadStore屏障。

Store:数据对其他处理器可见(即:刷新到内存中)
Load:让缓存中的数据失效,重新从主内存加载数据

volatile保证可见性原理

volatile内存屏障插入策略中有一条,“在每个volatile写操作的后面插入一个StoreLoad屏障”。
StoreLoad屏障会生成一个Lock前缀的指令,Lock前缀的指令在多核处理器下会引发了两件事:

  1. 将当前处理器缓存行的数据写回到系统内存。
  2. 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。
jMM

1.硬件的发展中,一直存在一个矛盾,CPU、内存、I/O设备的速度差异。
速度排序:CPU >> 内存 >> I/O设备
为了平衡这三者的速度差异,做了如下优化:
CPU 增加了缓存,以均衡内存与CPU的速度差异;
操作系统增加了进程、线程,以分时复用CPU,进而均衡I/O设备与CPU的速度差异;
编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。
2.JMM定义了线程和主内存之间的抽象关系:
线程之间的共享变量存储在主内存中(堆内存在线程之间共享,存储在堆内存中所有实例域、静态域和数组元素都是共享变量)
每个线程都有一个私有的本地内存,本地内存中存储了该线程用以读/写共享变量的副本
3.线程A与线程B通信:
线程A把本地内存A中的共享变量刷新到主内存中去。
线程B到主内存中去读取线程A之前已更新过的共享变量。
从整体来看,这个过程就是线程A在向线程B发送消息。这个通信过程必须要经过主内存。JMM通过控制主内存与每个线程的本地内存之间的交互,来为Java程序员提供内存可见性保证。
4.8种原子操作
在这里插入图片描述
5.JMM对volatile的特殊规则

volatile变量V,W,线程T,进行read load use assign store write操作:

对于T来说,必须保证对V的load和use连续一起出现,即在工作内存中,每次使用V前都必须先从主内存中刷新最新值,用于保证能看到其他线程对V的修改后的值。

T对V的assign和store必须连续一起出现。即在工作内存中,每次修改V后都必须立刻同步回主内存中,保证其他线程看到自己对V 的修改。

假定A是T对V的use assign,F是A相关联的load或store;P是和F相应的对变量V的read或write;同样,B是T对W的use或assign,G是B相关联的load或store,Q是G相应的对变量W的read或write。如果A先于B,那么P先于Q(volatile修饰的变量不会被指令重排序优化,保证代码的执行顺序与程序顺序相同)。

创建多线程方式

一共有3中创建多线程的方式,1.继承Thread类;2实现Runnable类;3.实现Callable接口.

区别

继承Thread类使用起来简单,但是java中的类的单继承限制其使用范围.
实现Runnable则提供了比较灵活的扩展,是最常用的方式.
使用Callable是因为,可以在Callable创建的线程中获取到线程的返回值.

实现一下Callable
public class RunMain {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //为了获取到结果,线程需要使用FutureTask类进行一下包装
        FutureTask<String> task = new FutureTask<>(new NewCallableThread());
        //然后使用Thread类在包装一下FutureTask类
        Thread thread = new Thread(task);
        thread.start();
        //该方法会阻塞当前线程,直到Callable线程执行完毕得出结果,才会获取.
        System.out.println(task.get());
    }
}

class NewCallableThread implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "这是Callable线程的返回值";
    }
}

三.线程状态

新建:采用 new 语句创建完成
就绪:执行 start 后
运行:占用 CPU 时间,处理任务
阻塞:执行了 wait 语句、执行了 sleep 语句和等待某个对象锁,等待输入的场合
终止:退出 run()方法
在这里插入图片描述

多线程使用

sleep和wait

sleep()

sleep()使当前线程进入停滞状态(阻塞当前线程),让出CUP的使用、目的是不让当前线程独自霸占该进程所获的CPU资源,以留一定时间给其他线程执行的机会;sleep()是Thread类的Static(静态)的方法;因此他不能改变对象的机锁,所以当在一个Synchronized块中调用Sleep()方法是,线程虽然休眠了,但是对象的机锁并木有被释放,其他线程无法访问这个对象(即使睡着也持有对象锁)。在sleep()休眠时间期满后,该线程不一定会立即执行,这是因为其它线程可能正在运行而且没有被调度为放弃执行,除非此线程具有更高的优先级。

wait()

使用说明

Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作。

从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内

从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。

需要注意的是**notify()调用后,并不是马上就释放对象锁的,而是在相应synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。**这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。

创建一个线程安全的单例
public class SingleSafe {
    //无参构造私有化
    private SingleSafe() {}
    //对象属内存可见,禁止指令重排,防止出现空指针
    private volatile static SingleSafe singleSafe=null;

    public static SingleSafe getInstance() {
        //第一次判断是否为空,避免每次,每一个线程都进入同步锁.提高效率.
        if (null==singleSafe) {
            //添加同步锁
            synchronized (SingleSafe.class) {
                //第二次检测,避免其他线程创建,保证了同时只有一个线程在这里创建对象
                if (null==singleSafe) {
                    //创建对象
                    singleSafe = new SingleSafe();
                }
            }
        }
        return singleSafe;
    }

}
使用notify实现线程协同
package com.jd.thread;

/**
 * 实现线程的协同
 */
public class NotifyAndWait {
    //创建共享对象
    private static Object object = new Object();
    //创建轮询标志位,false为没有改变.true为变化了
    private static boolean flag = false;

    //创建一个等待线程
    static class WaitThread implements Runnable {
        @Override
        public void run() {
            //加锁
            synchronized (object) {
                //轮询当没有改变(false)等待
                while (!flag) {
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //当改变时执行任务
                System.out.println(Thread.currentThread().getName() + 
                		    "flag是" + flag + "等待被唤醒了,开始执行任务");
                //执行完成后将标志位回归原始
                flag = false;
            }
            //休眠一下
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //转入通知线程
            synchronized (object) {
                //当标志已经改变了就不在重复唤醒
                while (flag) {
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //当没有改变的时候要改变
                flag = true;
                //同时唤醒其他线程
                object.notifyAll();
                //额外输出
                System.out.println(Thread.currentThread().getName() +
                			 "flag是" + flag + "等待线程转入通知线程了");
            }
        }
    }

    //创建一个通知线程
    static class NotifyThread implements Runnable {
        @Override
        public void run() {
            //加个锁
            synchronized (object) {
                //当有其他线程改变了标志就不在改变
                while (flag) {
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //当状态没有发生该改变,执行任务
                System.out.println(Thread.currentThread().getName() + 
                					"flag是" + flag + "通知线程执行任务");
                //改变状态
                flag = true;
                object.notifyAll();
            }
            //休眠一下
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //转入等待线程
            synchronized (object) {
                while (!flag) {
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //额外输出
                System.out.println(Thread.currentThread().getName() +
                				 "flag是" + flag + "通知线程开始等待了");
            }
        }
    }

    //测试一下
    public static void main(String[] args) {
        Thread waitThread = new Thread(new WaitThread());
        Thread notifyThread = new Thread(new NotifyThread());
        waitThread.start();
        notifyThread.start();
    }
}
简单实现一个消费者和生产者
package com.jd.thread;

public class ProductAndConsumer {
    //创建商品的库存标志
    private static int num = 0;
    //共享对象
    private static Object object = new Object();

    //创建一个生产者
    static class Product implements Runnable {
        @Override
        public void run() {
            while (true) {
                //加锁
                synchronized (object) {
                    //当商品库存低于5时开始生产
                    while (num < 5) {
                        num++;
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("11111生产者生产一个商品" + num);
                        //唤醒消费者
                    }
                    //切记先唤醒在休眠
                    //唤醒消费者
                    object.notifyAll();
                    //超过5个
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

    }

    //创建一个消费者
    static class Consumer implements Runnable {
        @Override
        public void run() {
            while (true) {
                synchronized (object) {
                    //当库存高于1开始卖
                    while (num > 0) {
                        //开始售卖
                        num--;
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("22222消费者消费了一个商品" + num);
                    }
                    //切记先唤醒在休眠
                    object.notifyAll();
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
	//测试一下
    public static void main(String[] args) {
        Thread product = new Thread(new Product());
        Thread consumer = new Thread(new Consumer());
        product.start();
        consumer.start();
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值