• java线程深入学习


    一、java中的线程是通过Thread类创建的,

     1 //下面是构造函数,一个共同的特点就是:都是调用init()进行创建的
     2    public Thread() {
     3         init(null, null, "Thread-" + nextThreadNum(), 0);
     4     }
     5 
     6     public Thread(Runnable target) {
     7         init(null, target, "Thread-" + nextThreadNum(), 0);
     8     }
     9 
    10     Thread(Runnable target, AccessControlContext acc) {
    11         init(null, target, "Thread-" + nextThreadNum(), 0, acc);
    12     }
    13 
    14     public Thread(ThreadGroup group, Runnable target) {
    15         init(group, target, "Thread-" + nextThreadNum(), 0);
    16     }
    17 
    18     public Thread(String name) {
    19         init(null, null, name, 0);
    20     }
    21 
    22     public Thread(ThreadGroup group, String name) {
    23         init(group, null, name, 0);
    24     }
    25 
    26     public Thread(Runnable target, String name) {
    27         init(null, target, name, 0);
    28     }
    29 
    30     public Thread(ThreadGroup group, Runnable target, String name) {
    31         init(group, target, name, 0);
    32     }
    33 
    34     public Thread(ThreadGroup group, Runnable target, String name,
    35                   long stackSize) {
    36         init(group, target, name, stackSize);
    37     }
    38 
    39 //init()方法有两个
    40     private void init(ThreadGroup g, Runnable target, String name,
    41                       long stackSize) {
    42         init(g, target, name, stackSize, null);
    43     }
    44 /*g:用于将线程分组管理
    45  *target:用于指定线程将要执行的任务
    46  *name:线程的名字
    47  *stackSize:
    48  *acc:
    49  */
    50     private void init(ThreadGroup g, Runnable target, String name,
    51                       long stackSize, AccessControlContext acc) {
    52         //线程必须有一个名字,默认情况下是Thread-x,x是从0开始的int型数
    53         if (name == null) {
    54             throw new NullPointerException("name cannot be null");
    55         }
    56 
    57         this.name = name.toCharArray();
    58 
    59         Thread parent = currentThread();
    60         SecurityManager security = System.getSecurityManager();
    61         if (g == null) {
    62             /* Determine if it's an applet or not */
    63 
    64             /* If there is a security manager, ask the security manager
    65                what to do. */
    66             if (security != null) {
    67                 g = security.getThreadGroup();
    68             }
    69 
    70             /* If the security doesn't have a strong opinion of the matter
    71                use the parent thread group. */
    72             if (g == null) {
    73                 g = parent.getThreadGroup();
    74             }
    75         }
    Thread源码

    从构造函数可以看出,创建一个有意义的线程(有可执行的任务),就是向其中传递一个实现Runnable的对象即可,但是也可以继承Thread类(因为该类已经实现了前一条),然后重写run()即可。后一种方法不推荐,因为这个类最重要的是提供需要执行的方法即可。

    上图显示了线程状态转换的条件,这些方法的源码见下:

      1 public synchronized void start() {
      2         /**
      3          * 0 状态代表 "NEW".
      4          */
      5         if (threadStatus != 0)
      6             throw new IllegalThreadStateException();
      7 
      8         group.add(this);
      9 
     10         boolean started = false;
     11         try {
     12             start0();
     13             started = true;
     14         } finally {
     15             try {
     16                 if (!started) {
     17                     group.threadStartFailed(this);
     18                 }
     19             } catch (Throwable ignore) {
     20                 /* do nothing. If start0 threw a Throwable then
     21                   it will be passed up the call stack */
     22             }
     23         }
     24     }
     25 
     26     private native void start0();
     27 
     28     //执行target的run(),在start之后自动执行,
     29     public void run() {
     30         if (target != null) {
     31             target.run();
     32         }
     33     }
     34 
     35     //中断线程
     36     public void interrupt() {
     37         //判断是否是当前正在执行的线程,检查权限
     38         if (this != Thread.currentThread())
     39             checkAccess();
     40 
     41         synchronized (blockerLock) {
     42             Interruptible b = blocker;
     43             if (b != null) {
     44                 interrupt0();           // Just to set the interrupt flag
     45                 b.interrupt(this);
     46                 return;
     47             }
     48         }
     49         interrupt0();
     50     }    
     51 
     52     //从源码可以看到,该方法调用wait(),使alive状态(start之后,die之前)线程进入等待状态
     53     public final synchronized void join(long millis)
     54     throws InterruptedException {
     55         long base = System.currentTimeMillis();
     56         long now = 0;
     57 
     58         if (millis < 0) {
     59             throw new IllegalArgumentException("timeout value is negative");
     60         }
     61 
     62         if (millis == 0) {
     63             while (isAlive()) {
     64                 wait(0);
     65             }
     66         } else {
     67             while (isAlive()) {
     68                 long delay = millis - now;
     69                 if (delay <= 0) {
     70                     break;
     71                 }
     72                 wait(delay);
     73                 now = System.currentTimeMillis() - base;
     74             }
     75         }
     76     }
     77 
     78     public final synchronized void join(long millis, int nanos)
     79     throws InterruptedException {
     80 
     81         if (millis < 0) {
     82             throw new IllegalArgumentException("timeout value is negative");
     83         }
     84 
     85         if (nanos < 0 || nanos > 999999) {
     86             throw new IllegalArgumentException(
     87                                 "nanosecond timeout value out of range");
     88         }
     89 
     90         if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
     91             millis++;
     92         }
     93 
     94         join(millis);
     95     }
     96 
     97     public final void join() throws InterruptedException {
     98         join(0);
     99     }
    100 
    101     //不能指定时间的休眠
    102     public static native void yield();
    103 
    104     //让线程休眠指定时间
    105     public static native void sleep(long millis) throws InterruptedException;
    106 
    107     public static void sleep(long millis, int nanos)
    108     throws InterruptedException {
    109         if (millis < 0) {
    110             throw new IllegalArgumentException("timeout value is negative");
    111         }
    112 
    113         if (nanos < 0 || nanos > 999999) {
    114             throw new IllegalArgumentException(
    115                                 "nanosecond timeout value out of range");
    116         }
    117 
    118         if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
    119             millis++;
    120         }
    121 
    122         sleep(millis);
    123     }
    124 
    125 
    126 //java线程的几种状态判断
    127     //start之后die之前都是alive状态
    128     public final native boolean isAlive();
    129 
    130 Thread源码
    Thread源码

      需要注意的是start()是启动一个线程,而run只是执行一个普通方法

      一个线程要执行需要满足两个条件,一是获取CPU,二是获取锁(在有同步情况下),这就是阻塞产生的原因。由前可知阻塞有两种情况,一种是阻塞自身(当前)线程(等待阻塞),另一种就是阻塞其他线程(同步阻塞)。上面有些方法类似,但是就是有这些细小的区别。例如:

      ①sleep:java中该方法是静态方法(所以一般应该有Thread而非实例进行调用),调用该方法会当前线程使释放CPU,但是不释放锁。下面的例子中在main()中使用t1.sleep()不能使t1休眠,因为t1只是一个普通的Thread对象,而不是线程,其在主线程中使用,所以是主线程休眠,所以出现以下效果。另外注意sleep是静态方法,最好使用类名即Thread调用

     1     public static void main(String[] args) throws InterruptedException {
     2         Thread t1=new Thread(new Runnable(){
     3             @Override
     4             public void run() {
     5                 for(int i=0;i<10;i++){
     6                     System.out.println("i="+i);
     7                 }
     8             }
     9         });
    10         Thread t2=new Thread(new Runnable(){
    11             @Override
    12             public void run() {
    13                 for(int j=0;j<10;j++){
    14                     System.out.println("j="+j);
    15                 }
    16             }
    17         });
    18         t1.start();
    19 //        t1.sleep(5000);        //和下面效果一样,但是最好使用下面的方式
    20         Thread.sleep(5000);
    21         System.out.println("a");
    22         t2.start();
    23     }
    等待阻塞

      ②wait:当前线程休眠,释放CPU,同时释放锁,wait是Object对象的方法,必须在同步块中使用,并且由notify或者notifyAll进行唤醒。那么需要使用那个对象的wait()呢,这个问题其实和在那个对象上同步是一样的问题,其实就是在多个线程需要使用的那个对象,在该对象上加锁,并且调用这个对象的wait()

    更过关于这些方法的区别参见:http://blog.csdn.net/evane1890/article/details/3313416

     二、多线程:上面是线程的一些基本情况,但是通常都是有多个线程一起使用的。线程之间共享同一片内存区域,所以当多个线程访问同一个数据时就可能出错,为了获得最佳速度,java允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。这就引起了多线程的一个问题:可见性。另一个问题就是对于一段代码,只允许一个线程操作,即互斥性。

      volatile关键字可以解决可见性的问题,所以对于多个线程访问的变量可以使用。但是其并不能解决原子性,所以并不能保证在多线程中的安全。参考:http://blog.csdn.net/zmissm/article/details/23484457http://blog.csdn.net/ns_code/article/details/17101369  

    三、Java中的锁机制:

      1、同步锁:

        Java中最早解决多线程访问的锁机制就是同步锁,即通过synchronized关键字,并使用的是一个对象(本质上是对象的内置锁)来对一段代码进行锁定,直到当前代码执行完成才释放锁。而其他线程必须等到释放锁之后才能重新获取执行。

      注意:①一个对象只有一个锁,所以使用这个对象作为锁的代码块(可以是多个)只能有一个线程执行。例如有两个线程a和b,分别执行x和y代码块,但是由于x和y同时使用o作为锁,所以当a执行x时,b不能执行y。

        ②synchronized可以用于方法上(此时默认使用this对象,不需要手动设置),也可以使用在方法中的一段代码上,此时需要指定用哪个对象进行锁定,通常是那个多个线程需要访问的对象。也可以在一个静态方法上使用synchronized,使用的是this.class对象作为锁,它会锁住整个类中的代码块;相同的,如果要在一个静态块中使用同步,也必须使用this.class作为锁。

        ③如果在一段同步代码之内进行多个线程间进行消息传递,使用的是wait()/notify()/notifyAll(),就是那个用来锁定的对象Object的方法。

      2、Lock锁:synchronized是语法上的实现,在Java1.5版本之后引入了Lock概念,使锁作为一种类的存在,在java.util.concurrent.locks包下。

        其中Lock和ReadWriteLock是接口,定义了锁需要实现的功能,主要的就是lock()/unlock()和readLock()/writeLock()。这里就和synchronized有一个很大的区别即是Lock需要手动的释放锁,并且通常需要在加锁的代码上使用try并将解锁的部分在finally中执行

     1     public boolean offer(E e) {
     2         checkNotNull(e);
     3         final ReentrantLock lock = this.lock;
     4         lock.lock();
     5         try {
     6             if (count == items.length)
     7                 return false;
     8             else {
     9                 insert(e);
    10                 return true;
    11             }
    12         } finally {
    13             lock.unlock();
    14         }
    15     }
    ArrayBlockingQueue中offer()

        在1.5版本中,提供了两个实现:ReentrantLock,ReentrantReadWriteLock。其中,ReentrantLock的效果更类似与synchronized,但是增加了一些特性以提高性能;而ReentrantReadWriteLock则是读写锁,在实现上如果写锁执行,则其他线程都阻塞,但是如果读锁执行则会阻塞写锁而不会阻塞其他的读锁

        同样的,为了在多个线程中实现通信,提供了Condition接口,其中await()/signal()/signalAll()对应了object中的三个方法,区别是这几个方法可以不在锁的范围内进行操作。该接口的实现类由Lock的实现类提供,调用其newCondition()即可。也就是说Condition对象是绑定到Lock对象上的,而且一个Lock对象可以生成多个Condition。

     1     public ArrayBlockingQueue(int capacity, boolean fair) {
     2         if (capacity <= 0)
     3             throw new IllegalArgumentException();
     4         this.items = new Object[capacity];
     5         lock = new ReentrantLock(fair);
     6         notEmpty = lock.newCondition();
     7         notFull =  lock.newCondition();
     8     }
     9 
    10     private void insert(E x) {
    11         items[putIndex] = x;
    12         putIndex = inc(putIndex);
    13         ++count;
    14         //可以看到这里调用了signal来唤醒相关线程,但是锁是在offer()中添加的
    15         notEmpty.signal();
    16     }
    ArrayBlockingQueue部分函数

        下面是ReentrantLock的类图关系,ReentrantLock中的实现都是依赖其中的Sync的有效子类NonfairSync(非公平锁)和FairSync(公平锁)。而锁的实现依赖于其父类。

     

     1     private final Sync sync;
     2 
     3     //构造函数
     4     public ReentrantLock() {
     5         sync = new NonfairSync();
     6     }
     7     public ReentrantLock(boolean fair) {
     8         sync = fair ? new FairSync() : new NonfairSync();
     9     }
    10 
    11     //功能代码
    12     public void lock() {
    13         sync.lock();
    14     }
    15 
    16     public boolean tryLock() {
    17         return sync.nonfairTryAcquire(1);
    18     }
    19 
    20     public void unlock() {
    21         sync.release(1);
    22     }
    23 
    24     public Condition newCondition() {
    25         return sync.newCondition();
    26     }
    ReetrantLock源码1
      1     abstract static class Sync extends AbstractQueuedSynchronizer {
      2         private static final long serialVersionUID = -5179523762034025860L;
      3 
      4         abstract void lock();
      5 
      6         final boolean nonfairTryAcquire(int acquires) {
      7             final Thread current = Thread.currentThread();
      8             int c = getState();
      9             if (c == 0) {
     10                 if (compareAndSetState(0, acquires)) {
     11                     setExclusiveOwnerThread(current);
     12                     return true;
     13                 }
     14             }
     15             else if (current == getExclusiveOwnerThread()) {
     16                 int nextc = c + acquires;
     17                 if (nextc < 0) // overflow
     18                     throw new Error("Maximum lock count exceeded");
     19                 setState(nextc);
     20                 return true;
     21             }
     22             return false;
     23         }
     24 
     25         protected final boolean tryRelease(int releases) {
     26             int c = getState() - releases;
     27             if (Thread.currentThread() != getExclusiveOwnerThread())
     28                 throw new IllegalMonitorStateException();
     29             boolean free = false;
     30             if (c == 0) {
     31                 free = true;
     32                 setExclusiveOwnerThread(null);
     33             }
     34             setState(c);
     35             return free;
     36         }
     37 
     38         protected final boolean isHeldExclusively() {
     39             // While we must in general read state before owner,
     40             // we don't need to do so to check if current thread is owner
     41             return getExclusiveOwnerThread() == Thread.currentThread();
     42         }
     43 
     44         final ConditionObject newCondition() {
     45             return new ConditionObject();
     46         }
     47 
     48         // Methods relayed from outer class
     49 
     50         final Thread getOwner() {
     51             return getState() == 0 ? null : getExclusiveOwnerThread();
     52         }
     53 
     54         final int getHoldCount() {
     55             return isHeldExclusively() ? getState() : 0;
     56         }
     57 
     58         final boolean isLocked() {
     59             return getState() != 0;
     60         }
     61 
     62         private void readObject(java.io.ObjectInputStream s)
     63             throws java.io.IOException, ClassNotFoundException {
     64             s.defaultReadObject();
     65             setState(0); // reset to unlocked state
     66         }
     67     }
     68 
     69     /**
     70      * Sync object for non-fair locks
     71      */
     72     static final class NonfairSync extends Sync {
     73         private static final long serialVersionUID = 7316153563782823691L;
     74 
     75         /**
     76          * Performs lock.  Try immediate barge, backing up to normal
     77          * acquire on failure.
     78          */
     79         final void lock() {
     80             if (compareAndSetState(0, 1))
     81                 setExclusiveOwnerThread(Thread.currentThread());
     82             else
     83                 acquire(1);
     84         }
     85 
     86         protected final boolean tryAcquire(int acquires) {
     87             return nonfairTryAcquire(acquires);
     88         }
     89     }
     90 
     91     /**
     92      * Sync object for fair locks
     93      */
     94     static final class FairSync extends Sync {
     95         private static final long serialVersionUID = -3000897897090466540L;
     96 
     97         final void lock() {
     98             acquire(1);
     99         }
    100 
    101         /**
    102          * Fair version of tryAcquire.  Don't grant access unless
    103          * recursive call or no waiters or is first.
    104          */
    105         protected final boolean tryAcquire(int acquires) {
    106             final Thread current = Thread.currentThread();
    107             int c = getState();
    108             if (c == 0) {
    109                 if (!hasQueuedPredecessors() &&
    110                     compareAndSetState(0, acquires)) {
    111                     setExclusiveOwnerThread(current);
    112                     return true;
    113                 }
    114             }
    115             else if (current == getExclusiveOwnerThread()) {
    116                 int nextc = c + acquires;
    117                 if (nextc < 0)
    118                     throw new Error("Maximum lock count exceeded");
    119                 setState(nextc);
    120                 return true;
    121             }
    122             return false;
    123         }
    124     }
    ReetrantLock源码2

    有关AbstractQueuedSynchronizer实现原理的内容参考:http://www.infoq.com/cn/articles/jdk1.8-abstractqueuedsynchronizer,其中有两个基本:1,使用volatile修饰的state变量用于设置是否有线程获得锁,2,一个FIFO的队列用于保存挂起的线程。

    四、有了以上的基础,就可以实现多线程中的一个经典例子:生产者消费者模型,就是有一个仓库,生产者将商品放入其中,当仓满时则不能生产,并阻塞所有的生成线程;此时只能由消费者线程进行消费,但是当仓空时就需要阻塞消费者线程而唤醒生产者线程进行生产。

      1、使用同步锁,以及wait/notifyAll机制实现,其中需要注意的是wait的使用规范,详细参见http://www.importnew.com/16453.html,有一条重要的就是:在while中而不是if中使用wait.

      1 package thread;
      2 
      3 import java.util.LinkedList;
      4 import java.util.Queue;
      5 
      6 public class ProducerConsumer2 {
      7     public static void main(String[] args) {
      8         Storage s=new Storage();
      9         
     10         Producer p1=new Producer(s);
     11         Producer p2=new Producer(s);
     12         Producer p3=new Producer(s);
     13         
     14         Consumer c1=new Consumer(s);
     15         Consumer c2=new Consumer(s);
     16         Consumer c3=new Consumer(s);
     17         Consumer c4=new Consumer(s);
     18         Consumer c5=new Consumer(s);
     19         
     20         Thread t1=new Thread(p1,"p1");
     21         Thread t2=new Thread(p2,"p2");
     22         Thread t3=new Thread(p3,"p3");
     23         Thread t4=new Thread(c1,"c1");
     24         Thread t5=new Thread(c2,"c2");
     25         Thread t6=new Thread(c3,"c3");
     26         Thread t7=new Thread(c4,"c4");
     27         Thread t8=new Thread(c5,"c5");
     28         
     29         t1.start();
     30         t2.start();
     31         t3.start();
     32         t4.start();
     33         t5.start();
     34         t6.start();
     35         t7.start();
     36         t8.start();
     37     }
     38 }
     39 
     40 class Product{
     41     private int id;
     42     
     43     public Product(int id) {
     44         this.id=id;
     45     }
     46 
     47     @Override
     48     public String toString() {
     49         return "Product [id=" + id + "]";
     50     }
     51 }
     52 
     53 //仓库对象,生产者和消费者之间的桥梁
     54 class Storage{
     55     //仓库容量
     56     Queue<Product> queues = new LinkedList<Product>();
     57     
     58     //生产,即向仓库中添加产品
     59     public void push(Product s){
     60         queues.add(s);
     61     }
     62     
     63     //消费
     64     public Product pop(){
     65         return queues.remove();
     66     }
     67 }
     68 
     69 class Producer implements Runnable{
     70     private Storage s;
     71     
     72     public Producer(Storage s) {
     73         this.s=s;
     74     }
     75     
     76     @Override
     77     public void run() {
     78             while(true){
     79                 synchronized(s.queues){
     80                     while(s.queues.size()>=10){
     81                         try{
     82                             System.out.println(Thread.currentThread().getName()+",队满,不能添加");
     83                             s.queues.wait();
     84                         }catch(InterruptedException e){
     85                             e.printStackTrace();
     86                         }
     87                     }
     88                     Product p=new Product((int)(Math.random()*10000));
     89                     s.push(p);
     90                     System.out.println(Thread.currentThread().getName()+",生产了产品,现在有"+s.queues.size());
     91                     System.out.println("============================================");
     92                     s.queues.notifyAll();
     93                 }
     94             }
     95     }
     96 }
     97 
     98 class Consumer implements Runnable{
     99     private Storage s;
    100     
    101     public Consumer(Storage s) {
    102         this.s=s;
    103     }
    104     
    105     @Override
    106     public void run() {
    107             while(true){
    108                 synchronized(s.queues){
    109                     //注意这里的while不能换为if
    110                     while(s.queues.isEmpty()){
    111                         try{
    112                             System.out.println("队空,不能消费");
    113                             s.queues.wait();
    114                         }catch(InterruptedException e){
    115                             e.printStackTrace();
    116                         }
    117                     }
    118                     s.pop();
    119                     System.out.println(Thread.currentThread().getName()+",消费了产品,现在有"+s.queues.size());
    120                     System.out.println("============================================");
    121                     s.queues.notifyAll();
    122                 }
    123             }
    124     }
    125 }
    生产者消费者模型-1

      2、使用Lock锁实现

     1 class Sto{
     2     //使用Lock对象代替synchronized,使用Condition进行线程间通讯
     3     Lock lock=new ReentrantLock();
     4     Condition condition=lock.newCondition();
     5     
     6     //仓库容量
     7     Queue<Pro> queues = new LinkedList<Pro>();
     8     
     9     //生产,即向仓库中添加产品
    10     public void push(){
    11         lock.lock();
    12 //        synchronized(queues){
    13             try {
    14                 while(queues.size()>=10){
    15                     System.out.println("队满,不能生成");
    16                     try {
    17 //                        queues.wait();
    18                         condition.await();
    19                     } catch (InterruptedException e) {
    20                         e.printStackTrace();
    21                     }
    22                 }
    23                 Pro p=new Pro((int)Math.random());
    24                 queues.add(p);
    25                 System.out.println("p=================="+queues.size()+"=====================");
    26 //                queues.notifyAll();
    27                 condition.signalAll();
    28             } finally{
    29                 lock.unlock();
    30             }
    31 //        }
    32     }
    33     
    34     //消费
    35     public void pop(){
    36         lock.lock();
    37 //        synchronized(queues){
    38             while(queues.isEmpty()){
    39                 System.out.println("队空,不能消费");
    40                 try {
    41 //                    queues.wait();
    42                     condition.await();
    43                 } catch (InterruptedException e) {
    44                     e.printStackTrace();
    45                 }
    46             }
    47             queues.remove();
    48             System.out.println("c======================"+queues.size()+"==========================");
    49 //            queues.notifyAll();
    50             condition.signalAll();
    51 //        }
    52     }
    53 }
    生产者和消费者模型-2

      3、使用阻塞队列实现

    五、其他。

    1、ThreadLocal:为当前线程保存一份私有变量,隔离其他线程的访问.主要的操作就是添加/获取/移除/initialValue。

     1 //获取当前线程保存的此线程局部的初始值,实现未返回null,提供给子类进行覆写的
     2     protected T initialValue() {
     3         return null;
     4     }
     5 //为当前线程设置线程私有变量
     6     public void set(T value) {
     7         Thread t = Thread.currentThread();
     8         ThreadLocalMap map = getMap(t);
     9         if (map != null)
    10             map.set(this, value);
    11         else
    12             createMap(t, value);
    13     }
    14 //获取当前线程保存的线程私有变量
    15     public T get() {
    16         Thread t = Thread.currentThread();
    17         ThreadLocalMap map = getMap(t);
    18         if (map != null) {
    19             ThreadLocalMap.Entry e = map.getEntry(this);
    20             if (e != null) {
    21                 @SuppressWarnings("unchecked")
    22                 T result = (T)e.value;
    23                 return result;
    24             }
    25         }
    26         return setInitialValue();
    27     }
    28     private T setInitialValue() {
    29         T value = initialValue();
    30         Thread t = Thread.currentThread();
    31         ThreadLocalMap map = getMap(t);
    32         if (map != null)
    33             map.set(this, value);
    34         else
    35             createMap(t, value);
    36         return value;
    37     }
    38 //移除
    39      public void remove() {
    40          ThreadLocalMap m = getMap(Thread.currentThread());
    41          if (m != null)
    42              m.remove(this);
    43      }
    ThreadLocal源码

      之所以这些变量操作和线程绑定,是因为①操作时首先获取当前线程对象,②Thread类中有一个ThreadLocal.ThreadLocalMap的成员进行保存变量

    参考:http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83]%E8%A7%A3%E5%AF%86ThreadLocal/

    2、Executor框架线程池.

  • 相关阅读:
    【JZOJ3188】找数【数论,数学】
    【JZOJ3187】的士【模拟】
    【JZOJ3187】的士【模拟】
    【洛谷P1641】生成字符串【数论,数学】
    【洛谷P1896】互不侵犯【状压dp】
    聚集索引与非聚集索引
    哈希索引
    索引能提高检索速度,降低维护速度
    MySQL索引基本知识
    注解
  • 原文地址:https://www.cnblogs.com/songfeilong2325/p/5140092.html
Copyright © 2020-2023  润新知