前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >并发编程原理剖析——多线程及其原理分析 顶

并发编程原理剖析——多线程及其原理分析 顶

作者头像
须臾之余
发布2019-07-09 19:00:31
7130
发布2019-07-09 19:00:31
举报
文章被收录于专栏:须臾之余

什么情况下应该使用多线程

线程出现的目的:解决阻塞问题。(CPU闲置)问题。

多线程使用场景

1、通过并行运算提高程序执行性能 2、需要等待网络、I/O响应导致耗费大量的执行时间,可以采用异步线程的方式来减少阻塞

tomcat7以前的IO模型

多线程应用场景

客户端阻塞:如果客户端只有一个线程,这个线程发起读取文件的额操作必须等待IO流返回,客户端才能做其它事情。

线程级别阻塞 (BIO):客户端一个线程情况下,导致整个客户端阻塞。那么我们可以使用多线程在等待IO操作返回,其它线程可以继续做其它事情。此时客户端不会闲置。

如何应用多线程

在Java中,有多种方式实现多线程。继承Thread类,实现Runnable接口,使用ExecutorService、Callable、Future实现带返回结果的多线程。

1、继承Thread类创建线程

代码语言:javascript
复制
public class MyThread extends Thread {
    public void run() {
        System.out.println("MyThread.run()");
    }
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();
        myThread1.start();
        myThread2.start();
    }
}

2、实现Runnable接口创建线程

代码语言:javascript
复制
public class MyThread1 implements Runnable {
    public void run() {
        System.out.println("MyThread.run()");
    }
    public static void main(String[] args) {
        MyThread1 myThread1 = new MyThread1();
        new Thread(myThread1).start();
    }
}

3、实现Callable接口通过FutureTask包装器来创建Thread线程

代码语言:javascript
复制
public class CallableDemo implements Callable<String> {
    public static void main(String[] args)throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Future<String> future = executorService.submit(new CallableDemo());
        //阻塞
        System.out.println(future.get());
        executorService.shutdown();
    }
    @Override
    public String call() throws Exception {
        return "string"+1;
    }
}

如何把多线程用的更加优雅

通过阻塞队列以及多线程的方式,实现对请求的异步化处理,提升处理性能

Request

代码语言:javascript
复制
public class Request {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Request{" +
                "name='" + name + '\'' +
                '}';
    }
}

RequestProcessor

代码语言:javascript
复制
public interface RequestProcessor {

    void processorRequest(Request request);
}

PrintProcessor

代码语言:javascript
复制
public class PrintProcessor extends Thread implements RequestProcessor {
    LinkedBlockingDeque<Request> linkedBlockingDequene =new LinkedBlockingDeque();
    private final RequestProcessor nextProcessor;

    public PrintProcessor(RequestProcessor nextProcessor){
        this.nextProcessor=nextProcessor;
    }

    @Override
    public void run() {
        while (true){
            try {
                Request request= linkedBlockingDequene.take();
                System.out.println("print data:"+request);
                nextProcessor.processorRequest(request);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    @Override
    public void processorRequest(Request request) {
        linkedBlockingDequene.add(request);
    }
}

SaveProcessor

代码语言:javascript
复制
public class SaveProcessor extends Thread implements RequestProcessor {
    LinkedBlockingDeque<Request> linkedBlockingDequene =new LinkedBlockingDeque();
    @Override
    public void run() {
        while (true){
            try {
                Request request= linkedBlockingDequene.take();
                System.out.println("save data:"+request);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    @Override
    public void processorRequest(Request request) {
        linkedBlockingDequene.add(request);
    }
}

Demo

代码语言:javascript
复制
public class Demo {
    PrintProcessor printProcessor;
    public Demo(){
        SaveProcessor saveProcessor = new SaveProcessor();
        saveProcessor.start();
        printProcessor=new PrintProcessor(saveProcessor);
        printProcessor.start();
    }
    public static void main(String[] args) {
        Request request=new Request();
        request.setName("xuyu");
        new Demo().doTest(request);
    }

    public void doTest( Request request){
        printProcessor.processorRequest(request);
    }
}

运行结果

print data:Request{name='xuyu'} save data:Request{name='xuyu'}

Java并发编程的基础

线程的状态

Java线程既然能够创建,那么势必也会被销毁,所以线程是存在生命周期的。

线程一共有六种状态(new、runnable、blocked、waiting、time_waiting、terminated(结束)) new:初始状态,线程被构建,但是还没有被调用start方法 runnable:运行状态,Java线程把操作系统中的就绪和运行两种状态统一称为 “运行中” blocked:阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了CPU的使用权,阻塞也分为几种情况 ###等待阻塞:运行的线程执行wait方法,jvm会把当前线程放入到等待队列 ###同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其它线程占用了,那么jvm会把当前线程放入到锁池中 ###其它阻塞:运行的线程执行Thread.sleep或者t.join方法,或者发出了I/O请求时,JVM会把当前线程设置为阻塞状态,当Sleep结束,join线程终止,IO处理完毕则线程恢复 waiting:等待状态,进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断) time_waiting:超时等待状态,超时以后自动返回 terminated:终止状态,表示当前线程执行完毕

通过代码演示线程状态

编写如下代码

代码语言:javascript
复制
public class ThreadStates {
    public static void main(String[] args) {
        new Thread(()->{
            while (true){
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"timewaiting").start();
        new Thread(()->{
            while (true){
                synchronized (ThreadStates.class){
                    try {
                        ThreadStates.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        },"waiting").start();
        new Thread(new BlockedDemo(),"BlockDemo-01").start();
        new Thread(new BlockedDemo(),"BlockDemo-02").start();
    }

    static class BlockedDemo extends Thread{
        @Override
        public void run() {
            while (true){
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

通过相应命令显示线程状态

1.打开终端或者命令提示符,键入“jps”,(JDK1.5 提供的一个显示当前所有 java进程 pid 的命令),可以获得相应进程的 pid 2.根据上一步骤获得的 pid,继续输入 jstack pid(jstack 是 java 虚拟机自带的一种堆栈跟踪工具。jstack 用于打印出给定的 java 进程 ID 或 core file 或远程调试服务的 Java 堆栈信息)

终端打印的信息

代码语言:javascript
复制
E:\idea\idea-code\thread-test\target\classes\com\demo1\processor>jps
13364 Jps
5236 Launcher
19704 ThreadStates
24588 RemoteMavenServer
24828

E:\idea\idea-code\thread-test\target\classes\com\demo1\processor>jstack 19704
2019-07-06 20:00:40
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.131-b11 mixed mode):

"DestroyJavaVM" #18 prio=5 os_prio=0 tid=0x0000000000e3e000 nid=0x24bc waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"BlockDemo-02" #17 prio=5 os_prio=0 tid=0x0000000029368800 nid=0x3a4c waiting on condition [0x0000000029ebf000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.demo1.processor.ThreadStates$BlockedDemo.run(ThreadStates.java:36)
        at java.lang.Thread.run(Thread.java:748)

"BlockDemo-01" #15 prio=5 os_prio=0 tid=0x0000000029367000 nid=0x463c waiting on condition [0x0000000029dbf000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.demo1.processor.ThreadStates$BlockedDemo.run(ThreadStates.java:36)
        at java.lang.Thread.run(Thread.java:748)

"waiting" #13 prio=5 os_prio=0 tid=0x0000000029366000 nid=0x437c in Object.wait() [0x0000000029cbf000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x0000000716108290> (a java.lang.Class for com.demo1.processor.ThreadStates)
        at java.lang.Object.wait(Object.java:502)
        at com.demo1.processor.ThreadStates.lambda$main$1(ThreadStates.java:20)
        - locked <0x0000000716108290> (a java.lang.Class for com.demo1.processor.ThreadStates)
        at com.demo1.processor.ThreadStates$$Lambda$2/1831932724.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

"timewaiting" #12 prio=5 os_prio=0 tid=0x0000000029365800 nid=0x5534 waiting on condition [0x0000000029bbe000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.demo1.processor.ThreadStates.lambda$main$0(ThreadStates.java:10)
        at com.demo1.processor.ThreadStates$$Lambda$1/990368553.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000000002777e800 nid=0x2d24 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x00000000276cf800 nid=0x31b0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x00000000276cd000 nid=0x2898 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x00000000276cc000 nid=0x620c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x00000000276b6000 nid=0x6220 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x0000000027688000 nid=0x48fc runnable [0x0000000028cbe000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        - locked <0x0000000716251450> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        - locked <0x0000000716251450> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000027618800 nid=0x35d0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000027675800 nid=0x2778 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000027603000 nid=0x3840 in Object.wait() [0x000000002895f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x0000000715f88ec8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
        - locked <0x0000000715f88ec8> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000002d9a000 nid=0x5bb4 in Object.wait() [0x000000002885f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x0000000715f86b68> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x0000000715f86b68> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=2 tid=0x00000000275e2800 nid=0x3964 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002cb7800 nid=0x658c runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002cb9800 nid=0x5e9c runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x0000000002cbb000 nid=0x26f0 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x0000000002cbc800 nid=0x14d4 runnable

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000002cbe800 nid=0x5e1c runnable

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002cc0000 nid=0x6148 runnable

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002cc4000 nid=0x4fdc runnable

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002cc5000 nid=0x3c84 runnable

"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x0000000002cc6800 nid=0x5d84 runnable

"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x0000000002cc7800 nid=0x47ac runnable

"VM Periodic Task Thread" os_prio=2 tid=0x00000000276c5000 nid=0x65c8 waiting on condition

JNI global references: 336

线程的停止

线程的终止,并不是简单的调用stop命令去停止线程,不建议使用,因为stop方法在结束一个线程时并不会保证线程资源正常释放,因此会导致程序出现一些不确定的状态。

要优雅的去中断一个线程,在线程中提供了一个interrupt方法

interrupt方法

Java中的线程中断是一种线程间的协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断的状态自行处理。

线程通过检查资源是否中断来进行响应,可以通过isInterrupted()来判断是否被中断

代码演示

代码语言:javascript
复制
public class InterruptDemo {
    private static int i;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("1:"+Thread.currentThread().isInterrupted());
                i++;
                System.out.println("5:"+Thread.currentThread().isInterrupted());
            }
            System.out.println("NUM:"+i);
            System.out.println("2:"+Thread.currentThread().isInterrupted());
        }, "interruptDemo");
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        System.out.println("3:"+Thread.currentThread().isInterrupted());
        thread.interrupt();
        System.out.println("4:"+Thread.currentThread().isInterrupted());
    }
}

执行结果

代码语言:javascript
复制
.......
1:false
5:false
1:false
5:false
3:false
1:false
5:false
4:false
1:true
5:true
NUM:60511
2:true

这种标识位或者中断操作的方式能够使线程终止时有机会去清理资源,而不是武断的将线程停止,因此这种终止线程的做法更加安全和优雅。

Thread.interrupted

上面的案例中,通过interrupt,设置了一个标识告诉线程可以终止了。线程中还提供了静态方法Thread.interrupted()对设置的中断标识的线程复位。比如在上面的案例中,外面的线程调用thread.interrupt来设置中断标识,而在线程里面,又通过Thread.interrupted把线程的标识进行复位。

代码语言:javascript
复制
public class InterruptDemo01 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
                Boolean li = Thread.currentThread().isInterrupted();
                if (li) {
                    System.out.println("before:" + li);
                    Thread.interrupted();//对线程进行复位,中断标识复位为false
                    System.out.println("after:" + Thread.currentThread().isInterrupted());
                }
            }
        });
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();//设置中断标识,中断标识为true
    }
}

执行结果

before:true after:false

其它线程的复位

除了通过 Thread.interrupted 方法对线程中断标识进行复位以外,还有一种被动复位的场景,就是对抛出 InterruptedException 异常的方法,在InterruptedException 抛出之前,JVM 会先把线程的中断标识位清除,然后才会抛出 InterruptedException,这个时候如果调用isInterrupted 方法,将会返回 false.

代码语言:javascript
复制
public class InterruptDemo01 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            while (true) {
               while(true) {
                    try {
                        Thread.sleep(10000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        });
        thread.start();
        TimeUnit.SECONDS.sleep(1);
        thread.interrupt();//设置中断标识,中断标识为true
        TimeUnit.SECONDS.sleep(1);
        System.out.println(thread.isInterrupted());//false
    }
}

那么就有疑问了,为啥要线程复位?,首先我们来看线程执行interrupt以后的源码是如何实现的

threap.cpp

代码语言:javascript
复制
void Thread::interrupt(Thread* thread) {  
  trace("interrupt", thread);  
  debug_only(check_for_dangling_thread_pointer(thread);
) 
 os::interrupt(thread); 
} 

os_linux.cpp

代码语言:javascript
复制
void os::interrupt(Thread* thread) {  
  assert(Thread::current() == thread || Threads_lock->owned_by_self(),    
  "possibility of dangling Thread pointer"); 
  
  OSThread* osthread = thread->osthread(); 
 
 if (!osthread->interrupted()) {    
  osthread->set_interrupted(true);    
  // More than one thread can get here with the same value of osthread,    
  // resulting in multiple notifications. We do, however, want the store    
  // to interrupted() to be visible to other threads before we execute unpark().    
  OrderAccess::fence();    
  ParkEvent * const slp = thread->_SleepEvent ;    
  if (slp != NULL) slp->unpark() ; 
} 
 
 // For JSR166. Unpark even if interrupt status already was set  
  if (thread->is_Java_thread())   
  ((JavaThread*)thread)->parker()->unpark(); 
 
  ParkEvent * ev = thread->_ParkEvent ;  
  if (ev != NULL) ev->unpark() ; 
 
} 

其实就是通过unpark去唤醒当前线程,并且设置一个标识位为true,并没有所谓的中断线程的操作,所以实际上,线程复位可以用来实现多个线程之间的通信。

线程的停止方法之二

定义一个volatile关键字修饰的成员变量,来控制线程的终止。这实际上是应用了volatile能够实现多线程之间共享变量的可见性这一特点来实现的。

代码语言:javascript
复制
public class VolatileDemo {
    private volatile static boolean stop=false;

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            int i = 0;
            while (!stop) {
                i++;
            }
        });
        thread.start();
        System.out.println("begin start thread...");
        Thread.sleep(1000);
        stop=true;
    }
}

输出结果

begin start thread...

线程的安全性问题

其实线程安全问题可以总结为:可见性、原子性、有序性这几个问题。解决这三个问题,那么线程安全性问题也就解决了。

CPU 的高速缓存

线程是CPU调度的最小单元,使用线程最终目的就是为了充分利用计算机处理的效能,但是绝大部分的运算任务不能只依靠处理器“计算”就能完成的,处理器还需要与内存交互,比如: 读取运算数据、存储运算结果,这个IO操作是很难消除的。而由于计算机的存储设备与处理器的运算速度差距非常大,所以现代计算机系统都会增加一层读写速度尽可能接近处理器运算速度的高速缓存 来作为内存与处理器之间的缓冲;将运算所需的数据复制到缓冲中,让运算能快速进行,当运算结束后再从缓存同步到内存之中。

CPU高速缓存参考链接:

https://www.cnblogs.com/jokerjason/p/9584402.html

http://www.360doc.com/content/18/0313/10/51484742_736583417.shtml

一级缓存

一级缓存(Level 1 Cache)简称L1 Cache,位于CPU内核的旁边,是与CPU结合最为紧密的CPU缓存,也是历史上最早出现的CPU缓存。由于一级缓存的技术难度和制造成本最高,提高容量所带来的技术难度增加和成本增加非常大,所带来的性能提升却不明显,性价比很低,而且现有的一级缓存的命中率已经很高,所以一级缓存是所有缓存中容量最小的,比二级缓存要小得多。 一般来说,一级缓存可以分为一级数据缓存(Data Cache,D-Cache)和一级指令缓存(Instruction Cache,I-Cache)。 二者分别用来存放数据以及对执行这些数据的指令进行即时解码。大多数CPU的一级数据缓存和一级指令缓存具有相同的容量,例如AMD的Athlon XP就具有64KB的一级数据缓存和64KB的一级指令缓存,其一级缓存就以64KB 64KB来表示,其余的CPU的一级缓存表示方法以此类推。 Intel的采用NetBurst架构的CPU(最典型的就是Pentium 4)的一级缓存有点特殊,使用了新增加的一种一级追踪缓存(Execution Trace Cache,T-Cache或ETC)来替代一级指令缓存,容量为12KμOps,表示能存储12K条即12000条解码后的微指令。一级追踪缓存与一级指令缓存的运行机制是不相同的,一级指令缓存只是对指令作即时的解码而并不会储存这些指令,而一级追踪缓存同样会将一些指令作解码,这些指令称为微指令(micro-ops),而这些微指令能储存在一级追踪缓存之内,无需每一次都作出解码的程序,因此一级追踪缓存能有效地增加在高工作频率下对指令的解码能力,而μOps就是micro-ops,也就是微型操作的意思。它以很高的速度将μops提供给处理器核心。Intel NetBurst微型架构使用执行跟踪缓存,将解码器从执行循环中分离出来。这个跟踪缓存以很高的带宽将uops提供给核心,从本质上适于充分利用软件中的指令级并行机制。Intel并没有公布一级追踪缓存的实际容量,只知道一级追踪缓存能储存12000条微指令(micro-ops)。所以,我们不能简单地用微指令的数目来比较指令缓存的大小。实际上,单核心的NetBurst架构CPU使用8Kμops的缓存已经基本上够用了,多出的4kμops可以大大提高缓存命中率。而如果要使用超线程技术的话,12KμOps就会有些不够用,这就是为什么有时候Intel处理器在使用超线程技术时会导致性能下降的重要原因。 例如Northwood核心的一级缓存为8KB 12KμOps,就表示其一级数据缓存为8KB,一级追踪缓存为12KμOps;而Prescott核心的一级缓存为16KB 12KμOps,就表示其一级数据缓存为16KB。在这里12KμOps绝对不等于12KB,单位都不同,一个是μOps,一个是Byte(字节),而且二者的运行机制完全不同。所以那些把Intel的CPU一级缓存简单相加,例如把Northwood核心说成是20KB一级缓存,把Prescott核心说成是28KB一级缓存,并且据此认为Intel处理器的一级缓存容量远远低于AMD处理器128KB的一级缓存容量的看法是完全错误的,二者不具有可比性。在架构有一定区别的CPU对比中,很多缓存已经难以找到对应的东西,即使类似名称的缓存在设计思路和功能定义上也有区别了,此时不能用简单的算术加法来进行对比;而在架构极为近似的CPU对比中,分别对比各种功能缓存大小才有一定的意义。 L1Cache(一级缓存)是CPU第一层高速缓存,分为数据缓存指令缓存。内置的L1高速缓存的容量和结构对CPU的性能影响较大,不过高速缓冲存储器均由静态RAM组成,结构较复杂,在CPU管芯面积不能太大的情况下,L1级高速缓存的容量不可能做得太大。一般服务器CPU的L1缓存的容量通常在32—256KB。 cpu缓冲,其实是cpu全频处理cmos时间,而cmos时间只要缓冲一点就足够,其余的跳到cmos外,来处理操作系统和ms-DOS,就像cmos密码一样,修改setup后才是保护计算机安全,与cpu缓冲是一样的,除缓冲外就是用来进行处理硬盘数据,并非缓冲达到高速水平。也就是说, 开机速度是按检测数据,cmos数据没有cpu处理,速度是很慢的!主板上并没有通道来处理Bios(cmos)中的数据,显存从中起到一点作用! 而内存是否缓冲,就要看是否已跳出cmos区,来进行处理硬盘数据。对于系统是否要缓存,就看内存是否有包裹!内存是否要分流,按硬盘结构应当是不用的!IE是否连接主板芯片,是否在主板上缓冲,还是在系统是缓冲,这也一样!

二级缓存

L2 Cache(二级缓存)是CPU的第二层高速缓存,分内部和外部两种芯片。内部的芯片二级缓存运行速度与主频相同,而外部的二级缓存则只有主频的一半。L2高速缓存容量也会影响CPU的性能,原则是越大越好,现在家庭用CPU容量最大的是4MB,而服务器和工作站上用CPU的L2高速缓存普遍大于4MB,有的高达8MB或者19MB。

三级缓存

三级缓存是为读取二级缓存后未命中的数据设计的—种缓存,在拥有三级缓存的CPU中,只有约5%的数据需要从内存中调用,这进一步提高了CPU的效率。 L3 Cache(三级缓存),分为两种,早期的是外置,截止2012年都是内置的。而它的实际作用即是,L3缓存的应用可以进一步降低内存延迟,同时提升大数据量计算时处理器的性能。降低内存延迟和提升大数据量计算能力对游戏都很有帮助。而在服务器领域增加L3缓存在性能方面仍然有显著的提升。比方具有较大L3缓存的配置利用物理内存会更有效,故它比较慢的磁盘I/O子系统可以处理更多的数据请求。具有较大L3缓存的处理器提供更有效的文件系统缓存行为及较短消息和处理器队列长度。 其实最早的L3缓存被应用在AMD发布的K6-III处理器上,当时的L3缓存受限于制造工艺,并没有被集成进芯片内部,而是集成在主板上。在只能够和系统总线频率同步的L3缓存同主内存其实差不了多少。后来使用L3缓存的是英特尔为服务器市场所推出的Itanium处理器。接着就是P4EE和至强MP。Intel还打算推出一款9MB L3缓存的Itanium2处理器,和以后24MB L3缓存的双核心Itanium2处理器。 但基本上L3缓存对处理器的性能提高显得不是很重要,比方配备1MB L3缓存的Xeon MP处理器却仍然不是Opteron的对手,由此可见前端总线的增加,要比缓存增加带来更有效的性能提升。

缓存一致性问题

CPU-0读取主内存的数据,缓存到CPU-0的高速缓存中; CPU-1也做同样的事情,而CPU-1把count的值改为2,并且同步到CPU-1的高速缓存; 但是这个修改后的值并没有写入到主内存中 CPU-0访问该字节,由于缓存没有更新,所以仍然是之前的值,就会导致数据不一致问题。 引发这个问题的原因是因为多核心 CPU 情况下存在指令并行执行,而各个CPU 核心之间的数据不共享从而导致缓存一致性问题,为了解决这个问题,CPU 生产厂商提供了相应的解决方案

总线锁

当一个CPU对其缓存中的数据进行操作时,往总线发送一个Lock信号,其它处理器的请求将会被阻塞,那么该处理器可以独占共享内存、总线锁相当于把CPU和内存之间的通信锁住了, 所以这种方式导致的CPU的性能下降,所以p6系列以后的处理器,出现另外一种方式,就是缓存锁。

缓存锁

如果缓存在处理器缓存行中的内存区域在LOCK操作期间被锁定,当它执行锁操作回写内存时,处理不在总线上声明LOCK信号,而是修改内部的缓存地址,然后通过缓存一致性机制来保证操作的原子性,因为缓存一致性机制会阻止同时修改两个以上的处理器缓存的内存区域数据,当其它处理器回写已经被锁定的缓存行的数据时会导致该缓存行无效。 如果声明了CPU的锁机制,会生成一个LOCK指令,会产生两个作用: 1.Lock前缀指令会引起处理器缓存回写到内存,在P6以后的处理器中LOCK信号一般不锁总线,而是锁缓存。 2.一个处理器的缓存回写到内存会导致其它处理器的缓存无效

缓存一致性协议

处理器上有一套完整的协议,来保证Cache的一致性,比较经典的因该就是MESI协议了,它的方法是在CPU缓存中保存一个标记位,这个标记位有四种状态。 M(Modified)修改缓存,当前缓存CPU缓存已经被修改了,表示已经和内存中的数据不一致了。 I(Invalid)失效缓存,说明CPU的缓存已经不能使用了 E(Exclusive)独占缓存,当前CPU的缓存和内存中的数据保持一致,而且其它处理器没有缓存该数据 S(Shared)共享缓存,数据和内存中的数据一致,并且该数据存在多个CPU缓存中 每个 Core 的 Cache 控制器不仅知道自己的读写操作,也监听其它 Cache 的读 写操作,嗅探(snooping)"协议 CPU 的读取会遵循几个原则 1. 如果缓存的状态是 I,那么就从内存中读取,否则直接从缓存读取 2. 如果缓存处于 M 或者 E 的 CPU 嗅探到其他 CPU 有读的操作,就把自己的缓存写入到内存,并把自己的状态设置为 S 3. 只有缓存状态是 M 或 E 的时候,CPU 才可以修改缓存中的数据,修改后,缓存状态变为 MC

并发编程的问题

比如【缓存一致性】就导致【可见性问题】,【指令重排序】会导致【有序性问题】。为了解决这些问题,所以在JVM中引入了JMM的概念

内存模型

内存模型定义了共享内存系统中多线程程序读写操作行为的规范,来屏蔽各种 硬件和操作系统的内存访问差异,来实现 Java 程序在各个平台下都能达到一致的内存访问效果。Java 内存模型的主要目标是定义程序中各个变量的访问规则,也就是在虚拟机中将变量存储到内存以及从内存中取出变量(这里的变 量,指的是共享变量,也就是实例对象、静态字段、数组对象等存储在堆内存 中的变量。而对于局部变量这类的,属于线程私有,不会被共享)这类的底层 细节。通过这些规则来规范对内存的读写操作,从而保证指令执行的正确性。 它与处理器有关、与缓存有关、与并发有关、与编译器也有关。他解决了 CPU多级缓存、处理器优化、指令重排等导致的内存访问问题,保证了并发场景下 的可见性、原子性和有序性,。内存模型解决并发问题主要采用两种方式:限 制处理器优化和使用内存屏障 Java 内存模型定义了线程和内存的交互方式,在 JMM 抽象模型中,分为主内存、工作内存。主内存是所有线程共享的,工作内存是每个线程独有的。线程 对变量的所有操作(读取、赋值)都必须在工作内存中进行,不能直接读写主 内存中的变量。并且不同的线程之间无法访问对方工作内存中的变量,线程间 的变量值的传递都需要通过主内存来完成,他们三者的交互关系如下 。

jmm
jmm
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么情况下应该使用多线程
    • 多线程使用场景
      • 多线程应用场景
        • 如何应用多线程
          • 线程的停止
            • 其它线程的复位
              • 线程的停止方法之二
                • 一级缓存
                • 二级缓存
                • 三级缓存
            相关产品与服务
            大数据
            全栈大数据产品,面向海量数据场景,帮助您 “智理无数,心中有数”!
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档