高并发多线程之线程基础中生命周期线程封闭cpu缓存

Posted 踩踩踩从踩

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高并发多线程之线程基础中生命周期线程封闭cpu缓存相关的知识,希望对你有一定的参考价值。

前言

本篇文章主要介绍线程的基础,什么是线程、线程的状态、线程封闭、cpu缓存和内存屏障、指令重排等概念

什么是java线程

 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

现在的项目中,如果只使用单线程,是肯定不满足项目的需求,而多线程充分压榨计算机的cpu和内存降低任务处理时间,也就是一个任务需要40分钟,可以将任务分为5个线程,同时运行,虽然增加内存和cpu的消耗,但是任务处理时间是降低;这其中就涉及了许多概念,如何去处理多线程之间协同,以及如何充分利用cpu,管理多线程。这里面非常多的概念,需要从基础开始慢慢深入理解。

线程状态

java中线程状态分为6个状态:6个状态中定义 

java.lang.Thread.State

  • New :尚未启动的线程状态

     例如   Thread thread=new Thread();   从new开始的状态 还没有start 之前就是 new的状态

  • Runnable:可运行线程的线程状态,等待cpu状态 

     也就是 start的时候,这个时候是可以执行,并不是马上执行。 这里面也区分开两种状态,也就是cpu正在执行 ,可以被执行 还在等待着。等待cpu的调度

  •  Blocked 状态:线程阻塞等待监视器锁定的线程状态

        处于synchronized同步代码块或方法中阻塞。这只针对synchronied 

  •  waiting状态:线程等待状态

不带timeout参数的方式调用object.wait thread.join locksupport.park.线程等待,如果没有唤醒则会处于一直等待的状态

  • timewariting状态:具有等待时间的等待线程的线程状态。例如下面方式中有超时属性的

thread.sleep  object.wait  thread.join locksupport.parknanos. locksupport.parkuntil

  • terminated:终止线程状态。线程正常完成执行或者出现异常

从代码中来看 首先  新建 运行 到终止的代码

    public static void test1() throws InterruptedException {
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                System.out.println("子线程运行。。。");
            }
        });

        System.out.println("1、调用start方法前,thread1状态:" + thread1.getState().toString());
        Thread.sleep(1000L);
        thread1.start();
        System.out.println("2、调用start方法后,thread1状态:" + thread1.getState().toString());

        Thread.sleep(1000L); // 等待thread1执行结束,再看状态
        System.out.println("3、1s后,thread1状态:" + thread1.getState().toString());
    }

而得到的结果是  ,在start前后的状态,和 线程执行成过后的状态 变化

1、调用start方法前,thread1状态:NEW
2、调用start方法后,thread1状态:RUNNABLE
子线程运行。。。
3、1s后,thread1状态:TERMINATED

然后在其中等待唤醒的状态

  public static void test2() throws InterruptedException {
        Thread thread2 = new Thread(new Runnable() {
            public void run() {
                try {// 将线程2移动到等待状态,1500后自动唤醒
                    Thread.sleep(5000);
                    System.out.println("3、Sleep结束," + Thread.currentThread().getName() + "当前状态:" + Thread.currentThread().getState());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread2.start();
        System.out.println("1、调用start后,thread2状态:" + thread2.getState().toString());

        Thread.sleep(2000L); // 等待2000毫秒,再看状态
        System.out.println("2、等待2s后,thread2状态:" + thread2.getState().toString());

    }

这里需要获取到线程,并让sleep在那里  去获取到状态

1、调用start后,thread2状态:RUNNABLE
2、等待2s后,thread2状态:TIMED_WAITING
3、Sleep结束,Thread-0当前状态:RUNNABLE

对于blocked状态,这里需要用对象给锁住

    public static void test4() throws InterruptedException {
    	Thread thread2=new Thread(new Runnable() {
			
			public void run() {
				System.out.println("2、抢锁之前,thread2状态:"+Thread.currentThread().getState().toString());
				synchronized (Demo2_ThreadState.class) {
					System.out.println("5、抢锁之后,thread2状态:"+Thread.currentThread().getState().toString());
					
				}
			}
		});
    	
    	synchronized (Demo2_ThreadState.class) {
    		System.out.println("1、主线程拿到锁,启动thread2");
    		thread2.start();
    		Thread.sleep(2000L);
    		 System.out.println("3、thread2的状态" +  thread2.getState());
    		 Thread.sleep(4000L);
    		 
    	}
    	
        System.out.println("4、主线程释放锁");
    }

得到的结果,这里就能看出synchronized进行锁住,互斥锁

1、主线程拿到锁,启动thread2
2、抢锁之前,thread2状态:RUNNABLE
3、thread2的状态BLOCKED
4、主线程释放锁
5、抢锁之后,thread2状态:RUNNABLE

中止线程

  • 中止线程的方式直接使用stop命令,进行中止线程。

因为stop方法是很暴力的,可能会导致不可控的情况,例如在线程执行中有存在某个值相加的情况,不能保证数据的原子性,例如下面种情况

MyThread mythread=new MyThread();
    	mythread.start();
    	Thread.sleep(1000);
    	mythread.stop();
    	while (mythread.isAlive()) {
		}
    	
    	mythread.print();


class MyThread extends Thread{
	private int  i=0,j=0;
	@Override
	public void run() {
		
		synchronized (this) {
			++i;
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			++j;
		}
	}
	
	public void print() {
		System.out.print("打印出i的值为:"+i+",j的值为"+j);

	}

得到的结果却是

打印出i的值为:1,j的值为0

两个值明显不一致。并且stop方法使得synchronized都无法保证原子性。因此被弃用了

  • interrupt 方法进行中断线程,在timewaiting状态下也可以捕获到异常,达到终止线程的情况
mythread.interrupt();

然后得到的结果就是

java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.dongnao.concurrent.period1.MyThread.run(Demo2_ThreadState.java:120)
打印出i的值为:1,j的值为1

保证了数据的原子性,打断了会继续执行。

  • 使用状态位进行中断线程

修改flag的值,来中断,只有线程里面是while 循环时,就可以使用这种方式进行中断线程。y个flag一定是全局变量

public volatile static boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            try {
                while (flag) { // 判断是否运行
                    System.out.println("运行中");
                    Thread.sleep(1000L);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        // 3秒之后,将状态标志改为False,代表不继续运行
        Thread.sleep(3000L);
        flag = false;
        System.out.println("程序运行结束");
    }

比如某些需要实时监控的线程需要进行停止线程扫描。

线程封闭

多线程访问共享可变数据时,涉及到线程间数据同步的问题。

 

 我们想要的方案是,某个线程在访问共享区域的变量时,其他线程不会访问变量;

并不是所有时候,都要用到共享数据,若数据都被封闭在各自线程中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步技术称为线程封闭;

ThreadLocal实现线程封闭

通过下面代码来实现

 /** threadLocal变量,每个线程都有一个副本,互不干扰 */
    public static ThreadLocal<String> value = new ThreadLocal<>();


    public void threadLocalTest() throws Exception {

        // threadlocal线程封闭示例
        value.set("123"); // 主线程设置值
        String v = value.get();
        System.out.println("主线程,v:" + v);

        new Thread(new Runnable() {
            @Override
            public void run() {
                String v = value.get();
                System.out.println("子线程:线程1取到的值:" + v);

                // 设置 threadLocal
                value.set("456");

                v = value.get();
                System.out.println("子线程:重新设置之后,线程1取到的值:" + v);
                System.out.println("子线程 执行结束");
            }
        }).start();

        Thread.sleep(12000L); // 等待所有线程执行结束

        v = value.get();
        System.out.println("主线程,v:" + v);

    }

threadLocal的源码,他里面还是通过一个全局map来达到线程封闭的。key则是线程,value是对应设置的值。

相当于每个线程互存副本的。

通过栈封闭达到线程封闭的效果

局部变量的固有属性之一就是封闭在线程之中。它们位于执行线程的栈中,其他线程无法访问这个栈。

局部变量的属性。

共享的对象。

cpu缓存和指令重排

cpu性能优化和缓存

cpu对程序执行进行优化,cpu高速缓存; cpu高速缓存中的数据是内存中的一小部分,但这一小部分是短时间内cpu即将访问的,当cpu调用大量数据时,就避开内存而直接访问高速缓存。

多级缓存

l1 cache时cpu第一层高速缓存,它的容量很小,技术难度增加和成本增加非常大

l2 cache  l3 cache 这些

cpu查找时,也是通过一级缓存、二级缓存、三级缓存、往下查找的

缓存同步协议

 就如jvm内存协议规范中规定一样,有个缓存一致性协议(MESI协议) 多数厂商都对它进行了实现

多处理器时,单个cpu对缓存中数据进行改动,需要通知其他cpu,也就是意味着,cpu处理要控制自己的读写操作,还要监听其他cpu发出的通知,从而保持最终一致性。

cpu在优化运行效率-运行时指令重排

 指令重排,这其中涉及到cpu指令重排和jit编译指令重排。

出现场景,还是cpu在执行某个操作的时候,发现当前缓存区块被占用,因此他就优先执行后面的区块,提高了cpu的处理性能。

as-if-serial语义:指令在重排时,不管怎么做,单个线程的结果不能被改变

出现的问题

高速缓存会导致数据短暂得不一致;

cpu指令重排,在多线程得情况下可能会出现数据混乱。

内存屏障

处理器提供了两个内存屏障指令来解决 数据缓存和多线程下指令重排问题。

写内存屏障:

在写指令后插入store barrier ,能让写入缓存中的最新数据更新写入主内存,让其他线程马上看到。强制写入主内存,这种显示调用,cpu就不会因为性能考虑而去对指令重排。

读内存屏障:

在读指令前插入load barrier,可以让高速缓存中的数据失效,强制新从的主内存加载数据,强制读取主内存内容,让cpu高速缓存的内容和主内存保持一致,避免缓存不一致导致的一致性问题

其实这两个指令 jvm在运行时,会根据java程序中 volatile 关键字修饰的变量,自动添加指令的运行。

总结

整篇文章描述的比较基础,如果说有什么没描述的,我觉得是java怎么创建线程的方式,这个我觉得不需要在本篇文章描述,比较简单;本篇文章描述的是、线程状态、如何优雅的中止线程、线程封闭的概念和实现、cpu缓存和指令重排;主要是为多线程打基础。线程池、以及线程间通信这些内容我们放到下篇文章中写入

以上是关于高并发多线程之线程基础中生命周期线程封闭cpu缓存的主要内容,如果未能解决你的问题,请参考以下文章

并发基础之线程的生命周期

高并发线程的生命周期其实没有我们想象的那么简单!!

多线程与高并发原理篇:1_cpu多级缓存模型

Java并发基础Java线程的生命周期

Java并发编程系列之二线程基础

单进程单线程的Redis如何能够高并发