线程相关的知识归纳整理

2020-08-06  本文已影响0人  积跬步以致千里_ylc

CPU的核心数和线程数的关系

CPU的核心数和线程数一般是1:1的关系,Intel推出的超线程技术能使电脑的核心数和线程数的比达到 1:2 (基于一个物理核心模拟两个逻辑核心),即是一个4核的CPU同时可运行4个线程,如果使用了超线程技术就可以同时运行8个线程,linux 系统下一个进程最大创建1000个线程,windows系统下一个进程最大创建2000个线程)

CPU时间片轮转机制(RR调度)

操作系统把所有就绪进程按先入先出的原则排成一个队列。新来的进程加到就绪队列末尾。每当执行进程调度时,进程调度程序总是选出就绪队列的队首进程,让它在CPU上运行一个时间片的时间。时间片是一个很小的时间单位,通常为10~100ms数量级。当进程用完分给它的时间片后,系统的计时器发出时钟中断,调度程序便停止该进程的运行,把它放入就绪队列的末尾;然后把CPU分给就绪队列的队首进程,同样也让它运行一个时间片,如此往复。(当前时间片执行完成后,线程还未完成,就会保存资源,切换到下一个时间片 执行下一个线程,这个过程叫上下文切换 大概消耗20000个cpu时间周期,一个cpu时间周期大约为 执行一个1+1 操作)

进程和线程的定义和区别

并行和并发

高并发编程的意义,好处和注意事项(java里的程序天生就是多线程的)

认识Java里的线程

线程的生命周期图

线程的声明周期

线程的使用

线程的停止方式

深入理解run()和start()

Thread类是Java里对线程概念的抽象,可以这样理解:我们通过new Thread()其实只是new出一个Thread的实例,还没有操作系统中真正的线程挂起钩来。只有执行了start()方法后,才实现了真正意义上的启动线程。start()方法让一个线程进入就绪队列等待分配cpu,分到cpu后才调用实现的run()方法,start()方法不能重复调用。而run方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并没有任何区别,可以重复执行,可以被单独调用。

其他的线程方法

线程间的共享

线程开始运行,拥有自己的栈空间,就如同一个脚本一样,按照既定的代码一步一步地执行,直到终止。但是,每个运行中的线程,如果仅仅是孤立地运行,那么没有一点儿价值,或者说价值很少,如果多个线程能够相互配合完成工作,包括数据之间的共享,协同处理事情。这将会带来巨大的价值。
Java支持多个线程同时访问一个对象或者对象的成员变量,关键字synchronized可以修饰方法或者以同步块的形式来进行使用,它主要确保多个线程在同一个时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性,又称为内置锁机制。

线程间的协作

线程之间相互配合,完成某项工作,比如:一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行又是另一个线程。前者是生产者,后者就是消费者,这种模式隔离了“做什么”(what)和“怎么做”(How),简单的办法是让消费者线程不断地循环检查变量是否符合预期在while循环中设置不满足的条件,如果条件满足则退出while循环,从而完成消费者的工作。却存在如下问题:
1)难以确保及时性。
2)难以降低开销。如果降低睡眠的时间,比如休眠1毫秒,这样消费者能更加迅速地发现条件变化,但是却可能消耗更多的处理器资源,造成了无端的浪费。

ThreadLocal

ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值, ThreadLocal往往用来实现变量在线程之间的隔离。threadLocal类接口很简单,只有4个方法,我们先来了解一下:
• void set(Object value)
设置当前线程的线程局部变量的值。
• public Object get()
该方法返回当前线程所对应的线程局部变量。
• public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
• protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

public final static ThreadLocal<String> RESOURCE = new ThreadLocal<String>(){
    @Override
    protected String initialValue(){
        return “string的初始值”;
    }
};

RESOURCE代表一个能够存放String类型的ThreadLocal对象。此时不论什么一个线程能够并发访问这个变量,对它进行写入、读取操作,都是线程安全的。

显式锁

Lock接口和核心方法

在finally块中释放锁,目的是保证在获取到锁之后,最终能够被释放。

  private Lock lock = new ReentrantLock();
    public void lockUse(){
        lock.lock();
        try {
            //执行业务代码
            goodsInfo.changeNumber(60);
        }finally {
            //使用try finally 是为了保证即使业务代码报错 也能释放锁
            lock.unlock();
        }
    }
lock的方法解析

可重入锁ReentrantLock、所谓锁的公平和非公平

读写锁ReentrantReadWriteLock

之前提到锁(synchronized和ReentrantLock)基本都是排他锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。
除了保证写操作对读操作的可见性以及并发性的提升之外,读写锁能够简化读写交互场景的编程方式。假设在程序中定义一个共享的用作缓存数据结构,它大部分时间提供读服务(例如查询和搜索),而写操作占有的时间很少,但是写操作完成之后的更新需要对后续的读服务可见。
一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的。在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量

import java.util.concurrent.locks.*;
public class UsWrLock implements GoodsService {
    private GoodsInfo goodsInfo;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock write = lock.writeLock();
    private final Lock read = lock.readLock();
    @Override
    public GoodsInfo getGoodsInfo() {
        read.lock();
        try {
            //读操作 返回商品信息
            SleepTools.ms(5);
            return this.goodsInfo;
        }finally { read.unlock(); }
    }

    @Override
    public void sellGoods(int sellNumber) {
        write.lock();
        try {
            //写操作,改变商品信息
            SleepTools.ms(5);
            goodsInfo.changeNumber(sellNumber);
        }finally { write.unlock(); }
    }

    public UsWrLock(GoodsInfo goodsInfo) {
        this.goodsInfo = goodsInfo;
    }
}

Condition接口

任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包括wait()、wait(long timeout)、notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。
用Lock和Condition实现等待通知, condition.signal()只能随机唤醒一个等待线程,condition.signalAll()唤醒所有等待线程。如下是一份使用condition实现的等待通知模式的代码:

public class ExpressCond {
    public final static String CITY = "ChengDu";
    private int km;/*快递运输里程数*/
    private String site;/*快递到达地点*/
    private Lock lock = new ReentrantLock();
    private Condition siteCond = lock.newCondition();
    private Condition kmCond = lock.newCondition();

    public ExpressCond(int km, String site) {
        this.km = km;
        this.site = site;
    }

    /* 变化公里数,然后通知处于wait状态并需要处理公里数的线程进行业务处理*/
    public void changeKm(int mileage){
        lock.lock();
        try {
            km = mileage;
            kmCond.signalAll();
        }finally {
            lock.unlock();
        }
    }

    /* 变化地点,然后通知处于wait状态并需要处理地点的线程进行业务处理*/
    public  void changeSite(String cityName){
        lock.lock();
        try {
            this.site = cityName;
            siteCond.signalAll();
        }finally {
            lock.unlock();
        }
    }

    /*当快递的里程数大于100时更新数据库*/
    public void waitKm(){
        //TODO
        lock.lock();
        try {
            while (km < 100){
                try {
                    kmCond.await();
                    System.out.println("check KM thread["+Thread.currentThread().getId() +"] is be notifed.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }finally {
            lock.unlock();
        }
        System.out.println("the Km is "+this.km+",I will change db");
    }

    /*当快递到达目的地时通知用户*/
    public void waitSite(){
        lock.lock();
        try {
            while(CITY.equals(this.site)) {
                try {
                    siteCond.await();
                    System.out.println("check Site thread["+Thread.currentThread().getId() +"] is be notifed.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }finally {
            lock.unlock();
        }
        System.out.println("the site is "+this.site+",I will call user");
    }
}

public class TestCond {
    private static ExpressCond express = new ExpressCond(0,ExpressCond.CITY);

    /*检查里程数变化的线程,不满足条件,线程一直等待*/
    private static class CheckKm extends Thread{
        @Override
        public void run() {
            express.waitKm();
        }
    }

    /*检查地点变化的线程,不满足条件,线程一直等待*/
    private static class CheckSite extends Thread{
        @Override
        public void run() {
            express.waitSite();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<3;i++){
            new CheckSite().start();
        }
        for(int i=0;i<3;i++){
            new CheckKm().start();
        }

        Thread.sleep(1000);
        express.changeKm(102);//快递里程变化
        express.changeSite("北京天安门");//快递里程变化
    }
}
上一篇下一篇

猜你喜欢

热点阅读