线程,锁,高并发

线程

线程(英语:thread)是操做系统可以进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运做单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中能够并发多个线程,每条线程并行执行不一样的任务。Java 虚拟机容许应用程序并发地运行多个执行线程。 php

Thread类:该类的对象表明一个线程css

主要方法:html

Start()方法: Java虚拟机调用该线程的 run 方法,使该线程开始执行(启动线程)前端

sleep()方法: 限时等待 休眠,sleep有时间java

join()方法: 当前线程进入等待状态,没有时间,要等到该线程终止。node

线程的启动方式

①继承Thread类重写run方法mysql

②实现Runnable接口,实现run方法nginx

③实现Callable接口,新建当前类对象c++

线程同步问题怎么解决

当多线程共同访问同一个对象(临界资源)的时候, 若是破坏了不可分割的操做(原子操做),就可能发生数据不一致,有可能出现多个线程前后更改数据,形成所获得的数据是脏数据web

解决方法:用锁。

在Java中一般实现锁有两种方式,一种是synchronized关键字,另外一种是Lock。

①使用 synchronized。必需要获取当前对象的互斥锁标记,若是得不到就被阻塞,直到获得互斥锁标记。线程执行完同步方法,会自动归还互斥锁标记

②使用Lock。 Lock接口的经常使用实现类 ReentrantLock /riː'entrənt/ :互斥锁

二者的区别:

①首先最大的不一样:synchronized是基于JVM层面实现的,而Lock是基于JDK层面实现的。

②synchronized是一个关键字,Lock是一个接口.

③synchronized代码块执行完成以后会自动释放锁对象,Lock必须手动调用方法释放锁对象。

④synchronized代码块出现了异常也会自动释放锁对象,Lock接口中出现异常也须要手动释放锁对象。

⑤在并发量比较小的状况下,使用synchronized;可是在并发量比较高的状况下,其性能降低会很严重,此时推荐使用ReentrantLock。

 

Lock锁
package day20;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestLock {

    public static void main(String[] args) throws Exception {
        MyList2 list = new MyList2();
        Thread t1 = new Thread(new Runnable(){
            public void run(){
                list.add("C");
            }
        });
        Thread t2 = new Thread(new Runnable(){
            public void run(){
                list.add("D");
            }
        });
        t1.start();
        t2.start();
        
        t1.join();
        t2.join();
        list.add("E");
        list.print();
    }

}
class MyList2{
    String[] data = {"A","B","","","",""};
    int index = 2;
    Lock lock = new ReentrantLock();//Lock接口,ReentrantLock为实现类
    
    public void add(String s){
        try{
            lock.lock();//加锁
            //lock.tryLock();尝试加锁,失败时返回false,此时可进行其余操做,但有可能形成活锁。
            data[index] = s ; 
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            index++;
        }
        finally{
            lock.unlock();//释放锁,为了不锁内的代码块出现异常后直接返回而没有释放锁的问题,将此句代码放到Finally中
        }
    }
    public void print(){
        for(int i = 0 ; i < data.length ; i++){
            System.out.println(data[i]);
        }
    }
}
死锁问题

线程通讯来避免死锁的问题:

o.wait():线程会释放锁标记,进入等待状态

o.notify()/o.notifyAll():从等待状态中释放一个/所有线程

以上三个方法必须出如今对o加锁的同步代码块中,

生产者消费者:

1 永远在synchronized的方法或对象里使用wait、notify和notifyAll,否则Java虚拟机会生成 IllegalMonitorStateException。

2 永远在while循环里而不是if语句下使用wait。这样,循环会在线程睡眠先后都检查wait的条件,并在条件实际上并未改变的状况下处理唤醒通知。

3 永远在多线程间共享的对象(在生产者消费者模型里即缓冲区队列)上使用wait。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TestProducerConsumer {
    public static void main(String[] args) {
        MyStack stack = new MyStack();

        Runnable task1 = new Runnable(){
            public void run(){
                for(char c = 'A' ; c<='Z' ; c++){
                    stack.push(c+"");
                }
            }
        };
        Runnable task2 = new Runnable(){
            public void run(){
                for(int i = 1 ; i <= 26; i++){
                    stack.pop();
                }
            }
        };
        new Thread(task1).start();
        new Thread(task1).start();
        new Thread(task2).start();
        new Thread(task2).start();
    }
}

class MyStack{
    String[] data = {"","","","","",""};
    int index;
    
    Lock lock = new ReentrantLock();
    Condition full = lock.newCondition();//得到Condition实例
    Condition empty = lock.newCondition();
    
    public void push(String s){
        try {
            lock.lock();
            while (data.length == index) {
                try {
                    full.await();//(不符合条件的等待)满了即等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.print(s + " pushed   ");
            data[index] = s;
            index++;
            print();
            empty.signalAll();//通知消费者
        } 
        finally{
            lock.unlock();
        }
    }
    public void pop(){
        try {
            lock.lock();
            while (index == 0) {
                try {
                    empty.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            index--;
            String o = data[index];
            data[index] = "";
            System.out.print(o + " poped    ");
            print();
            full.signalAll(); //通知生产者
        } 
        finally{
            lock.unlock();
        }
    }
    public void print(){
        for(int i = 0 ; i < data.length ; i++){
            System.out.print(data[i]+" ");
        }
        System.out.println();
    }
}

public interface Condition (接口)

条件(Conditio也称为条件队列条件变量 )为一个线程的暂停执行(“等待”)提供了一种方法,直到另外一个线程通知某些状态如今可能为真。

Condition取代了对象监视器方法的使用。能够使用两个Condition实例来实现

一个Condition实例本质上绑定到一个锁。 要得到特定Condition实例的Condition实例,使用其newCondition()方法。

void await() 致使当前线程等到发信号或 interrupted
void signal() 唤醒一个等待线程。
void signalAll() 唤醒全部等待线程。
  • 例如,假设咱们有一个有限的缓冲区,它支持puttake方法。 若是在一个空的缓冲区尝试一个take ,则线程将阻塞直到一个项目可用; 若是put试图在一个完整的缓冲区,那么线程将阻塞,直到空间变得可用。 咱们但愿在单独的等待集中等待put线程和take线程,以便咱们能够在缓冲区中的项目或空间可用的时候使用仅通知单个线程的优化。 这能够使用两个Condition实例来实现。

  •   class BoundedBuffer {
       final Lock lock = new ReentrantLock();
       final Condition notFull  = lock.newCondition(); 
       final Condition notEmpty = lock.newCondition(); 
    
       final Object[] items = new Object[100];
       int putptr, takeptr, count;
    
       public void put(Object x) throws InterruptedException {
         lock.lock(); try {
           while (count == items.length)
             notFull.await();//满了,put等待
           items[putptr] = x;
           if (++putptr == items.length) putptr = 0;
           ++count;
           notEmpty.signal();//唤醒take
         } finally { lock.unlock(); }
       }
    
       public Object take() throws InterruptedException {
         lock.lock(); try {
           while (count == 0)
             notEmpty.await();//空了,take等待
           Object x = items[takeptr];
           if (++takeptr == items.length) takeptr = 0;
           --count;
           notFull.signal();//唤醒put
           return x;
         } finally { lock.unlock(); }
       }
     } 
    • ArrayBlockingQueue类提供此功能,所以没有理由实现此示例使用类。)

    数字和字母交替打印
  • public class TestNumberCharPrint {
    
        public static void main(String[] args) throws InterruptedException {
            final Object o = new Object();//全局对象,用于分别不一样时间拿到锁标记来交替
            
            Runnable task1 = new Runnable(){
                public void run(){
                    synchronized (o) {//加锁保证此处原子操做
                        for (int i = 1; i <= 52; i++) {
                            System.out.println(i);
                            if (i % 2 ==0){
                                o.notifyAll();//释放字母线程
                                try {
                                    if(i!=52) o.wait();//若等于52时进入等待,则此线程已完成所有任务单还没结束
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    }
                }
            };    
            Runnable task2 = new Runnable(){
                public void run(){
                    synchronized (o) {
                        for (char c = 'A'; c <= 'Z'; c++) {
                            System.out.println(c);
                            o.notifyAll();//释放数字线程
                            try {
                                if (c!='Z') o.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            };
            
            Thread t1 = new Thread(task1);
            Thread t2 = new Thread(task2);
            t1.start();
            Thread.sleep(1);
            t2.start();
        }
    
    }

    1. ReentrantLock 是什么

    Lock接口的一个实现类

    1. ReadWriteLock (读写锁)是什么

    ReadWriteLock维护了一对相关的锁,一个用于只读操做,另外一个用于写入操做。

    读锁和写锁不能被同时加载,写锁加载则不能读,读锁加载则不能写。

    若写锁未被加载,读取锁能够多个读线程同时保持,

    若读锁未被加载,写入锁也是独占的,不能同时写。

  • package day20;
    
    import java.util.ArrayList;
    import java.util.concurrent.CopyOnWriteArrayList;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class TestMyList {
    
        public static void main(String[] args) {
            CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
            list.add("A");
        }
    
    }
    //改造ArrayList为线程安全的(部分方法,经过加读或写锁来实现)
    class MyList extends ArrayList{
        ReadWriteLock rwl = new ReentrantReadWriteLock();//读写锁
        Lock rl = rwl.readLock();//读锁
        Lock wl = rwl.writeLock();//写锁
        
        @Override
        public int size() {
            try{
                rl.lock();
                return super.size();
            }
            finally{
                rl.unlock();
            }
        }
    
        @Override
        public Object get(int index) {
            try{
                rl.lock();
                return super.get(index);
            }
            finally{
                rl.unlock();
            }
        }
    
        @Override
        public boolean add(Object e) {
            try{
                wl.lock();
                return super.add(e);
            }
            finally{
                wl.unlock();
            }
        }
    
        @Override
        public Object remove(int index) {
            try{
                wl.lock();
                return super.remove(index);
            }
            finally{
                wl.unlock();
            }
        }
    
        @Override
        public void clear() {
            try{
                wl.lock();
                super.clear();
            }
            finally{
                wl.unlock();
            }
        }
    }
    线程安全的集合:
  • 如下集合的效率都比直接加锁的效率高

    CopyOnWriteArrayList 利用复制数组的方式实现数组元素的修改, 写效率低 读效率高(读操做远多于写操做) 整体效率提升

    CopyOnWriteArraySet 线程安全的Set集合

    ConcurrentHashMap 分段锁,将HashMap的数组链表分为16段,多个线程读取和写入同一段时,需依次进行(需等待),读取或写入不一样段时互不影响,因为HashCode相等的几率不大,因此效率远高于HashTable。

    ConcurrentLinkedQueue 线程安全的队列(链表实现的) 利用一个无锁算法(CAS,和预期值比较,不一样则重试)实现线程安全——效率高

    Queue:队列 FIFO

    经常使用方法:

    add() :添加元素

    offer():添加元素 优先使用

    remove():删除元素

    poll ():删除元素,优先使用

    element():获取队列的头元素

    peek():获取队列的头元素 优先使用

    实现类:LinkedList ConcurrentLinkedQueue

    BlockQueue 阻塞队列 (是个接口)

    put () 添加元素到队列中 若是队列满,则等待

    take()删除队列头元素 , 若是队列空,则等待

    实现类:ArrayBlockingQueue 数组实现 有界队列 put方法可能会等待

    LinkedBlockingQueue 链表实现 无界队列 put方法不等待

  • package day21;
    
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class TestBlockingQueue {
    
        public static void main(String[] args) {
            BlockingQueue<String> queue = new ArrayBlockingQueue<String>(6);//队列
            Runnable task1 = new Runnable(){
                public void run(){
                    for(int i = 1 ; i<= 100; i++){
                        try {
                            queue.put("A"+i);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
            Runnable task2 = new Runnable(){
                public void run(){
                    for(int i = 1 ; i<= 100; i++){
                        try {
                            queue.take();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };        
            ExecutorService es = Executors.newFixedThreadPool(2);
            es.submit(task1);
            es.submit(task2);
            es.shutdown();
            
        }
    
    }
    集合的整理

    Collection |- List (ArrayList LinkedList Vector CopyOnWriteArrayList) |- Set (HashSet LinkedHashSet CopyOnWriteArraySet) |- SortedSet (TreeSet) |- Queue(LinkedList ConcurrentLinkedQueue) |- BlockingQueue (ArrayBlockingQueue LinkedBlockingQueue)

    Map (HashMap LinkedHashMap Hashtable Properties ConcurrentHashMap ) |- SortedMap (TreeMap)

     

    i++的不安全问题与AtomicInteger

    i++是先把数i读到另一个寄存器,加1运算后再写回到原寄存器,中间过程被另一个线程打断时就不是原子操做了,会形成结果不一致

  • package day21;
    
    import java.util.concurrent.atomic.AtomicInteger;
    public class TestAtomicInteger {
        static int i = 0 ; //error!
        static AtomicInteger a = new AtomicInteger(0);//利用AtomicInteger解决,还有AtomicBoolean等,利用了不加锁的比较算法(不是预期值时,撤回,从新加)
        static Integer b = Integer.valueOf(0);//error!无临界资源,由于Integer+1后成为了另一个对象Integer,能够定义其余类型的对象来加锁,以下列的obj
        static MyObject obj = new MyObject();
        public static void main(String[] args) throws Exception{
            Thread[] ts = new Thread[10];
            for(int k = 0 ; k<ts.length ; k++){
                ts[k] = new Thread(new Runnable(){
                    public void run(){
                        for(int k = 1 ; k <= 10000; k++){
                            i++;
                            a.incrementAndGet();
                            synchronized(b){
                                b = Integer.valueOf(b.intValue()+1);
                            }
                            synchronized(obj){
                                obj.x++;
                            }
                        }
                    }
                });
                ts[k].start();
            }
            for(int k=0; k <ts.length ; k++){
                ts[k].join();
            }
            System.out.println(i);
            System.out.println(a);
            System.out.println(b);
            System.out.println(obj.x);
        }
    }
    class MyObject{
        public int x=0;
    }

    Fork/Join框架

     

    Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每一个小任务结果后获得大任务结果的框架。

    Fork就是把一个大任务切分为若干子任务并行的执行,Join就是合并这些子任务的执行结果,最后获得这个大任务的结果。Fork/Join的运行流程图以下:

  • ForkJoinPool工做窃取算法

    工做窃取(work-stealing)算法是指某个线程从其余队列里窃取任务来执行。工做窃取的运行流程图以下:

  • 把任务分割为若干互不依赖的子任务,为了减小线程间的竞争,把这些子任务分别放到不一样的队列里,并为每一个队列建立一个单独的线程来执行队列里的任务,线程和队列一一对应,干完活的线程与其等着,不如去帮其余线程干活,因而它就去其余线程的队列里窃取一个任务来执行。一般会使用双端队列,被窃取任务线程永远从双端队列的头部拿任务执行,而窃取任务的线程永远从双端队列的尾部拿任务执行。

    优势是充分利用线程进行并行计算,并减小了线程间的竞争,其缺点是在某些状况下仍是存在竞争,好比双端队列里只有一个任务时。而且消耗了更多的系统资源,好比建立多个线程和多个双端队列。

    ForkJoinPool是ExecutorService的实现类,所以是一种特殊的线程池。ForkJoinPool提供了以下两个经常使用的构造器。

    public ForkJoinPool(int parallelism):建立一个包含parallelism个并行线程的ForkJoinPool public ForkJoinPool() :以Runtime.getRuntime().availableProcessors()的返回值做为parallelism来建立ForkJoinPool 建立ForkJoinPool实例后,能够调用ForkJoinPool的

    submit(ForkJoinTask<T> task)或者invoke(ForkJoinTask<T> task)来执行指定任务。其中ForkJoinTask表明一个能够并行、合并的任务。ForkJoinTask是一个抽象类,它有两个抽象子类:RecursiveAction和RecursiveTask。

    RecursiveTask表明有返回值的任务————join()方法来启动RecursiveAction表明没有返回值的任务。————fork()方法来启动

    Recursive 递归的,循环的 fork叉,搬走

    RecursiveTask的例子:

  • import java.util.concurrent.ForkJoinPool;
    import java.util.concurrent.RecursiveTask;
    
    public class TestForkJoinAdd {
        public static void main(String[] args) {
        System.out.println(Runtime.getRuntime().availableProcessors());
            
            ForkJoinPool pool = new ForkJoinPool();//线程池
            AddTask main = new AddTask(1 , 100000);//任务
            Long result = pool.invoke(main);
            //<T> T invoke(ForkJoinTask<T> task) 执行给定的任务,在完成后返回其结果。 
            System.out.println(result);
        }
    }
    class AddTask extends RecursiveTask<Long>{
        int start;
        int end;
        static final int THRESHOLD = 1000;
        public AddTask(int start, int end) {
            super();
            this.start = start;
            this.end = end;
        }
        @Override
        public Long compute() {
            //若是start和end之间差距低于THRESHOLD 直接计算,THRESHOLD是定下的是否分割任务的临界值
            if (end - start <= THRESHOLD){
                long result = 0 ; 
                for(int i = start ; i<= end ; i++){
                    result += i;
                }
                return result;
            }
            //不然 就要把任务分割为两个子任务
            else{
                int middle = (start+end)/2;
                AddTask task1 = new AddTask(start , middle);
                AddTask task2 = new AddTask(middle+1 , end);
                invokeAll(task1 , task2);
                long r1 = task1.join();
                long r2 = task2.join();
                return r1+r2;
            }
        }    
    }

    Semaphore

    Semaphore 是 synchronized 的增强版,做用是控制线程的并发数量(控制线程的数量)。

    方法 acquire( int permits ) 参数做用,及动态添加 permits 许可数量  

      acquire( int permits ) 中的参数是什么意思呢? new Semaphore(6) 表示初始化了 6个通路, semaphore.acquire(2) 表示每次线程进入将会占用2个通路,semaphore.release(2) 运行时表示归还2个通路。没有通路,则线程就没法进入代码块。

    void acquire() 从该信号量获取许可证,阻止直到可用,或线程为 interrupted
    void acquire(int permits) 从该信号量获取给定数量的许可证,阻止直到全部可用,不然线程为 interrupted

     

    void release() 释放许可证,将其返回到信号量。
    void release(int permits) 释放给定数量的许可证,将其返回到信号量。
  • package day21;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.atomic.AtomicBoolean;
    
    public class TestSemaphore {
    
        public static void main(String[] args) {
            List<PhoneRoom> rooms = new ArrayList<>();
            rooms.add(new PhoneRoom("Room 1"));
            rooms.add(new PhoneRoom("Room 2"));
            rooms.add(new PhoneRoom("Room 3"));
            rooms.add(new PhoneRoom("Room 4"));
            rooms.add(new PhoneRoom("Room 5"));
            
            Semaphore s = new Semaphore(5);
            
            class Task implements Runnable{
                public void run(){
                    try {
                        s.acquire();
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                    for(int i = 0 ; i < rooms.size() ; i++){
                        PhoneRoom room = rooms.get(i);
                        if (room.isFree()){
                            room.setFree(false);
                            System.out.println(Thread.currentThread().getName()+" entered "+room.getName());
                            try {
                                Thread.sleep(2000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName()+" exited "+room.getName());
                            room.setFree(true);
                            
                            s.release();
                            return;
                        }
                    }
                }
            }
            
            for(int i = 1 ; i <= 10 ; i++){
                Runnable task = new Task();
                Thread t = new Thread(task);
                t.start();
            }
        }
    
    }
    
    class PhoneRoom{
        AtomicBoolean isFree = new AtomicBoolean(true);//原子操做的boolean,只容许一个线程拿到
        String name;
        
        
        public PhoneRoom(String name) {
            super();
            this.name = name;
        }
    
        public boolean isFree() {
            return isFree.get();
        }
    
        public void setFree(boolean flag) {
            this.isFree.set(flag);
        }
        public String getName(){
            return name;
        }
    }

    Atomic

    Package java.util.concurrent.atomic

    一个小型工具包,支持单个变量上的无锁线程安全编程。

    经常使用:

    • AtomicBoolean 一个 boolean值能够用原子更新。
      AtomicInteger 可能原子更新的 int值。

      CountDownLatch 是什么



      • java.lang.Object

        • java.util.concurrent.CountDownLatch

          容许一个或多个线程等待直到在其余线程中执行的一组操做完成的同步辅助。

          A CountDownLatch用给定的计数初始化。 await方法阻塞,直到因为countDown()方法的调用而致使当前计数达到零,以后全部等待线程被释放,而且任何后续的await 调用当即返回。 这是一个一次性的现象 - 计数没法重置。 若是您须要重置计数的版本,请考虑使用CyclicBarrier

      CountDownLatch是一个线程计数器,在建立线程的时候能够设置任务数量 每执行完一个线程 调用方法让计数器减一 若是计数器减为了0 主线程再向下执行。

      CountDownLatch主要有两个方法:countDown()和await()。countDown()方法用于使计数器减一,通常是执行任务的线程调用,await()方法则使调用该方法的线程处于等待状态,通常是主线程调用。

      package day21;
      
      import java.util.concurrent.CountDownLatch;
      import java.util.concurrent.CyclicBarrier;
      
      public class TestCountDownLatch {
      
          public static void main(String[] args) {
              CountDownLatch cdl = new CountDownLatch(2);
              CyclicBarrier cb = new CyclicBarrier(3); 
              
              Thread t1 = new Thread(){
                  public void run(){
                      for(int i = 1 ; i <= 100 ; i++){
                          System.out.println("### "+i);
                          if (i == 50) cdl.countDown();
                          try {
                              Thread.sleep(100);
                              if (i == 99) cb.await();
                              
                          } catch (Exception e) {
                              e.printStackTrace();
                          }
                      }
                  }
              };
              Thread t2 = new Thread(){
                  public void run(){
                      for(int i = 1 ; i <= 100 ; i++){
                          System.out.println("$$$ "+i);
                          if (i == 50) cdl.countDown();
                          try {
                              Thread.sleep(100);
                              if (i==99) cb.await();
                          } catch (Exception e) {
                              e.printStackTrace();
                          }
                      }            
                  }
              };
              Thread t3 = new Thread(){
                  public void run(){
                      try {
                          cdl.await();
                      } catch (InterruptedException e1) {
                          e1.printStackTrace();
                      }
                      for(int i = 1 ; i <= 100 ; i++){
                          System.out.println("*** "+i);
                          try {
                              Thread.sleep(100);
                              if (i==99) cb.await();
                          } catch (Exception e) {
                              e.printStackTrace();
                          }
                      }                
                  }
              };
              t1.start();
              t2.start();
              t3.start();
          }
      }

      ThreadLocal 是什么?怎么用?

      ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。

      当使用ThreadLocal维护变量时,ThreadLocal为每一个使用该变量的线程提供独立的变量副本,因此每个线程均可以独立地改变本身的副本,而不会影响其它线程所对应的副本。

        从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。

      它只有4个方法 :

      T get() 返回当前线程的此线程局部变量的副本中的值。
      protected T initialValue() 返回此线程局部变量的当前线程的“初始值”。
      void remove() 删除此线程局部变量的当前线程的值。
      void set(T value) 将当前线程的此线程局部变量的副本设置为指定的值。
      static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) Creates a thread local variable.

      set(T value)和T get()分别为设置和得到当前线程的线程局部变量的值。

      remove()是将当前线程局部变量的值删除,目的是为了减小内存的占。须要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,因此显式调用该方法清除线程的局部变量并非必须的操做,但它能够加快内存回收的速度。

      T initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,而且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

      ThreadLocal是如何作到为每个线程维护变量的副本的呢?

      其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。 咱们本身就能够提供一个简单的实现版本:

    • package com.test;  
        
      public class TestNum {  
          // ①经过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值  
          private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>() {  
              public Integer initialValue() {  
                  return 0;  
              }  
          };  
        
          // ②获取下一个序列值  
          public int getNextNum() {  
              seqNum.set(seqNum.get() + 1);  
              return seqNum.get();  
          }  
        
          public static void main(String[] args) {  
              TestNum sn = new TestNum();  
              // ③ 3个线程共享sn,各自产生序列号  
              TestClient t1 = new TestClient(sn);  
              TestClient t2 = new TestClient(sn);  
              TestClient t3 = new TestClient(sn);  
              t1.start();  
              t2.start();  
              t3.start();  
          }  
        
          private static class TestClient extends Thread {  
              private TestNum sn;  
        
              public TestClient(TestNum sn) {  
                  this.sn = sn;  
              }  
        
              public void run() {  
                  for (int i = 0; i < 3; i++) {  
                      // ④每一个线程打出3个序列值  
                      System.out.println("thread[" + Thread.currentThread().getName() + "] --> sn["  
                               + sn.getNextNum() + "]");  
                  }  
              }  
          }  
      }  
      Thread与同步机制的比较

        ThreadLocal和线程同步机制相比有什么优点呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

        在同步机制中,经过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析何时对变量进行读写,何时须要锁定某个对象,何时释放对象锁等繁杂的问题,程序设计和编写难度相对较大。

        而ThreadLocal则从另外一个角度来解决多线程的并发访问。ThreadLocal会为每个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。由于每个线程都拥有本身的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,能够把不安全的变量封装进ThreadLocal。

        因为ThreadLocal中能够持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,须要强制类型转换。但JDK 5.0经过泛型很好的解决了这个问题,在必定程度地简化ThreadLocal的使用,

        归纳起来讲,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不一样的线程排队访问,然后者为每个线程都提供了一份变量,所以能够同时访问而互不影响。

        Spring使用ThreadLocal解决线程安全问题咱们知道在通常状况下,只有无状态的Bean才能够在多线程环境下共享,在Spring中,绝大部分Bean均可以声明为singleton做用域。就是由于Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,由于有状态的Bean就能够在多线程中共享了。

        通常的Web应用划分为展示层、服务层和持久层三个层次,在不一样的层中编写对应的逻辑,下层经过接口向上层开放功能调用。在通常状况下,从接收请求到返回响应所通过的全部程序调用都同属于一个线程,如图所示:

        同一线程贯通三层这样你就能够根据须要,将一些非线程安全的变量以ThreadLocal存放,在同一次请求响应的调用线程中,全部关联的对象引用到的都是同一个变量。

      ThreadLocal<T>的具体实现

    • /** 
          * Sets the current thread's copy of this thread-local variable 
          * to the specified value.  Most subclasses will have no need to 
          * override this method, relying solely on the {@link #initialValue} 
          * method to set the values of thread-locals. 
          * 
          * @param value the value to be stored in the current thread's copy of 
          *        this thread-local. 
          */  
         public void set(T value) {  
             Thread t = Thread.currentThread();  
             ThreadLocalMap map = getMap(t);  
             if (map != null)  
                 map.set(this, value);  
             else  
                 createMap(t, value);  
         }  

      首先经过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,而后将变量的值设置到这个ThreadLocalMap对象中,固然若是获取到的ThreadLocalMap对象为空,就经过createMap方法建立。 线程隔离的秘密,就在于ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每一个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类经过操做每个线程特有的ThreadLocalMap副本,从而实现了变量访问在不一样线程中的隔离。由于每一个线程的变量都是本身特有的,彻底不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。

       

      ThreadLocal类可让你建立的变量只被同一个线程进行读和写操做,线程与线程之间独立,单一线程内共享数据,是线程安全的,

      ①做用:单一线程内共享数据。

      ​ ThreadLocal能够操做线程内部的Map,能够存取值,而在web环境下一个业务操做过程当中的类与方法的调用都是处于一个线程内部的,那么就能够使用ThreadLocal

      ​ 将一个对象存入当前Thread,而后处于当前线程下的任意类和任意方法中得到该对象。

      ​ 2.TreadLocal原理:

       

      ​ ThreadLocal是如何操做Thread当中的Map对象的呢?

      ​ ThreadLocal#set(Value):

      ​ 把当前的ThreadLocal做为key,将Value做为值 存储到当前线程的Map属性中了。

      ​ ThreadLocal#get() :

      ​ 将ThreadLocal自己做为key 去查询Thread的Map对象。

      ​ ThreadLcoal#remove:

      ​ 将ThreadLocal自己做为key 重Thread 的Map属性删除

       

      并发编程

      1.并发编程三要素

      · 原子性原子,即一个不可再被分割的颗粒。在Java中原子性指的是一个或多个操做要么所有执行成功要么所有执行失败。

      · 有序性程序执行的顺序按照代码的前后顺序执行。(处理器可能会对指令进行重排序)

      · 可见性当多个线程访问同一个变量时,若是其中一个线程对其做了修改,其余线程能当即获取到最新的值。

      2. 线程的五大状态

      建立,就绪,运行,阻塞,死亡

      New、start()、run()、sleep或join、run执行完,或者遇到异常

       

      Sleep、wait和join方法的区别?

      sleep是Thread类的方法

      wait是object类的方法,进入这个状态后,是不能自动唤醒的,必须依靠其余线程调用notify()或notifyAll()方法才能被唤醒

      主线程建立并启动子线程,若是子线程中须要进行大量的耗时计算,主线程每每早于子线程结束。这时,若是主线程想等待子线程执行结束以后再结束,好比子线程处理一个数据,主线程要取得这个数据,就要调用join() 方法(谁须要谁等待)。

        sleep(long)方法在睡眠时不释放对象锁,

      join(long)方法在等待的过程当中释放对象锁

      wait和sleep的区别

      wait sleep
      wait()方法是Object类里的方法 sleep()是Thread类的static(静态)的方法
      wait()睡眠时,释放对象锁 sleep()睡眠时,保持对象锁,仍然占有该锁
      经常使用于线程间通讯 经常使用于暂停执行
      wait和notify/notifyAll是成对出现的, 必须在synchronize块中被调用  
      阻塞状态(Blocked)

      阻塞状态其实是一种比较特殊的等待状态,

      处于其余等待状态的线程是在等着别的线程执行结束,等着拿CPU的使用权;而处于阻塞状态的线程等待的不只仅是CPU的使用权,主要是锁标记,没有拿到锁标记,即使是CPU有空也没有办法执行。(关于锁见下节:线程同步)

      等待和阻塞的区别

      等待 阻塞
      已经拿到锁对象,或者说不存在拿不到执行不了的状况 等待拿到锁对象
      等待被唤醒 等待拿到锁对象
      终止线程(Terminated)

      已经终止的线程会处于该种状态。

       

      3.悲观锁与乐观锁

      · 悲观锁:每次操做都会加锁,会形成线程阻塞。

      · 乐观锁:每次操做不加锁,而是假设没有冲突而去完成某项操做,若是由于冲突失败就重试,直到成功为止,不会形成线程阻塞。

      多线程多任务并行执行使用场景

      在一个方法中执行多个操做(3个以上)

      单个任务执行时间都很长

      多线程开发

      多任务并行执行的代码思路

      进入service方法以后

      建立三个线程

      三个线程分别执行三个查询

      须要获得查询结果封装一个map中统一返回

      多线程相关的知识

       

      1. 建立线程的方式

      ①Thread

      ②Runable

      ③Callable

      3.主线程等待

      Future + Callable

      CountDownLatch 类

      能够认为是一个线程计数器 在建立线程的时候能够设置任务数量 每次执行完一个线程 调用方法让计数器减一 若是计数器减为了0 主线程向下执行

       

      线程池

      java.util.concurrent

      Interface Executor

       

      提供了一种将任务提交从每一个任务的运行机制分解的方式,包括线程使用,调度等的Executor 。一般使用Executor而不是显式建立线程。

      例如,不是为一组任务调用new Thread(new(RunnableTask())).start() ,您能够使用:

        Executor executor = anExecutor;
      executor.execute(new RunnableTask1());
      executor.execute(new RunnableTask2());

      方法:void execute(Runnable command)

      在未来的某个时间执行给定的命令。 该命令能够在一个新线程,一个合并的线程中或在调用线程中执行,由Executor实现。 command - 可运行的任务

      concurrent ,该包中提供的Executor实现了ExecutorService ,这是一个更普遍的界面。 ThreadPoolExecutor类提供了一个可扩展的线程池实现。 Executors类为这些执行人员提供了方便的工厂方法。

      ThreadPoolExecutor是线程池的核心类,此类的构造方法以下:

    • public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                  BlockingQueue<Runnable> workQueue);
       
          public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                  BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
       
          public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
                  BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
       
          public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,
              BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);

      构造方法的参数及意义:

      corePoolSize:核心线程池的大小,若是核心线程池有空闲位置,这时新的任务就会被核心线程池新建一个线程执行,执行完毕后不会销毁线程,线程会进入缓存队列等待再次被运行。

      maximunPoolSize:线程池能建立最大的线程数量。若是核心线程池和缓存队列都已经满了,新的任务进来就会建立新的线程来执行。可是数量不能超过maximunPoolSize,否侧会采起拒绝接受任务策略,咱们下面会具体分析。

      keepAliveTime:非核心线程可以空闲的最长时间,超过期间,线程终止。这个参数默认只有在线程数量超过核心线程池大小时才会起做用。只要线程数量不超过核心线程大小,就不会起做用。

      unit:时间单位,和keepAliveTime配合使用。

      workQueue:缓存队列,用来存放等待被执行的任务。

      threadFactory:线程工厂,用来建立线程,通常有三种选择策略。

      ArrayBlockingQueue;
      LinkedBlockingQueue;
      SynchronousQueue;

      handler:拒绝处理策略,线程数量大于最大线程数就会采用拒绝处理策略,四种策略为

      ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
      ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,可是不抛出异常。
      ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,而后从新尝试执行任务(重复此过程)
      ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

      二.线程池实现原理

      线程池图:

       

      1.线程池状态

      线程池和线程同样拥有本身的状态,

      在ThreadPoolExecutor类中定义了一个volatile变量runState来表示线程池的状态,线程池有四种状态,分别为RUNNING、SHUTDOWN、STOP、TERMINATED。

      线程池建立后处于RUNNING状态。

      调用shutdown后处于SHUTDOWN状态,线程池不能接受新的任务,会等待缓冲队列的任务完成。

      调用shutdownNow后处于STOP状态,线程池不能接受新的任务,并尝试终止正在执行的任务。

      当线程池处于SHUTDOWN或STOP状态,而且全部工做线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。

      总结:

      • 若是当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会建立一个线程去执行这个任务;

      • 若是当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(通常来讲是任务缓存队列已满),则会尝试建立新的线程去执行这个任务;

      • 若是当前线程池中的线程数目达到maximumPoolSize,则会采起任务拒绝策略进行处理;

      • 若是线程池中的线程数量大于 corePoolSize时,若是某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;若是容许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

      Executors是工具类,里面全是静态方法,直接经过类名来调用。

      不定长的线程池——newCachedThreadPool()线程数有任务数决定,不够时新建线程,结束后全部线程不销毁

      定长的线程池——newFixedThreadPool(2)建立的线程池线程数量固定

    • package day20;
      
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      
      public class TestExecutor {
      
          public static void main(String[] args) {
              //ExecutorService es = Executors.newFixedThreadPool(2);
              //Executors是工具类,里面全是静态方法,直接经过类名来调用newFixedThreadPool(2)建立的线程池是固定的线程数,结束后全部线程不销毁
              ExecutorService es = Executors.newCachedThreadPool();
          //newCachedThreadPool()建立的是不定长的线程池,线程数有任务数决定,不够时新建线程,结束后全部线程不销毁
              Runnable r1 = new Runnable(){
                  public void run(){
                      for(int i = 1 ; i <= 100 ; i++){
                          System.out.println("### "+i);
                          try {
                              Thread.sleep(100);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              };
              Runnable r2 = new Runnable(){
                  public void run(){
                      for(int i = 1 ; i <= 100 ; i++){
                          System.out.println("$$$ "+i);
                          try {
                              Thread.sleep(100);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }                
                  }
              };
              Runnable r3 = new Runnable(){
                  public void run(){
                      for(int i = 1 ; i <= 100 ; i++){
                          System.out.println("*** "+i);
                          try {
                              Thread.sleep(100);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }    
                  }
              };
              
              es.submit(r1);//线程池的提交方法,将线程对象提交来执行
              es.submit(r2);
              es.submit(r3);
              
              es.shutdown();//关闭线程池
          }
      
      }

      因为Runable接口没有返回值,且不能抛异常,引入Interface Callable<V>接口

      此接口有个V call()方法,返回值V是泛型,返回值用Future对象来接收。Future中有个方法有get()方法能够拿到返回值。

      Callable有返回值,可抛异常,在多线程并发的时候,异步通讯,利用Future来接收返回值

    • package day20;
      
      import java.util.concurrent.Callable;
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      import java.util.concurrent.Future;
      
      public class TestCallable {
          public static void main(String[] args) throws Exception{
              ExecutorService es = Executors.newCachedThreadPool();
              
              Callable<Integer> task1 = new Callable<Integer>(){
                  //Callable接口
                  public Integer call() throws Exception{
                      //方法call至关于run方法,只不过有返回值
                      System.out.println("task1 start working");
                      int result = 0 ; 
                      for(int i = 1 ; i < 100 ; i+=2){
                          result += i;
                          Thread.sleep(100);
                      }
                      System.out.println("task1 end working");
                      return result;
                  }
              };
              Callable<Integer> task2 = new Callable<Integer>(){
                  public Integer call() throws Exception{
                      System.out.println("task2 start working");
                      int result = 0 ; 
                      for(int i = 2 ; i <= 100 ; i+=2){
                          result += i;
                          Thread.sleep(100);
                      }
                      System.out.println("task2 end working");
                      return result;
                  }
              };        
              Future<Integer> f1 = es.submit(task1);//返回值对象用Future来接收
              Future<Integer> f2 = es.submit(task2);
              System.out.println("main do sth");
              int result = f1.get()+f2.get();
              System.out.println(result);
              es.shutdown();
          }
      }

      第一,什么是线程,线程和进程的区别是什么

      第二,线程中的基本概念,线程的生命周期

      第三,单线程和多线程

      第四,线程池的原理解析

      第五,常见的几种线程池的特色以及各自的应用场景

      1、

      线程,程序执行流的最小执行单位,是行程中的实际运做单位,

      线程和进程究竟有什么区别呢?

      首先,进程是一个动态的过程,是一个活动的实体。简单来讲,一个应用程序的运行就能够被看作是一个进程,

      而线程,是运行中的实际的任务执行者。能够说,进程中包含了多个能够同时运行的线程。

      2、

      线程的生命周期,线程的生命周期能够利用如下的图解来更好的理解:

       

      第一步,是用new Thread()的方法新建一个线程,在线程建立完成以后,线程就进入了就绪(Runnable)状态,此时建立出来的线程进入抢占CPU资源的状态,当线程抢到了CPU的执行权以后,线程就进入了运行状态(Running),当该线程的任务执行完成以后或者是很是态的调用的stop()方法以后,线程就进入了死亡状态。而咱们在图解中能够看出,线程还具备一个则色的过程,这是怎么回事呢?当面对如下几种状况的时候,容易形成线程阻塞,第一种,当线程主动调用了sleep()方法时,线程会进入则阻塞状态,除此以外,当线程中主动调用了阻塞时的IO方法时,这个方法有一个返回参数,当参数返回以前,线程也会进入阻塞状态,还有一种状况,当线程进入正在等待某个通知时,会进入阻塞状态。那么,为何会有阻塞状态出现呢?咱们都知道,CPU的资源是十分宝贵的,因此,当线程正在进行某种不肯定时长的任务时,Java就会收回CPU的执行权,从而合理应用CPU的资源。咱们根据图能够看出,线程在阻塞过程结束以后,会从新进入就绪状态,从新抢夺CPU资源。这时候,咱们可能会产生一个疑问,如何跳出阻塞过程呢?又以上几种可能形成线程阻塞的状况来看,都是存在一个时间限制的,当sleep()方法的睡眠时长过去后,线程就自动跳出了阻塞状态,第二种则是在返回了一个参数以后,在获取到了等待的通知时,就自动跳出了线程的阻塞过程

       

      3、

       

      什么是单线程和多线程?

       

      单线程,顾名思义便是只有一条线程在执行任务,这种状况在咱们平常的工做学习中不多遇到,因此咱们只是简单作一下了解

       

      多线程,建立多条线程同时执行任务,这种方式在咱们的平常生活中比较常见。可是,在多线程的使用过程当中,还有许多须要咱们了解的概念。好比,在理解上并行和并发的区别,以及在实际应用的过程当中多线程的安全问题,对此,咱们须要进行详细的了解。

       

      并行和并发:在咱们看来,都是能够同时执行多种任务,那么,到底他们两者有什么区别呢?

       

      并发,从宏观方面来讲,并发就是同时进行多种时间,实际上,这几种时间,并非同时进行的,而是交替进行的,而因为CPU的运算速度很是的快,会形成咱们的一种错觉,就是在同一时间内进行了多种事情

       

      而并发,则是真正意义上的同时进行多种事情。这种只能够在多核CPU的基础下完成。

       

      还有就是多线程的安全问题?为何会形成多线程的安全问题呢?咱们能够想象一下,若是多个线程同时执行一个任务,name意味着他们共享同一种资源,因为线程CPU的资源不必定能够被谁抢占到,这是,第一条线程先抢占到CPU资源,他刚刚进行了第一次操做,而此时第二条线程抢占到了CPU的资源,name,共享资源还来不及发生变化,就同时有两条数据使用了同一条资源,具体请参考多线程买票问题。这个问题咱们应该如何解决那?

       

      有形成问题的缘由咱们能够看出,这个问题主要的矛盾在于,CPU的使用权抢占和资源的共享发生了冲突,解决时,咱们只须要让一条线程战歌了CPU的资源时,阻止第二条线程同时抢占CPU的执行权,在代码中,咱们只须要在方法中使用同步代码块便可。在这里,同步代码块很少进行赘述,能够自行了解。

       

      四,线程池

       

      又以上介绍咱们能够看出,在一个应用程序中,咱们须要屡次使用线程,也就意味着,咱们须要屡次建立并销毁线程。而建立并销毁线程的过程势必会消耗内存。而在Java中,内存资源是及其宝贵的,因此,咱们就提出了线程池的概念。

       

      线程池:Java中开辟出了一种管理线程的概念,这个概念叫作线程池,从概念以及应用场景中,咱们能够看出,线程池的好处,就是能够方便的管理线程,也能够减小内存的消耗。

       

      那么,咱们应该如何建立一个线程池那?Java中已经提供了建立线程池的一个类:Executor

       

      而咱们建立时,通常使用它的子类:ThreadPoolExecutor.

       

      public ThreadPoolExecutor(int corePoolSize,

      ​ int maximumPoolSize,

      ​ long keepAliveTime,

      ​ TimeUnit unit,

      ​ BlockingQueue<Runnable> workQueue,

      ​ ThreadFactory threadFactory,

      ​ RejectedExecutionHandler handler)

      这是其中最重要的一个构造方法,这个方法决定了建立出来的线程池的各类属性,下面依靠一张图来更好的理解线程池和这几个参数:

       

       

       

      又图中,咱们能够看出,线程池中的corePoolSize就是线程池中的核心线程数量,这几个核心线程,只是在没有用的时候,也不会被回收,maximumPoolSize就是线程池中能够容纳的最大线程的数量,而keepAliveTime,就是线程池中除了核心线程以外的其余的最长能够保留的时间,由于在线程池中,除了核心线程即便在无任务的状况下也不能被清除,其他的都是有存活时间的,意思就是非核心线程能够保留的最长的空闲时间,而util,就是计算这个时间的一个单位,workQueue,就是等待队列,任务能够储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。threadFactory,就是建立线程的线程工厂,最后一个handler,是一种拒绝策略,咱们能够在任务满了知乎,拒绝执行某些任务。

       

      线程池的执行流程又是怎样的呢?

       

       

       

      有图咱们能够看出,任务进来时,首先执行判断,判断核心线程是否处于空闲状态,若是不是,核心线程就先就执行任务,若是核心线程已满,则判断任务队列是否有地方存放该任务,若果有,就将任务保存在任务队列中,等待执行,若是满了,再判断最大可容纳的线程数,若是没有超出这个数量,就开创非核心线程执行任务,若是超出了,就调用handler实现拒绝策略。

       

      handler的拒绝策略:

       

      有四种:第一种AbortPolicy:不执行新任务,直接抛出异常,提示线程池已满

       

      ​ 第二种DisCardPolicy:不执行新任务,也不抛出异常

       

      ​ 第三种DisCardOldSetPolicy:将消息队列中的第一个任务替换为当前新进来的任务执行

       

      ​ 第四种CallerRunsPolicy:直接调用execute来执行当前任务

       

      五,四种常见的线程池:

       

      CachedThreadPool:可缓存的线程池,没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有须要时建立线程来执行任务,没有须要时回收线程,

      适用于耗时少,任务量大的状况。

       

      SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。

      适用于执行周期性的任务。

       

      SingleThreadPool:只有一条线程来执行任务,

      适用于有顺序的任务的应用场景。

       

      FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程

       

      Q:谈谈对线程池的认识?

      A:在一个应用程序中咱们须要屡次使用线程,每一次线程的建立及销毁都会消耗内存,而内存是宝贵的资源,使用线程池能够方便的管理线程,也能够减小内存的消耗。

      java中提供了两种建立线程池的方法:

      java.util.concurrent.ThreadPoolExecutor类直接建立

       

      new ThreadPoolExecutor(2, 4, 1, TimeUnit.SECONDS, new LinkedBlockingDeque());

      1

      java.util.concurrent.Executors类直接建立

       

      ExecutorService executorService = Executors.newFixedThreadPool(1);

      ExecutorService executorService1 = Executors.newCachedThreadPool();

      ExecutorService executorService2 = Executors.newSingleThreadExecutor();

      ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(6);

       

      须要配置参数:

      一、corePoolSize 线程池线程基本数量;

      二、maximumPoolSize 线程池最大建立的最大线程数;

      三、keepAliveTime 线程最大活跃时间;

      四、TimeUnit 线程活跃的时间单位;

      五、BlockingQueue 承载任务的阻塞队列;

      阻塞队列分为四种:

      ArrayBlockingQueue 基于数组的有界阻塞队列,实行先进先出规则(FIFO);

      LinkedBlockingQueue 基于链表的阻塞队列,实行先进先出规则(FIFO),newFixedThreadPool()方法建立的线程池使用此队列,吞吐量大于ArrayBlockingQueue

      SynchronousQueue 不存储元素的阻塞队列,每次插入必须等到另外一个线程调用移除操做,不然一直阻塞,newCachedThreadPool()方法建立的线程使用此队列,吞吐量大于LinkedBlockingQueue

      PriorityQueue 具备优先级的无界阻塞队列;

      六、RejectedExecutionHandler 拒绝策略,当线程池及队列都饱和时采起的拒绝策略;

      AbortPolicy 默认的拒绝策略,直接抛出异常;

      CallerRunsPolicy 只有调用者所在线程执行任务;

      DiscardOldestPolicy 抛弃队列中最近的一个任务,执行当前任务;

      DiscardPolicy 直接抛弃,不执行;

      七、threadFactory 建立线程的工厂;

       

      Q:怎么建立自定义注解?

      A:

       

      @Documented//声明式注解

      @Inherited//声明式注解

      @Target({ElementType.METHOD,ElementType.TYPE})//定义注解的做用域

      @Retention(RetentionPolicy.RUNTIME)//定义注解保留的时间

      public @interface AnnotationTest {

      ​ public String value() default "";

      }

      线程池的种类?

      一、newSingleThreadExecutor建立一个单线程化的线程池,它只会用惟一的工做线程来执行任务,保证全部任务按照指定顺序(FIFO, LIFO, 优先级)执行。

      二、newFixedThreadPool建立一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

      三、newScheduledThreadPool建立一个定长线程池,支持定时及周期性任务执行。

      四、newCachedThreadPool建立一个可缓存线程池,若是线程池长度超过处理须要,可灵活回收空闲线程,若无可回收,则新建线程。

      线程池不容许使用Executors去建立,而是经过ThreadPoolExecutor的方式,这样的处理方式让写的同窗更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors各个方法的弊端:1)newFixedThreadPool和newSingleThreadExecutor:  主要问题是堆积的请求处理队列可能会耗费很是大的内存,甚至OOM。2)newCachedThreadPool和newScheduledThreadPool:  主要问题是线程数最大数是Integer.MAX_VALUE,可能会建立数量很是多的线程,甚至OOM。

      线程池都有哪几种工做队列

      一、ArrayBlockingQueue是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。二、LinkedBlockingQueue一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量一般要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列三、SynchronousQueue一个不存储元素的阻塞队列。每一个插入操做必须等到另外一个线程调用移除操做,不然插入操做一直处于阻塞状态,吞吐量一般要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。四、PriorityBlockingQueue一个具备优先级的无限阻塞队列

      线程池中的几种重要的参数及流程说明

      corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有很是大的关系。在建立了线程池后,默认状况下,线程池中并无任何线程,而是等待有任务到来才建立线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就能够看出,是预建立线程的意思,即在没有任务到来以前就建立corePoolSize个线程或者一个线程。默认状况下,在建立了线程池后,线程池中的线程数为0,当有任务来以后,就会建立一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

      maximumPoolSize:线程池最大线程数,这个参数也是一个很是重要的参数,它表示在线程池中最多能建立多少个线程;

      keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认状况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起做用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,若是一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。可是若是调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起做用,直到线程池中的线程数为0;

      unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

      workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,通常来讲,这里的阻塞队列有如下几种选择:

      ArrayBlockingQueue

      LinkedBlockingQueue

      SynchronousQueue

      PriorityBlockingQueue

      ArrayBlockingQueue和PriorityBlockingQueue使用较少,通常使用LinkedBlockingQueue和SynchronousQueue。线程池的排队策略与BlockingQueue有关。

      threadFactory:用于设置建立线程的工厂,能够经过线程工厂给每一个建立出来的线程作些更有意义的事情,好比设置daemon和优先级等等

      handler:表示当拒绝处理任务时的策略,有如下四种取值:

      一、AbortPolicy:直接抛出异常。二、CallerRunsPolicy:只用调用者所在线程来运行任务。三、DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。四、DiscardPolicy:不处理,丢弃掉。五、也能够根据应用场景须要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。

      线程池任务执行流程:

      当线程池小于corePoolSize时,新提交任务将建立一个新线程执行任务,即便此时线程池中存在空闲线程。

      当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行

      当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会建立新线程执行任务

      当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理

      当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程

      当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

       

       

      高并发解决方案

      1. 高并发和大流量解决方案

      高并发架构相关概念  并发:在操做系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行;在互联网时代,所讲的并发,高并发一般是指并发访问,也就是在某个时间点,有多少个访问同时到来。一般一个系统的日PV在千万以上,有多是一个高并发的系统。有的公司彻底不走技术路线,全靠机器堆,这不在讨论范围内。  QPS:每秒钟请求或者查询的数量,在互联网领域,指每秒响应请求数(指HTTP请求);并发链接数是系统同时处理的请求数量  吞吐量:单位时间内处理的请求数量(一般由QPS与并发数决定)  响应时间:从请求发出到收到响应花费的时间。例如系统处理一个HTTP请求须要100ms。  PV:综合浏览量(page view),即页面浏览量或者点击量,一个访客在24小时内访问的页面数量;同一我的浏览网站同一页面,只记做一次PV  UV:独立访客(unique visitor),即必定时间范围内相同访客屡次访问网站,只计算为一个独立访客  带宽:计算带宽大小需关注两个指标,峰值流量和页面的平均大小  日网站带宽=PV/统计时间(换算到s)平均页面大小(单位KB)8;峰值通常是平均值的倍数,根据实际状况来定  峰值每秒请求数(QPS)=(总PV数80%)/(6小时秒数20%);80%的访问量集中在20%的时间  压力测试:测试能承受的最大并发,测试最大承受的QPS值  经常使用性能测试工具:ab,wrk,http_load,web_bench,siege,apache jmeter;ab全称是apache benchmark,apache官方推出的工具,建立多个并发访问线程,模拟多个访问者同时对某一trl地址进行访问,它的测试目标是基于url的,所以既能够用来测试apache的负载能力,也能够测试nginx,lighthttp,tomcat,IIS等其它web服务器的压力;ab的使用:模拟并发请求100次,总共请求5000次,ab -c 100 -n 5000 待测试网站;测试机器与被测试机器分开,不要对线上服务作压力测试,观察测试工具ab所在机器,以及被测试的前端机的CPU,内存,网络等都不超过最高限度的75%  QPS达到极限:随着QPS的增加,每一个阶段须要根据实际状况来进行优化,优化的方案也与硬件条件、网络带宽息息相关;QPS达到50,能够称之为小型网站,通常的服务器就能够应付;QPS达到100,假设关系型数据库的每次请求在0.01s完成,假设单页面只有一个SQL查询,那么100QPS意味着1s完成100次请求,可是此时并不能保证数据库查询能完成100次,数据库缓存层,数据库的负载均衡;QPS达到800,假设使用百兆带宽,意味着网站出口的实际带宽是8M左右,假设每一个页面只有10k,在这个并发条件下,百兆带宽已经吃完,CDN加速,负载均衡;QPS达到1000,假设使用memcache缓存数据库查询数据,每一个页面对memcache的请求远大于直接对db的请求,memcache的悲观并发数在2w左右,但有可能在以前内网带宽已经吃光,表现出不稳定,静态HTML缓存;QPS达到2000,这个级别下,文件系统访问锁都成为了灾难,作业务分离,分布式存储

      高并发解决方案案例  流量优化:防盗链处理  前端优化:减小HTTP请求,合并css或js,添加异步请求,启用浏览器缓存和文件压缩,CDN加速,创建独立图片服务器,  服务端优化:页面静态化,并发处理,队列处理  数据库优化:数据库缓存,分库分表,分区操做,读写分离,负载均衡  web服务器优化:负载均衡,nginx反向代理,7,4层LVS软件

      2. web资源防盗链

        盗链:在本身的页面上展现一些并不在本身服务器上的内容,得到他人服务器上的资源地址,绕过别人的资源展现页面,直接在本身的页面上向最终用户提供此内容,常见的是小站盗用大站的图片,音乐,视频,软件等资源,经过盗链的方法能够减轻本身服务器的负担,由于真实的空间和流量均是来自别人的服务器

        防盗链:防止别人经过一些技术手段绕过本站的资源展现页面,盗用本站的资源,让绕开本站资源展现页面的资源连接失效,能够大大减轻服务器及带宽的压力

        工做原理:经过请求头中的referer或者签名,网站能够检测目标网页访问的来源网页,若是是资源文件,则能够跟踪到显示它的网页地址,一旦检测到来源不是本站即进行阻止或者返回制定的页面,经过计算签名的方式,判断请求是否合法,若是合法则显示,不然返回错误信息

        实现方法:referer:nginx模块ngx_http_referer_module用于阻挡来源非法的域名请求,nginx指令valid_referers none | blocked | server_names | string...,none表示referer来源头部为空的状况,blocked表示referer来源头部不为空,可是里面的值被代理或者防火墙删除了,这些值都不以http://或者https://开头,server_names表示referer来源头部包含当前的server_names,全局变量$invalid_referer。不能完全防范,只能提升门槛。也能够针对目录进行防盗链。

      //在nginx的conf中配置location ~.*.(gif|jpg|png|flv|swf|rar|zip)$

      {

      ​ valid_referers none blocked zi.com *.zi.com;

      ​ if($invalid_referer)

      ​ {

      #return 403;

      ​ rewrite ^/ http://www.zi.com/403.jpg; }

      }

        传统防盗链遇到的问题:伪造referer:能够使用加密签名解决

        加密签名:使用第三方模块HttpAccessKeyModule实现Nginx防盗链。accesskey on|off 模块开关,accesskey_hashmethod md5|sha-1 签名加密方式,accesskey_arg GET参数名称,accesskey_signature 加密规则,在nginx的conf中设置

      location ~.*.(gif|jpg|png|flv|swf|rar|zip)$

      {

      ​ accesskey on;

      ​ accesskey_hashmethod md5;

      ​ accesskey_arg sign;

      ​ accesskey_signature "jason$remote_addr";

      }

       

      <?php$sign = md5('jason'.$SERVER['REMOTE_ADDR']);echo '';

      3. 减小HTTP请求次数

        性能黄金法则:只有10%-20%的最终用户响应时间花在接收请求的HTML文档上,剩下的80%-90%时间花在HTML文档所引用的全部组件(img,script,css,flash等)进行的HTTP请求上。

        如何改善:改善响应时间的最简单途径就是减小组件的数量,并由此减小HTTP请求的数量

        HTTP链接产生的开销:域名解析--TCP链接--发送请求--等待--下载资源--解析时间

        疑问:DNS缓存,查找DNS缓存也须要时间,多个缓存就要查找屡次有可能缓存会被清除;Keep-Alive,HTTP1.1协议规定请求只能串行发送,前面的一个请求完成才能开始下个请求

        减小HTTP请求的方式:图片地图:容许在一个图片上关联多个URL,目标URL的选择取决于用户单击了图片上的哪一个位置,以位置信息定位超连接,把HTTP请求减小为一个,能够保证设计的完整性和功能的齐全性,使用map和area标签;

      <map name="map">

      <area shape="rect" coords="0,0,30,30" href=... title="">

      ​ ... </map>

          CSS Sprites:CSS精灵,经过使用合并图片,经过指定css的background-image和background-position来显示元素。图片地图与css精灵的响应时间基本上相同,但比使用各自独立图片的方式要快50%以上

          合并脚本和样式表:使用外部的js和css文件引用的方式,由于这要比直接写在页面中性能要更好一点;独立的一个js比用多个js文件组成的页面载入要快38%;把多个脚本合并为一个脚本,把多个样式表合并为一个样式表

          图片使用base64编码减小页面请求数:采用base64的编码方式将图片直接嵌入到网页中,而不是从外部载入

      4. 浏览器缓存和数据压缩优化

        HTTP缓存机制:若是请求成功会有三种状况:200 from cache:直接从本地缓存中获取相应,最快速,最省流量,由于根本没有向服务器进行请求;304 not modified:协商缓存,浏览器在本地没有命中的状况下请求头中发送必定的校验数据到服务端,若是服务端数据没有改变浏览器从本地缓存响应,返回304,快速,发送的数据不多,只返回一些基本的响应头信息,数据量很小,不发送实际响应体;200 OK:以上两种缓存所有失败,服务器返回完整响应,没有用到缓存,相对最慢。

        浏览器认为本地缓存能够使用,不会去请求服务端。相关header:pragma:HTTP1.0时代的遗留产物,该字段被设置为no-cache时,会告知浏览器禁用本地缓存,即每次都向服务器发送请求;expires:HTTP1.0时代用来启用本地缓存的字段,浏览器与服务器的时间没法保持一致,若是时间差距大,就会影响缓存结果;cache-control:HTTP1.1针对expires时间不一致的解决方案,告知浏览器缓存过时的时间间隔而不是时刻,即便具体时间不一致,也不影响缓存的管理;能够设置的值:no-store:禁止浏览器缓存响应;no-cache:不容许直接使用本地缓存,先发起请求和服务器协商;max-age=delta-seconds:告知浏览器该响应本地缓存有效的最长期限,以秒为单位;优先级:pragma >cache-control > expires。当浏览器没有命中本地缓存,如本地缓存过时或者响应中声明不容许直接使用本地缓存,那么浏览器确定会发起服务端请求;

        服务端会验证数据是否修改,若是没有通知浏览器使用本地缓存。相关header:last-modified:通知浏览器资源的最后修改时间;if-modified-since:获得资源的最后修改时间后,会将这个信息经过它提交到服务器作检查,若是没有修改,返回304状态码;ETag:HTTP1.1推出,文件的指纹标识符,若是文件内容修改,指纹会改变;if-none-match:本地缓存失效,会携带此值去请求服务端,服务端判断该资源是否改变,若是没有改变,直接使用本地缓存,返回304

        缓存策略的选择:适合缓存的内容:不变的图像,如logo,图标等,js,css静态文件,可下载的内容,媒体文件;建议使用协商缓存:html文件,常常替换的图片,常常修改的js,css文件,js和css文件的加载能够加入文件的签名来拒绝缓存,如a.css?签名或a.签名.js;不建议缓存的内容:用户隐私等敏感数据,常常改变的api数据接口

        nginx配置缓存策略:  本地缓存配置:add_header指令:添加状态码为2xx和3xx的响应头信息,add_header name value [always];,能够设置Pragma/Expires/Cache-Control,能够继承;expires指令:通知浏览器过时时长,expires time;,为负值时表示Cache-Control: no-cache;,当为正或者0时,就表示Cache-Control: max-age=指定的时间;;当为max时,Cache-Control设置到10年;  协商缓存相关配置:Etag指令:指定签名;etag on|off;,默认是on

        前端代码和资源的压缩:让资源文件更小,加快文件在网络中的传输,让网页更快的展示,下降带宽和流量开销;压缩方式:js,css,图片,html代码的压缩,Gzip压缩。js代码压缩:通常是去掉多余的空格和回车,替换长变量名,简化一些代码写法等,代码压缩工具不少UglifyJS(压缩,语法检查,美化代码,代码缩减,转化)、YUI Compressor(来自yahoo,只有压缩功能)、Closure Compiler(来自google,功能和UglifyJS相似,压缩的方式不同),有在线工具tool.css-js.com,应用程序,编辑器插件。css代码压缩:原理和js压缩原理相似,一样是去除空白符,注释而且优化一些css语义规则等,压缩工具CSS Compressor(能够选择模式)。html代码压缩:不建议使用代码压缩,有时会破坏代码结构,能够使用Gzip压缩,固然也能够使用htmlcompressor工具,不过转换后必定要检查代码结构。img压缩:通常图片在web系统的比重都比较大,压缩工具:tinypng,JpegMini,ImageOptim。Gzip压缩:配置nginx服务,gzip on|off,gzip_buffers 32 4K|16 8K #缓冲(在内存中缓存几块?每块多大),gzip_comp_level [1-9] #推荐6 压缩级别(级别越高,压的越小,越浪费CPU计算资源),gzip_disable #正则匹配UA 什么样的uri不进行gzip,gzip_min_length 200 #开始压缩的最小长度,gzip_http_version 1.0|1.1 #开始压缩的http协议版本,gzip_proxied #设置请求者代理服务器,该如何缓存内容,gzip_types text/plain applocation/xml #对哪些类型的文件用压缩,gzip_vary on|off #是否传输gzip压缩标志。其余工具:自动化构建工具Grunt。

      5. CDN加速

        CDN:Content Delivery Network,内容分发网络,尽量避开互联网上有可能影响数据传输速度和稳定性的瓶颈和环节,使内容传输的更快更稳定;在网络各处放置节点服务器所构成的在现有的互联网基础之上的一层智能虚拟网络;CDN系统可以实时的根据网络流量和各节点的链接,负载状况以及到用户的距离和响应时间等综合信息将用户的请求从新导向离用户最近的服务节点上。本地cache加速,提升了企业站点(尤为含有大量img和静态页面站点)的访问速度;跨运营商的网络加速,保证不一样网络的用户都获得良好的访问质量;远程访问用户根据DNS负载均衡技术智能自动选择cache服务器;自动生成服务器的远程Mirror cache服务器,远程用户访问时从cache服务器上读数据,减小远程访问的带宽,分担网络流量,减轻原站点web服务器负载等功能;普遍分布的CDN节点加上节点之间的只能智能冗余机制,能够有效的预防黑客入侵

        CDN的工做原理:传统访问:用户在浏览器输入域名发起请求--解析域名获取服务器IP地址--根据IP地址找到对应的服务器--服务器响应并返回数据;使用CDN访问:用户发起请求--智能DNS的解析(根据IP判断地理位置,接入网类型,选择路由最短和负载最轻的服务器)--取得缓存服务器IP--把内容返回给用户(若是缓存中有)--向源站发起请求--将结果返回给用户--将结果存入缓存服务器

        CDN适用场景:站点或者应用中大量静态资源的加速分发,如css,js,img和html;大文件下载;直播网站等

        CDN的实现:BAT等都有提供CDN服务,可用LVS作4层负载均衡;可用nginx,Varnish,Squid,Apache TrafficServer作7层负载均衡和cache;使用squid反向代理,或者nginx等的反向代理

      6. 创建独立的图片服务器

        独立的必要性:分担web服务器的I/O负载-将耗费资源的图片服务分离出来,提升服务器的性能和稳定性;可以专门的图片服务器进行优化-为图片服务设置有针对性的缓存方案,减小带宽成本,提升访问速度;提升网站的可扩展性-经过增长图片服务器,提升图片吞吐能力

        采用独立域名:缘由:同一域名下浏览器的并发链接数有限制,突破浏览器链接数的限制;因为cookie的缘由,对缓存不利,大部分web cache都只缓存不带cookie的请求,致使每次的图片请求都不能命中cache

        独立后的问题:如何进行图片上传和图片同步:NFS共享方式;利用FTP同步

      7. 动态语言静态化

        将现有PHP等动态语言的逻辑代码生成为静态HTML文件,用户访问动态脚本重定向到静态HTML文件的过程。对实时性要求不高的页面比较适合。缘由:动态脚本一般会作逻辑计算和数据查询,访问量越大,服务器压力越大;访问量大时可能会形成CPU负载太高,数据库服务器压力过大;静态化能够下降逻辑处理压力,下降数据库服务器查询压力

        静态化的实现方式:  使用模板引擎:能够使用smarty的缓存机制生成静态HTML缓存文件;$smarty->cache-dir = $ROOT."/cache";//缓存目录,$smarty->caching=true;//是否开启缓存,$smarty->cache_lifetime="3600";//缓存时间,$smarty->display(string template[, string cache_id[, string compile_id]]);,$smarty->clear_all_cache();//清除全部缓存,$smarty->clear_cache('a.html');//清除指定的缓存,$smarty->clear_cache('a.html', $art_id);//清除同一个模板下的指定缓存号的缓存  利用ob系列的函数:ob_start():打开输出控制缓冲,ob_ge_contents():返回输出缓冲区内容,ob_clean():清空输出缓冲区,ob_end_flush():冲刷出(送出)输出缓冲区内容并关闭缓冲,能够判断文件的inode修改时间,判断是否过时使用filectime函数

      8. 动态语言层的并发处理

        进程:计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操做数据结构的基础,是一个“执行中的程序”;进程的三态模型:多道程序系统中,进程在处理器上交替运行,状态不断的发生变化;运行:当一个进程在处理机上运行时,称该进程处于运行状态,处于此状态的进程的数目小于等于处理器的数目,对于单处理机系统,处于运行状态的进程只有一个,在没有其余进程能够执行时(如全部进程都在阻塞状态),一般会自动执行系统的空闲进程;就绪:当一个进程得到了除处理机之外的一切所需资源,一旦获得处理机便可运行,则称此进程处于就绪状态,就绪进程能够按多个优先级来划分队列,如当一个进程因为时间片用完而进入就绪状态时,排入低优先级队列,当进程由I/O操做完成而进入就绪状态时,排入高优先级队列;阻塞:也称为等待或睡眠状态,一个进程正在等待某一事件发生(如请求I/O而等待I/O完成等)而暂时中止运行,这时即便把处理机分配给进程也没法运行;进程的五态模型:对于一个实际的系统,进程的状态及其转换更为复杂,新建态:对应于进程刚刚被建立时没有被提交的状态,并等待系统完成建立进程的全部必要信息;活跃就绪/静止就绪:进程在主存而且可被调度的状态/指进程被对换到辅存时的就绪状态,是不能被直接调度的状态,只有当主存中没有活跃就绪态进程,或者是挂起就绪态进程具备更高的优先级,系统将把挂起就绪态进程调回主存并转换为活跃就绪;运行,活跃阻塞/静止阻塞:指进程已在主存,一旦等待的时间产生便进入活跃就绪状态/进程对换到辅存时的阻塞状态,一旦等待的事件产生便进入静止就绪状态;终止态:进程已结束运行,回收除进程控制块以外的其余资源,并让其余进程从进程控制块中收集有关信息;因为用户的并发请求,为每个请求都建立一个进程显然是行不通的,从系统资源开销方面或是响应用户请求的效率方面来看,所以线程的概念被引进。

        线程:有时被称为轻量级进程,是程序执行流的最小单元。是进程中的一个实体,是被系统独立调度和分派的基本单位,本身不拥有系统资源,只拥有一点在运行中必不可少的资源但它可与同属一个进程的其它进程共享进程所拥有的所有资源。一个线程能够建立和撤销另外一个线程,同一进程中的多个线程之间能够并发执行。线程是程序中一个单一的顺序控制流程,进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不一样的工做成为多线程。每个程序都至少有一个线程,若程序只有一个线程,那就是程序自己。线程的状态:就绪:线程具有运行的全部条件,逻辑上能够运行,在等待处理机;运行:线程占有处理机正在运行;阻塞:线程在等待一个事件(如某个信号量),逻辑上不可执行。

        协程:是一种用户态的轻量级线程,调度彻底由用户控制;协程拥有本身的寄存器上下文和栈;协程调度切换时,将寄存器上下文和栈保存到其余地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操做栈则基本没有内核切换的开销,能够不加锁的访问全局变量,因此上下文的切换很是快。

        进程和线程的区别:线程是进程内的一个执行单元,进程内至少有一个线程,共享进程的地址空间,而进程有本身独立的地址空间;进程是资源分配和拥有的单元,同一个进程内的线程共享进程的资源;线程是处理器调度的基本单位,但进程不是;两者都可并发执行;每一个独立的线程有一个程序运行的入口,顺序执行序列和程序的出口,可是线程不可以独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制

        线程和协程的区别:一个线程能够多个协程,一个进程也能够单独拥有多个协程;进程线程都是同步机制,而协程则是异步;协程能保留上一次调用时的状态,每次过程重入时,就至关于进入上一次调用的状态。

        多进程:同一时间里,同一个计算机系统中若是容许两个或两个以上的进程处于运行状态;多开一个进程,多分配一份资源,进程间通信不方便;

        多线程:线程就是把一个进程分为不少片,每一片均可以是一个独立的流程,与多进程的区别是只会使用一个进程的资源,线程间能够直接通讯;

        同步阻塞:多进程:最先的服务器端程序都是经过多进程,多线程来解决并发I/O的问题;一个请求建立一个进程,而后子进程进入循环同步阻塞地与客户端链接进行交互,收发处理数据;多线程:线程中能够直接向某一个客户端链接发送数据;步骤:建立一个socket,进入while循环,阻塞在进程accept操做上,等待客户端链接进入,主进程在多进程模型下经过fork建立子进程,多线程模型下能够建立子线程,子进程/线程建立成功后进入while循环,阻塞在recv调用上,等待客户端向服务器发送数据,收到数据后服务器程序进行处理而后使用send向客户端发送响应,当客户端链接关闭时,子进程/线程退出并销毁全部资源。主进程/线程会回收掉此子进程/线程;缺点:这种模型严重依赖进程的数量解决并发问题,启动大量进程会带来额外的进程调度消耗

        异步非阻塞:如今各类高并发异步IO的服务器程序都是基于epoll(无限数量链接,无需轮询)实现的。IO复用异步非阻塞程序使用经典的Reactor模型,Reactor顾名思义就是反应堆的意思,它自己不处理任何数据收发,只是能够监视一个socket句柄的事件变化。Reactor模型:Add:添加一个socket到Reactor,Set:修改socket对应的事件,如可读可写,Del:从Reactor中移除,Callback:事件发生后回调指定的函数。Nginx:多线程Reactor,swoole:多线程Reactor+多进程Worker

        PHP的swoole扩展:PHP的异步,并行,高性能网络通讯引擎,使用纯c语言编写,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络客户端,异步mysql,异步redis,数据库链接池,asynctask,消息队列,毫秒定时器,异步文件读写,异步DNS查询;除了异步IO的支持以外,swoole为PHP多进程的模式设计了多个并发数据结构和IPC通讯机制,能够大大简化多进程并发编程的工做;swoole2.0支持了相似Go语言的协程,能够使用彻底同步的代码实现异步程序

        消息队列:用户注册后,须要发注册邮件和注册短信;串行方式:将注册信息写入数据库成功后,发送注册邮件,再发送注册短信;并行方式:将注册信息写入数据库成功后,发送注册邮件的同时,发送注册短信;消息队列方式:将注册信息写入数据库成功后,将成功信息写入队列,此时直接返回成功给用户,写入队列的时间很是短,能够忽略不计,而后异步发送邮件和短信。应用解耦:场景说明:用户下单后,订单系统须要通知库存系统。假如库存系统没法访问,则订单减库存将失败,从而致使订单失败;订单系统与库存系统耦合;引用队列:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功,订阅下单的消息,采用拉/推的方式,获取下单信息,库存系统根据下单信息,进行库存操做。流量削峰:应用场景:秒杀活动,流量瞬时激增,服务器压力大。用户发起请求,服务器接收后,先写入消息队列,假如消息队列长度超过最大值,则直接报错或提示用户,后续程序读取消息队列再作处理,控制请求量,缓解高流量。日志处理:应用场景:解决大量日志的传输。日志采集程序将程序写入消息队列,而后经过日志处理程序的订阅消费日志。消息通信:应用场景:聊天室。多个客户端订阅同一主题,进行消息发布和接收。常见消息队列产品:Kafka,ActiveMQ,ZeroMQ,RabbitMQ,Redis等

        接口的并发请求:curl_multi_init

      9. 数据库缓存层的优化

        mysql等一些常见的关系型数据库的数据都存储在磁盘当中,在高并发场景下,业务应用对mysql产生的增删改查的操做形成的巨大的IO开销和查询压力,这无疑对数据库和服务器都是一种巨大的压力,为了解决此类问题,缓存数据的概念应运而生。极大的解决数据库服务器的压力,提升应用数据的响应速度。常见的缓存形式:内存缓存,文件缓存。

        缓存数据是为了让客户端不多甚至不访问数据库服务器进行数据的查询,高并发下,能最大程度的下降对数据库服务器的访问压力。默认状况下:用户请求->数据查询->链接数据库服务器并查询数据->将数据缓存起来(html,内存,json,序列化数据)->显示给客户端;用户再次请求或者新用户访问->数据查询->直接从缓存中获取数据->显示给客户端

        mysql的查询缓存:query_cache_type:查询缓存类型,有0,1,2三个取值,0则不使用查询缓存,1表示始终使用查询缓存,2表示按需使用查询缓存。query_cache_type为1时,亦可关闭查询缓存,SELECT SQL_NO_CACHE * FROM my_table WHERE condition。query_cache_type为2时,可按需使用查询缓存,SELECT SQL_CACHE * FROM my_table WHERE condition。query_cache_size:默认状况下为0,表示为查询缓存预留的内存为0,则没法使用查询缓存。SET GLOBAL query_cache_size = 134217728。查询缓存能够看作是SQL文本和查询结果的映射。第二次查询的SQL和第一次查询的SQL彻底相同,则会使用缓存。SHOW STATUS LIKE 'Qcache_hits'; 查看命中次数。表的结构或数据发生改变时,查询缓存中的数据再也不有效。清理缓存:FLUSH QUERY CACHE; //清理查询缓存内存碎片,RESET QUERY CACHE; //从查询缓存中移出全部查询,FLUSH TABLES; //关闭全部打开的表,同时该操做将会清空查询缓存中的内容

        使用memcache缓存查询数据:对于大型站点,若是没有中间缓存层,当流量打入数据库层时,即使有以前的几层为咱们挡住一部分流量,可是在大并发的状况下,仍是会有大量请求涌入数据库层,这样对于数据库服务器的压力冲击很大,响应速度也会降低,所以添加中间缓存层颇有必要。memcache是一套分布式的高速缓存系统,由livejournal的bradfitzpatrick开发,但目前被许多网站使用以提高网站的访问速度,尤为对于一些大型的,须要频繁访问数据库的网站访问速度提高效果十分显著。

        memcache工做原理:是一个高性能的分布式的内存对象缓存系统,经过在内存里维护一个统一的巨大的hash表,可以用来存储各类格式的数据,包括图像,视频,文件以及数据库检索的结果等,简单的说就是将数据调用到内存,而后从内存中读取,从而大大提升读取速度。

        memcache工做流程:先检查客户端的请求数据是否在memcache中,若有,直接把请求数据返回,再也不对数据库进行任何操做;若是请求的数据不在memcache中,就去查数据库,把从数据库中获取的数据返回给客户端,同时把数据缓存一份到memcache中。

        memcache方法:获取:get(key) 设置:set(key, val, expire) 删除:delete(key)

        通用缓存机制:用查询的方法名+参数做为查询时的key value对中的key值

        使用redis缓存查询数据:与memcache的区别:性能相差不大,redis在2.0版本后增长了本身的VM特性,突破物理内存的限制,memcache能够修改最大可用内存,采用LRU算法;redis依赖客户端来实现分布式读写,memcache自己没有数据冗余机制;redis支持快照,AOF,依赖快照进行持久化,aof加强了可靠性的同时,对性能有所影响,memcache不支持持久化,一般作缓存,提高性能;memcache在并发场景下,用cas保证一致性,redis事务支持比较弱,只能保证事务中的每一个操做连续执行;redis支持多种类的数据类型;redis用于数据量较小的高性能操做和运算上,memcache用于在动态系统中减小数据库负载,提高性能,适合作缓存,提升性能

        缓存其余数据:session:session_set_save_handler

      10. mysql数据库层的优化

        优化方向:数据表数据类型优化,索引优化,sql语句优化,存储引擎的优化,数据表结构设计的优化,数据库服务器架构的优化

        数据表数据类型优化:字段使用什么样的数据类型更合适,性能更快,tinyint、smallint、bigint,考虑空间和范围的问题;char、varchar,存储字符串长度是否固定;enum,特定固定的分类能够使用enum存储,效率更快;IP地址的存储,ip2long(),使用整型存储IP地址

        索引的优化:创建合适的索引,索引在什么场景下效率最高,索引的建立原则:不是越多越好,在合适的字段上建立合适的索引,复合索引的前缀原则,like查询%的问题,全表扫描优化,or条件索引使用状况,字符串类型索引失效的问题

        sql语句的优化:优化查询过程当中的数据访问,优化长难句、特定类型的查询语句。使用limit,返回列不用*,变复杂为简单,切分查询,分解关联查询,优化count(),优化关联查询,优化子查询,优化group by和distinct,优化limit和union

        存储引擎的优化:尽可能使用innoDB存储引擎

        数据表结构设计的优化:分区操做,经过特定的策略对数据表进行物理拆分,对用户透明,partition by;分库分表,水平拆分,垂直拆分

        数据库架构的优化:主从复制,读写分离,双主热备,binlog日志,中继日志,主从库binlog的交换,事件传输;负载均衡,经过LVS的三种基本模式实现负载均衡,mycat数据库中间件实现负载均衡

      11. web服务器的负载均衡

        七层负载均衡的实现:基于URL等应用层信息的负载均衡,nginx的proxy是它一个很强大的功能,实现了7层负载均衡,功能强大,性能卓越,运行稳定,配置简单灵活,可以自动剔除工做不正常的后端服务器,上传文件使用异步模式,支持多种分配策略,能够分配权重,分配方式灵活。

        nginx负载均衡:内置策略:IP Hash,加权轮询;扩展策略:fair策略,通用hash,一致性hash  加权轮询:首先将请求都分给高权重的机器,直到该机器的权值降到了比其余机器低,才开始将请求分给下一个高权重的机器,当全部后端机器都down掉时,nginx会当即将全部机器的标志位清成初始状态,以免形成全部的机器都处在timeout的状态;IP Hash:流程和轮询很相似,只是其中的算法和具体的策略有些变化,算法是一种变相的轮询算法;fair:根据后端服务器的响应时间判断负载状况,从中选出负载最轻的机器进行分流;通用hash,一致性hash:通用hash比较简单,能够以nginx内置的变量为key进行hash,一致性hash采用了nginx内置的一致性hash环,支持memcache

        nginx配置:

      http {

      ​ upstream cluster{#ip_hash;

      ​ server srv1 weight=1;

      ​ server srv2;

      ​ server srv3;

      ​ }

      ​ server {

      ​ listen 80;

      ​ location / {

      ​ proxy_pass http://cluster;

      ​ }

      ​ }

      }

        四层负载均衡的实现:经过报文中的目标地址和端口,再加上负载均衡设备设置的服务器选择方式,决定最终选择的内部服务器。LVS实现服务器集群负载均衡有三种方式,NAT,DR和TUN。

相关文章
相关标签/搜索