ThreadLocal使用

ThreadLocal可以让线程拥有本地变量,在web环境中,为了方便代码解耦,我们通常用它来保存上下文信息,然后用一个util类提供访问入口,从controller层到service层可以很方便的获取上下文。下面我们通过代码来研究一下ThreadLocal。

新建一个ThreadContext类,用于保存线程上下文信息

  1. public class ThreadContext {
  2. private static ThreadLocal<UserObj> userResource = new ThreadLocal<UserObj>();
  3. public static UserObj getUser() {
  4. return userResource.get();
  5. }
  6. public static void bindUser(UserObj user) {
  7. userResource.set(user);
  8. }
  9. public static UserObj unbindUser() {
  10. UserObj obj = userResource.get();
  11. userResource.remove();
  12. return obj;
  13. }
  14. }

新建一个sessionFilter ,用来操作线程变量

  1. @Override
  2. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  3. HttpServletRequest request = (HttpServletRequest) servletRequest;
  4. try {
  5. // 假设这里是从cookie拿token信息, 调用服务/或者从缓存查询用户信息
  6. // 为了避免后续逻辑中多次查询/请求缓存服务器, 这里拿到user后放到线程本地变量中
  7. UserObj user = ThreadContext.getUser();
  8. // 如果当前线程中没有绑定user对象,那么绑定一个新的user
  9. if (user == null) {
  10. ThreadContext.bindUser(new UserObj("usertest"));
  11. }
  12. filterChain.doFilter(servletRequest, servletResponse);
  13. } finally {
  14. // ThreadLocal的生命周期不等于一次request请求的生命周期
  15. // 每个request请求的响应是tomcat从线程池中分配的线程, 线程会被下个请求复用.
  16. // 所以请求结束后必须删除线程本地变量
  17. // ThreadContext.unbindUser();
  18. }
  19. }

新建UserUtils工具类

  1. /**
  2. * 配合SessionFilter使用,从上下文中取user信息
  3. */
  4. public class UserUtils {
  5. public static UserObj getCurrentUser() {
  6. return ThreadContext.getUser();
  7. }
  8. }

新建一个servlet测试

  1. public class HelloworldServlet extends HttpServlet {
  2. private static Logger logger = LoggerFactory.getLogger(HelloworldServlet.class);
  3. @Override
  4. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  5. UserObj user = UserUtils.getCurrentUser();
  6. logger.info(user.getName() + user.hashCode());
  7. super.doGet(req, resp);
  8. }
  9. @Override
  10. protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  11. super.doGet(req, resp);
  12. }
  13. }

循环请求servlet,控制台显示结果如下。可以发现tomcat线程池的初始大小是10个,后面的请求复用了前面的线程,ThreadContext中的user对象的hashcode也一样。

  1. 2016-11-29 17:21:35.975 INFO 36672 --- [nio-8080-exec-2] com.zallds.xy.servlet.HelloworldServlet : usertest818202673
  2. 2016-11-29 17:21:38.923 INFO 36672 --- [nio-8080-exec-3] com.zallds.xy.servlet.HelloworldServlet : usertest1582591702
  3. 2016-11-29 17:21:45.810 INFO 36672 --- [nio-8080-exec-4] com.zallds.xy.servlet.HelloworldServlet : usertest55755037
  4. 2016-11-29 17:21:46.773 INFO 36672 --- [nio-8080-exec-5] com.zallds.xy.servlet.HelloworldServlet : usertest1495466807
  5. 2016-11-29 17:21:47.345 INFO 36672 --- [nio-8080-exec-6] com.zallds.xy.servlet.HelloworldServlet : usertest1149360245
  6. 2016-11-29 17:21:47.613 INFO 36672 --- [nio-8080-exec-7] com.zallds.xy.servlet.HelloworldServlet : usertest518375339
  7. 2016-11-29 17:21:47.837 INFO 36672 --- [nio-8080-exec-8] com.zallds.xy.servlet.HelloworldServlet : usertest92458992
  8. 2016-11-29 17:21:48.012 INFO 36672 --- [nio-8080-exec-9] com.zallds.xy.servlet.HelloworldServlet : usertest944867034
  9. 2016-11-29 17:21:48.199 INFO 36672 --- [io-8080-exec-10] com.zallds.xy.servlet.HelloworldServlet : usertest1410972809
  10. 2016-11-29 17:21:48.378 INFO 36672 --- [nio-8080-exec-1] com.zallds.xy.servlet.HelloworldServlet : usertest805332046
  11. 2016-11-29 17:21:48.552 INFO 36672 --- [nio-8080-exec-2] com.zallds.xy.servlet.HelloworldServlet : usertest818202673
  12. 2016-11-29 17:21:48.730 INFO 36672 --- [nio-8080-exec-3] com.zallds.xy.servlet.HelloworldServlet : usertest1582591702
  13. 2016-11-29 17:21:48.903 INFO 36672 --- [nio-8080-exec-4] com.zallds.xy.servlet.HelloworldServlet : usertest55755037
  14. 2016-11-29 17:21:49.072 INFO 36672 --- [nio-8080-exec-5] com.zallds.xy.servlet.HelloworldServlet : usertest1495466807
  15. 2016-11-29 17:21:49.247 INFO 36672 --- [nio-8080-exec-6] com.zallds.xy.servlet.HelloworldServlet : usertest1149360245
  16. 2016-11-29 17:21:49.402 INFO 36672 --- [nio-8080-exec-7] com.zallds.xy.servlet.HelloworldServlet : usertest518375339

去掉注释// ThreadContext.unbindUser(); 重新请求,每次从ThreadLocal中拿到的user对象完全不一样了。

  1. 2016-11-29 17:30:37.150 INFO 36903 --- [nio-8080-exec-1] com.zallds.xy.servlet.HelloworldServlet : usertest413138571
  2. 2016-11-29 17:30:42.932 INFO 36903 --- [nio-8080-exec-2] com.zallds.xy.servlet.HelloworldServlet : usertest1402191945
  3. 2016-11-29 17:30:43.124 INFO 36903 --- [nio-8080-exec-3] com.zallds.xy.servlet.HelloworldServlet : usertest1957579173
  4. 2016-11-29 17:30:43.313 INFO 36903 --- [nio-8080-exec-4] com.zallds.xy.servlet.HelloworldServlet : usertest1582591702
  5. 2016-11-29 17:30:43.501 INFO 36903 --- [nio-8080-exec-5] com.zallds.xy.servlet.HelloworldServlet : usertest1917479582
  6. 2016-11-29 17:30:43.679 INFO 36903 --- [nio-8080-exec-6] com.zallds.xy.servlet.HelloworldServlet : usertest772036767
  7. 2016-11-29 17:30:43.851 INFO 36903 --- [nio-8080-exec-7] com.zallds.xy.servlet.HelloworldServlet : usertest162020761
  8. 2016-11-29 17:30:44.024 INFO 36903 --- [nio-8080-exec-8] com.zallds.xy.servlet.HelloworldServlet : usertest682232950
  9. 2016-11-29 17:30:44.225 INFO 36903 --- [nio-8080-exec-9] com.zallds.xy.servlet.HelloworldServlet : usertest2140650341
  10. 2016-11-29 17:30:44.419 INFO 36903 --- [io-8080-exec-10] com.zallds.xy.servlet.HelloworldServlet : usertest1327601763
  11. 2016-11-29 17:30:44.593 INFO 36903 --- [nio-8080-exec-1] com.zallds.xy.servlet.HelloworldServlet : usertest647738411
  12. 2016-11-29 17:30:44.787 INFO 36903 --- [nio-8080-exec-2] com.zallds.xy.servlet.HelloworldServlet : usertest944867034
  13. 2016-11-29 17:30:45.045 INFO 36903 --- [nio-8080-exec-3] com.zallds.xy.servlet.HelloworldServlet : usertest1886154520
  14. 2016-11-29 17:30:45.317 INFO 36903 --- [nio-8080-exec-4] com.zallds.xy.servlet.HelloworldServlet : usertest1592904273
  15. 2016-11-29 17:30:46.380 INFO 36903 --- [nio-8080-exec-5] com.zallds.xy.servlet.HelloworldServlet : usertest1410972809
  16. 2016-11-29 17:30:46.524 INFO 36903 --- [nio-8080-exec-6] com.zallds.xy.servlet.HelloworldServlet : usertest1705570689
  17. 2016-11-29 17:30:46.692 INFO 36903 --- [nio-8080-exec-7] com.zallds.xy.servlet.HelloworldServlet : usertest1105134375
  18. 2016-11-29 17:30:46.802 INFO 36903 --- [nio-8080-exec-8] com.zallds.xy.servlet.HelloworldServlet : usertest407377722

ThreadLocal子线程场景

需求新增, 需要在原有的业务逻辑中增加一个给用户发送邮件的操作。发送邮件我们采用异步处理,新建一个线程来执行。

  1. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  2. UserObj user = UserUtils.getCurrentUser();
  3. logger.info(user.getName() + user.hashCode());
  4. SendEmailTask emailThread = new SendEmailTask();
  5. new Thread(emailThread).start();
  6. super.doGet(req, resp);
  7. }
  8. class SendEmailTask implements Runnable {
  9. @Override
  10. public void run() {
  11. UserObj user = UserUtils.getCurrentUser();
  12. logger.info("子线程中:" + (user == null ? "user为null" : user.getName() + user.hashCode()));
  13. }
  14. }

主线程中创建异步线程,子线程中能拿到吗?通过测试发现是不能的

  1. 2016-11-29 18:09:16.482 INFO 38092 --- [nio-8080-exec-2] com.zallds.xy.servlet.HelloworldServlet : usertest1425505918
  2. 2016-11-29 18:09:16.483 INFO 38092 --- [ Thread-4] com.zallds.xy.servlet.HelloworldServlet : 子线程中:usernull
  3. 2016-11-29 18:09:20.995 INFO 38092 --- [nio-8080-exec-3] com.zallds.xy.servlet.HelloworldServlet : usertest1280373552
  4. 2016-11-29 18:09:20.996 INFO 38092 --- [ Thread-5] com.zallds.xy.servlet.HelloworldServlet : 子线程中:usernull

子线程怎么拿到父线程的ThreadLocal数据?jdk给我们提供了解决办法,ThreadLocal有一个子类InheritableThreadLocal,创建ThreadLocal时候我们采用InheritableThreadLocal类可以实现子线程获取到父线程的本地变量。

  1. private static ThreadLocal<UserObj> userResource = new InheritableThreadLocal<UserObj>();

然后子线程中就可以正常拿到user对象了

  1. 2016-11-29 19:07:01.518 INFO 39644 --- [nio-8080-exec-2] com.zallds.xy.servlet.HelloworldServlet : usertest495550128
  2. 2016-11-29 19:07:01.518 INFO 39644 --- [ Thread-4] com.zallds.xy.servlet.HelloworldServlet : 子线程中:usertest495550128
  3. 2016-11-29 19:07:05.839 INFO 39644 --- [nio-8080-exec-3] com.zallds.xy.servlet.HelloworldServlet : usertest1851717404
  4. 2016-11-29 19:07:05.840 INFO 39644 --- [ Thread-5] com.zallds.xy.servlet.HelloworldServlet : 子线程中:usertest1851717404

ThreadLocal 子线程传递-线程池场景

当我们执行异步任务时,大多会采用线程池的机制(如Executor)。这样就会存在一个问题,即使父线程已经结束,子线程依然存在并被池化。这样,线程池中的线程在下一次请求被执行的时候,ThreadLocal的get()方法返回的将不是当前线程中设定的变量,因为池中的“子线程”根本不是当前线程创建的,当前线程设定的ThreadLocal变量也就无法传递给线程池中的线程。

我们修改一下发送邮件的代码,改用线程池来实现。

  1. 2016-11-29 19:51:51.973 INFO 40937 --- [nio-8080-exec-1] com.zallds.xy.servlet.HelloworldServlet : usertest1417641261
  2. 2016-11-29 19:51:51.974 INFO 40937 --- [pool-1-thread-1] com.zallds.xy.servlet.HelloworldServlet : 子线程中:usertest1417641261
  3. 2016-11-29 19:51:55.746 INFO 40937 --- [nio-8080-exec-2] com.zallds.xy.servlet.HelloworldServlet : usertest1116537955
  4. 2016-11-29 19:51:55.746 INFO 40937 --- [pool-1-thread-1] com.zallds.xy.servlet.HelloworldServlet : 子线程中:usertest1417641261
  5. 2016-11-29 19:51:58.825 INFO 40937 --- [nio-8080-exec-3] com.zallds.xy.servlet.HelloworldServlet : usertest1489938856
  6. 2016-11-29 19:51:58.826 INFO 40937 --- [pool-1-thread-1] com.zallds.xy.servlet.HelloworldServlet : 子线程中:usertest1417641261

可以发现发送邮件的任务三次用的都是同一个线程[pool-1-thread-1],第一次子线程和父线程中的user对象相同,后面的“子线程”(前面提到过,后面的已经不是子线程了)中的user对象都是和第一个父线程中的相同。

那么在线程池的场景下,怎么能让“子线程”正常拿到父线程传递过来的变量呢?如果我们能在创建task的时候主动传递过去就好了。按照这个想法我们来实施一下。

继续修改代码

  1. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  2. UserObj user = UserUtils.getCurrentUser();
  3. logger.info(user.getName() + user.hashCode());
  4. SendEmailTask emailThread = new SendEmailTask();
  5. executor.execute(new UserRunnable(emailThread, user));
  6. super.doGet(req, resp);
  7. }
  8. /**
  9. * 做一个wrapper, 将目标任务做一层包装, 在run方法中传递线程本地变量
  10. */
  11. class UserRunnable implements Runnable {
  12. /**
  13. * 目标任务对象
  14. */
  15. Runnable runnable;
  16. /**
  17. * 要绑定的user对象
  18. */
  19. UserObj user;
  20. public UserRunnable(Runnable runnable, UserObj user) {
  21. this.runnable = runnable;
  22. this.user = user;
  23. }
  24. @Override
  25. public void run() {
  26. ThreadContext.bindUser(user);
  27. runnable.run();
  28. ThreadContext.unbindUser();
  29. }
  30. }
  31. class SendEmailTask implements Runnable {
  32. @Override
  33. public void run() {
  34. UserObj user = UserUtils.getCurrentUser();
  35. logger.info("子线程中:" + (user == null ? "user为null" : user.getName() + user.hashCode()));
  36. }
  37. }

重新请求,得到我们想要的结果

  1. 2016-11-29 20:04:12.153 INFO 41258 --- [nio-8080-exec-1] com.zallds.xy.servlet.HelloworldServlet : usertest1565180744
  2. 2016-11-29 20:04:12.154 INFO 41258 --- [pool-1-thread-1] com.zallds.xy.servlet.HelloworldServlet : 子线程中:usertest1565180744
  3. 2016-11-29 20:04:14.142 INFO 41258 --- [nio-8080-exec-2] com.zallds.xy.servlet.HelloworldServlet : usertest481396704
  4. 2016-11-29 20:04:14.142 INFO 41258 --- [pool-1-thread-1] com.zallds.xy.servlet.HelloworldServlet : 子线程中:usertest481396704
  5. 2016-11-29 20:04:15.248 INFO 41258 --- [nio-8080-exec-3] com.zallds.xy.servlet.HelloworldServlet : usertest400717395
  6. 2016-11-29 20:04:15.249 INFO 41258 --- [pool-1-thread-1] com.zallds.xy.servlet.HelloworldServlet : 子线程中:usertest400717395

到此为止,ThreadLocal常见的场景和对应解决方案应该可以满足了。接下来就是怎么在实际应用中运用了。

为了引出此文的初衷以及后面要讲的东西,针对最后一个解决方案,我们可以进一步完善一下。

  1. ThreadContext.bindUser(user);
  2. runnable.run();
  3. ThreadContext.unbindUser();

这个地方在bind的时候是直接覆盖,无法对线程之前的状态进行保存和恢复。要实现这一点,我们可以抽象一个ThreadState来保存线程的状态,在bind之前保存original,任务执行完以后进行restore。

  1. public interface ThreadState {
  2. void bind();
  3. void restore();
  4. void clear();
  5. }
  6. public class UserThreadState implements ThreadState {
  7. private UserObj original;
  8. private UserObj user;
  9. public UserThreadState(UserObj user) {
  10. this.user = user;
  11. }
  12. @Override
  13. public void bind() {
  14. this.original = ThreadContext.getUser();
  15. ThreadContext.bindUser(this.user);
  16. }
  17. @Override
  18. public void restore() {
  19. ThreadContext.bindUser(this.original);
  20. }
  21. @Override
  22. public void clear() {
  23. ThreadContext.unbindUser();
  24. }
  25. }
  26. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  27. UserObj user = UserUtils.getCurrentUser();
  28. logger.info(user.getName() + user.hashCode());
  29. SendEmailTask emailThread = new SendEmailTask();
  30. executor.execute(new UserRunnable(emailThread, new UserThreadState(user)));
  31. super.doGet(req, resp);
  32. }
  33. /**
  34. * 做一个wrapper, 将目标任务做一层包装, 在run方法中传递线程本地变量
  35. */
  36. class UserRunnable implements Runnable {
  37. /**
  38. * 目标任务对象
  39. */
  40. Runnable runnable;
  41. /**
  42. * 要绑定的user对象
  43. */
  44. UserThreadState userThreadState;
  45. public UserRunnable(Runnable runnable, UserThreadState userThreadState) {
  46. this.runnable = runnable;
  47. this.userThreadState = userThreadState;
  48. }
  49. @Override
  50. public void run() {
  51. userThreadState.bind();
  52. runnable.run();
  53. userThreadState.restore();
  54. UserObj userOrig = UserUtils.getCurrentUser();
  55. logger.info("original:" + userOrig.getName() + userOrig.hashCode());
  56. }
  57. }
  58. class SendEmailTask implements Runnable {
  59. @Override
  60. public void run() {
  61. UserObj user = UserUtils.getCurrentUser();
  62. logger.info("子线程中:" + (user == null ? "user为null" : user.getName() + user.hashCode()));
  63. }
  64. }

实现效果是相同的,至于为什么三次的original对象都是一样的,通过前面的说明应该能够理解

  1. 2016-11-29 20:19:48.694 INFO 41671 --- [nio-8080-exec-1] com.zallds.xy.servlet.HelloworldServlet : usertest114760676
  2. 2016-11-29 20:19:48.699 INFO 41671 --- [pool-1-thread-1] com.zallds.xy.servlet.HelloworldServlet : 子线程中:usertest114760676
  3. 2016-11-29 20:19:48.700 INFO 41671 --- [pool-1-thread-1] com.zallds.xy.servlet.HelloworldServlet : original:usertest114760676
  4. 2016-11-29 20:19:57.123 INFO 41671 --- [nio-8080-exec-2] com.zallds.xy.servlet.HelloworldServlet : usertest941302199
  5. 2016-11-29 20:19:57.123 INFO 41671 --- [pool-1-thread-1] com.zallds.xy.servlet.HelloworldServlet : 子线程中:usertest941302199
  6. 2016-11-29 20:19:57.123 INFO 41671 --- [pool-1-thread-1] com.zallds.xy.servlet.HelloworldServlet : original:usertest114760676
  7. 2016-11-29 20:20:04.385 INFO 41671 --- [nio-8080-exec-3] com.zallds.xy.servlet.HelloworldServlet : usertest1489938856
  8. 2016-11-29 20:20:04.385 INFO 41671 --- [pool-1-thread-1] com.zallds.xy.servlet.HelloworldServlet : 子线程中:usertest1489938856
  9. 2016-11-29 20:20:04.385 INFO 41671 --- [pool-1-thread-1] com.zallds.xy.servlet.HelloworldServlet : original:usertest114760676

由于在使用shiro框架的SecurityUtils.getSubject()过程中碰到问题,才有了本文的示例,例子中的部分代码参考了shiro框架的实现机制。后面会再研究一下shiro的subject相关设计。

http://shiro.apache.org/subject.html

  1.  
  2. 作者:99793933e682
    链接:http://www.jianshu.com/p/85d96fe9358b
    來源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

当ThreadLocal碰上线程池的更多相关文章

  1. ThreadLocal与线程池使用的问题

    感谢博主的这篇分享,见 https://www.cnblogs.com/qifenghao/p/8977378.html 在今天的面试中,突然被考官问了这个问题,当时脱口而出的是 threadloca ...

  2. ThreadLocal遇到线程池时, 各线程间的数据会互相干扰, 串来串去

    最近遇到一个比较隐蔽而又简单地问题,在使用ThreadLocal时发现出现多个线程中值串来串去,排查一番,确定问题为线程池的问题,线程池中的线程是会重复利用的,而ThreadLocal是用线程来做Ke ...

  3. ThreadLocal,LinkedBlockingQueue,线程池 获取数据库连接2改进

    package com.ctl.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQL ...

  4. Java线程池进阶

    线程池是日常开发中常用的技术,使用也非常简单,不过想使用好线程池也不是件容易的事,开发者需要不断探索底层的实现原理,才能在不同的场景中选择合适的策略,最大程度发挥线程池的作用以及避免踩坑. 一.线程池 ...

  5. 0041 Java学习笔记-多线程-线程池、ForkJoinPool、ThreadLocal

    什么是线程池 创建线程,因为涉及到跟操作系统交互,比较耗费资源.如果要创建大量的线程,而每个线程的生存期又很短,这时候就应该使用线程池了,就像数据库的连接池一样,预先开启一定数量的线程,有任务了就将任 ...

  6. 线程池与Threadlocal

    线程池与Threadlocal 线程池: 线程池是为了使线程能够得到循环的利用,线程池里面养着一些线程,有任务需要使用线程的时候就往线程池里抓线程对象出来使用.线程池里的线程能够重复使用,所以在资源上 ...

  7. ThreadLocal 遇上线程池的问题及解决办法

    ThreadLocal 称为线程本地存储,它为每一个使用它的线程提供一个其值(value)的副本.可以将 ThreadLocal<T> 理解成 Map<Thread, T>,即 ...

  8. ThreadLocal 定义,以及是否可能引起的内存泄露(threadlocalMap的Key是弱引用,用线程池有可能泄露)

    ThreadLocal 也可以跟踪一个请求,从接收请求,处理请求,到返回请求,只要线程不销毁,就可以在线程的任何地方,调用这个参数,这是百度二面的题目,参考: Threadlocal 传递参数(百度二 ...

  9. 线程池-Threadlocal

    ThreadLoclc初衷是线程并发时,解决变量共享问题,但是由于过度设计,比如弱引用的和哈希碰撞,导致理解难度大.使用成本高,反而成为故障高发点,容易出现内存泄露,脏数据.贡献对象更新等问题.单从T ...

随机推荐

  1. ubuntu 禁止错误信息 report 问题弹出

    发现ubuntu只要出现点问题就会提示要不要report error, 光点cancel就点烦了. 而且今天重新安装了一台ubuntu结果开机就蹦出error, 好烦啊. 下面说个特别有效的解决办法, ...

  2. C语言--递归问题

    1,一个经典的例子,理解递归过程的展开 #include<stdio.h> void func(int i){ ) func(i/); printf("%d",i) } ...

  3. ** Error in `./g2o_viewer': realloc(): invalid pointer:

    问题: defe@defe-Precision-Tower-3620:~/project/Demo/UseG2OforPoseGraph/useg2oforposegraph$ ./g2o_viewe ...

  4. 【Hadoop】MapReduce笔记(四):MapReduce优化策略总结

    Cloudera 提供给客户的服务内容之一就是调整和优化MapReduce job执行性能.MapReduce和HDFS组成一个复杂的分布式系统,并且它们运行着各式各样用户的代码,这样导致没有一个快速 ...

  5. jquery登录的异步验证

    //定义一个json var validate = { username : false, pwd : false, pwded : false, verify : false, loginUsern ...

  6. Linux系统调用及其效率

    操作系统相关概念: 操作系统---管理计算机硬件与软件资源的软件,是用户与系统操作交互的接口,为在它上面运行的程序提供服务. 操作系统内核 ----操作系统的核心.负责管理系统的进程.内核.设备驱动程 ...

  7. js对象 数组Array详解 (参照MDN官网:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)

    一:数组的创建方式: 1.采用直接量创建 var arr = [];//创建一个空数组 var arr2 = [1,2,3];//创建一个有三个元素的数组 2.采用构造函数创建 a.var arr1 ...

  8. 【BZOJ 3233】 [Ahoi2013]找硬币

    [题目 描述] 小蛇是金融部部长. 最近她决定制造一系列新的货币. 假设她要制造的货币 的面值为 x1, x2, x3… 那么 x1 必须为 1, xb 必须为 xa 的正整数倍(b>a). 例 ...

  9. Tomcat日志文件的输出在Linux和Windows下的差异

    前言 最近老大发现Tomcat的日志文件catalina.out里存在着大量的和公司项目相关的log信息,因为一般都是会使用日志框架并另外将log信息输出到另外的文件里的,catalina.out文件 ...

  10. 分布式通信-tcp/ip 广播

    服务端 /** * 广播 */ public class MulticastServer { public static void main(String[] args) { try { //地址是2 ...