tomcat线程池的实现

Tomcat的线程池主要使用了ThreadPool.java、ThreadPoolRunnable.java、ThreadWithAttributes.java,其中ThreadPoolRunnable.java是一个接口,所有的需要使用线程池的服务都可以实现这个接口。而实现的核心则再ThreadPool.java中的两个内部类:MonitorRunnable.java和ControlRunnable.java。

MonitorRunnable.java在线程池启动之后定期(60s)的扫描线程数,如果空闲的线程大于最大空闲线程数,则结束多余的线程。

ControlRunnable.java是所有启动的线程,若由任务需要执行,ThreadPool会先找一个空闲的ControlRunnable来执行,若没有空闲的,则创建,若现有的busy线程已经达到最大值,则等待。任务执行结束后通知ControlRunnable继续wait,直到有任务执行或被MonitorRunnable回收。

若要使用线程池可以实现Runnable接口,或者可以实现ThreadPoolRunnable 接口,当然自己还可以扩展这个类,以便实现更多的使用线程池的方式。

ThreadPool.java

Java代码 复制代码
  1. package com.xiao.tomcatthreadpool;   
  2.   
  3. import java.util.Hashtable;   
  4.   
  5. public class ThreadPool {   
  6.        
  7.     public static final int MAX_THREADS = 10;   
  8.     public static final int MAX_THREADS_MIN = 4;   
  9.     public static final int MAX_SPARE_THREADS = 5;   
  10.     public static final int MIN_SPARE_THREADS = 2;   
  11.     public static final int WORK_WAIT_TIMEOUT = 10*1000;   
  12.        
  13.     private String name = "TP";   
  14.     private boolean isDaemon;   
  15.     private boolean stopThePool;   
  16.     private int maxSpareThreads;   
  17.     private int minSpareThreads;   
  18.     private int currentThreadCount;   
  19.     private int currentThreadsBusy;   
  20.     private int maxThreads;   
  21.        
  22.     private int threadPriority = Thread.NORM_PRIORITY;   
  23.     private int sequence = 0;   
  24.        
  25.     private ControlRunnable[] pool;   
  26.     private MonitorRunnable monitor;   
  27.        
  28.     protected Hashtable threads=new Hashtable();   
  29.        
  30.     public ThreadPool() {   
  31.         maxThreads = MAX_THREADS;   
  32.         maxSpareThreads = MAX_SPARE_THREADS;   
  33.         minSpareThreads = MIN_SPARE_THREADS;   
  34.         currentThreadCount = 0;   
  35.         currentThreadsBusy = 0;   
  36.         stopThePool = false;   
  37.     }   
  38.        
  39.     public static ThreadPool createThreadPool() {   
  40.         return new ThreadPool();   
  41.     }   
  42.        
  43.     public synchronized void start() {   
  44.         stopThePool = false;   
  45.         currentThreadCount = 0;   
  46.         currentThreadsBusy = 0;   
  47.         pool = new ControlRunnable[maxThreads];   
  48.         openThreads(minSpareThreads);   
  49.         if (maxSpareThreads < maxThreads) {   
  50.             monitor = new MonitorRunnable(this);   
  51.         }   
  52.     }   
  53.        
  54.     public void run(Runnable r) {   
  55.         ControlRunnable c = findControlRunnable();   
  56.         c.runIt(r);   
  57.     }   
  58.        
  59.     public void runIt(ThreadPoolRunnable r) {   
  60.         if(null == r) {   
  61.             throw new NullPointerException();   
  62.         }   
  63.         ControlRunnable c = findControlRunnable();   
  64.         c.runIt(r);   
  65.     }   
  66.        
  67.     public ControlRunnable findControlRunnable() {   
  68.         ControlRunnable c;   
  69.         if ( stopThePool ) {   
  70.             throw new IllegalStateException();   
  71.         }   
  72.         synchronized(this) {   
  73.             while (currentThreadsBusy == currentThreadCount) {   
  74.                 if (currentThreadCount < maxThreads) {   
  75.                     int toOpen = currentThreadCount + minSpareThreads;   
  76.                     openThreads(toOpen);   
  77.                 } else {   
  78.                     try {   
  79.                         this.wait();   
  80.                     }   
  81.                     catch(InterruptedException e) {   
  82.                            
  83.                     }   
  84.                 }   
  85.             }   
  86.             if(0 == currentThreadCount || stopThePool) {   
  87.                 throw new IllegalStateException();   
  88.             }   
  89.             int pos = currentThreadCount - currentThreadsBusy - 1;   
  90.             c = pool[pos];   
  91.             pool[pos] = null;   
  92.             currentThreadsBusy++;   
  93.         }   
  94.         return c;   
  95.     }   
  96.        
  97.     public void openThreads(int toOpen) {   
  98.         if(toOpen > maxThreads) {   
  99.             toOpen = maxThreads;   
  100.         }   
  101.         for(int i=currentThreadCount; i<toOpen; i++) {   
  102.             pool[i - currentThreadsBusy] = new ControlRunnable(this);   
  103.         }   
  104.         currentThreadCount = toOpen;   
  105.     }   
  106.        
  107.     public void addThread(ThreadWithAttributes t, ControlRunnable r) {   
  108.         threads.put(t, r);   
  109.     }   
  110.        
  111.     public void removeThread(Thread t) {   
  112.         threads.remove(t);   
  113.     }   
  114.        
  115.     public synchronized void notifyThreadEnd(ControlRunnable r) {   
  116.         currentThreadCount --;   
  117.         currentThreadsBusy --;   
  118.         notify();   
  119.     }   
  120.        
  121.     public synchronized void returnController(ControlRunnable r) {   
  122.         if(0 == currentThreadCount || stopThePool) {   
  123.             r.terminate();   
  124.             return;   
  125.         }   
  126.         currentThreadsBusy--;   
  127.         pool[currentThreadCount - currentThreadsBusy - 1] = r;   
  128.         notify();   
  129.     }   
  130.        
  131.     public synchronized void checkSpareControllers() {   
  132.         if(stopThePool) {   
  133.             return;   
  134.         }   
  135.         if((currentThreadCount - currentThreadsBusy) > maxSpareThreads) {   
  136.             int toFree = currentThreadCount - currentThreadsBusy - maxSpareThreads;   
  137.             for(int i=0; i<toFree; i++) {   
  138.                 ControlRunnable cr = pool[currentThreadCount-currentThreadsBusy -1];   
  139.                 cr.terminate();   
  140.                 pool[currentThreadCount-currentThreadsBusy -1] = null;   
  141.                 currentThreadCount --;   
  142.             }   
  143.         }   
  144.            
  145.     }   
  146.        
  147.     /**  
  148.      * MonitorRunnable主要任务是监控线程数  
  149.      * 如果线程数大于最大线程则回收线程  
  150.      */  
  151.     public static class MonitorRunnable implements Runnable {   
  152.         ThreadPool tp;   
  153.         Thread t;   
  154.         boolean shouldTerminate;   
  155.         int interval = WORK_WAIT_TIMEOUT;   
  156.         public MonitorRunnable(ThreadPool tp) {   
  157.             this.tp = tp;   
  158.             this.start();   
  159.         }   
  160.            
  161.         public void setInterval(int i) {   
  162.             interval = i;   
  163.         }   
  164.   
  165.         public void start() {   
  166.             shouldTerminate = false;   
  167.             t = new Thread(this);   
  168.             t.setDaemon(tp.getDaemon());   
  169.             t.setName(tp.getName() + "-Monitor");   
  170.             t.start();   
  171.         }   
  172.            
  173.         public void stop() {   
  174.             terminal();   
  175.         }   
  176.            
  177.         public synchronized void terminal() {   
  178.             this.shouldTerminate = true;   
  179.             this.notify();   
  180.         }   
  181.            
  182.         public void run() {   
  183.             while(true) {   
  184.                 try {   
  185.                     synchronized(this) {   
  186.                         this.wait(interval);   
  187.                     }   
  188.                     if(shouldTerminate) {   
  189.                         break;   
  190.                     }   
  191.                     //System.out.println("currentThreadCount=" + currentThreadCount + " currentThreadsBusy=" + currentThreadsBusy + " ");  
  192.                     tp.checkSpareControllers();   
  193.                 } catch(InterruptedException e) {   
  194.                        
  195.                 }   
  196.             }   
  197.         }   
  198.     }   
  199.        
  200.     public static class ControlRunnable implements Runnable {   
  201.         private ThreadPool tp;   
  202.         private boolean shouldTerminate;   
  203.         private ThreadWithAttributes     t;   
  204.            
  205.         private ThreadPoolRunnable   toRun;   
  206.         private Runnable toRunRunnable;   
  207.         private boolean    shouldRun;   
  208.            
  209.         public ControlRunnable(ThreadPool tp) {   
  210.             toRun = null;   
  211.             shouldTerminate = false;   
  212.             shouldRun = false;   
  213.             this.tp = tp;   
  214.             t = new ThreadWithAttributes(tp, this);   
  215.             t.setDaemon(true);   
  216.             t.setName(tp.getName() + "-Processor" + tp.incSequence());   
  217.             t.setPriority(tp.getThreadPriority());   
  218.             tp.addThread(t, this);   
  219.             t.start();   
  220.         }   
  221.            
  222.         public void run() {   
  223.             boolean _shouldRun = false;   
  224.             boolean _shouldTerminate = false;   
  225.             ThreadPoolRunnable _toRun = null;   
  226.             try {   
  227.                 while(true) {   
  228.                     try {   
  229.                         synchronized(this) {   
  230.                             System.out.println("shouldRun=" + shouldRun);   
  231.                             while(!shouldRun && !shouldTerminate) {   
  232.                                 this.wait();   
  233.                             }   
  234.                             _shouldRun = shouldRun;   
  235.                             _shouldTerminate = shouldTerminate;   
  236.                             _toRun = toRun;   
  237.                         }   
  238.                         if (_shouldTerminate)   
  239.                             break;   
  240.                         try {   
  241.                             if(_shouldRun) {   
  242.                                 if (_toRun != null) {   
  243.                                     _toRun.runIt(t.getThreadData(tp));   
  244.                                 } else if (toRunRunnable != null) {   
  245.                                     toRunRunnable.run();   
  246.                                 } else {   
  247.                                 }   
  248.                             }   
  249.                         } catch(Throwable r) {   
  250.                             _shouldTerminate = true;   
  251.                             _shouldRun = false;   
  252.                             tp.notifyThreadEnd(this);   
  253.                         } finally {   
  254.                             if(_shouldRun) {   
  255.                                 shouldRun = false;   
  256.                                 tp.returnController(this);   
  257.                             }   
  258.                                
  259.                         }   
  260.                         if (_shouldTerminate) {   
  261.                             break;   
  262.                         }   
  263.                     } catch(InterruptedException e) {   
  264.                            
  265.                     }   
  266.                 }   
  267.             } finally {   
  268.                 tp.removeThread(Thread.currentThread());   
  269.             }   
  270.                
  271.         }   
  272.            
  273.         public synchronized void runIt(Runnable toRun) {   
  274.             this.toRunRunnable = toRun;   
  275.             shouldRun = true;   
  276.             this.notify();   
  277.         }   
  278.            
  279.         public synchronized void runIt(ThreadPoolRunnable toRun) {   
  280.             this.toRun = toRun;   
  281.             shouldRun = true;   
  282.             this.notify();   
  283.         }   
  284.            
  285.         public void stop() {   
  286.             this.terminate();   
  287.         }   
  288.   
  289.         public void kill() {   
  290.             t.stop();   
  291.         }   
  292.            
  293.         public synchronized void terminate() {   
  294.             shouldTerminate = true;   
  295.             this.notify();   
  296.         }   
  297.     }   
  298.        
  299.     public String getName() {   
  300.         return name;   
  301.     }   
  302.        
  303.     public boolean getDaemon() {   
  304.         return isDaemon;   
  305.     }   
  306.   
  307.     public int getThreadPriority() {   
  308.         return threadPriority;   
  309.     }   
  310.        
  311.     public int incSequence() {   
  312.         return sequence ++;   
  313.     }   
  314.   
  315.     public void setThreadPriority(int threadPriority) {   
  316.         this.threadPriority = threadPriority;   
  317.     }   
  318.   
  319. }  
package com.xiao.tomcatthreadpool;

import java.util.Hashtable;

public class ThreadPool {
	
	public static final int MAX_THREADS = 10;
    public static final int MAX_THREADS_MIN = 4;
    public static final int MAX_SPARE_THREADS = 5;
    public static final int MIN_SPARE_THREADS = 2;
    public static final int WORK_WAIT_TIMEOUT = 10*1000;
    
    private String name = "TP";
    private boolean isDaemon;
    private boolean stopThePool;
    private int maxSpareThreads;
    private int minSpareThreads;
    private int currentThreadCount;
    private int currentThreadsBusy;
    private int maxThreads;
    
    private int threadPriority = Thread.NORM_PRIORITY;
    private int sequence = 0;
    
    private ControlRunnable[] pool;
    private MonitorRunnable monitor;
    
    protected Hashtable threads=new Hashtable();
    
    public ThreadPool() {
    	maxThreads = MAX_THREADS;
    	maxSpareThreads = MAX_SPARE_THREADS;
    	minSpareThreads = MIN_SPARE_THREADS;
    	currentThreadCount = 0;
    	currentThreadsBusy = 0;
    	stopThePool = false;
    }
    
    public static ThreadPool createThreadPool() {
    	return new ThreadPool();
    }
    
    public synchronized void start() {
    	stopThePool = false;
    	currentThreadCount = 0;
    	currentThreadsBusy = 0;
    	pool = new ControlRunnable[maxThreads];
    	openThreads(minSpareThreads);
    	if (maxSpareThreads < maxThreads) {
            monitor = new MonitorRunnable(this);
        }
    }
    
    public void run(Runnable r) {
    	ControlRunnable c = findControlRunnable();
        c.runIt(r);
    }
    
    public void runIt(ThreadPoolRunnable r) {
    	if(null == r) {
            throw new NullPointerException();
        }
        ControlRunnable c = findControlRunnable();
        c.runIt(r);
    }
    
    public ControlRunnable findControlRunnable() {
    	ControlRunnable c;
    	if ( stopThePool ) {
            throw new IllegalStateException();
        }
    	synchronized(this) {
    		while (currentThreadsBusy == currentThreadCount) {
    			if (currentThreadCount < maxThreads) {
                    int toOpen = currentThreadCount + minSpareThreads;
                    openThreads(toOpen);
                } else {
                	try {
                        this.wait();
                    }
                    catch(InterruptedException e) {
                    	
                    }
                }
    		}
    		if(0 == currentThreadCount || stopThePool) {
                throw new IllegalStateException();
            }
    		int pos = currentThreadCount - currentThreadsBusy - 1;
            c = pool[pos];
            pool[pos] = null;
            currentThreadsBusy++;
    	}
    	return c;
    }
    
    public void openThreads(int toOpen) {
    	if(toOpen > maxThreads) {
    		toOpen = maxThreads;
    	}
    	for(int i=currentThreadCount; i<toOpen; i++) {
    		pool[i - currentThreadsBusy] = new ControlRunnable(this);
    	}
    	currentThreadCount = toOpen;
    }
    
    public void addThread(ThreadWithAttributes t, ControlRunnable r) {
    	threads.put(t, r);
    }
    
    public void removeThread(Thread t) {
    	threads.remove(t);
    }
    
    public synchronized void notifyThreadEnd(ControlRunnable r) {
    	currentThreadCount --;
    	currentThreadsBusy --;
    	notify();
    }
    
    public synchronized void returnController(ControlRunnable r) {
    	if(0 == currentThreadCount || stopThePool) {
            r.terminate();
            return;
        }
    	currentThreadsBusy--;
    	pool[currentThreadCount - currentThreadsBusy - 1] = r;
        notify();
    }
    
    public synchronized void checkSpareControllers() {
    	if(stopThePool) {
            return;
        }
    	if((currentThreadCount - currentThreadsBusy) > maxSpareThreads) {
    		int toFree = currentThreadCount - currentThreadsBusy - maxSpareThreads;
    		for(int i=0; i<toFree; i++) {
    			ControlRunnable cr = pool[currentThreadCount-currentThreadsBusy -1];
    			cr.terminate();
    			pool[currentThreadCount-currentThreadsBusy -1] = null;
    			currentThreadCount --;
    		}
    	}
    	
    }
    
    /**
     * MonitorRunnable主要任务是监控线程数
     * 如果线程数大于最大线程则回收线程
     */
    public static class MonitorRunnable implements Runnable {
    	ThreadPool tp;
    	Thread t;
    	boolean shouldTerminate;
    	int interval = WORK_WAIT_TIMEOUT;
    	public MonitorRunnable(ThreadPool tp) {
    		this.tp = tp;
    		this.start();
    	}
    	
    	public void setInterval(int i) {
    		interval = i;
    	}

    	public void start() {
    		shouldTerminate = false;
    		t = new Thread(this);
    		t.setDaemon(tp.getDaemon());
    	    t.setName(tp.getName() + "-Monitor");
    		t.start();
    	}
    	
    	public void stop() {
    		terminal();
    	}
    	
    	public synchronized void terminal() {
    		this.shouldTerminate = true;
    		this.notify();
    	}
    	
    	public void run() {
    		while(true) {
    			try {
    				synchronized(this) {
    					this.wait(interval);
    				}
    				if(shouldTerminate) {
                        break;
                    }
    				//System.out.println("currentThreadCount=" + currentThreadCount + " currentThreadsBusy=" + currentThreadsBusy + " ");
    				tp.checkSpareControllers();
    			} catch(InterruptedException e) {
    				
    			}
    		}
    	}
    }
    
    public static class ControlRunnable implements Runnable {
    	private ThreadPool tp;
    	private boolean shouldTerminate;
    	private ThreadWithAttributes     t;
    	
    	private ThreadPoolRunnable   toRun;
        private Runnable toRunRunnable;
        private boolean    shouldRun;
    	
    	public ControlRunnable(ThreadPool tp) {
    		toRun = null;
    		shouldTerminate = false;
            shouldRun = false;
            this.tp = tp;
            t = new ThreadWithAttributes(tp, this);
            t.setDaemon(true);
            t.setName(tp.getName() + "-Processor" + tp.incSequence());
            t.setPriority(tp.getThreadPriority());
            tp.addThread(t, this);
            t.start();
    	}
    	
    	public void run() {
    		boolean _shouldRun = false;
            boolean _shouldTerminate = false;
            ThreadPoolRunnable _toRun = null;
            try {
            	while(true) {
            		try {
            			synchronized(this) {
            				System.out.println("shouldRun=" + shouldRun);
            				while(!shouldRun && !shouldTerminate) {
            					this.wait();
            				}
            				_shouldRun = shouldRun;
                            _shouldTerminate = shouldTerminate;
                            _toRun = toRun;
            			}
            			if (_shouldTerminate)
                            break;
            			try {
            				if(_shouldRun) {
            					if (_toRun != null) {
            						_toRun.runIt(t.getThreadData(tp));
            					} else if (toRunRunnable != null) {
            						toRunRunnable.run();
            					} else {
            					}
            				}
            			} catch(Throwable r) {
            				_shouldTerminate = true;
                            _shouldRun = false;
                            tp.notifyThreadEnd(this);
            			} finally {
            				if(_shouldRun) {
            					shouldRun = false;
            					tp.returnController(this);
            				}
            				
            			}
            			if (_shouldTerminate) {
                            break;
                        }
            		} catch(InterruptedException e) {
            			
            		}
            	}
            } finally {
            	tp.removeThread(Thread.currentThread());
            }
            
    	}
    	
    	public synchronized void runIt(Runnable toRun) {
    	    this.toRunRunnable = toRun;
            shouldRun = true;
            this.notify();
        }
    	
    	public synchronized void runIt(ThreadPoolRunnable toRun) {
    	    this.toRun = toRun;
            shouldRun = true;
            this.notify();
        }
    	
    	public void stop() {
            this.terminate();
        }

        public void kill() {
            t.stop();
        }
    	
    	public synchronized void terminate() {
    		shouldTerminate = true;
    		this.notify();
    	}
    }
    
    public String getName() {
    	return name;
    }
    
    public boolean getDaemon() {
    	return isDaemon;
    }

	public int getThreadPriority() {
		return threadPriority;
	}
	
	public int incSequence() {
		return sequence ++;
	}

	public void setThreadPriority(int threadPriority) {
		this.threadPriority = threadPriority;
	}

}

 ThreadWithAttributes.java

Java代码 复制代码
  1. package com.xiao.tomcatthreadpool;   
  2.   
  3. import java.util.Hashtable;   
  4.   
  5. public class ThreadWithAttributes extends Thread {   
  6.        
  7.     private Object control;   
  8.     public static int MAX_NOTES=16;   
  9.     private Object notes[]=new Object[MAX_NOTES];   
  10.     private Hashtable attributes=new Hashtable();   
  11.     private String currentStage;   
  12.     private Object param;   
  13.        
  14.     private Object thData[];   
  15.   
  16.     public ThreadWithAttributes(Object control, Runnable r) {   
  17.         super(r);   
  18.         this.control=control;   
  19.     }   
  20.        
  21.     public final Object[] getThreadData(Object control ) {   
  22.         return thData;   
  23.     }   
  24.        
  25.     public final void setThreadData(Object control, Object thData[] ) {   
  26.         this.thData=thData;   
  27.     }   
  28.   
  29.     public final void setNote( Object control, int id, Object value ) {   
  30.         ifthis.control != control ) return;   
  31.         notes[id]=value;   
  32.     }   
  33.   
  34.     public final String getCurrentStage(Object control) {   
  35.         ifthis.control != control ) return null;   
  36.         return currentStage;   
  37.     }   
  38.   
  39.     /** Information about the current request ( or the main object 
  40.      * we are processing )  
  41.      */  
  42.     public final Object getParam(Object control) {   
  43.         ifthis.control != control ) return null;   
  44.         return param;   
  45.     }   
  46.   
  47.     public final void setCurrentStage(Object control, String currentStage) {   
  48.         ifthis.control != control ) return;   
  49.         this.currentStage = currentStage;   
  50.     }   
  51.   
  52.     public final void setParam( Object control, Object param ) {   
  53.         ifthis.control != control ) return;   
  54.         this.param=param;   
  55.     }   
  56.   
  57.     public final Object getNote(Object control, int id ) {   
  58.         ifthis.control != control ) return null;   
  59.         return notes[id];   
  60.     }   
  61.   
  62.     public final Hashtable getAttributes(Object control) {   
  63.         ifthis.control != control ) return null;   
  64.         return attributes;   
  65.     }   
  66. }  
package com.xiao.tomcatthreadpool;

import java.util.Hashtable;

public class ThreadWithAttributes extends Thread {
    
    private Object control;
    public static int MAX_NOTES=16;
    private Object notes[]=new Object[MAX_NOTES];
    private Hashtable attributes=new Hashtable();
    private String currentStage;
    private Object param;
    
    private Object thData[];

    public ThreadWithAttributes(Object control, Runnable r) {
        super(r);
        this.control=control;
    }
    
    public final Object[] getThreadData(Object control ) {
        return thData;
    }
    
    public final void setThreadData(Object control, Object thData[] ) {
        this.thData=thData;
    }

    public final void setNote( Object control, int id, Object value ) {
        if( this.control != control ) return;
        notes[id]=value;
    }

    public final String getCurrentStage(Object control) {
        if( this.control != control ) return null;
        return currentStage;
    }

    /** Information about the current request ( or the main object
     * we are processing )
     */
    public final Object getParam(Object control) {
        if( this.control != control ) return null;
        return param;
    }

    public final void setCurrentStage(Object control, String currentStage) {
        if( this.control != control ) return;
        this.currentStage = currentStage;
    }

    public final void setParam( Object control, Object param ) {
        if( this.control != control ) return;
        this.param=param;
    }

    public final Object getNote(Object control, int id ) {
        if( this.control != control ) return null;
        return notes[id];
    }

    public final Hashtable getAttributes(Object control) {
        if( this.control != control ) return null;
        return attributes;
    }
}

 ThreadPoolRunnable.java

Java代码 复制代码
  1. package com.xiao.tomcatthreadpool;   
  2.   
  3. public interface ThreadPoolRunnable {   
  4.        
  5.     public Object[] getInitData();   
  6.        
  7.     public void runIt(Object thData[]);   
  8.   
  9. }  
package com.xiao.tomcatthreadpool;

public interface ThreadPoolRunnable {
	
	public Object[] getInitData();
	
	public void runIt(Object thData[]);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值