Java面试宝典Beta5.0

2020年05月09日 阅读数:144
这篇文章主要向大家介绍Java面试宝典Beta5.0,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

pdf下载地址:Java面试宝典javascript

第一章内容介绍 20css

第二章JavaSE基础 21html

1、Java面向对象 21前端

1. 面向对象都有哪些特性以及你对这些特性的理解 21vue

2. 访问权限修饰符public、private、protected, 以及不写(默认)时的区别(2017-11-12) 22java

3. 如何理解clone对象 22node

2、JavaSE语法(2017-11-12-wl) 26mysql

1. Java有没有goto语句?(2017-11-12-wl) 26jquery

2. & 和 && 的区别(2017-11-12-wl) 27linux

3. 在Java中,如何跳出当前的多重嵌套循环(2017-11-14-wl) 27

4. 两个对象值相同(x.equals(y) == true),但却可有不一样的hashCode,这句话对不对?(2017-11-14-wl) 27

5. 是否能够继承String (2017-11-14-wl) 28

6. 当一个对象被看成参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里究竟是值传递仍是引用传递?(2017-11-14-wl) 29

7. 重载(overload)和重写(override)的区别?重载的方法可否根据返回类型进行区分?(2017-11-15-wl) 29

8. 为何函数不能根据返回类型来区分重载?(2017-11-15-wl) 30

9. char 型变量中能不能存储一个中文汉字,为何?(2017-11-16-wl) 31

10. 抽象类(abstract class)和接口(interface)有什么异同?(2017-11-16-wl) 31

11. 抽象的(abstract)方法是否可同时是静态的(static), 是否可同时是本地方法(native),是否可同时被synchronized(2017-11-16-wl) 32

12. 阐述静态变量和实例变量的区别?(2017-11-16-wl) 32

13. ==和equals的区别?(2017-11-22-wzz) 33

14. break和continue的区别?(2017-11-23-wzz) 33

15. String s = "Hello";s = s + " world!";这两行代码执行后,原始的String对象中的内容到底变了没有?(2017-12-1-lyq) 33

3、Java中的多态 35

1. Java中实现多态的机制是什么? 35

4、Java的异常处理 35

1. Java中异常分为哪些种类 35

2. 调用下面的方法,获得的返回值是什么? 35

3. error和exception的区别?(2017-2-23) 36

4. java异常处理机制(2017-2-23) 37

5. 请写出你最多见的5个RuntimeException(2017-11-22-wzz) 37

6. throw和throws的区别(2017-11-22-wzz) 38

7. final、finally、finalize的区别?(2017-11-23-wzz) 38

5、JavaSE经常使用API 39

1. Math.round(11.5)等于多少?Math.round(- 11.5) 又等于多少?(2017-11-14-wl) 39

2. switch是否能做用在byte上,是否能做用在long上,是否能做用在String上?(2017-11-14-wl) 39

3. 数组有没有length() 方法?String有没有length() 方法?(2017-11-14-wl) 39

4. String 、StringBuilder 、StringBuffer的区别?(2017-11-14-wl) 39

5. 什么状况下用“+”运算符进行字符串链接比调用StringBuffer/StringBuilder 对象的append方法链接字符串性能更好?(2017-11-14-wl) 40

6. 请说出下面程序的输出(2017-11-14-wl) 47

7. Java中的日期和时间(2017-11-19-wl) 48

6、Java的数据类型 70

1. Java的基本数据类型都有哪些各占几个字节 70

2. String是基本数据类型吗?(2017-11-12-wl) 71

3. short s1 = 1; s1 = s1 + 1; 有错吗?short s1 = 1; s1 += 1有错吗;(2017-11-12-wl) 71

4. int 和 和 Integer有什么区别?(2017-11-12-wl) 71

5. 下面Integer类型的数值比较输出的结果为?(2017-11-12-wl) 72

6. String类经常使用方法(2017-11-15-lyq) 74

7. String、StringBuffer、StringBuilder的区别?(2017-11-23-wzz) 74

8. 数据类型之间的转换(2017-11-23-wzz) 75

7、Java的IO 75

1. Java中有几种类型的流(2017-11-23-wzz) 75

2. 字节流如何转为字符流 76

3. 如何将一个java对象序列化到文件里 76

4. 字节流和字符流的区别(2017-11-23-wzz) 77

5. 如何实现对象克隆?(2017-11-12-wl) 77

6. 什么是java序列化,如何实现java序列化?(2017-12-7-lyq) 80

8、Java的集合 81

1. HashMap排序题,上机题。(本人主要靠这道题入职的第一家公司) 81

2. 集合的安全性问题 83

3. ArrayList内部用什么实现的?(2015-11-24) 83

4. 并发集合和普通集合如何区别?(2015-11-24) 89

5. List的三个子类的特色(2017-2-23) 91

6. List和Map、Set的区别(2017-11-22-wzz) 91

7. HashMap 和HashTable有什么区别?(2017-2-23) 92

8. 数组和链表分别比较适合用于什么场景,为何?(2017-2-23) 93

9. Java中ArrayList和Linkedlist区别?(2017-2-23) 96

10. List a=new ArrayList()和ArrayList a =new ArrayList()的区别?(2017-2-24) 97

11. 要对集合更新操做时,ArrayList和LinkedList哪一个更适合?(2017-2-24) 97

12. 请用两个队列模拟堆栈结构(2017-2-24) 101

13. Collection和Map的集成体系(2017-11-14-lyq) 102

14. Map中的key和value能够为null么?(2017-11-21-gxb) 103

9、Java的多线程和并发库 104

(一)多线程基础知识--传统线程机制的回顾(2017-12-11-wl) 104

(二)多线程基础知识--线程并发库(2017-12-11-wl) 118

(三)多线程面试题 246

10、Java内部类 272

1. 静态嵌套类(Static Nested Class) 和内部类(Inner Class)的不一样?(2017-11-16-wl) 272

2. 下面的代码哪些地方会产生编译错误?(2017-11-16-wl) 272

第三章JavaSE高级 273

1、Java中的反射 273

1. 说说你对Java中反射的理解 273

2、Java中的动态代理 273

1. 写一个ArrayList的动态代理类(笔试题) 273

2. 动静态代理的区别,什么场景使用?(2015-11-25) 274

3、Java中的设计模式&回收机制 274

1. 你所知道的设计模式有哪些 274

2. 单例设计模式 274

3. 工厂设计模式 276

4. 建造者模式(Builder) 279

5. 适配器设计模式 280

6. 装饰模式(Decorator) 282

7. 策略模式(strategy) 283

8. 观察者模式(Observer) 284

9. JVM垃圾回收机制和常见算法 286

10. 谈谈JVM的内存结构和内存分配 290

11. Java中引用类型都有哪些?(重要) 291

12. heap和stack有什么区别(2017-2-23) 294

13. 解释内存中的栈 (stack) 、堆 (heap) 和方法区 (method area) 的用法(2017-11-12-wl) 301

4、Java的类加载器(2015-12-2) 301

1. Java的类加载器的种类都有哪些? 301

2. 类何时被初始化? 302

3. Java类加载体系之ClassLoader双亲委托机制 (2017-2-24) 302

4. 描述一下JVM加载class (2017-11-15-wl) 306

5. 得到一个类对象有哪些方式?(2017-11-23-wzz) 307

5、JVM基础知识(2017-11-16-wl) 307

1. 既然有GC机制,为何还会有内存泄露的状况(2017-11-16-wl) 307

6、GC基础知识(2017-11-16-wl) 309

1. Java中为何会有GC机制呢?(2017-11-16-wl) 309

2. 对于Java的GC哪些内存须要回收(2017-11-16-wl) 309

3. Java的GC何时回收垃圾(2017-11-16-wl) 309

7、Java8的新特性以及使用(2017-12-02-wl) 311

1. 经过10个示例来初步认识Java8中的lambda表达式(2017-12-02-wl) 311

2. Java8中的lambda表达式要点(2017-12-02-wl) 319

3. Java8中的Optional类的解析(2017-12-02-wl) 321

8、在开发中遇到过内存溢出么?缘由有哪些?解决方法有哪些?(2017-11-23-gxb) 328

第四章JavaWEB 基础 329

1、JDBC技术 329

1. 说下原生jdbc操做数据库流程?(2017-11-25-wzz) 329

2. 什么要使用PreparedStatement?(2017-11-25-wzz) 330

3. 关系数据库中链接池的机制是什么?(2017-12-6-lyq) 330

3、Http协议 331

1. http的长链接和短链接(2017-11-14-lyq) 331

2. HTTP/1.1与HTTP/1.0的区别(2017-11-21-wzy) 332

3. http常见的状态码有哪些?(2017-11-23-wzz) 335

4. GET和POST的区别?(2017-11-23-wzz) 335

5. http中重定向和请求转发的区别?(2017-11-23-wzz) 337

4、Cookie和Session 337

1. Cookie和Session的区别(2017-11-15-lyq) 337

2. session共享怎么作的(分布式如何实现session共享)? 337

3. 在单点登陆中,若是cookie被禁用了怎么办?(2017-11-23-gxb) 340

5、jsp技术 341

1. 什么是jsp,什么是Servlet?jsp和Servlet有什么区别?(2017-11-23-wzz) 341

2. jsp有哪些域对象和内置对象及他们的做用?(2017-11-25-wzz) 342

6、XML技术 343

1. 什么是xml,使用xml的优缺点,xml的解析器有哪几种,分别有什么区别?(2017-11-25-wzz) 343

第五章JavaWEB高级 344

1、Filter和Listener 344

2、AJAX 344

1. 谈谈你对ajax的认识?(2017-11-23-wzz) 344

2. jsonp原理(2017-11-21-gxb) 345

3、Linux 346

1. 说一下经常使用的Linux命令 346

2. Linux中如何查看日志?(2017-11-21-gxb) 347

3. Linux怎么关闭进程(2017-11-21-gxb) 348

4、常见的前端框架有哪些 349

1. EasyUI(2017-11-23-lyq) 349

2. MiniUI(2017-11-23-lyq) 351

1. jQueryUI(2017-11-23-lyq) 352

2. Vue.js(2017-11-23-lyq) 353

3. AngularJS (2017-11-23-lyq) 355

第六章数据库 359

1、Mysql 359

1. SQL的select语句完整的执行顺序(2017-11-15-lyq) 359

2. SQL之聚合函数(2017-11-15-lyq) 361

3. SQL之链接查询(左链接和右链接的区别)(2017-11-15-lyq) 361

4. SQL之sql注入(2017-11-15-lyq) 362

5. Mysql性能优化(2017-11-15-lyq) 362

6. 必看sql面试题(学生表_课程表_成绩表_教师表)(2017-11-25-wzz) 363

7. Mysql数据库架构图(2017-11-25-wzz) 364

8. Mysql架构器中各个模块都是什么?(2017-11-25-wzz) 365

9. Mysql存储引擎有哪些?(2017-11-25-wzz) 366

10. MySQL事务介绍(2017-11-25-wzz) 367

11. MySQL怎么建立存储过程(2017-11-25-wzz) 369

12. MySQL触发器怎么写?(2017-11-25-wzz) 370

13. MySQL语句优化(2017-11-26-wzz) 371

14. MySQL中文乱码问题完美解决方案(2017-12-07-lwl) 372

15. 如何提升MySQL的安全性(2017-12-8-lwl) 374

2、Oracle 376

1. 什么是存储过程,使用存储过程的好处?(2017-11-25-wzz) 376

2. Oracle存储过程怎么建立?(2017-11-25-wzz) 377

3. 如何使用Oracle的游标?(2017-11-25-wzz) 378

4. Oracle中字符串用什么链接?(2017-11-25-wzz) 378

5. Oracle中是如何进行分页查询的?(2017-11-25-wzz) 379

6. 存储过程和存储函数的特色和区别?(2017-11-25-wzz) 379

7. 存储过程与SQL的对比?(2017-11-21-gxb) 379

8. 你以为存储过程和SQL语句该使用哪一个?(2017-11-21-gxb) 380

9. 触发器的做用有哪些?(2017-11-21-gxb) 381

10. 在千万级的数据库查询中,如何提升效率?(2017-11-23-gxb) 381

第七章框架 385

1、SpringMVC 385

1. SpringMVC的工做原理(2017-11-13-lyq) 385

2. SpringMVC经常使用注解都有哪些?(2017-11-24-gxb) 386

3. 如何开启注解处理器和适配器?(2017-11-24-gxb) 386

4. 如何解决get和post乱码问题?(2017-11-24-gxb) 386

2、Spring 387

1. 谈谈你对Spring的理解(2017-11-13-lyq) 387

2. Spring中的设计模式(2017-11-13-lyq) 387

3. Spring的经常使用注解(2017-11-13-lyq) 388

4. 简单介绍一下Spring bean的生命周期(2017-11-21-gxb) 389

5. Spring结构图(2017-11-22-lyq) 390

6. Spring能帮咱们作什么?(2017-11-22-lyq) 392

7. 请描述一下Spring的事务(2017-11-22-lyq) 393

8. BeanFactory 经常使用的实现类有哪些?(2017-12-03-gxb) 396

9. 解释Spring JDBC、Spring DAO和Spring ORM(2017-12-03-gxb) 397

10. 简单介绍一下Spring WEB 模块。(2017-12-03-gxb) 397

11. Spring配置文件有什么做用?(2017-12-03-gxb) 398

12. 什么是Spring IOC 容器?(2017-12-03-gxb) 398

13. IOC的优势是什么? 398

14. ApplicationContext的实现类有哪些?(2017-12-03-gxb) 398

15. BeanFactory与AppliacationContext有什么区别(2017-12-03-gxb) 399

16. 什么是Spring的依赖注入?(2017-12-04-gxb) 399

17. 有哪些不一样类型的IOC(依赖注入)方式?(2017-12-04-gxb) 399

18. 什么是Spring beans?(2017-12-04-gxb) 400

19. 一个Spring Beans的定义须要包含什么?(2017-12-04-gxb) 400

20. 你怎样定义类的做用域?(2017-12-04-gxb) 401

21. Spring支持的几种bean的做用域。(2017-12-04-gxb) 401

22. Spring框架中的单例bean是线程安全的吗?(2017-12-04-gxb) 401

23. 什么是Spring的内部bean?(2017-12-04-gxb) 401

24. 在Spring中如何注入一个java集合?(2017-12-04-gxb) 402

25. 什么是bean的自动装配?(2017-12-04-gxb) 402

26. 解释不一样方式的自动装配。(2017-12-04-gxb) 402

27. 什么是基于Java的Spring注解配置? 给一些注解的例子(2017-12-05-gxb) 403

28. 什么是基于注解的容器配置?(2017-12-05-gxb) 403

29. 怎样开启注解装配?(2017-12-05-gxb) 403

30. 在Spring框架中如何更有效地使用JDBC?(2017-12-05-gxb) 403

31. 使用Spring经过什么方式访问Hibernate?(2017-12-05-gxb) 404

32. Spring支持的ORM框架有哪些?(2017-12-05-gxb) 404

33. 简单解释一下spring的AOP(2017-12-05-gxb) 404

34. 在Spring AOP 中,关注点和横切关注的区别是什么?(2017-12-05-gxb) 405

35. 什么是链接点?(2017-12-05-gxb) 405

36. Spring的通知是什么?有哪几种类型?(2017-12-05-gxb) 405

37. 什么是切点?(2017-12-05-gxb) 406

38. 什么是目标对象?(2017-12-05-gxb) 406

39. 什么是代理?(2017-12-05-gxb) 406

40. 什么是织入?什么是织入应用的不一样点?(2017-12-05-gxb) 406

3、Shiro 406

1. 简单介绍一下Shiro框架(2017-11-23-gxb) 406

2. Shiro主要的四个组件(2017-12-2-wzz) 407

3. Shiro运行原理(2017-12-2-wzz) 408

4. Shiro的四种权限控制方式(2017-12-2-wzz) 408

5. 受权实现的流程(2017-12-2-wzz) 409

4、Mybatis 410

1. Mybatis中#和$的区别?(2017-11-23-gxb) 410

2. Mybatis的编程步骤是什么样的?(2017-12-2-wzz) 411

3. JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的?(2017-12-2-wzz) 411

4. 使用MyBatis的mapper接口调用时有哪些要求?(2017-12-2-wzz) 411

5. Mybatis中一级缓存与二级缓存?(2017-12-4-lyq) 412

6. MyBatis在insert插入操做时返回主键ID(2017-12-4-lyq) 412

5、Struts2 413

1. 简单介绍一下Struts2(2017-11-24-gxb) 413

2. Struts2的执行流程了解么?(2017-11-24-gxb) 414

3. Struts2中Action配置的注意事项有哪些?(2017-11-24-gxb) 416

4. 拦截器和过滤器有哪些区别?(2017-11-24-gxb) 417

5. Struts2的封装方式有哪些?(2017-11-24-gxb) 417

6. 简单介绍一下Struts2的值栈。(2017-11-24-gxb) 419

7. SpringMVC和Struts2的区别?(2017-11-23-gxb) 420

8. Struts2中的 # 和 % 分别是作什么的?(2017-11-30-wzz) 421

9. Struts2中有哪些经常使用结果类型?(2017-12-1-lyq) 422

6、Hibernate 422

1. 简述一下hibernate的开发流程(2017-11-24-gxb) 422

2. hibernate中对象的三种状态(2017-11-24-gxb) 423

3. hibernate的缓存机制。(2017-11-24-gxb) 423

4. Hibernate的查询方式有哪些?(2017-11-24-gxb) 424

5. Hibernate和Mybatis的区别?(2017-11-23-gxb) 424

6. Hibernate和JDBC优缺点对比(2017-11-29-wzz) 425

7. 关于Hibernate的orm思想你了解多少?(2017-11-29-wzz) 426

8. get和load的区别?(2017-11-30-wzz) 426

9. 如何进行Hibernate的优化?(2017-11-30-wzz) 427

10. 什么是Hibernate 延迟加载?(2017-12-1-lyq) 428

11. No Session问题原理及解决方法?(2017-12-4-lyq) 428

12. Spring的两种代理JDK和CGLIB的区别浅谈(2017-12-4-lyq) 429

13. 叙述Session的缓存的做用(2017-12-9-lwl) 430

14. Session的清理和清空有什么区别?(2017-12-10-lwl) 430

15. 请简述Session的特色有哪些?(2017-12-10-lwl) 430

16. 比较Hibernate三种检索策略的优缺点(2017-12-10-lwl) 431

7、Quartz 定时任务 432

1. 什么是Quartz 框架(2017-12-2-wzz) 432

2.配置文件 applicationContext_job.xml各个属性做用(2017-12-2-wzz) 432

3.Cron表达式详解(2017-12-2-wzz) 432

4. 如何监控 Quartz 的 job 执行状态:运行中,暂停中,等待中? (2017-12-2-wzz) 433

第八章最新技术 433

1、Redis 433

1. Redis的特色?(2017-11-25-wzz) 433

2. 为何redis须要把全部数据放到内存中?(2017-11-25-wzz) 434

3. Redis常见的性能问题都有哪些?如何解决?(2017-11-25-wzz) 434

4. Redis最适合的场景有哪些?(2017-11-25-wzz) 435

5. Memcache与Redis的区别都有哪些?(2017-11-25-wzz) 435

6. Redis用过RedisNX吗?Redis有哪几种数据结构?(2017-11-14-lyq) 435

7. Redis的优缺点(2017-11-22-lyq) 436

8. Redis的持久化(2017-11-23-lyq) 437

2、消息队列ActiveMQ 439

1. 如何使用ActiveMQ解决分布式事务?(2017-11-21-gxb) 439

2. 了解哪些消息队列?(2017-11-24-gxb) 440

3. ActiveMQ若是消息发送失败怎么办?(2017-11-24-gxb) 442

3、Dubbo 443

1. Dubbo的容错机制有哪些。(2017-11-23-gxb) 443

2. 使用dubbo遇到过哪些问题?(2017-11-23-gxb) 444

3. Dubbo的链接方式有哪些?(2017-12-1-lyq) 445

4、并发相关 448

1. 如何测试并发量?(2017-11-23-gxb) 448

5、Nginx 448

1. Nginx反向代理为何可以提高服务器性能?(2017-11-24-gxb) 448

2. Nginx 和 Apache 各有什么优缺点? (2017-11-24-gxb) 449

3. Nginx 多进程模型是如何实现高并发的?(2017-12-5-lyq) 449

6、Zookeeper 450

1. 简单介绍一下zookeeper以及zookeeper的原理。(2017-11-24-gxb) 450

7、solr 451

1. 简单介绍一下solr(2017-11-24-gxb) 451

2. solr怎么设置搜索结果排名靠前?(2017-11-24-gxb) 452

3. solr中IK分词器原理是什么?(2017-11-24-gxb) 452

8、webService 452

1. 什么是webService?(2017-11-24-lyq) 452

2. 常见的远程调用技术(2017-11-24-lyq) 452

9、Restful 453

1. 谈谈你对restful的理解以及在项目中的使用?(2017-11-30-wzz) 453

第九章企业实战面试题 454

1、智慧星(2017-11-25-wmm) 454

1. 选择题 454

2. 编程题 457

2、中讯志远科技(2017-11-26-wmm) 460

1. 问答题 460

3、腾讯(2016年校招面试题2017-11-29-wzy) 464

1. 选择题 464

4、北京宝蓝德股份科技有限公司(2017-12-03-wmm) 477

1.选择题 477

2.问答题 479

5、智慧流(2017-12-04-wmm) 481

1.选择题 481

2. 问答题 486

3. 逻辑思惟题 487

6、某公司 (2017-12-05-wmm) 491

1. 选择题 491

2. 问答题 500

7、华胜天成(2017-12-11-wzy) 516

1. 不定项选择题 516

2. 简答题 527

8、诚迈(2017-12-7-lyq) 527

1. 选择题 527

2. 判断题 529

3. 简答题 529

4. 编程题 533

5. linux试题 537

6. 数据库试题 539

7. 应用服务器试题 540

9、科大讯飞(2017-12-11-lyq) 542

10、泰瑞(2017-12-16-wmm) 547

1. 笔试题 547

2. 上机题 548

11、文思创新(2017-12-17-wmm) 551

1. 什么叫对象?什么叫类?什么面向对象(OOP)? 551

2. 相对于JDK1.4,JDK1.5 有哪些新特性? 552

3. JAVA中使用final修饰符,对程序有哪些影响? 552

4. Java环境变量Unix/Linux下如何配置? 553

5. 写出5个你在JAVA开发中经常使用的包含(全名),并简述其做用。 554

6. 写出5个常见的运行时异常(RuntimeException)。 555

7. 方法重载(overload)须要知足什么条件,方法覆盖/方法重写(override)须要知足什么条件?(二选一) 555

8. 继承(inheritance)的优缺点是什么? 556

9. 为何要使用接口和抽象类? 556

10. 什么是自定义异常?如何自定义异常? 558

11. Set,List,Map有什么区别? 558

12. 什么叫对象持久化(OBJect   PERSIstence),为何要进行对象持久化? 558

13. JavaScript 有哪些优缺点? 559

14. Jsp有什么特色? 560

15. 什么叫脏数据,什么叫脏读(Dirty Read) 561

第十章项目业务逻辑问题 561

1、传统项目(2017-12-5-lyq) 561

1. 什么是BOS? 561

2. Activity 工做流 562

 

[if !supportLists]第一章 [endif]内容介绍

该宝典是一份知识点全面又能不断更新,与时俱进的学习手册,不只收录了做者亲身面试遇到的问题,还收录了近上万名黑马学子面试时遇到的问题。咱们会一直不断地更新和充实该宝典,同时也但愿读者朋友可以多多提供优质的面试题,也许下一个版本就有你提供的面试题哦。

本人的面试实战记录发布在黑马论坛:http://bbs.itheima.com/thread-196394-1-1.html

你们能够访问上面的网址,经过阳哥的实战记录略微感知一下真实面试的状况,从中学习一些面试技巧以便让本身在将来的面试中可以驾轻就熟,顺利拿到本身喜欢的offer。

注意:该面试宝典仅供参考,因为做者本人的知识水平有限加之编写时间仓促所以不免有bug的存在,但愿你们见谅。

该宝典的一个明确目标是可以让90%以上的Java技术面试题都落到该宝典中,若是您有不错的知识或者面试题,您能够发送到wangzhenyang@itcast.cn,本人将不胜感激。让天下没有难学的知识,但愿你个人努力能帮到更多的莘莘学子。

世间事,不少均可投机取巧,但技术却必须靠日积月累的努力来提升。本宝典更加注重的是知识的掌握,而不只仅是对面试题的应付。在展现常见的面试问题以及回答技巧的同时还详细讲解了每一道题所包含的知识点,让读者不只知其然,更知其因此然。

 

[if !supportLists]第二章 [endif]JavaSE基础

[if !supportLists]1、[endif]Java面向对象

[if !supportLists]1. [endif]面向对象都有哪些特性以及你对这些特性的理解

1)继承:继承是从已有类获得继承信息建立新类的过程。提供继承信息的类被称为父类(超类、基类);获得继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了必定的延续性,同时继承也是封装程序中可变因素的重要手段。

2)封装:一般认为封装是把数据和操做数据的方法绑定起来,对数据的访问只能经过已定义的接口。面向对象的本质就是将现实世界描绘成一系列彻底自治、封闭的对象。咱们在类中编写的方法就是对实现细节的一种封装;咱们编写一个类就是对数据和数据操做的封装。能够说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。

3)多态性:多态性是指容许不一样子类型的对象对同一消息做出不一样的响应。简单的说就是用一样的对象引用调用一样的方法可是作了不一样的事情。多态性分为编译时的多态性和运行时的多态性。若是将对象的方法视为对象向外界提供的服务,那么运行时的多态性能够解释为:当A系统访问B系统提供的服务时,B系统有多种提供服务的方式,但一切对A系统来讲都是透明的。方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态须要作两件事:1. 方法重写(子类继承父类并重写父类中已有的或抽象的方法);2. 对象造型(用父类型引用引用子类型对象,这样一样的引用调用一样的方法就会根据子类对象的不一样而表现出不一样的行为)。

4)抽象:抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。

注意:默认状况下面向对象有3大特性,封装、继承、多态,若是面试官问让说出4大特性,那么咱们就把抽象加上去。

[if !supportLists]2. [endif]访问权限修饰符public、private、protected, 以及不写(默认)时的区别(2017-11-12)

该题目比较简单,不一样的权限修饰符的区别见下表。

修饰符当前类同 包子 类其余包

public  √√√√

protected√√√×

default√√××

private√×××

 

[if !supportLists]3. [endif]如何理解clone对象

3.1为何要用clone?

在实际编程过程当中,咱们经常要遇到这种状况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会须要一个和A彻底相同新对象B,而且此后对B 任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但B的初始值是由A对象肯定的。在Java语言中,用简单的赋值语句是不能知足这种需求的。要知足这种需求虽然有不少途径,但实现clone()方法是其中最简单,也是最高效的手段。

3.2 new一个对象的过程和clone一个对象的过程区别

new操做符的本意是分配内存。程序执行到new操做符时,首先去看new操做符后面的类型,由于知道了类型,才能知道要分配多大的内存空间。分配完内存以后,再调用构造函数,填充对象的各个域,这一步叫作对象的初始化,构造方法返回后,一个对象建立完毕,能够把他的引用(地址)发布到外部,在外部就能够使用这个引用操纵这个对象。

clone在第一步是和new类似的,都是分配内存,调用clone方法时,分配的内存和原对象(即调用clone方法的对象)相同,而后再使用原对象中对应的各个域,填充新对象的域,填充完成以后,clone方法返回,一个新的相同的对象被建立,一样能够把这个新对象的引用发布到外部。

3.3 clone对象的使用

3.3.1复制对象和复制引用的区别

[if !supportLists]1. [endif]Person p = new Person(23, "zhang");

[if !supportLists]2. [endif]Person p1 = p; 

[if !supportLists]3. [endif]System.out.println(p);

[if !supportLists]4. [endif]System.out.println(p1);

当Person p1 = p;执行以后, 是建立了一个新的对象吗? 首先看打印结果:

[if !supportLists]1.[endif]com.itheima.Person@2f9ee1ac

[if !supportLists]2.[endif]com.itheima.Person@2f9ee1ac

能够看出,打印的地址值是相同的,既然地址都是相同的,那么确定是同一个对象。p和p1只是引用而已,他们都指向了一个相同的对象Person(23, “zhang”) 。 能够把这种现象叫作引用的复制。上面代码执行完成以后, 内存中的情景以下图所示:

 

而下面的代码是真真正正的克隆了一个对象。

[if !supportLists]1.[endif]Person p = new Person(23, "zhang"); 

[if !supportLists]2.[endif]Person p1 = (Person) p.clone();   

[if !supportLists]3.[endif]System.out.println(p); 

[if !supportLists]4.[endif]System.out.println(p1);

从打印结果能够看出,两个对象的地址是不一样的,也就是说建立了新的对象,而不是把原对象的地址赋给了一个新的引用变量:

[if !supportLists]1. [endif]com.itheima.Person@2f9ee1ac

[if !supportLists]2. [endif]com.itheima.Person@67f1fba0

以上代码执行完成后,内存中的情景以下图所示:

 

3.3.2 深拷贝和浅拷贝

上面的示例代码中,Person中有两个成员变量,分别是name和age, name是String类型, age是int类型。代码很是简单,以下所示:

[if !supportLists]1.[endif]public class Person implements Cloneable{ 

[if !supportLists]2.[endif]privatint age ; 

[if !supportLists]3.[endif]    private String name; 

[if !supportLists]4.[endif]    public Person(int age, String name) { 

[if !supportLists]5.[endif]        this.age = age; 

[if !supportLists]6.[endif]        this.name = name; 

[if !supportLists]7.[endif]    }   

[if !supportLists]8.[endif]    public Person() {}   

[if !supportLists]9.[endif]    public int getAge() { 

[if !supportLists]10.[endif]        return age; 

[if !supportLists]11.[endif]    }   

[if !supportLists]12.[endif]    public String getName() { 

[if !supportLists]13.[endif]        return name; 

[if !supportLists]14.[endif]    }   

[if !supportLists]15.[endif]    @Override 

[if !supportLists]16.[endif]    protected Object clone() throws CloneNotSupportedException { 

[if !supportLists]17.[endif]        return (Person)super.clone(); 

[if !supportLists]18.[endif]    } 

[if !supportLists]19.[endif]}

因为age是基本数据类型, 那么对它的拷贝没有什么疑议,直接将一个4字节的整数值拷贝过来就行。可是name是String类型的, 它只是一个引用, 指向一个真正的String对象,那么对它的拷贝有两种方式: 直接将原对象中的name的引用值拷贝给新对象的name字段, 或者是根据原Person对象中的name指向的字符串对象建立一个新的相同的字符串对象,将这个新字符串对象的引用赋给新拷贝的Person对象的name字段。这两种拷贝方式分别叫作浅拷贝和深拷贝。深拷贝和浅拷贝的原理以下图所示:

 

下面经过代码进行验证。若是两个Person对象的name的地址值相同, 说明两个对象的name都指向同一个String对象,也就是浅拷贝, 而若是两个对象的name的地址值不一样, 那么就说明指向不一样的String对象, 也就是在拷贝Person对象的时候, 同时拷贝了name引用的String对象, 也就是深拷贝。验证代码以下:

[if !supportLists]1. [endif]Person p = new Person(23, "zhang");

[if !supportLists]2. [endif]Person p1 = (Person) p.clone();

[if !supportLists]3. [endif]String result = p.getName() == p1.getName()

[if !supportLists]4. [endif]? "clone是浅拷贝的" : "clone是深拷贝的";

[if !supportLists]5. [endif]System.out.println(result);

打印结果为:

[if !supportLists]6. [endif]clone是浅拷贝的

因此,clone方法执行的是浅拷贝, 在编写程序时要注意这个细节。

如何进行深拷贝:

由上一节的内容能够得出以下结论:若是想要深拷贝一个对象,这个对象必需要实现Cloneable接口,实现clone方法,而且在clone方法内部,把该对象引用的其余对象也要clone一份,这就要求这个被引用的对象必须也要实现Cloneable接口而且实现clone方法。那么,按照上面的结论,实现如下代码 Body类组合了Head类,要想深拷贝Body类,必须在Body类的clone方法中将Head类也要拷贝一份。代码以下:

[if !supportLists]1.[endif]static class Body implements Cloneable{ 

[if !supportLists]2.[endif]    public Head head; 

[if !supportLists]3.[endif]    public Body() {} 

[if !supportLists]4.[endif]    public Body(Head head) {this.head = head;} 

[if !supportLists]5.[endif]    @Override 

[if !supportLists]6.[endif]    protected Object clone() throws CloneNotSupportedException { 

[if !supportLists]7.[endif]        Body newBody =  (Body) super.clone(); 

[if !supportLists]8.[endif]        newBody.head = (Head) head.clone(); 

[if !supportLists]9.[endif]        return newBody; 

[if !supportLists]10.[endif]    } 

[if !supportLists]11.[endif]} 

[if !supportLists]12.[endif]static class Head implements Cloneable{ 

[if !supportLists]13.[endif]    public  Face face;   

[if !supportLists]14.[endif]    public Head() {} 

[if !supportLists]15.[endif]    @Override 

[if !supportLists]16.[endif]    protected Object clone() throws CloneNotSupportedException { 

[if !supportLists]17.[endif]        return super.clone(); 

[if !supportLists]18.[endif]    }  }     

[if !supportLists]19.[endif]public static void main(String[] args) throws CloneNotSupportedException {   

[if !supportLists]20.[endif]    Body body = new Body(new Head(new Face())); 

[if !supportLists]21.[endif]    Body body1 = (Body) body.clone(); 

[if !supportLists]22.[endif]    System.out.println("body == body1 : " + (body == body1) ); 

[if !supportLists]23.[endif]    System.out.println("body.head == body1.head : " +  (body.head == body1.head)); 

[if !supportLists]24.[endif]}

打印结果为:

[if !supportLists]1. [endif]body == body1 : false

[if !supportLists]2. [endif]body.head == body1.head : false

[if !supportLists]2、[endif]JavaSE语法2017-11-12-wl

[if !supportLists]1. [endif]Java有没有goto语句?(2017-11-12-wl)

goto是Java 中的保留字,在目前版本的Java中没有使用。根据 James Gosling(Java 之父)编写的《The Java Programming Language》一书的附录中给出了一个 Java 关键字列表,其中有 goto 和 const,可是这两个是目前没法使用的关键字,所以有些地方将其称之为保留字,其实保留字这个词应该有更普遍的意义,由于熟悉 C 语言的程序员都知道,在系统类库中使用过的有特殊意义的单词或单词的组合都被视为保留字。

[if !supportLists]2. [endif]& 和 && 的区别(2017-11-12-wl)

&运算符有两种用法:(1)按位与;(2)逻辑与。

&&运算符是短路与运算。逻辑与跟短路与的差异是很是巨大的,虽然两者都要求运算符左右两端的布尔值都是 true 整个表达式的值才是 true。

&&之因此称为短路运算是由于,若是&&左边的表达式的值是 false,右边的表达式会被直接短路掉,不会进行运算。不少时候咱们可能都须要用&&而不是&,例如在验证用户登陆时断定用户名不是 null 并且不是空字符串,应当写为username != null &&!username.equals(""),两者的顺序不能交换,更不能用&运算符,由于第一个条件若是不成立,根本不能进行字符串的 equals 比较,不然会产生 NullPointerException 异常。注意:逻辑或运算符(|)和短路或运算符(||)的差异也是如此。

[if !supportLists]3. [endif]在Java中,如何跳出当前的多重嵌套循环(2017-11-14-wl)

在最外层循环前加一个标记如A,而后用 break A;能够跳出多重循环。(Java 中支持带标签的break和continue 语句,做用有点相似于 C 和 C++中的 goto 语句,可是就像要避免使用 goto 同样,应该避免使用带标签的 break 和 continue,由于它不会让你的程序变得更优雅,不少时候甚至有相反的做用)。

[if !supportLists]4. [endif]两个对象值相同(x.equals(y) == true),但却可有不一样的hashCode,这句话对不对?(2017-11-14-wl)

不对,若是两个对象x 和 y 知足 x.equals(y) == true,它们的哈希码(hashCode)应当相同。

Java 对于eqauls 方法和 hashCode 方法是这样规定的:(1)若是两个对象相同(equals 方法返回 true),那么它们的hashCode 值必定要相同;(2)若是两个对象的 hashCode 相同,它们并不必定相同。固然,你未必要按照要求去作,可是若是你违背了上述原则就会发如今使用容器时,相同的对象能够出如今 Set 集合中,同时增长新元素的效率会大大降低(对于使用哈希存储的系统,若是哈希码频繁的冲突将会形成存取性能急剧降低)。

关于equals 和 hashCode方法,不少Java程序员都知道,但不少人也就是仅仅知道而已,在Joshua Bloch

的大做《Effective Java》(不少软件公司,《Effective Java》、《Java 编程思想》以及《重构:改善既有代码质量》是 Java 程序员必看书籍,若是你还没看过,那就赶忙去买一本吧)中是这样介绍 equals 方法的。

首先equals 方法必须知足自反性(x.equals(x)必须返回 true)、对称性(x.equals(y)返回 true 时,y.equals(x)也必须返回 true)、传递性(x.equals(y)和 y.equals(z)都返回 true 时,x.equals(z)也必须返回 true)和一致性(当x 和 y 引用的对象信息没有被修改时,屡次调用 x.equals(y)应该获得一样的返回值),并且对于任何非 null值的引用 x,x.equals(null)必须返回false。实现高质量的equals方法的诀窍包括:1. 使用==操做符检查"参数是否为这个对象的引用";2. 使用 instanceof 操做符检查"参数是否为正确的类型";3. 对于类中的关键属性,检查参数传入对象的属性是否与之相匹配;4. 编写完 equals 方法后,问本身它是否知足对称性、传递性、一致性;5. 重写 equals 时老是要重写 hashCode;6. 不要将 equals 方法参数中的 Object 对象替换为其余的类型,在重写时不要忘掉@Override 注解。

[if !supportLists]5. [endif]是否能够继承String (2017-11-14-wl)

String类是final类,不能够被继承。

继承String 自己就是一个错误的行为,对 String 类型最好的重用方式是关联关系(Has-A)和依赖关系(Use-A)而不是继承关系(Is-A)。

[if !supportLists]6. [endif]当一个对象被看成参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里究竟是值传递仍是引用传递?(2017-11-14-wl)

是值传递。Java语言的方法调用只支持参数的值传递。当一个对象实例做为一个参数被传递到方法中时,参数的值就是对该对象的引用。对象的属性能够在被调用过程当中被改变,但对对象引用的改变是不会影响到调用者的。C++和 C#中能够经过传引用或传输出参数来改变传入的参数的值。说明:Java中没有传引用实在是很是的不方便,这一点在 Java 8 中仍然没有获得改进,正是如此在 Java 编写的代码中才会出现大量的 Wrapper 类(将须要经过方法调用修改的引用置于一个 Wrapper 类中,再将Wrapper 对象传入方法),这样的作法只会让代码变得臃肿,尤为是让从 C 和 C++转型为 Java 程序员的开发者没法容忍。

[if !supportLists]7. [endif]重载(overload)和重写(override)的区别?重载的方法可否根据返回类型进行区分?(2017-11-15-wl)

方法的重载和重写都是实现多态的方式,区别在于前者实现的是编译时的多态性,然后者实现的是运行时的多态性。重载发生在一个类中,同名的方法若是有不一样的参数列表(参数类型不一样、参数个数不一样或者两者都不一样)则视为重载;重写发生在子类与父类之间,重写要求子类被重写方法与父类被重写方法有相同的返回类型,比父类被重写方法更好访问,不能比父类被重写方法声明更多的异常(里氏代换原则)。重载对返回类型没有特殊的要求。

方法重载的规则:

[if !supportLists]1.[endif]方法名一致,参数列表中参数的顺序,类型,个数不一样。

[if !supportLists]2.[endif]重载与方法的返回值无关,存在于父类和子类,同类中。

[if !supportLists]3.[endif]能够抛出不一样的异常,能够有不一样修饰符。

方法重写的规则:

[if !supportLists]1.[endif]参数列表必须彻底与被重写方法的一致,返回类型必须彻底与被重写方法的返回类型一致。

[if !supportLists]2.[endif]构造方法不能被重写,声明为final的方法不能被重写,声明为static的方法不能被重写,可是可以被再次声明。

[if !supportLists]3.[endif]访问权限不能比父类中被重写的方法的访问权限更低。

[if !supportLists]4.[endif]重写的方法可以抛出任何非强制异常(UncheckedException,也叫非运行时异常),不管被重写的方法是否抛出异常。可是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更普遍的强制性异常,反之则能够。

[if !supportLists]8. [endif]为何函数不能根据返回类型来区分重载?(2017-11-15-wl)

该道题来自华为面试题。

由于调用时不能指定类型信息,编译器不知道你要调用哪一个函数。例如:

[if !supportLists]1.[endif]float max(int a, int b);

[if !supportLists]2.[endif]int max(int a, int b);

当调用max(1, 2);时没法肯定调用的是哪一个,单从这一点上来讲,仅返回值类型不一样的重载是不该该容许的。

再好比对下面这两个方法来讲,虽然它们有一样的名字和自变量,但实际上是很容易区分的:

[if !supportLists]1.[endif]void f() {}

[if !supportLists]2.[endif]int f() {}

 

若编译器可根据上下文(语境)明确判断出含义,好比在int x=f()中,那么这样作彻底没有问题。然而,咱们也可能调用一个方法,同时忽略返回值;咱们一般把这称为“为它的反作用去调用一个方法”,由于咱们关心的不是返回值,而是方法调用的其余效果。因此假如咱们像下面这样调用方法:f(); Java 怎样判断f()的具体调用方式呢?并且别人如何识别并理解代码呢?因为存在这一类的问题,因此不能。

函数的返回值只是做为函数运行以后的一个“状态”,他是保持方法的调用者与被调用者进行通讯的关键。并不能做为某个方法的“标识”。

[if !supportLists]9. [endif]char 型变量中能不能存储一个中文汉字,为何?(2017-11-16-wl)

char 类型能够存储一个中文汉字,由于Java中使用的编码是 Unicode(不选择任何特定的编码,直接

使用字符在字符集中的编号,这是统一的惟一方法),一个char 类型占 2 个字节(16 比特),因此放一个中文是没问题的。

补充:使用Unicode意味着字符在JVM内部和外部有不一样的表现形式,在JVM内部都是 Unicode,当这个字符被从JVM内部转移到外部时(例如存入文件系统中),须要进行编码转换。因此Java中有字节流和字符流,以及在字符流和字节流之间进行转换的转换流,如 InputStreamReader 和 OutputStreamReader,这两个类是字节流和字符流之间的适配器类,承担了编码转换的任务;对于 C 程序员来讲,要完成这样的编码转换恐怕要依赖于 union(联合体/共用体)共享内存的特征来实现了。

[if !supportLists]10. [endif]抽象类(abstract class)和接口(interface)有什么异同?(2017-11-16-wl)

不一样:

抽象类:

1.抽象类中能够定义构造器

2.能够有抽象方法和具体方法

3.接口中的成员全都是public的

4.抽象类中能够定义成员变量

5.有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法

6.抽象类中能够包含静态方法

7.一个类只能继承一个抽象类

接口:

1.接口中不能定义构造器

2.方法所有都是抽象方法

3.抽象类中的成员能够是 private、默认、protected、public

4.接口中定义的成员变量实际上都是常量

5.接口中不能有静态方法

6.一个类能够实现多个接口

相同:

1.不可以实例化

2.能够将抽象类和接口类型做为引用类型

3.一个类若是继承了某个抽象类或者实现了某个接口都须要对其中的抽象方法所有进行实现,不然该类仍然须要被声明为抽象类

[if !supportLists]11. [endif]抽象的(abstract)方法是否可同时是静态的(static), 是否可同时是本地方法(native),是否可同时被synchronized(2017-11-16-wl)

都不能。抽象方法须要子类重写,而静态的方法是没法被重写的,所以两者是矛盾的。本地方法是由

本地代码(如C 代码)实现的方法,而抽象方法是没有实现的,也是矛盾的。synchronized 和方法的实现细节有关,抽象方法不涉及实现细节,所以也是相互矛盾的。

[if !supportLists]12. [endif]阐述静态变量和实例变量的区别?(2017-11-16-wl)

静态变量是被static 修饰符修饰的变量,也称为类变量,它属于类,不属于类的任何一个对象,一个类无论建立多少个对象,静态变量在内存中有且仅有一个拷贝;

实例变量必须依存于某一实例,须要先建立对象而后经过对象才能访问到它。静态变量能够实现让多个对象共享内存。

[if !supportLists]13. [endif]==和equals的区别?(2017-11-22-wzz)

equals和== 最大的区别是一个是方法一个是运算符。

==:若是比较的对象是基本数据类型,则比较的是数值是否相等;若是比较的是引用数据类型,则比较的是对象的地址值是否相等。

equals():用来比较方法两个对象的内容是否相等。

注意:equals方法不能用于基本数据类型的变量,若是没有对equals方法进行重写,则比较的是引用类型的变量所指向的对象的地址。

[if !supportLists]14. [endif]break和continue的区别?(2017-11-23-wzz)

break和continue都是用来控制循环的语句。

break用于彻底结束一个循环,跳出循环体执行循环后面的语句。

continue用于跳过本次循环,执行下次循环。

[if !supportLists]15. [endif]String s = "Hello";s = s + " world!";这两行代码执行后,原始的String对象中的内容到底变了没有?(2017-12-1-lyq)

没有。由于String被设计成不可变(immutable)类,因此它的全部对象都是不可变对象。在这段代码中,s原先指向一个String对象,内容是 "Hello",而后咱们对s进行了“+”操做,那么s所指向的那个对象是否发生了改变呢?答案是没有。这时,s不指向原来那个对象了,而指向了另外一个 String对象,内容为"Hello world!",原来那个对象还存在于内存之中,只是s这个引用变量再也不指向它了。

经过上面的说明,咱们很容易导出另外一个结论,若是常常对字符串进行各类各样的修改,或者说,不可预见的修改,那么使用String来表明字符串的话会引发很大的内存开销。由于 String对象创建以后不能再改变,因此对于每个不一样的字符串,都须要一个String对象来表示。这时,应该考虑使用StringBuffer类,它容许修改,而不是每一个不一样的字符串都要生成一个新的对象。而且,这两种类的对象转换十分容易。同时,咱们还能够知道,若是要使用内容相同的字符串,没必要每次都new一个String。例如咱们要在构造器中对一个名叫s的String引用变量进行初始化,把它设置为初始值,应当这样作:

[if !supportLists]1.[endif]public class Demo {

[if !supportLists]2.[endif]   private String s;

[if !supportLists]3.[endif]   ...

[if !supportLists]4.[endif]   s = "Initial Value";

[if !supportLists]5.[endif]   ...

[if !supportLists]6.[endif]}

而非

[if !supportLists]1.[endif]s = new String("Initial Value");    

后者每次都会调用构造器,生成新对象,性能低下且内存开销大,而且没有意义,由于String对象不可改变,因此对于内容相同的字符串,只要一个String对象来表示就能够了。也就说,屡次调用上面的构造器建立多个对象,他们的String类型属性s都指向同一个对象。

上面的结论还基于这样一个事实:对于字符串常量,若是内容相同,Java认为它们表明同一个String对象。而用关键字new调用构造器,老是会建立一个新的对象,不管内容是否相同。 至于为何要把String类设计成不可变类,是它的用途决定的。其实不仅String,不少Java标准类库中的类都是不可变的。在开发一个系统的时候,咱们有时候也须要设计不可变类,来传递一组相关的值,这也是面向对象思想的体现。不可变类有一些优势,好比由于它的对象是只读的,因此多线程并发访问也不会有任何问题。固然也有一些缺点,好比每一个不一样的状态都要一个对象来表明,可能会形成性能上的问题。因此Java标准类库还提供了一个可变版本,即 StringBuffer。

[if !supportLists]3、[endif]Java中的多态

[if !supportLists]1. [endif]Java中实现多态的机制是什么?

靠的是父类或接口定义的引用变量能够指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,就是引用变量所指向的具体实例对象的方法,也就是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。

[if !supportLists]4、[endif]Java的异常处理

[if !supportLists]1. [endif]Java中异常分为哪些种类

[if !supportLists]1)[endif]按照异常须要处理的时机分为编译时异常(也叫强制性异常)也叫CheckedException和运行时异常(也叫非强制性异常)也叫RuntimeException。只有java语言提供了Checked异常,Java认为Checked异常都是能够被处理的异常,因此Java程序必须显式处理Checked异常。若是程序没有处理Checked异常,该程序在编译时就会发生错误没法编译。这体现了Java的设计哲学:没有完善错误处理的代码根本没有机会被执行。对Checked异常处理方法有两种:

1 当前方法知道如何处理该异常,则用try...catch块来处理该异常。

2 当前方法不知道如何处理,则在定义该方法是声明抛出该异常。

运行时异常只有当代码在运行时才发行的异常,编译时不须要try catch。Runtime如除数是0和数组下标越界等,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和运行效率影响很大。因此由系统自动检测并将它们交给缺省的异常处理程序。固然若是你有处理要求也能够显示捕获它们。

[if !supportLists]2. [endif]调用下面的方法,获得的返回值是什么

[if !supportLists]1. [endif]public int getNum(){

[if !supportLists]2. [endif]     try {

[if !supportLists]3. [endif] int a = 1/0;

[if !supportLists]4. [endif] return 1;

[if !supportLists]5. [endif] } catch (Exception e) {

[if !supportLists]6. [endif] return 2;

[if !supportLists]7. [endif] }finally{

[if !supportLists]8. [endif] return 3;

[if !supportLists]9. [endif] }

代码在走到第3行的时候遇到了一个MathException,这时第四行的代码就不会执行了,代码直接跳转到catch语句中,走到第6行的时候,异常机制有这么一个原则若是在catch中遇到了return或者异常等能使该函数终止的话那么有finally就必须先执行完finally代码块里面的代码而后再返回值。所以代码又跳到第8行,惋惜第8行是一个return语句,那么这个时候方法就结束了,所以第6行的返回结果就没法被真正返回。若是finally仅仅是处理了一个释放资源的操做,那么该道题最终返回的结果就是2。所以上面返回值是3。

[if !supportLists]3. [endif]error和exception的区别?(2017-2-23)

Error类和Exception类的父类都是Throwable类,他们的区别以下。

Error类通常是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等。对于这类错误的致使的应用程序中断,仅靠程序自己没法恢复和和预防,遇到这样的错误,建议让程序终止。

Exception类表示程序能够处理的异常,能够捕获且可能恢复。遇到这类异常,应该尽量处理异常,使程序恢复运行,而不该该随意终止异常。

Exception类又分为运行时异常(Runtime Exception)和受检查的异常(Checked Exception ),运行时异常;ArithmaticException,IllegalArgumentException,编译能经过,可是一运行就终止了,程序不会处理运行时异常,出现这类异常,程序会终止。而受检查的异常,要么用try。。。catch捕获,要么用throws字句声明抛出,交给它的父类处理,不然编译不会经过。

[if !supportLists]4. [endif]java异常处理机制(2017-2-23)

Java对异常进行了分类,不一样类型的异常分别用不一样的Java类表示,全部异常的根类为java.lang.Throwable,Throwable下面又派生了两个子类:Error和Exception,Error表示应用程序自己没法克服和恢复的一种严重问题。Exception表示程序还可以克服和恢复的问题,其中又分为系统异常和普通异常,系统异常是软件自己缺陷所致使的问题,也就是软件开发人员考虑不周所致使的问题,软件使用者没法克服和恢复这种问题,但在这种问题下还能够让软件系统继续运行或者让软件死掉,例如,数组脚本越界(ArrayIndexOutOfBoundsException),空指针异常(NullPointerException)、类转换异常(ClassCastException);普通异常是运行环境的变化或异常所致使的问题,是用户可以克服的问题,例如,网络断线,硬盘空间不够,发生这样的异常后,程序不该该死掉。

java为系统异常和普通异常提供了不一样的解决方案,编译器强制普通异常必须try..catch处理或用throws声明继续抛给上层调用方法处理,因此普通异常也称为checked异常,而系统异常能够处理也能够不处理,因此,编译器不强制用try..catch处理或用throws声明,因此系统异常也称为unchecked异常。

[if !supportLists]5. [endif]请写出你最多见的5个RuntimeException(2017-11-22-wzz)

下面列举几个常见的RuntimeException。

1)java.lang.NullPointerException 空指针异常;出现缘由:调用了未经初始化的对象或者是不存在的对象。

2)java.lang.ClassNotFoundException 指定的类找不到;出现缘由:类的名称和路径加载错误;一般都是程序试图经过字符串来加载某个类时可能引起异常。

3)java.lang.NumberFormatException 字符串转换为数字异常;出现缘由:字符型数据中包含非数字型字符。

4)java.lang.IndexOutOfBoundsException 数组角标越界异常,常见于操做数组对象时发生。

5)java.lang.IllegalArgumentException 方法传递参数错误。

6)java.lang.ClassCastException 数据类型转换异常。

7)java.lang.NoClassDefFoundException 未找到类定义错误。

8)SQLException SQL异常,常见于操做数据库时的SQL语句错误。

9)java.lang.InstantiationException实例化异常。

10)java.lang.NoSuchMethodException 方法不存在异常。

[if !supportLists]6. [endif]throw和throws的区别(2017-11-22-wzz)

throw:

1)throw语句用在方法体内,表示抛出异常,由方法体内的语句处理。

2)throw是具体向外抛出异常的动做,因此它抛出的是一个异常实例,执行throw必定是抛出了某种异常。

throws

1)throws语句是用在方法声明后面,表示若是抛出异常,由该方法的调用者来进行异常的处理。

2)throws主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道须要捕获的异常的类型。

3)throws表示出现异常的一种可能性,并不必定会发生这种异常。

[if !supportLists]7. [endif]final、finally、finalize的区别?(2017-11-23-wzz)

1)final:用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,被其修饰的类不可继承。

2)finally:异常处理语句结构的一部分,表示老是执行。

3)finalize:Object类的一个方法,在垃圾回收器执行的时候会调用被回收对象的此方法,能够覆盖此方法提供垃圾收集时的其余资源回收,例如关闭文件等。该方法更像是一个对象生命周期的临终方法,当该方法被系统调用则表明该对象即将“死亡”,可是须要注意的是,咱们主动行为上去调用该方法并不会致使该对象“死亡”,这是一个被动的方法(其实就是回调方法),不须要咱们调用。

[if !supportLists]5、[endif]JavaSE经常使用API

[if !supportLists]1. [endif]Math.round(11.5)等于多少?Math.round(- 11.5) 又等于多少?(2017-11-14-wl)

Math.round(11.5)的返回值是 12,Math.round(-11.5)的返回值是-11。四舍五入的原理是在参数上加 0.5而后进行取整。

[if !supportLists]2. [endif]switch是否能做用在byte上,是否能做用在long上,是否能做用在String上?(2017-11-14-wl)

Java5之前switch(expr)中,expr 只能是 byte、short、char、int。从 Java 5 开始,Java 中引入了枚举类型,expr 也能够是 enum 类型。

从Java 7 开始,expr 还能够是字符串(String),可是长整型(long)在目前全部的版本中都是不能够的。

[if !supportLists]3. [endif]数组有没有length() 方法?String有没有length() 方法?(2017-11-14-wl)

数组没有length()方法,而是有length 的属性。String有length()方法。JavaScript 中,得到字符串的长度是经过 length 属性获得的,这一点容易和 Java 混淆。

[if !supportLists]4. [endif]String 、StringBuilder 、StringBuffer的区别?(2017-11-14-wl)

Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,它们均可以储存和操做字符串,区别以下。

[if !supportLists]1)[endif]String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。初学者可能会有这样的误解:

[if !supportLists]1. [endif]String str = “abc”;

[if !supportLists]2. [endif]str = “bcd”;

如上,字符串str明明是能够改变的呀!其实否则,str仅仅是一个引用对象,它指向一个字符串对象“abc”。第二行代码的含义是让str从新指向了一个新的字符串“bcd”对象,而“abc”对象并无任何改变,只不过该对象已经成为一个不可及对象罢了。

2)StringBuffer/StringBuilder表示的字符串对象能够直接进行修改。

3)StringBuilder是Java5中引入的,它和 StringBuffer的方法彻底相同,区别在于它是在单线程环境下使用的,由于它的全部方法都没有被synchronized修饰,所以它的效率理论上也比StringBuffer要高。

[if !supportLists]5. [endif]什么状况下用“+”运算符进行字符串链接比调用StringBuffer/StringBuilder 对象的append方法链接字符串性能更好?(2017-11-14-wl)

该题来自华为。

字符串是Java程序中最经常使用的数据结构之一。在Java中String类已经重载了"+"。也就是说,字符串能够直接使用"+"进行链接,以下面代码所示:

[if !supportLists]1.[endif]String s = "abc" + "ddd";

但这样作真的好吗?固然,这个问题不能简单地回答yes or no。要根据具体状况来定。在Java中提供了一个StringBuilder类(这个类只在J2SE5及以上版本提供,之前的版本使用StringBuffer类),这个类也能够起到"+"的做用。那么咱们应该用哪一个呢?

下面让咱们先看看以下的代码:

[if !supportLists]1. [endif]package string;

[if !supportLists]2. [endif]  

[if !supportLists]3. [endif]  public class TestSimplePlus

[if !supportLists]4. [endif]  {

[if !supportLists]5. [endif]      public static void main(String[] args)

[if !supportLists]6. [endif]      {

[if !supportLists]7. [endif]          String s = "abc";

[if !supportLists]8. [endif]          String ss = "ok" + s + "xyz" + 5;

[if !supportLists]9. [endif]          System.out.println(ss);

[if !supportLists]10. [endif]      }

[if !supportLists]11. [endif]  }

上面的代码将会输出正确的结果。从表面上看,对字符串和整型使用"+"号并无什么区别,但事实真的如此吗?下面让咱们来看看这段代码的本质。

咱们首先使用反编译工具(如jdk带的javap、或jad)将TestSimplePlus反编译成Java Byte Code,其中的奥秘就一目了然了。在本文将使用jad来反编译,命令以下:jad -o -a -s d.java TestSimplePlus.class

反编译后的代码以下:

[if !supportLists]1. [endif]package string;

[if !supportLists]2. [endif]

[if !supportLists]3. [endif]import java.io.PrintStream;

[if !supportLists]4. [endif]

[if !supportLists]5. [endif]public class TestSimplePlus

[if !supportLists]6. [endif]{

[if !supportLists]7. [endif]    public TestSimplePlus()

[if !supportLists]8. [endif]    {

[if !supportLists]9. [endif]    //    0    0:aload_0         

[if !supportLists]10. [endif]    //    1    1:invokespecial   #8   

[if !supportLists]11. [endif]    //    2    4:return          

[if !supportLists]12. [endif]    }

[if !supportLists]13. [endif]

[if !supportLists]14. [endif]    public static void main(String args[])

[if !supportLists]15. [endif]    {

[if !supportLists]16. [endif]      String s = "abc";

[if !supportLists]17. [endif]    //    0    0:ldc1            #16  

[if !supportLists]18. [endif]    //    1    2:astore_1        

[if !supportLists]19. [endif]      String ss = (new StringBuilder("ok")).append(s).append("xyz").append(5).toString();

[if !supportLists]20. [endif]    //    2    3:new             #18  

[if !supportLists]21. [endif]    //    3    6:dup             

[if !supportLists]22. [endif]    //    4    7:ldc1            #20  

[if !supportLists]23. [endif]    //    5    9:invokespecial   #22  

[if !supportLists]24. [endif]    //    6   12:aload_1         

[if !supportLists]25. [endif]    //    7   13:invokevirtual   #25  

[if !supportLists]26. [endif]    //    8   16:ldc1            #29  

[if !supportLists]27. [endif]    //    9   18:invokevirtual   #25  

[if !supportLists]28. [endif]    //   10   21:iconst_5        

[if !supportLists]29. [endif]    //   11   22:invokevirtual   #31  

[if !supportLists]30. [endif]    //   12   25:invokevirtual   #34  

[if !supportLists]31. [endif]    //   13   28:astore_2        

[if !supportLists]32. [endif]      System.out.println(ss);

[if !supportLists]33. [endif]    //   14   29:getstatic       #38  

[if !supportLists]34. [endif]    //   15   32:aload_2         

[if !supportLists]35. [endif]    //   16   33:invokevirtual   #44  

[if !supportLists]36. [endif]    //   17   36:return          

[if !supportLists]37. [endif]    }

[if !supportLists]38. [endif]}

读者可能看到上面的Java字节码感到迷糊,不过你们没必要担忧。本文的目的并非讲解Java Byte Code,所以,并不用了解具体的字节码的含义。

使用jad反编译的好处之一就是能够同时生成字节码和源代码。这样能够进行对照研究。从上面的代码很容易看出,虽然在源程序中使用了"+",但在编译时仍然将"+"转换成StringBuilder。所以,咱们能够得出结论,Java中不管使用何种方式进行字符串链接,实际上都使用的是StringBuilder

那么是否是能够根据这个结论推出使用"+"和StringBuilder的效果是同样的呢?这个要从两个方面的解释。若是从运行结果来解释,那么"+"和StringBuilder是彻底等效的。但若是从运行效率和资源消耗方面看,那它们将存在很大的区别。

  固然,若是链接字符串行表达式很简单(如上面的顺序结构),那么"+"和StringBuilder基本是同样的,但若是结构比较复杂,如使用循环来链接字符串,那么产生的Java Byte Code就会有很大的区别。先让咱们看看以下的代码:

[if !supportLists]1. [endif]package string;

[if !supportLists]2. [endif]  

[if !supportLists]3. [endif]  import java.util.*;

[if !supportLists]4. [endif]  

[if !supportLists]5. [endif]  public class TestComplexPlus

[if !supportLists]6. [endif]  {

[if !supportLists]7. [endif]      public static void main(String[] args)

[if !supportLists]8. [endif]      {

[if !supportLists]9. [endif]          String s = "";

[if !supportLists]10. [endif]          Random rand = new Random();

[if !supportLists]11. [endif]          for (int i = 0; i < 10; i++)

[if !supportLists]12. [endif]          {

[if !supportLists]13. [endif]              s = s + rand.nextInt(1000) + " ";

[if !supportLists]14. [endif]          }

[if !supportLists]15. [endif]          System.out.println(s);

[if !supportLists]16. [endif]      }

[if !supportLists]17. [endif]  }

 上面的代码返编译后的Java Byte Code以下:

[if !supportLists]1. [endif]package string;

[if !supportLists]2. [endif]

[if !supportLists]3. [endif]import java.io.PrintStream;

[if !supportLists]4. [endif]import java.util.Random;

[if !supportLists]5. [endif]

[if !supportLists]6. [endif]public class TestComplexPlus

[if !supportLists]7. [endif]{

[if !supportLists]8. [endif]

[if !supportLists]9. [endif]    public TestComplexPlus()

[if !supportLists]10. [endif]    {

[if !supportLists]11. [endif]    //    0    0:aload_0         

[if !supportLists]12. [endif]    //    1    1:invokespecial   #8   

[if !supportLists]13. [endif]    //    2    4:return          

[if !supportLists]14. [endif]    }

[if !supportLists]15. [endif]

[if !supportLists]16. [endif]    public static void main(String args[])

[if !supportLists]17. [endif]    {

[if !supportLists]18. [endif]        String s = "";

[if !supportLists]19. [endif]    //    0    0:ldc1            #16  

[if !supportLists]20. [endif]    //    1    2:astore_1        

[if !supportLists]21. [endif]        Random rand = new Random();

[if !supportLists]22. [endif]    //    2    3:new             #18  

[if !supportLists]23. [endif]    //    3    6:dup             

[if !supportLists]24. [endif]    //    4    7:invokespecial   #20  

[if !supportLists]25. [endif]    //    5   10:astore_2        

[if !supportLists]26. [endif]        for(int i = 0; i < 10; i++)

[if !supportLists]27. [endif]    //*   6   11:iconst_0        

[if !supportLists]28. [endif]    //*   7   12:istore_3        

[if !supportLists]29. [endif]    //*   8   13:goto            49

[if !supportLists]30. [endif]         s = (new StringBuilder(String.valueOf(s))).append(rand.nextInt(1000)).append(" ").toString();

[if !supportLists]31. [endif]    //    9   16:new             #21  

[if !supportLists]32. [endif]    //   10   19:dup             

[if !supportLists]33. [endif]    //   11   20:aload_1         

[if !supportLists]34. [endif]    //   12   21:invokestatic    #23  

[if !supportLists]35. [endif]    //   13   24:invokespecial   #29  

[if !supportLists]36. [endif]    //   14   27:aload_2         

[if !supportLists]37. [endif]    //   15   28:sipush          1000

[if !supportLists]38. [endif]    //   16   31:invokevirtual   #32  

[if !supportLists]39. [endif]    //   17   34:invokevirtual   #36  

[if !supportLists]40. [endif]    //   18   37:ldc1            #40  

[if !supportLists]41. [endif]    //   19   39:invokevirtual   #42  

[if !supportLists]42. [endif]    //   20   42:invokevirtual   #45  

[if !supportLists]43. [endif]    //   21   45:astore_1        

[if !supportLists]44. [endif]

[if !supportLists]45. [endif]    //   22   46:iinc            3  1

[if !supportLists]46. [endif]    //   23   49:iload_3         

[if !supportLists]47. [endif]    //   24   50:bipush          10

[if !supportLists]48. [endif]    //   25   52:icmplt          16

[if !supportLists]49. [endif]        System.out.println(s);

[if !supportLists]50. [endif]    //   26   55:getstatic       #49  

[if !supportLists]51. [endif]    //   27   58:aload_1         

[if !supportLists]52. [endif]    //   28   59:invokevirtual   #55  

[if !supportLists]53. [endif]    //   29   62:return          

[if !supportLists]54. [endif]    }

[if !supportLists]55. [endif]}

  你们能够看到,虽然编译器将"+"转换成了StringBuilder,但建立StringBuilder对象的位置却在for语句内部。这就意味着每执行一次循环,就会建立一个StringBuilder对象(对于本例来讲,是建立了10个StringBuilder对象),虽然Java有垃圾回收器,但这个回收器的工做时间是不定的。若是不断产生这样的垃圾,那么仍然会占用大量的资源。解决这个问题的方法就是在程序中直接使用StringBuilder来链接字符串,代码以下:

[if !supportLists]1. [endif]package string;

[if !supportLists]2. [endif]

[if !supportLists]3. [endif]import java.util.*;

[if !supportLists]4. [endif]

[if !supportLists]5. [endif]public class TestStringBuilder

[if !supportLists]6. [endif]{

[if !supportLists]7. [endif]    public static void main(String[] args)

[if !supportLists]8. [endif]    {

[if !supportLists]9. [endif]        String s = "";

[if !supportLists]10. [endif]        Random rand = new Random();

[if !supportLists]11. [endif]        StringBuilder result = new StringBuilder();

[if !supportLists]12. [endif]        for (int i = 0; i < 10; i++)

[if !supportLists]13. [endif]        {

[if !supportLists]14. [endif]            result.append(rand.nextInt(1000));

[if !supportLists]15. [endif]            result.append(" ");

[if !supportLists]16. [endif]        }

[if !supportLists]17. [endif]        System.out.println(result.toString());

[if !supportLists]18. [endif]    }

[if !supportLists]19. [endif]}

上面代码反编译后的结果以下:

[if !supportLists]1. [endif]20.package string;

[if !supportLists]2. [endif]

[if !supportLists]3. [endif]import java.io.PrintStream;

[if !supportLists]4. [endif]import java.util.Random;

[if !supportLists]5. [endif]

[if !supportLists]6. [endif]public class TestStringBuilder

[if !supportLists]7. [endif]{

[if !supportLists]8. [endif]

[if !supportLists]9. [endif]    public TestStringBuilder()

[if !supportLists]10. [endif]    {

[if !supportLists]11. [endif]    //    0    0:aload_0         

[if !supportLists]12. [endif]    //    1    1:invokespecial   #8   

[if !supportLists]13. [endif]    //    2    4:return          

[if !supportLists]14. [endif]    }

[if !supportLists]15. [endif]

[if !supportLists]16. [endif]    public static void main(String args[])

[if !supportLists]17. [endif]    {

[if !supportLists]18. [endif]        String s = "";

[if !supportLists]19. [endif]    //    0    0:ldc1            #16  

[if !supportLists]20. [endif]    //    1    2:astore_1        

[if !supportLists]21. [endif]        Random rand = new Random();

[if !supportLists]22. [endif]    //    2    3:new             #18  

[if !supportLists]23. [endif]    //    3    6:dup             

[if !supportLists]24. [endif]    //    4    7:invokespecial   #20  

[if !supportLists]25. [endif]    //    5   10:astore_2        

[if !supportLists]26. [endif]        StringBuilder result = new StringBuilder();

[if !supportLists]27. [endif]    //    6   11:new             #21  

[if !supportLists]28. [endif]    //    7   14:dup             

[if !supportLists]29. [endif]    //    8   15:invokespecial   #23  

[if !supportLists]30. [endif]    //    9   18:astore_3        

[if !supportLists]31. [endif]        for(int i = 0; i < 10; i++)

[if !supportLists]32. [endif]    //*  10   19:iconst_0        

[if !supportLists]33. [endif]    //*  11   20:istore          4

[if !supportLists]34. [endif]    //*  12   22:goto            47

[if !supportLists]35. [endif]        {

[if !supportLists]36. [endif]            result.append(rand.nextInt(1000));

[if !supportLists]37. [endif]    //   13   25:aload_3         

[if !supportLists]38. [endif]    //   14   26:aload_2         

[if !supportLists]39. [endif]    //   15   27:sipush          1000

[if !supportLists]40. [endif]    //   16   30:invokevirtual   #24  

[if !supportLists]41. [endif]    //   17   33:invokevirtual   #28  

[if !supportLists]42. [endif]    //   18   36:pop             

[if !supportLists]43. [endif]            result.append(" ");

[if !supportLists]44. [endif]    //   19   37:aload_3         

[if !supportLists]45. [endif]    //   20   38:ldc1            #32  

[if !supportLists]46. [endif]    //   21   40:invokevirtual   #34  

[if !supportLists]47. [endif]    //   22   43:pop             

[if !supportLists]48. [endif]        }

[if !supportLists]49. [endif]

[if !supportLists]50. [endif]    //   23   44:iinc            4  1

[if !supportLists]51. [endif]    //   24   47:iload           4

[if !supportLists]52. [endif]    //   25   49:bipush          10

[if !supportLists]53. [endif]    //   26   51:icmplt          25

[if !supportLists]54. [endif]        System.out.println(result.toString());

[if !supportLists]55. [endif]    //   27   54:getstatic       #37  

[if !supportLists]56. [endif]    //   28   57:aload_3         

[if !supportLists]57. [endif]    //   29   58:invokevirtual   #43  

[if !supportLists]58. [endif]    //   30   61:invokevirtual   #47  

[if !supportLists]59. [endif]    //   31   64:return          

[if !supportLists]60. [endif]    }

[if !supportLists]61. [endif]}

从上面的反编译结果能够看出,建立StringBuilder的代码被放在了for语句外。虽然这样处理在源程序中看起来复杂,但却换来了更高的效率,同时消耗的资源也更少了。

在使用StringBuilder时要注意,尽可能不要"+"和StringBuilder混着用,不然会建立更多的StringBuilder对象,以下面代码所:

for (int i = 0; i < 10; i++)    {        result.append(rand.nextInt(1000));        result.append(" ");    }

改为以下形式:

for (int i = 0; i < 10; i++){     result.append(rand.nextInt(1000) + " ");}

则反编译后的结果以下:

 for(int i = 0; i < 10; i++)  //*  10   19:iconst_0          //*  11   20:istore          4  //*  12   22:goto            65   {    result.append((new StringBuilder(String.valueOf(rand.nextInt(1000)))).append(" ").toString());  //   13   25:aload_3           //   14   26:new             #21    //   15   29:dup             

从上面的代码能够看出,Java编译器将"+"编译成了StringBuilder,这样for语句每循环一次,又建立了一个StringBuilder对象。    若是将上面的代码在JDK1.4下编译,必须将StringBuilder改成StringBuffer,而JDK1.4将"+"转换为StringBuffer(由于JDK1.4并无提供StringBuilder类)。StringBuffer和StringBuilder的功能基本同样,只是StringBuffer是线程安全的,而StringBuilder不是线程安全的。所以,StringBuilder的效率会更高。

[if !supportLists]6. [endif]请说出下面程序的输出(2017-11-14-wl)

[if !supportLists]1. [endif]class StringEqualTest {

[if !supportLists]2. [endif]   public static void main(String[] args) {

[if !supportLists]3. [endif]        String s1 = "Programming";

[if !supportLists]4. [endif]        String s2 = new String("Programming");

[if !supportLists]5. [endif]        String s3 = "Program";

[if !supportLists]6. [endif]        String s4 = "ming";

[if !supportLists]7. [endif]        String s5 = "Program" + "ming";

[if !supportLists]8. [endif]        String s6 = s3 + s4;

[if !supportLists]9. [endif]        System.out.println(s1 == s2);               //false

[if !supportLists]10. [endif]       System.out.println(s1 == s5);               //true

[if !supportLists]11. [endif]       System.out.println(s1 == s6);              //false

[if !supportLists]12. [endif]       System.out.println(s1 == s6.intern());   //true

[if !supportLists]13. [endif]      System.out.println(s2 == s2.intern());    //false

[if !supportLists]14. [endif] }

[if !supportLists]15. [endif]}    

 

补充:解答上面的面试题须要知道以下两个知识点:

1. String对象的intern()方法会获得字符串对象在常量池中对应的版本的引用(若是常量池中有一个字符串与String 对象的 equals结果是 true),若是常量池中没有对应的字符串,则该字符串将被添加到常量池中,而后返回常量池中字符串的引用;

2. 字符串的+操做其本质是建立了StringBuilder 对象进行 append 操做,而后将拼接后的 StringBuilder 对象用 toString 方法处理成 String 对象,这一点能够用 javap -c StringEqualTest.class 命令得到 class 文件对应的 JVM 字节码指令就能够看出来。

[if !supportLists]7. [endif]Java中的日期和时间(2017-11-19-wl)

7.1如何取得年月日、小时分钟秒?(2017-11-19-wl)

[if !supportLists]1. [endif]public class DateTimeTest {

[if !supportLists]2. [endif] public static void main(String[] args) {

[if !supportLists]3. [endif]   Calendar cal = Calendar.getInstance();

[if !supportLists]4. [endif]   System.out.println(cal.get(Calendar.YEAR));

[if !supportLists]5. [endif]   System.out.println(cal.get(Calendar.MONTH)); // 0 - 11

[if !supportLists]6. [endif]   System.out.println(cal.get(Calendar.DATE));

[if !supportLists]7. [endif]   System.out.println(cal.get(Calendar.HOUR_OF_DAY));

[if !supportLists]8. [endif]   System.out.println(cal.get(Calendar.MINUTE));

[if !supportLists]9. [endif]   System.out.println(cal.get(Calendar.SECOND));

[if !supportLists]10. [endif]   // Java 8

[if !supportLists]11. [endif]   LocalDateTime dt = LocalDateTime.now();

[if !supportLists]12. [endif]   System.out.println(dt.getYear());

[if !supportLists]13. [endif]   System.out.println(dt.getMonthValue()); // 1 - 12

[if !supportLists]14. [endif]   System.out.println(dt.getDayOfMonth());

[if !supportLists]15. [endif]   System.out.println(dt.getHour());

[if !supportLists]16. [endif]   System.out.println(dt.getMinute());

[if !supportLists]17. [endif]   System.out.println(dt.getSecond());

[if !supportLists]18. [endif] }

[if !supportLists]19. [endif]}

 

7.2如何取得从1970年1月1日0时0分0 秒到如今的毫秒数(2017-11-19-wl)

[if !supportLists]1. [endif]Calendar.getInstance().getTimeInMillis();  //第一种方式

[if !supportLists]2. [endif]System.currentTimeMillis();  //第二种方式

[if !supportLists]3. [endif]// Java 8

[if !supportLists]4. [endif]Clock.systemDefaultZone().millis();

 

7.3如何取得某月的最后一天(2017-11-19-wl)

[if !supportLists]1. [endif]//获取当前月第一天:

[if !supportLists]2. [endif]Calendar c = Calendar.getInstance();    

[if !supportLists]3. [endif]c.add(Calendar.MONTH, 0);

[if !supportLists]4. [endif]c.set(Calendar.DAY_OF_MONTH,1);//设置为1号,当前日期既为本月第一天

[if !supportLists]5. [endif]String first = format.format(c.getTime());

[if !supportLists]6. [endif]System.out.println("===============first:"+first);

[if !supportLists]7. [endif]

[if !supportLists]8. [endif]//获取当前月最后一天

[if !supportLists]9. [endif] Calendar ca = Calendar.getInstance();   

[if !supportLists]10. [endif]ca.set(Calendar.DAY_OF_MONTH, ca.getActualMaximum(Calendar.DAY_OF_MONTH));

[if !supportLists]11. [endif]String last = format.format(ca.getTime());

[if !supportLists]12. [endif]System.out.println("===============last:"+last);

[if !supportLists]13. [endif]

[if !supportLists]14. [endif]//Java 8

[if !supportLists]15. [endif]LocalDate today = LocalDate.now();

[if !supportLists]16. [endif]//本月的第一天

[if !supportLists]17. [endif]LocalDate firstday = LocalDate.of(today.getYear(),today.getMonth(),1);

[if !supportLists]18. [endif]//本月的最后一天

[if !supportLists]19. [endif]LocalDate lastDay =today.with(TemporalAdjusters.lastDayOfMonth());

[if !supportLists]20. [endif]System.out.println("本月的第一天"+firstday);

[if !supportLists]21. [endif]System.out.println("本月的最后一天"+lastDay);

 

 

7.4如何格式化日期(2017-11-19-wl)

1)Java.text.DataFormat 的子类(如 SimpleDateFormat 类)中的 format(Date)方法可将日期格式化。

2)Java 8 中能够用 java.time.format.DateTimeFormatter来格式化时间日期,代码以下所示:

[if !supportLists]1. [endif]import java.text.SimpleDateFormat;

[if !supportLists]2. [endif]import java.time.LocalDate;

[if !supportLists]3. [endif]import java.time.format.DateTimeFormatter;

[if !supportLists]4. [endif]import java.util.Date;

[if !supportLists]5. [endif]class DateFormatTest {

[if !supportLists]6. [endif]

[if !supportLists]7. [endif]    public static void main(String[] args) {

[if !supportLists]8. [endif]       SimpleDateFormat oldFormatter = new SimpleDateFormat("yyyy/MM/dd");

[if !supportLists]9. [endif]       Date date1 = new Date();

[if !supportLists]10. [endif]       System.out.println(oldFormatter.format(date1));

[if !supportLists]11. [endif]

[if !supportLists]12. [endif]       // Java 8

[if !supportLists]13. [endif]       DateTimeFormatter newFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");

[if !supportLists]14. [endif]       LocalDate date2 = LocalDate.now();

[if !supportLists]15. [endif]       System.out.println(date2.format(newFormatter));

[if !supportLists]16. [endif]    }

[if !supportLists]17. [endif]}

 

补充:Java的时间日期API一直以来都是被诟病的东西,为了解决这一问题,Java 8中引入了新的时间日期API,其中包括 LocalDate、LocalTime、LocalDateTime、Clock、Instant 等类,这些的类的设计都使用了不变模式,所以是线程安全的设计。

 

7.5打印昨天的当前时刻? (2017-11-19-wl)

[if !supportLists]1. [endif]import java.util.Calendar;

[if !supportLists]2. [endif]class YesterdayCurrent {

[if !supportLists]3. [endif]   public static void main(String[] args){

[if !supportLists]4. [endif]    Calendar cal = Calendar.getInstance();

[if !supportLists]5. [endif]    cal.add(Calendar.DATE, -1);

[if !supportLists]6. [endif]    System.out.println(cal.getTime());

[if !supportLists]7. [endif]   }

[if !supportLists]8. [endif]}

[if !supportLists]9. [endif]

[if !supportLists]10. [endif]

[if !supportLists]11. [endif]//java-8

[if !supportLists]12. [endif]import java.time.LocalDateTime;

[if !supportLists]13. [endif]class YesterdayCurrent {

[if !supportLists]14. [endif]   public static void main(String[] args) {

[if !supportLists]15. [endif]      LocalDateTime today = LocalDateTime.now();

[if !supportLists]16. [endif]      LocalDateTime yesterday = today.minusDays(1);

[if !supportLists]17. [endif]      System.out.println(yesterday);

[if !supportLists]18. [endif]   }

[if !supportLists]19. [endif]}

 

7.6 Java8的日期特性? (2017-12-3-wl)

Java 8日期/时间特性

Java 8日期/时间API是JSR-310的实现,它的实现目标是克服旧的日期时间实现中全部的缺陷,新的日期/时间API的一些设计原则是:

[if !supportLists]l [endif]不变性:新的日期/时间API中,全部的类都是不可变的,这对多线程环境有好处。

[if !supportLists]l [endif]关注点分离:新的API将人可读的日期时间和机器时间(unix timestamp)明确分离,它为日期(Date)、时间(Time)、日期时间(DateTime)、时间戳(unix timestamp)以及时区定义了不一样的类。

[if !supportLists]l [endif]清晰:在全部的类中,方法都被明肯定义用以完成相同的行为。举个例子,要拿到当前实例咱们能够使用now()方法,在全部的类中都定义了format()和parse()方法,而不是像之前那样专门有一个独立的类。为了更好的处理问题,全部的类都使用了工厂模式和策略模式,一旦你使用了其中某个类的方法,与其余类协同工做并不困难。

[if !supportLists]l [endif]实用操做:全部新的日期/时间API类都实现了一系列方法用以完成通用的任务,如:加、减、格式化、解析、从日期/时间中提取单独部分,等等。

[if !supportLists]l [endif]可扩展性:新的日期/时间API是工做在ISO-8601日历系统上的,但咱们也能够将其应用在非ISO的日历上。

Java 8日期/时间API包解释

[if !supportLists]l [endif]java.time包:这是新的Java日期/时间API的基础包,全部的主要基础类都是这个包的一部分,如:LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration等等。全部这些类都是不可变的和线程安全的,在绝大多数状况下,这些类可以有效地处理一些公共的需求。

[if !supportLists]l [endif]java.time.chrono包:这个包为非ISO的日历系统定义了一些泛化的API,咱们能够扩展AbstractChronology类来建立本身的日历系统。

[if !supportLists]l [endif]java.time.format包:这个包包含可以格式化和解析日期时间对象的类,在绝大多数状况下,咱们不该该直接使用它们,由于java.time包中相应的类已经提供了格式化和解析的方法。

[if !supportLists]l [endif]java.time.temporal包:这个包包含一些时态对象,咱们能够用其找出关于日期/时间对象的某个特定日期或时间,好比说,能够找到某月的第一天或最后一天。你能够很是容易地认出这些方法,由于它们都具备“withXXX”的格式。

[if !supportLists]l [endif]java.time.zone包:这个包包含支持不一样时区以及相关规则的类。

 

Java 8日期/时间经常使用API

1.java.time.LocalDate

LocalDate是一个不可变的类,它表示默认格式(yyyy-MM-dd)的日期,咱们能够使用now()方法获得当前时间,也能够提供输入年份、月份和日期的输入参数来建立一个LocalDate实例。该类为now()方法提供了重载方法,咱们能够传入ZoneId来得到指定时区的日期。该类提供与java.sql.Date相同的功能,对于如何使用该类,咱们来看一个简单的例子。

package com.journaldev.java8.time;

 

import java.time.LocalDate;

import java.time.Month;

import java.time.ZoneId;

 

/**

 * LocalDate Examples

 * @author pankaj

 *

 */

public class LocalDateExample {

 

    public static void main(String[] args) {

 

        //Current Date

        LocalDate today = LocalDate.now();

        System.out.println("Current Date="+today);

 

        //Creating LocalDate by providing input arguments

        LocalDate firstDay_2014 = LocalDate.of(2014, Month.JANUARY, 1);

        System.out.println("Specific Date="+firstDay_2014);

 

        //Try creating date by providing invalid inputs

        //LocalDate feb29_2014 = LocalDate.of(2014, Month.FEBRUARY, 29);

        //Exception in thread "main" java.time.DateTimeException: 

        //Invalid date 'February 29' as '2014' is not a leap year

 

        //Current date in "Asia/Kolkata", you can get it from ZoneId javadoc

        LocalDate todayKolkata = LocalDate.now(ZoneId.of("Asia/Kolkata"));

        System.out.println("Current Date in IST="+todayKolkata);

 

        //java.time.zone.ZoneRulesException: Unknown time-zone ID: IST

        //LocalDate todayIST = LocalDate.now(ZoneId.of("IST"));

 

        //Getting date from the base date i.e 01/01/1970

        LocalDate dateFromBase = LocalDate.ofEpochDay(365);

        System.out.println("365th day from base date= "+dateFromBase);

 

        LocalDate hundredDay2014 = LocalDate.ofYearDay(2014, 100);

        System.out.println("100th day of 2014="+hundredDay2014);

    }

}

 

输出:

Current Date=2014-04-28

Specific Date=2014-01-01

Current Date in IST=2014-04-29

365th day from base date= 1971-01-01

100th day of 2014=2014-04-10

2.java.time.LocalTime

LocalTime是一个不可变的类,它的实例表明一个符合人类可读格式的时间,默认格式是hh:mm:ss.zzz。像LocalDate同样,该类也提供了时区支持,同时也能够传入小时、分钟和秒等输入参数建立实例,咱们来看一个简单的程序,演示该类的使用方法。

package com.journaldev.java8.time;

 

import java.time.LocalTime;

import java.time.ZoneId;

 

/**

 * LocalTime Examples

 */

public class LocalTimeExample {

 

    public static void main(String[] args) {

 

        //Current Time

        LocalTime time = LocalTime.now();

        System.out.println("Current Time="+time);

 

        //Creating LocalTime by providing input arguments

        LocalTime specificTime = LocalTime.of(12,20,25,40);

        System.out.println("Specific Time of Day="+specificTime);

 

        //Try creating time by providing invalid inputs

        //LocalTime invalidTime = LocalTime.of(25,20);

        //Exception in thread "main" java.time.DateTimeException: 

        //Invalid value for HourOfDay (valid values 0 - 23): 25

 

        //Current date in "Asia/Kolkata", you can get it from ZoneId javadoc

        LocalTime timeKolkata = LocalTime.now(ZoneId.of("Asia/Kolkata"));

        System.out.println("Current Time in IST="+timeKolkata);

 

        //java.time.zone.ZoneRulesException: Unknown time-zone ID: IST

        //LocalTime todayIST = LocalTime.now(ZoneId.of("IST"));

 

        //Getting date from the base date i.e 01/01/1970

        LocalTime specificSecondTime = LocalTime.ofSecondOfDay(10000);

        System.out.println("10000th second time= "+specificSecondTime);

 

    }

}

输出:

Current Time=15:51:45.240

Specific Time of Day=12:20:25.000000040

Current Time in IST=04:21:45.276

10000th second time= 02:46:40

 

3. java.time.LocalDateTime

LocalDateTime是一个不可变的日期-时间对象,它表示一组日期-时间,默认格式是yyyy-MM-dd-HH-mm-ss.zzz。它提供了一个工厂方法,接收LocalDate和LocalTime输入参数,建立LocalDateTime实例。咱们来看一个简单的例子。

package com.journaldev.java8.time;

 

import java.time.LocalDate;

import java.time.LocalDateTime;

import java.time.LocalTime;

import java.time.Month;

import java.time.ZoneId;

import java.time.ZoneOffset;

 

public class LocalDateTimeExample {

 

    public static void main(String[] args) {

 

        //Current Date

        LocalDateTime today = LocalDateTime.now();

        System.out.println("Current DateTime="+today);

 

        //Current Date using LocalDate and LocalTime

        today = LocalDateTime.of(LocalDate.now(), LocalTime.now());

        System.out.println("Current DateTime="+today);

 

        //Creating LocalDateTime by providing input arguments

        LocalDateTime specificDate = LocalDateTime.of(2014, Month.JANUARY, 1, 10, 10, 30);

        System.out.println("Specific Date="+specificDate);

 

        //Try creating date by providing invalid inputs

        //LocalDateTime feb29_2014 = LocalDateTime.of(2014, Month.FEBRUARY, 28, 25,1,1);

        //Exception in thread "main" java.time.DateTimeException: 

        //Invalid value for HourOfDay (valid values 0 - 23): 25

 

        //Current date in "Asia/Kolkata", you can get it from ZoneId javadoc

        LocalDateTime todayKolkata = LocalDateTime.now(ZoneId.of("Asia/Kolkata"));

        System.out.println("Current Date in IST="+todayKolkata);

 

        //java.time.zone.ZoneRulesException: Unknown time-zone ID: IST

        //LocalDateTime todayIST = LocalDateTime.now(ZoneId.of("IST"));

 

        //Getting date from the base date i.e 01/01/1970

        LocalDateTime dateFromBase = LocalDateTime.ofEpochSecond(10000, 0, ZoneOffset.UTC);

        System.out.println("10000th second time from 01/01/1970= "+dateFromBase);

    }

}

输出:

Current DateTime=2014-04-28T16:00:49.455

Current DateTime=2014-04-28T16:00:49.493

Specific Date=2014-01-01T10:10:30

Current Date in IST=2014-04-29T04:30:49.493

10000th second time from 01/01/1970= 1970-01-01T02:46:40

在全部这三个例子中,咱们已经看到若是咱们提供了无效的参数去建立日期/时间,那么系统会抛出java.time.DateTimeException,这是一种运行时异常,咱们并不须要显式地捕获它。

同时咱们也看到,可以经过传入ZoneId获得日期/时间数据,你能够从它的Javadoc中获得支持的Zoneid的列表,当运行以上类时,能够获得以上输出。

4. java.time.Instant

Instant类是用在机器可读的时间格式上的,它以Unix时间戳的形式存储日期时间,咱们来看一个简单的程序

package com.journaldev.java8.time;

 

import java.time.Duration;

import java.time.Instant;

 

public class InstantExample {

 

    public static void main(String[] args) {

        //Current timestamp

        Instant timestamp = Instant.now();

        System.out.println("Current Timestamp = "+timestamp);

 

        //Instant from timestamp

        Instant specificTime = Instant.ofEpochMilli(timestamp.toEpochMilli());

        System.out.println("Specific Time = "+specificTime);

 

        //Duration example

        Duration thirtyDay = Duration.ofDays(30);

        System.out.println(thirtyDay);

    }

}

输出:

Current Timestamp = 2014-04-28T23:20:08.489Z

Specific Time = 2014-04-28T23:20:08.489Z

PT720H

5.日期API工具

咱们早些时候提到过,大多很多天期/时间API类都实现了一系列工具方法,如:加/减天数、周数、月份数,等等。还有其余的工具方法可以使用TemporalAdjuster调整日期,并计算两个日期间的周期。

package com.journaldev.java8.time;

 

import java.time.LocalDate;

import java.time.LocalTime;

import java.time.Period;

import java.time.temporal.TemporalAdjusters;

 

public class DateAPIUtilities {

 

    public static void main(String[] args) {

 

        LocalDate today = LocalDate.now();

 

        //Get the Year, check if it's leap year

        System.out.println("Year "+today.getYear()+" is Leap Year? "+today.isLeapYear());

 

        //Compare two LocalDate for before and after

        System.out.println("Today is before 01/01/2015? "+today.isBefore(LocalDate.of(2015,1,1)));

 

        //Create LocalDateTime from LocalDate

        System.out.println("Current Time="+today.atTime(LocalTime.now()));

 

        //plus and minus operations

        System.out.println("10 days after today will be "+today.plusDays(10));

        System.out.println("3 weeks after today will be "+today.plusWeeks(3));

        System.out.println("20 months after today will be "+today.plusMonths(20));

 

        System.out.println("10 days before today will be "+today.minusDays(10));

        System.out.println("3 weeks before today will be "+today.minusWeeks(3));

        System.out.println("20 months before today will be "+today.minusMonths(20));

 

        //Temporal adjusters for adjusting the dates

System.out.println("First date of this month= "+today.

with(TemporalAdjusters.firstDayOfMonth()));

        LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear());

        System.out.println("Last date of this year= "+lastDayOfYear);

 

        Period period = today.until(lastDayOfYear);

        System.out.println("Period Format= "+period);

        System.out.println("Months remaining in the year= "+period.getMonths());        

    }

}

输出:

Year 2014 is Leap Year? false

Today is before 01/01/2015? true

Current Time=2014-04-28T16:23:53.154

10 days after today will be 2014-05-08

3 weeks after today will be 2014-05-19

20 months after today will be 2015-12-28

10 days before today will be 2014-04-18

3 weeks before today will be 2014-04-07

20 months before today will be 2012-08-28

First date of this month= 2014-04-01

Last date of this year= 2014-12-31

Period Format= P8M3D

Months remaining in the year= 8

6.解析和格式化

将一个日期格式转换为不一样的格式,以后再解析一个字符串,获得日期时间对象,这些都是很常见的。咱们来看一下简单的例子。

package com.journaldev.java8.time;

import java.time.Instant;

import java.time.LocalDate;

import java.time.LocalDateTime;

import java.time.format.DateTimeFormatter;

 

public class DateParseFormatExample {

 

    public static void main(String[] args) {

 

        //Format examples

        LocalDate date = LocalDate.now();

        //default format

        System.out.println("Default format of LocalDate="+date);

        //specific format

        System.out.println(date.format(DateTimeFormatter.ofPattern("d::MMM::uuuu")));

        System.out.println(date.format(DateTimeFormatter.BASIC_ISO_DATE));

 

        LocalDateTime dateTime = LocalDateTime.now();

        //default format

        System.out.println("Default format of LocalDateTime="+dateTime);

        //specific format

    System.out.println(dateTime.format(DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss")));

        System.out.println(dateTime.format(DateTimeFormatter.BASIC_ISO_DATE));

 

        Instant timestamp = Instant.now();

        //default format

        System.out.println("Default format of Instant="+timestamp);

 

        //Parse examples

        LocalDateTime dt = LocalDateTime.parse("27::Apr::2014 21::39::48",

                DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss"));

        System.out.println("Default format after parsing = "+dt);

    }

}

输出:

Default format of LocalDate=2014-04-28

28::Apr::2014

20140428

Default format of LocalDateTime=2014-04-28T16:25:49.341

28::Apr::2014 16::25::49

20140428

Default format of Instant=2014-04-28T23:25:49.342Z

Default format after parsing = 2014-04-27T21:39:48

 

 

7.旧的日期时间支持

旧的日期/时间类已经在几乎全部的应用程序中使用,所以作到向下兼容是必须的。这也是为何会有若干工具方法帮助咱们将旧的类转换为新的类,反之亦然。咱们来看一下简单的例子。

package com.journaldev.java8.time;

 

import java.time.Instant;

import java.time.LocalDateTime;

import java.time.ZoneId;

import java.time.ZonedDateTime;

import java.util.Calendar;

import java.util.Date;

import java.util.GregorianCalendar;

import java.util.TimeZone;

 

public class DateAPILegacySupport {

 

    public static void main(String[] args) {

 

        //Date to Instant

        Instant timestamp = new Date().toInstant();

        //Now we can convert Instant to LocalDateTime or other similar classes

        LocalDateTime date = LocalDateTime.ofInstant(timestamp, 

                        ZoneId.of(ZoneId.SHORT_IDS.get("PST")));

        System.out.println("Date = "+date);

 

        //Calendar to Instant

        Instant time = Calendar.getInstance().toInstant();

        System.out.println(time);

        //TimeZone to ZoneId

        ZoneId defaultZone = TimeZone.getDefault().toZoneId();

        System.out.println(defaultZone);

 

        //ZonedDateTime from specific Calendar

        ZonedDateTime gregorianCalendarDateTime = new GregorianCalendar().toZonedDateTime();

        System.out.println(gregorianCalendarDateTime);

 

        //Date API to Legacy classes

        Date dt = Date.from(Instant.now());

        System.out.println(dt);

 

        TimeZone tz = TimeZone.getTimeZone(defaultZone);

        System.out.println(tz);

 

        GregorianCalendar gc = GregorianCalendar.from(gregorianCalendarDateTime);

        System.out.println(gc);

 

    }

}

输出:

Date = 2014-04-28T16:28:54.340

2014-04-28T23:28:54.395Z

America/Los_Angeles

2014-04-28T16:28:54.404-07:00[America/Los_Angeles]

Mon Apr 28 16:28:54 PDT 2014

sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]]

java.util.GregorianCalendar[time=1398727734404,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2014,MONTH=3,WEEK_OF_YEAR=18,WEEK_OF_MONTH=5,DAY_OF_MONTH=28,DAY_OF_YEAR=118,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=4,HOUR_OF_DAY=16,MINUTE=28,SECOND=54,MILLISECOND=404,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]

 

补充:咱们能够看到,旧的TimeZone和GregorianCalendar类的toString()方法太啰嗦了,一点都不友好。

7.7 Java8以前的日期和时间使用的槽点 (2017-11-19-wl)

Tiago Fernandez 作过一次投票,选举最烂的 JAVA API,排第一的 EJB2.X,第二的就是日期 API(DateCalender

[if !supportLists]1. [endif]槽点一

最开始的时候,Date 既要承载日期信息,又要作日期之间的转换,还要作不一样日期格式的显示,职责较繁杂(不懂单一职责,你妈妈知道吗?纯属恶搞~哈哈)

后来从JDK 1.1 开始,这三项职责分开了:

·  1)使用Calendar 类实现日期和时间字段之间转换;

·  2)使用DateFormat 类来格式化和分析日期字符串;

·  3)而Date 只用来承载日期和时间信息。

原有Date 中的相应方法已废弃。不过,不管是 Date,仍是 Calendar,都用着太不方便了,这是 API 没有设计好的地方。

 

[if !supportLists]2. [endif]槽点二

坑爹的yearmonth。

咱们看下面的代码:

[if !supportLists]1. [endif]Date date = new Date(2012,1,1);

[if !supportLists]2. [endif]System.out.println(date);

输出Thu Feb 01 00:00:00 CST 3912

观察输出结果,year 是 2012+1900,而 month,月份参数我不是给了 1 吗?怎么输出二月(Feb)了?

应该曾有人告诉你,若是你要设置日期,应该使用java.util.Calendar,像这样...

[if !supportLists]1. [endif]Calendar calendar = Calendar.getInstance();

[if !supportLists]2. [endif]calendar.set(2013, 8, 2);

这样写又不对了,calendar 的 month 也是从 0 开始的,表达 8 月份应该用 7 这个数字,要么就干脆用枚举

[if !supportLists]1. [endif]calendar.set(2013, Calendar.AUGUST, 2);

注意上面的代码,Calendar 年份的传值不须要减去 1900(固然月份的定义和 Date 仍是同样),这种不一致真是让人抓狂!有些人可能知道,Calendar 相关的 API 是 IBM 捐出去的,因此才致使不一致。

[if !supportLists]3. [endif]槽点三

java.util.Date 与 java.util.Calendar 中的全部属性都是可变的

下面的代码,计算两个日期之间的天数....

[if !supportLists]1. [endif]public static void main(String[] args) {

[if !supportLists]2. [endif]   Calendar birth = Calendar.getInstance();

[if !supportLists]3. [endif]   birth.set(1975, Calendar.MAY, 26);

[if !supportLists]4. [endif]   Calendar now = Calendar.getInstance();

[if !supportLists]5. [endif]   System.out.println(daysBetween(birth, now));

[if !supportLists]6. [endif]System.out.println(daysBetween(birth, now)); //显示 0?

[if !supportLists]7. [endif]}

[if !supportLists]8. [endif]

[if !supportLists]9. [endif]public static long daysBetween(Calendar begin, Calendar end) {

[if !supportLists]10. [endif]   long daysBetween = 0;

[if !supportLists]11. [endif]  while(begin.before(end)) {

[if !supportLists]12. [endif]     begin.add(Calendar.DAY_OF_MONTH, 1);

[if !supportLists]13. [endif]     daysBetween++;

[if !supportLists]14. [endif]  }

[if !supportLists]15. [endif]  return daysBetween;

[if !supportLists]16. [endif]}

daysBetween 有点问题,若是连续计算两个 Date 实例的话,第二次会取得 0,由于 Calendar 状态是可变的,考虑到重复计算的场合,最好复制一个新的 Calendar

[if !supportLists]1. [endif]public static long daysBetween(Calendar begin, Calendar end) {

[if !supportLists]2. [endif]Calendar calendar = (Calendar) begin.clone(); //复制

[if !supportLists]3. [endif]   long daysBetween = 0;

[if !supportLists]4. [endif]   while(calendar.before(end)) {

[if !supportLists]5. [endif]      calendar.add(Calendar.DAY_OF_MONTH, 1);

[if !supportLists]6. [endif]      daysBetween++;

[if !supportLists]7. [endif]   }

[if !supportLists]8. [endif]   return daysBetween;

[if !supportLists]9. [endif]}

以上种种,致使目前有些第三方的java 日期库诞生,好比普遍使用的 JODA-TIME,还有 Date4j 等,虽然第三方库已经足3 / 8够强大,好用,但仍是有兼容问题的,好比标准的 JSF 日期转换器与 joda-time API 就不兼容,你须要编写本身的转换器,因此标准的 API 仍是必须的,因而就有了JSR310。

7.8 Java8日期实现JSR310规范 (2017-11-23-wl)

[if !supportLists]1. [endif]JSR310介绍

JSR 310 实际上有两个日期概念。第一个是 Instant,它大体对应于 java.util.Date 类,由于它表明了一个肯定的时间点,即相对于标准 Java 纪元(1970 年 1 月 1 日)的偏移量;但与 java.util.Date 类不一样的是其精确到了纳秒级别。

第二个对应于人类自身的观念,好比LocalDate 和 LocalTime。他们表明了通常的时区概念,要么是日期(不包含时间),要么是时间(不包含日期),相似于 java.sql 的表示方式。此外,还有一个 MonthDay,它能够存储某人的生日(不包含年份)。每一个类都在内部存储正确的数据而不是像 java.util.Date 那样利用午夜 12 点来区分日期,利用 1970-01-01 来表示时间。

目前Java8 已经实现了 JSR310 的所有内容。新增了 java.time 包定义的类表示了日期-时间概念的规则,包括 instants,durations, dates, times, time-zones and periods。这些都是基于 ISO 日历系统,它又是遵循 Gregorian 规则的。最重要的一点是值不可变,且线程安全,经过下面一张图,咱们快速看下 java.time 包下的一些主要的类的值的格式,方便理解。

 

[if !supportLists]2. [endif]Java8方法概览

java.time包下的方法概览

方法名说明

Of静态工厂方法

parse静态工厂方法,关注于解析

get获取某些东西的值

is检查某些东西的是不是 true

with不可变的 setter 等价物

plus加一些量到某个对象

minus从某个对象减去一些量

to转换到另外一个类型

at把这个对象与另外一个对象组合起来

与旧的API相比

 

[if !supportLists]3. [endif]简单实用java.time的API实用

 

[if !supportLists]1. [endif]public class TimeIntroduction {

[if !supportLists]2. [endif]  public static void testClock() throws InterruptedException {

[if !supportLists]3. [endif]//时钟提供给咱们用于访问某个特定 时区的 瞬时时间、日期 和 时间的。

[if !supportLists]4. [endif]Clock c1 = Clock.systemUTC(); //系统默认 UTC 时钟(当前瞬时时间 System.currentTimeMillis())

[if !supportLists]5. [endif]System.out.println(c1.millis()); //每次调用将返回当前瞬时时间(UTC)

[if !supportLists]6. [endif]Clock c2 = Clock.systemDefaultZone(); //系统默认时区时钟(当前瞬时时间)

[if !supportLists]7. [endif]Clock c31 = Clock.system(ZoneId.of("Europe/Paris")); //巴黎时区

[if !supportLists]8. [endif]System.out.println(c31.millis()); //每次调用将返回当前瞬时时间(UTC)

[if !supportLists]9. [endif]Clock c32 = Clock.system(ZoneId.of("Asia/Shanghai"));//上海时区

[if !supportLists]10. [endif]System.out.println(c32.millis());//每次调用将返回当前瞬时时间(UTC)

[if !supportLists]11. [endif]Clock c4 = Clock.fixed(Instant.now(), ZoneId.of("Asia/Shanghai"));//固定上海时区时钟

[if !supportLists]12. [endif]    System.out.println(c4.millis());

[if !supportLists]13. [endif]    Thread.sleep(1000);

[if !supportLists]14. [endif]

[if !supportLists]15. [endif]System.out.println(c4.millis()); //不变 即时钟时钟在那一个点不动

[if !supportLists]16. [endif]Clock c5 = Clock.offset(c1, Duration.ofSeconds(2)); //相对于系统默认时钟两秒的时钟

[if !supportLists]17. [endif]    System.out.println(c1.millis());

[if !supportLists]18. [endif]    System.out.println(c5.millis());

[if !supportLists]19. [endif] }

[if !supportLists]20. [endif] public static void testInstant() {

[if !supportLists]21. [endif]//瞬时时间 至关于之前的 System.currentTimeMillis()

[if !supportLists]22. [endif]      Instant instant1 = Instant.now();

[if !supportLists]23. [endif]System.out.println(instant1.getEpochSecond());//精确到秒 获得相对于 1970-01-01 00:00:00

[if !supportLists]24. [endif]UTC的一个时间

[if !supportLists]25. [endif]System.out.println(instant1.toEpochMilli()); //精确到毫秒

[if !supportLists]26. [endif]Clock clock1 = Clock.systemUTC(); //获取系统 UTC 默认时钟

[if !supportLists]27. [endif]Instant instant2 = Instant.now(clock1);//获得时钟的瞬时时间

[if !supportLists]28. [endif]      System.out.println(instant2.toEpochMilli());

[if !supportLists]29. [endif]Clock clock2 = Clock.fixed(instant1, ZoneId.systemDefault()); //固定瞬时时间时钟

[if !supportLists]30. [endif]Instant instant3 = Instant.now(clock2);//获得时钟的瞬时时间

[if !supportLists]31. [endif]      System.out.println(instant3.toEpochMilli());//equals instant1

[if !supportLists]32. [endif]  }

[if !supportLists]33. [endif]   public static void testLocalDateTime() {

[if !supportLists]34. [endif]//使用默认时区时钟瞬时时间建立 Clock.systemDefaultZone() -->即相对于 ZoneId.systemDefault()

[if !supportLists]35. [endif]默认时区

[if !supportLists]36. [endif]   LocalDateTime now = LocalDateTime.now();

[if !supportLists]37. [endif]   System.out.println(now);

[if !supportLists]38. [endif]//自定义时区

[if !supportLists]39. [endif]   LocalDateTime now2 = LocalDateTime.now(ZoneId.of("Europe/Paris"));

[if !supportLists]40. [endif]System.out.println(now2);//会以相应的时区显示日期

[if !supportLists]41. [endif]//自定义时钟

[if !supportLists]42. [endif]   Clock clock = Clock.system(ZoneId.of("Asia/Dhaka"));

[if !supportLists]43. [endif]   LocalDateTime now3 = LocalDateTime.now(clock);

[if !supportLists]44. [endif]System.out.println(now3);//会以相应的时区显示日期

[if !supportLists]45. [endif]//不须要写什么相对时间 如 java.util.Date 年是相对于 1900 月是从 0 开始

[if !supportLists]46. [endif]   //2013-12-31 23:59

[if !supportLists]47. [endif]   LocalDateTime d1 = LocalDateTime.of(2013, 12, 31, 23, 59);

[if !supportLists]48. [endif]//年月日 时分秒 纳秒

[if !supportLists]49. [endif]   LocalDateTime d2 = LocalDateTime.of(2013, 12, 31, 23, 59, 59, 11);

[if !supportLists]50. [endif]//使用瞬时时间 + 时区

[if !supportLists]51. [endif]   Instant instant = Instant.now();

[if !supportLists]52. [endif]   LocalDateTime d3 = LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault());

[if !supportLists]53. [endif]   System.out.println(d3);

[if !supportLists]54. [endif]//解析 String--->LocalDateTime

[if !supportLists]55. [endif]   LocalDateTime d4 = LocalDateTime.parse("2013-12-31T23:59");

[if !supportLists]56. [endif]   System.out.println(d4);

[if !supportLists]57. [endif]LocalDateTime d5 = LocalDateTime.parse("2013-12-31T23:59:59.999");//999毫秒 等价于

[if !supportLists]58. [endif]999000000纳秒

[if !supportLists]59. [endif]

[if !supportLists]60. [endif]   System.out.println(d5);

[if !supportLists]61. [endif]//使用 DateTimeFormatter API 解析 和 格式化

[if !supportLists]62. [endif]   DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");

[if !supportLists]63. [endif]   LocalDateTime d6 = LocalDateTime.parse("2013/12/31 23:59:59", formatter);

[if !supportLists]64. [endif]   System.out.println(formatter.format(d6));

[if !supportLists]65. [endif]//时间获取

[if !supportLists]66. [endif]   System.out.println(d6.getYear());

[if !supportLists]67. [endif]   System.out.println(d6.getMonth());

[if !supportLists]68. [endif]   System.out.println(d6.getDayOfYear());

[if !supportLists]69. [endif]   System.out.println(d6.getDayOfMonth());

[if !supportLists]70. [endif]   System.out.println(d6.getDayOfWeek());

[if !supportLists]71. [endif]   System.out.println(d6.getHour());

[if !supportLists]72. [endif]   System.out.println(d6.getMinute());

[if !supportLists]73. [endif]   System.out.println(d6.getSecond());

[if !supportLists]74. [endif]   System.out.println(d6.getNano());

[if !supportLists]75. [endif]//时间增减

[if !supportLists]76. [endif]   LocalDateTime d7 = d6.minusDays(1);

[if !supportLists]77. [endif]   LocalDateTime d8 = d7.plus(1, IsoFields.QUARTER_YEARS);

[if !supportLists]78. [endif]//LocalDate即年月日 无时分秒

[if !supportLists]79. [endif]//LocalTime即时分秒 无年月日

[if !supportLists]80. [endif]//API和 LocalDateTime 相似就不演示了

[if !supportLists]81. [endif]   }

[if !supportLists]82. [endif]   public static void testZonedDateTime() {

[if !supportLists]83. [endif]//即带有时区的 date-time 存储纳秒、时区和时差(避免与本地 date-time 歧义)。

[if !supportLists]84. [endif]//API和 LocalDateTime 相似,只是多了时差(如 2013-12-20T10:35:50.711+08:00[Asia/Shanghai])

[if !supportLists]85. [endif]   ZonedDateTime now = ZonedDateTime.now();

[if !supportLists]86. [endif]   System.out.println(now);

[if !supportLists]87. [endif]   ZonedDateTime now2 = ZonedDateTime.now(ZoneId.of("Europe/Paris"));

[if !supportLists]88. [endif]   System.out.println(now2);

[if !supportLists]89. [endif]//其余的用法也是相似的 就不介绍了

[if !supportLists]90. [endif]   ZonedDateTime z1 = ZonedDateTime.parse("2013-12-31T23:59:59Z[Europe/Paris]");

[if !supportLists]91. [endif]   System.out.println(z1);

[if !supportLists]92. [endif]}

[if !supportLists]93. [endif]   public static void testDuration() {

[if !supportLists]94. [endif]//表示两个瞬时时间的时间段

[if !supportLists]95. [endif]   Duration d1 = Duration.between(Instant.ofEpochMilli(System.currentTimeMillis() - 12323123),

[if !supportLists]96. [endif] Instant.now())

[if !supportLists]97. [endif];

[if !supportLists]98. [endif]//获得相应的时差

[if !supportLists]99. [endif]   System.out.println(d1.toDays());

[if !supportLists]100. [endif]   System.out.println(d1.toHours());

[if !supportLists]101. [endif]   System.out.println(d1.toMinutes());

[if !supportLists]102. [endif]

[if !supportLists]103. [endif]   System.out.println(d1.toMillis());

[if !supportLists]104. [endif]  System.out.println(d1.toNanos());

[if !supportLists]105. [endif]//1天时差 相似的还有如 ofHours()

[if !supportLists]106. [endif]  Duration d2 = Duration.ofDays(1);

[if !supportLists]107. [endif]  System.out.println(d2.toDays());

[if !supportLists]108. [endif]}

[if !supportLists]109. [endif]  public static void testChronology() {

[if !supportLists]110. [endif]//提供对 java.util.Calendar 的替换,提供对年历系统的支持

[if !supportLists]111. [endif]    Chronology c = HijrahChronology.INSTANCE;

[if !supportLists]112. [endif]    ChronoLocalDateTime d = c.localDateTime(LocalDateTime.now());

[if !supportLists]113. [endif]    System.out.println(d);

[if !supportLists]114. [endif]  }

[if !supportLists]115. [endif]/**

[if !supportLists]116. [endif]*新旧日期转换

[if !supportLists]117. [endif]*/

[if !supportLists]118. [endif] public static void testNewOldDateConversion(){

[if !supportLists]119. [endif]  Instant instant=new Date().toInstant();

[if !supportLists]120. [endif]  Date date=Date.from(instant);

[if !supportLists]121. [endif]  System.out.println(instant);

[if !supportLists]122. [endif]  System.out.println(date);

[if !supportLists]123. [endif] }

[if !supportLists]124. [endif] public static void main(String[] args) throws InterruptedException {

[if !supportLists]125. [endif]  testClock();

[if !supportLists]126. [endif]  testInstant();

[if !supportLists]127. [endif]  testLocalDateTime();

[if !supportLists]128. [endif]  testZonedDateTime();

[if !supportLists]129. [endif]  testDuration();

[if !supportLists]130. [endif]  testChronology();

[if !supportLists]131. [endif]  testNewOldDateConversion();

[if !supportLists]132. [endif] }

[if !supportLists]133. [endif]}

7.9 JSR310规范Joda-Time的区别 (2017-11-23-wl)

其实JSR310 的规范领导者 Stephen Colebourne,同时也是 Joda-Time 的建立者,JSR310 是在 Joda-Time 的基础上创建的,参考了绝大部分的 API,但并非说 JSR310=JODA-Time,下面几个比较明显的区别是:

1.  最明显的变化就是包名(从 org.joda.time 以及 java.time)

2.  JSR310 不接受 NULL 值,Joda-Time 视 NULL 值为 0

3.  JSR310 的计算机相关的时间(Instant)和与人类相关的时间(DateTime)之间的差异变得更明显

4.  JSR310 全部抛出的异常都是 DateTimeException 的子类。虽然 DateTimeException 是一个 RuntimeException

7.10 总结 (2017-11-23-wl)

Java.timejava.util.Calendar 以及 Date

流畅的 API不流畅的 API

实例不可变实例可变

线程安全非线程安全

 

[if !supportLists]6、[endif]Java的数据类型

[if !supportLists]1. [endif]Java的基本数据类型都有哪些各占几个字节

以下表所示:

四类八种字节数数据表示范围

 

byte1-128~127

short2-32768~32767

int4-2147483648~2147483647

long8-263~263-1

浮点型float4-3.403E38~3.403E38

double8-1.798E308~1.798E308

字符型char2表示一个字符,如('a','A','0','家')

布尔型boolean1只有两个值true与false

 

[if !supportLists]2. [endif]String是基本数据类型吗?(2017-11-12-wl)

String是引用类型,底层用char数组实现的。

[if !supportLists]3. [endif]short s1 = 1; s1 = s1 + 1; 有错吗?short s1 = 1; s1 += 1有错吗;(2017-11-12-wl)

前者不正确,后者正确。对于short s1 = 1; s1 = s1 + 1;因为1是 int 类型,所以 s1+1 运算结果也是 int 型,须要强制转换类型才能赋值给short型。而 short s1 = 1; s1 += 1;能够正确编译,由于 s1+= 1;至关于 s1 = (short)(s1 + 1);其中有隐含的强制类型转换。

[if !supportLists]4. [endif]int 和 和 Integer有什么区别?(2017-11-12-wl)

Java是一个近乎纯洁的面向对象编程语言,可是为了编程的方便仍是引入了基本数据类型,为了可以将这些基本数据类型当成对象操做,Java为每个基本数据类型都引入了对应的包装类型(wrapper class),int 的包装类就是 Integer,从 Java 5 开始引入了自动装箱/拆箱机制,使得两者能够相互转换。

Java为每一个原始类型提供了包装类型:

- 原始类型: boolean,char,byte,short,int,long,float,double

- 包装类型:Boolean,Character,Byte,Short,Integer,Long,Float,Double

 

 

 

 

 

 

 

 

[if !supportLists]5. [endif]下面Integer类型的数值比较输出的结果为?(2017-11-12-wl)

 

 

 

 

 

 

 

 

若是不明就里很容易认为两个输出要么都是true 要么都是 false。首先须要注意的是 f一、f二、f三、f4 四个变量都是 Integer 对象引用,因此下面的==运算比较的不是值而是引用。装箱的本质是什么呢?当咱们给一个Integer 对象赋一个 int 值的时候,会调用 Integer 类的静态方法 valueOf,若是看看 valueOf 的源代码就知道发生了什么。

源码:

 

 

 

 

 

 

IntegerCache 是 Integer 的内部类,其代码以下所示:

 

 

 

 

 

 

 

 

 

 

 

 

 

简单的说,若是整型字面量的值在-128 到 127 之间,那么不会 new 新的 Integer 对象,而是直接引用常量池中的 Integer 对象,因此上面的面试题中 f1==f2 的结果是 true,而 f3==f4 的结果是 false。

提醒:越是貌似简单的面试题其中的玄机就越多,须要面试者有至关深厚的功力。

 

 

[if !supportLists]6. [endif]String类经常使用方法(2017-11-15-lyq)

 

[if !supportLists]7. [endif]String、StringBuffer、StringBuilder的区别?(2017-11-23-wzz)

(1)、可变不可变

String:字符串常量,在修改时不会改变自身;若修改,等于从新生成新的字符串对象。

StringBuffer:在修改时会改变对象自身,每次操做都是对StringBuffer对象自己进行修改,不是生成新的对象;使用场景:对字符串常常改变状况下,主要方法:append(),insert()等。

[if !supportLists](2)[endif]、线程是否安全

String:对象定义后不可变,线程安全。

StringBuffer:是线程安全的(对调用方法加入同步锁),执行效率较慢,适用于多线程下操做字符串缓冲区大量数据。

StringBuilder:是线程不安全的,适用于单线程下操做字符串缓冲区大量数据。

[if !supportLists](3)[endif]、共同点

StringBuilder与StringBuffer有公共父类AbstractStringBuilder(抽象类)。

StringBuilder、StringBuffer的方法都会调用AbstractStringBuilder中的公共方法,如super.append(...)。只是StringBuffer会在方法上加synchronized关键字,进行同步。最后,若是程序不是多线程的,那么使用StringBuilder效率高于StringBuffer。

[if !supportLists]8. [endif]数据类型之间的转换(2017-11-23-wzz)

(1)、字符串如何转基本数据类型?

调用基本数据类型对应的包装类中的方法parseXXX(String)或valueOf(String)便可返回相应基本类型。

[if !supportLists](2)[endif]、基本数据类型如何转字符串?

一种方法是将基本数据类型与空字符串(“”)链接(+)便可得到其所对应的字符串;另外一种方法是调用String 类中的valueOf()方法返回相应字符串。

[if !supportLists]7、[endif]Java的IO

[if !supportLists]1. [endif]Java中有几种类型的流(2017-11-23-wzz)

按照流的方向:输入流(inputStream)和输出流(outputStream)。

按照实现功能分:节点流(能够从或向一个特定的地方(节点)读写数据。如FileReader)和处理流(是对一个已存在的流的链接和封装,经过所封装的流的功能调用实现数据读写。如BufferedReader。处理流的构造方法老是要带一个其余的流对象作参数。一个流对象通过其余流的屡次包装,称为流的连接。)

按照处理数据的单位:字节流和字符流。字节流继承于InputStream和OutputStream,字符流继承于InputStreamReader 和OutputStreamWriter。

 

 

[if !supportLists]2. [endif]字节流如何转为字符流

字节输入流转字符输入流经过InputStreamReader实现,该类的构造函数能够传入InputStream对象。

字节输出流转字符输出流经过OutputStreamWriter实现,该类的构造函数能够传入OutputStream对象。

[if !supportLists]3. [endif]如何将一个java对象序列化到文件里

在java中可以被序列化的类必须先实现Serializable接口,该接口没有任何抽象方法只是起到一个标记做用。

[if !supportLists]1. [endif]//对象输出流

[if !supportLists]2. [endif] ObjectOutputStream objectOutputStream =

[if !supportLists]3. [endif]new ObjectOutputStream(new FileOutputStream(new File("D://obj")));

[if !supportLists]4. [endif] objectOutputStream.writeObject(new User("zhangsan", 100));

[if !supportLists]5. [endif] objectOutputStream.close();

[if !supportLists]6. [endif] //对象输入流

[if !supportLists]7. [endif] ObjectInputStream objectInputStream =

[if !supportLists]8. [endif]new ObjectInputStream(new FileInputStream(new File("D://obj")));

[if !supportLists]9. [endif] User user = (User)objectInputStream.readObject();

[if !supportLists]10. [endif] System.out.println(user);

[if !supportLists]11. [endif] objectInputStream.close();

 

[if !supportLists]4. [endif]字节流和字符流的区别(2017-11-23-wzz)

字节流读取的时候,读到一个字节就返回一个字节;字符流使用了字节流读到一个或多个字节(中文对应的字节数是两个,在UTF-8码表中是3个字节)时。先去查指定的编码表,将查到的字符返回。 字节流能够处理全部类型数据,如:图片,MP3,AVI视频文件,而字符流只能处理字符数据。只要是处理纯文本数据,就要优先考虑使用字符流,除此以外都用字节流。字节流主要是操做byte类型数据,以byte数组为准,主要操做类就是OutputStream、InputStream

字符流处理的单元为2个字节的Unicode字符,分别操做字符、字符数组或字符串,而字节流处理单元为1个字节,操做字节和字节数组。因此字符流是由Java虚拟机将字节转化为2个字节的Unicode字符为单位的字符而成的,因此它对多国语言支持性比较好!若是是音频文件、图片、歌曲,就用字节流好点,若是是关系到中文(文本)的,用字符流好点。在程序中一个字符等于两个字节,java提供了Reader、Writer两个专门操做字符流的类。

[if !supportLists]5. [endif]如何实现对象克隆?(2017-11-12-wl)

有两种方式。

1). 实现 Cloneable 接口并重写 Object 类中的 clone()方法;

2). 实现 Serializable 接口,经过对象的序列化和反序列化实现克隆,能够实现真正的深度克隆,代码以下。

[if !supportLists]12. [endif]import java.io.ByteArrayInputStream;

[if !supportLists]13. [endif]import java.io.ByteArrayOutputStream;

[if !supportLists]14. [endif]import java.io.ObjectInputStream;

[if !supportLists]15. [endif]import java.io.ObjectOutputStream;

[if !supportLists]16. [endif]import java.io.Serializable;

[if !supportLists]17. [endif]public class MyUtil {

[if !supportLists]18. [endif]private MyUtil() {

[if !supportLists]19. [endif]throw new AssertionError();

[if !supportLists]20. [endif]}

[if !supportLists]21. [endif]@SuppressWarnings("unchecked")

[if !supportLists]22. [endif]public static T clone(T obj) throws Exception {

[if !supportLists]23. [endif]ByteArrayOutputStream bout = new ByteArrayOutputStream();

[if !supportLists]24. [endif]ObjectOutputStream oos = new ObjectOutputStream(bout);

[if !supportLists]25. [endif]oos.writeObject(obj);

[if !supportLists]26. [endif]ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());

[if !supportLists]27. [endif]ObjectInputStream ois = new ObjectInputStream(bin);

[if !supportLists]28. [endif]return (T) ois.readObject();

[if !supportLists]29. [endif]//说明:调用 ByteArrayInputStream 或 ByteArrayOutputStream 对象的 close 方法没有任何意义

[if !supportLists]30. [endif]//这两个基于内存的流只要垃圾回收器清理对象就可以释放资源,这一点不一样于对外部资源(如文件流)的释放

[if !supportLists]31. [endif]}

[if !supportLists]32. [endif]}

 

测试代码:

[if !supportLists]1. [endif]import java.io.Serializable;

[if !supportLists]2. [endif]/**

[if !supportLists]3. [endif]*人类

[if !supportLists]4. [endif] */

[if !supportLists]5. [endif]class Person implements Serializable {

[if !supportLists]6. [endif]private static final long serialVersionUID = -9102017020286042305L;

[if !supportLists]7. [endif]private String name; //姓名

[if !supportLists]8. [endif]private int age; //年龄

[if !supportLists]9. [endif]private Car car; //座驾

[if !supportLists]10. [endif]public Person(String name, int age, Car car) {

[if !supportLists]11. [endif]this.name = name;

[if !supportLists]12. [endif]this.age = age;

[if !supportLists]13. [endif]this.car = car;

[if !supportLists]14. [endif]}

[if !supportLists]15. [endif]public String getName() {

[if !supportLists]16. [endif]return name;

[if !supportLists]17. [endif]}

[if !supportLists]18. [endif]public void setName(String name) {

[if !supportLists]19. [endif]this.name = name;

[if !supportLists]20. [endif]}

[if !supportLists]21. [endif]public int getAge() {

[if !supportLists]22. [endif]return age;

[if !supportLists]23. [endif]}

[if !supportLists]24. [endif]public void setAge(int age) {

[if !supportLists]25. [endif]this.age = age;

[if !supportLists]26. [endif]}

[if !supportLists]27. [endif]public Car getCar() {

[if !supportLists]28. [endif]return car;

[if !supportLists]29. [endif]}

[if !supportLists]30. [endif]public void setCar(Car car) {

[if !supportLists]31. [endif]this.car = car;

[if !supportLists]32. [endif]}

[if !supportLists]33. [endif]@Override

[if !supportLists]34. [endif]public String toString() {

[if !supportLists]35. [endif]return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";

[if !supportLists]36. [endif]}

[if !supportLists]37. [endif]}

 

[if !supportLists]1. [endif]/**

[if !supportLists]2. [endif]*小汽车类

[if !supportLists]3. [endif]*/

[if !supportLists]4. [endif]class Car implements Serializable {

[if !supportLists]5. [endif]private static final long serialVersionUID = -5713945027627603702L;

[if !supportLists]6. [endif]private String brand; //品牌

[if !supportLists]7. [endif]private int maxSpeed; //最高时速

[if !supportLists]8. [endif]public Car(String brand, int maxSpeed) {

[if !supportLists]9. [endif]this.brand = brand;

[if !supportLists]10. [endif]this.maxSpeed = maxSpeed;

[if !supportLists]11. [endif]}

[if !supportLists]12. [endif]public String getBrand() {

[if !supportLists]13. [endif]return brand;

[if !supportLists]14. [endif]}

[if !supportLists]15. [endif]public void setBrand(String brand) {

[if !supportLists]16. [endif]this.brand = brand;

[if !supportLists]17. [endif]}

[if !supportLists]18. [endif]public int getMaxSpeed() {

[if !supportLists]19. [endif]return maxSpeed;

[if !supportLists]20. [endif]}

[if !supportLists]21. [endif]public void setMaxSpeed(int maxSpeed) {

[if !supportLists]22. [endif]this.maxSpeed = maxSpeed;

[if !supportLists]23. [endif]}

[if !supportLists]24. [endif]@Override

[if !supportLists]25. [endif]public String toString() {

[if !supportLists]26. [endif]return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";

[if !supportLists]27. [endif]}

[if !supportLists]28. [endif]}

 

[if !supportLists]1. [endif]class CloneTest {

[if !supportLists]2. [endif]public static void main(String[] args) {

[if !supportLists]3. [endif]try {

[if !supportLists]4. [endif]Person p1 = new Person("Hao LUO", 33, new Car("Benz", 300));

[if !supportLists]5. [endif]Person p2 = MyUtil.clone(p1); //深度克隆

[if !supportLists]6. [endif]p2.getCar().setBrand("BYD");

[if !supportLists]7. [endif]//修改克隆的 Person 对象 p2 关联的汽车对象的品牌属性

[if !supportLists]8. [endif]//原来的 Person 对象 p1 关联的汽车不会受到任何影响

[if !supportLists]9. [endif]//由于在克隆 Person 对象时其关联的汽车对象也被克隆了

[if !supportLists]10. [endif]System.out.println(p1);

[if !supportLists]11. [endif]} catch (Exception e) {

[if !supportLists]12. [endif]e.printStackTrace();

[if !supportLists]13. [endif]}

[if !supportLists]14. [endif]}

[if !supportLists]15. [endif]}

注意:基于序列化和反序列化实现的克隆不只仅是深度克隆,更重要的是经过泛型限定,能够检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object 类的 clone 方法克隆对象。让问题在编译的时候暴露出来老是好过把问题留到运行时。

[if !supportLists]6. [endif]什么是java序列化,如何实现java序列化?(2017-12-7-lyq)

序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。能够对流化后的对象进行读写操做,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操做时所引起的问题。

序列化的实现:将须要被序列化的类实现Serializable接口,该接口没有须要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,而后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Object obj)方法就能够将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。

原文连接:https://www.cnblogs.com/yangchunze/p/6728086.html

 

 

 

 

[if !supportLists]8、[endif]Java的集合

[if !supportLists]1. [endif]HashMap排序题,上机题。(本人主要靠这道题入职的第一家公司)

已知一个HashMap集合, User有name(String)和age(int)属性。请写一个方法实现对HashMap的排序功能,该方法接收HashMap为形参,返回类型为HashMap,要求对HashMap中的User的age倒序进行排序。排序时key=value键值对不得拆散。

注意:要作出这道题必须对集合的体系结构很是的熟悉。HashMap自己就是不可排序的,可是该道题恰恰让给HashMap排序,那咱们就得想在API中有没有这样的Map结构是有序的,LinkedHashMap,对的,就是他,他是Map结构,也是链表结构,有序的,更可喜的是他是HashMap的子类,咱们返回LinkedHashMap便可,还符合面向接口(父类编程的思想)。

但凡是对集合的操做,咱们应该保持一个原则就是能用JDK中的API就有JDK中的API,好比排序算法咱们不该该去用冒泡或者选择,而是首先想到用Collections集合工具类。

[if !supportLists]1. [endif]public class HashMapTest {

[if !supportLists]2. [endif] public static void main(String[] args) {

[if !supportLists]3. [endif] HashMap users = new HashMap<>();

[if !supportLists]4. [endif] users.put(1, new User("张三", 25));

[if !supportLists]5. [endif] users.put(3, new User("李四", 22));

[if !supportLists]6. [endif] users.put(2, new User("王五", 28));

[if !supportLists]7. [endif] System.out.println(users);

[if !supportLists]8. [endif] HashMap sortHashMap = sortHashMap(users);

[if !supportLists]9. [endif] System.out.println(sortHashMap);

[if !supportLists]10. [endif] /**

[if !supportLists]11. [endif] *控制台输出内容

[if !supportLists]12. [endif] * {1=User [name=张三, age=25], 2=User [name=王五, age=28], 3=User [name=李四, age=22]}

[if !supportLists]13. [endif] {2=User [name=王五, age=28], 1=User [name=张三, age=25], 3=User [name=李四, age=22]}

[if !supportLists]14. [endif]  */

[if !supportLists]15. [endif] }

[if !supportLists]16. [endif]

[if !supportLists]17. [endif] public static HashMap sortHashMap(HashMap map) {

[if !supportLists]18. [endif] //首先拿到map的键值对集合

[if !supportLists]19. [endif] Set> entrySet = map.entrySet();

[if !supportLists]20. [endif]

[if !supportLists]21. [endif] //将set集合转为List集合,为何,为了使用工具类的排序方法

[if !supportLists]22. [endif] List> list = new ArrayList>(entrySet);

[if !supportLists]23. [endif] //使用Collections集合工具类对list进行排序,排序规则使用匿名内部类来实现

[if !supportLists]24. [endif] Collections.sort(list, new Comparator>() {

[if !supportLists]25. [endif]

[if !supportLists]26. [endif] @Override

[if !supportLists]27. [endif] public int compare(Entry o1, Entry o2) {

[if !supportLists]28. [endif] //按照要求根据User的age的倒序进行排

[if !supportLists]29. [endif] return o2.getValue().getAge()-o1.getValue().getAge();

[if !supportLists]30. [endif] }

[if !supportLists]31. [endif] });

[if !supportLists]32. [endif] //建立一个新的有序的HashMap子类的集合

[if !supportLists]33. [endif] LinkedHashMap linkedHashMap = new LinkedHashMap();

[if !supportLists]34. [endif] //将List中的数据存储在LinkedHashMap中

[if !supportLists]35. [endif] for(Entry entry : list){

[if !supportLists]36. [endif] linkedHashMap.put(entry.getKey(), entry.getValue());

[if !supportLists]37. [endif] }

[if !supportLists]38. [endif] //返回结果

[if !supportLists]39. [endif] return linkedHashMap;

[if !supportLists]40. [endif] }

[if !supportLists]41. [endif]}

[if !supportLists]42. [endif]

 

[if !supportLists]2. [endif]集合的安全性问题

请问ArrayList、HashSet、HashMap是线程安全的吗?若是不是我想要线程安全的集合怎么办?

咱们都看过上面那些集合的源码(若是没有那就看看吧),每一个方法都没有加锁,显然都是线程不安全的。话又说过来若是他们安全了也就没第二问了。

在集合中Vector和HashTable却是线程安全的。你打开源码会发现其实就是把各自核心方法添加上了synchronized关键字。

Collections工具类提供了相关的API,能够让上面那3个不安全的集合变为安全的。

[if !supportLists]1. [endif]// Collections.synchronizedCollection(c)

[if !supportLists]2. [endif]// Collections.synchronizedList(list)

[if !supportLists]3. [endif]// Collections.synchronizedMap(m)

[if !supportLists]4. [endif]// Collections.synchronizedSet(s)

上面几个函数都有对应的返回值类型,传入什么类型返回什么类型。打开源码其实实现原理很是简单,就是将集合的核心方法添加上了synchronized关键字。

[if !supportLists]3. [endif]ArrayList内部用什么实现的?(2015-11-24)

(回答这样的问题,不要只回答个皮毛,能够再介绍一下ArrayList内部是如何实现数组的增长和删除的,由于数组在建立的时候长度是固定的,那么就有个问题咱们往ArrayList中不断的添加对象,它是如何管理这些数组呢?)

ArrayList内部是用Object[]实现的。接下来咱们分别分析ArrayList的构造、add、remove、clear方法的实现原理。

[if !supportLists]1、[endif]构造函数

1)空参构造

/**

     * Constructs a new {@code ArrayList} instance with zero initial capacity.

     */

    public ArrayList() {

        array = EmptyArray.OBJECT;

}

array是一个Object[]类型。当咱们new一个空参构造时系统调用了EmptyArray.OBJECT属性,EmptyArray仅仅是一个系统的类库,该类源码以下:

public final class EmptyArray {

    private EmptyArray() {}

 

    public static final boolean[] BOOLEAN = new boolean[0];

    public static final byte[] BYTE = new byte[0];

    public static final char[] CHAR = new char[0];

    public static final double[] DOUBLE = new double[0];

    public static final int[] INT = new int[0];

 

    public static final Class[] CLASS = new Class[0];

    public static final Object[] OBJECT = new Object[0];

    public static final String[] STRING = new String[0];

    public static final Throwable[] THROWABLE = new Throwable[0];

    public static final StackTraceElement[] STACK_TRACE_ELEMENT = new StackTraceElement[0];

}

也就是说当咱们new 一个空参ArrayList的时候,系统内部使用了一个new Object[0]数组。

[if !supportLists]2)[endif]带参构造1

 /**

     * Constructs a new instance of {@code ArrayList} with the specified

     * initial capacity.

     *

     * @param capacity

     *            the initial capacity of this {@code ArrayList}.

     */

    public ArrayList(int capacity) {

        if (capacity < 0) {

            throw new IllegalArgumentException("capacity < 0: " + capacity);

        }

        array = (capacity == 0 ? EmptyArray.OBJECT : new Object[capacity]);

}

该构造函数传入一个int值,该值做为数组的长度值。若是该值小于0,则抛出一个运行时异常。若是等于0,则使用一个空数组,若是大于0,则建立一个长度为该值的新数组。

3)带参构造2

/**

     * Constructs a new instance of {@code ArrayList} containing the elements of

     * the specified collection.

     *

     * @param collection

     *            the collection of elements to add.

     */

    public ArrayList(Collection collection) {

        if (collection == null) {

            throw new NullPointerException("collection == null");

        }

 

        Object[] a = collection.toArray();

        if (a.getClass() != Object[].class) {

            Object[] newArray = new Object[a.length];

            System.arraycopy(a, 0, newArray, 0, a.length);

            a = newArray;

        }

        array = a;

        size = a.length;

    }

若是调用构造函数的时候传入了一个Collection的子类,那么先判断该集合是否为null,为null则抛出空指针异常。若是不是则将该集合转换为数组a,而后将该数组赋值为成员变量array,将该数组的长度做为成员变量size。这里面它先判断a.getClass是否等于Object[].class,其实通常都是相等的,我也暂时没想明白为何多加了这个判断,toArray方法是Collection接口定义的,所以其全部的子类都有这样的方法,list集合的toArray和Set集合的toArray返回的都是Object[]数组。

这里讲些题外话,其实在看Java源码的时候,做者的不少意图都很费人心思,我能知道他的目标是啥,可是不知道他为什么这样写。好比对于ArrayList, array是他的成员变量,可是每次在方法中使用该成员变量的时候做者都会从新在方法中开辟一个局部变量,而后给局部变量赋值为array,而后再使用,有人可能说这是为了防止并发修改array,毕竟array是成员变量,你们均可以使用所以须要将array变为局部变量,而后再使用,这样的说法并非都成立的,也许有时候就是老外们写代码的一个习惯而已。

[if !supportLists]2、[endif]add方法

add方法有两个重载,这里只研究最简单的那个。

      /**

     * Adds the specified object at the end of this {@code ArrayList}.

     *

     * @param object

     *            the object to add.

     * @return always true

     */

    @Override public boolean add(E object) {

        Object[] a = array;

        int s = size;

        if (s == a.length) {

            Object[] newArray = new Object[s +

                    (s < (MIN_CAPACITY_INCREMENT / 2) ?

                     MIN_CAPACITY_INCREMENT : s >> 1)];

            System.arraycopy(a, 0, newArray, 0, s);

            array = a = newArray;

        }

        a[s] = object;

        size = s + 1;

        modCount++;

        return true;

    }

[if !supportLists]一、[endif]首先将成员变量array赋值给局部变量a,将成员变量size赋值给局部变量s。

[if !supportLists]二、[endif]判断集合的长度s是否等于数组的长度(若是集合的长度已经等于数组的长度了,说明数组已经满了,该从新分配新数组了),从新分配数组的时候须要计算新分配内存的空间大小,若是当前的长度小于MIN_CAPACITY_INCREMENT/2(这个常量值是12,除以2就是6,也就是若是当前集合长度小于6)则分配12个长度,若是集合长度大于6则分配当前长度s的一半长度。这里面用到了三元运算符和位运算,s >> 1,意思就是将s往右移1位,至关于s=s/2,只不过位运算是效率最高的运算。

[if !supportLists]三、[endif]将新添加的object对象做为数组的a[s]个元素。

[if !supportLists]四、[endif]修改集合长度size为s+1

[if !supportLists]五、[endif]modCotun++,该变量是父类中声明的,用于记录集合修改的次数,记录集合修改的次数是为了防止在用迭代器迭代集合时避免并发修改异常,或者说用于判断是否出现并发修改异常的。

[if !supportLists]六、[endif]return true,这个返回值意义不大,由于一直返回true,除非报了一个运行时异常。

[if !supportLists]3、[endif]remove方法

remove方法有两个重载,咱们只研究remove(int index)方法。

      /**

     * Removes the object at the specified location from this list.

     *

     * @param index

     *            the index of the object to remove.

     * @return the removed object.

     * @throws IndexOutOfBoundsException

     *             when {@code location < 0 || location >= size()}

     */

    @Override public E remove(int index) {

        Object[] a = array;

        int s = size;

        if (index >= s) {

            throwIndexOutOfBoundsException(index, s);

        }

        @SuppressWarnings("unchecked")

        E result = (E) a[index];

        System.arraycopy(a, index + 1, a, index, --s - index);

        a[s] = null;  // Prevent memory leak

        size = s;

        modCount++;

        return result;

    }

[if !supportLists]一、[endif]先将成员变量array和size赋值给局部变量a和s。

[if !supportLists]二、[endif]判断形参index是否大于等于集合的长度,若是成了则抛出运行时异常

[if !supportLists]三、[endif]获取数组中脚标为index的对象result,该对象做为方法的返回值

[if !supportLists]四、[endif]调用System的arraycopy函数,拷贝原理以下图所示。

 

[if !supportLists]五、[endif]接下来就是很重要的一个工做,由于删除了一个元素,并且集合总体向前移动了一位,所以须要将集合最后一个元素设置为null,不然就可能内存泄露。

[if !supportLists]六、[endif]从新给成员变量array和size赋值

[if !supportLists]七、[endif]记录修改次数

[if !supportLists]八、[endif]返回删除的元素(让用户再看最后一眼)

[if !supportLists]4、[endif]clear方法

       /**

     * Removes all elements from this {@code ArrayList}, leaving it empty.

     *

     * @see #isEmpty

     * @see #size

     */

    @Override public void clear() {

        if (size != 0) {

            Arrays.fill(array, 0, size, null);

            size = 0;

            modCount++;

        }

    }

若是集合长度不等于0,则将全部数组的值都设置为null,而后将成员变量size设置为0便可,最后让修改记录加1。

[if !supportLists]4. [endif]并发集合和普通集合如何区别?(2015-11-24)

并发集合常见的有ConcurrentHashMap、ConcurrentLinkedQueue、ConcurrentLinkedDeque等。并发集合位于java.util.concurrent包下,是jdk1.5以后才有的,主要做者是Doug Lea(http://baike.baidu.com/view/3141057.htm)完成的。

在java中有普通集合、同步(线程安全)的集合、并发集合。普通集合一般性能最高,可是不保证多线程的安全性和并发的可靠性。线程安全集合仅仅是给集合添加了synchronized同步锁,严重牺牲了性能,并且对并发的效率就更低了,并发集合则经过复杂的策略不只保证了多线程的安全又提升的并发时的效率。

参考阅读:

 ConcurrentHashMap是线程安全的HashMap的实现,默认构造一样有initialCapacity和loadFactor属性,不过还多了一个concurrencyLevel属性,三属性默认值分别为1六、0.75及16。其内部使用锁分段技术,维持这锁Segment的数组,在Segment数组中又存放着Entity[]数组,内部hash算法将数据较均匀分布在不一样锁中。

put操做:并无在此方法上加上synchronized,首先对key.hashcode进行hash操做,获得key的hash值。hash操做的算法和map也不一样,根据此hash值计算并获取其对应的数组中的Segment对象(继承自ReentrantLock),接着调用此Segment对象的put方法来完成当前操做。

ConcurrentHashMap基于concurrencyLevel划分出了多个Segment来对key-value进行存储,从而避免每次put操做都得锁住整个数组。在默认的状况下,最佳状况下可容许16个线程并发无阻塞的操做集合对象,尽量地减小并发时的阻塞现象。

get(key)

首先对key.hashCode进行hash操做,基于其值找到对应的Segment对象,调用其get方法完成当前操做。而Segment的get操做首先经过hash值和对象数组大小减1的值进行按位与操做来获取数组上对应位置的HashEntry。在这个步骤中,可能会由于对象数组大小的改变,以及数组上对应位置的HashEntry产生不一致性,那么ConcurrentHashMap是如何保证的?

对象数组大小的改变只有在put操做时有可能发生,因为HashEntry对象数组对应的变量是volatile类型的,所以能够保证如HashEntry对象数组大小发生改变,读操做可看到最新的对象数组大小。

在获取到了HashEntry对象后,怎么能保证它及其next属性构成的链表上的对象不会改变呢?这点ConcurrentHashMap采用了一个简单的方式,即HashEntry对象中的hash、key、next属性都是final的,这也就意味着没办法插入一个HashEntry对象到基于next属性构成的链表中间或末尾。这样就能够保证当获取到HashEntry对象后,其基于next属性构建的链表是不会发生变化的。

    ConcurrentHashMap默认状况下采用将数据分为16个段进行存储,而且16个段分别持有各自不一样的锁Segment,锁仅用于put和remove等改变集合对象的操做,基于volatile及HashEntry链表的不变性实现了读取的不加锁。这些方式使得ConcurrentHashMap可以保持极好的并发支持,尤为是对于读远比插入和删除频繁的Map而言,而它采用的这些方法也可谓是对于Java内存模型、并发机制深入掌握的体现。

推荐博客地址:http://m.oschina.net/blog/269037

 

[if !supportLists]5. [endif]List的三个子类的特色(2017-2-23)

ArrayList 底层结构是数组,底层查询快,增删慢。

LinkedList 底层结构是链表型的,增删快,查询慢。

voctor 底层结构是数组线程安全的,增删慢,查询慢。

[if !supportLists]6. [endif]List和Map、Set的区别(2017-11-22-wzz)

6.1结构特色

List和Set是存储单列数据的集合,Map是存储键和值这样的双列数据的集合;List中存储的数据是有顺序,而且容许重复;Map中存储的数据是没有顺序的,其键是不能重复的,它的值是能够有重复的,Set中存储的数据是无序的,且不容许有重复,但元素在集合中的位置由元素的hashcode决定,位置是固定的(Set集合根据hashcode来进行数据的存储,因此位置是固定的,可是位置不是用户能够控制的,因此对于用户来讲set中的元素仍是无序的);

6.2实现类

List接口有三个实现类(LinkedList:基于链表实现,链表内存是散乱的,每个元素存储自己内存地址的同时还存储下一个元素的地址。链表增删快,查找慢;ArrayList:基于数组实现,非线程安全的,效率高,便于索引,但不便于插入删除;Vector:基于数组实现,线程安全的,效率低)。

Map接口有三个实现类(HashMap:基于hash表的Map接口实现,非线程安全,高效,支持null值和null键;HashTable:线程安全,低效,不支持null值和null键;LinkedHashMap:是HashMap的一个子类,保存了记录的插入顺序;SortMap接口:TreeMap,可以把它保存的记录根据键排序,默认是键值的升序排序)。

Set接口有两个实现类(HashSet:底层是由HashMap实现,不容许集合中有重复的值,使用该方式时须要重写equals()和hashCode()方法;LinkedHashSet:继承与HashSet,同时又基于LinkedHashMap来进行实现,底层使用的是LinkedHashMp)。

6.3区别

List集合中对象按照索引位置排序,能够有重复对象,容许按照对象在集合中的索引位置检索对象,例如经过list.get(i)方法来获取集合中的元素;Map中的每个元素包含一个键和一个值,成对出现,键对象不能够重复,值对象能够重复;Set集合中的对象不按照特定的方式排序,而且没有重复对象,但它的实现类能对集合中的对象按照特定的方式排序,例如TreeSet类,能够按照默认顺序,也能够经过实现Java.util.Comparator接口来自定义排序方式。

[if !supportLists]7. [endif]HashMap 和HashTable有什么区别?(2017-2-23)

HashMap是线程不安全的,HashMap是一个接口,是Map的一个子接口,是将键映射到值得对象,不容许键值重复,容许空键和空值;因为非线程安全,HashMap的效率要较HashTable的效率高一些.

HashTable 是线程安全的一个集合,不容许null值做为一个key值或者Value值;

HashTable是sychronize,多个线程访问时不须要本身为它的方法实现同步,而HashMap在被多个线程访问的时候须要本身为它的方法实现同步;

[if !supportLists]8. [endif]数组和链表分别比较适合用于什么场景,为何?(2017-2-23)

8.1 数组和链表简介

在计算机中要对给定的数据集进行若干处理,首要任务是把数据集的一部分(当数据量很是大时,可能只能一部分一部分地读取数据到内存中来处理)或所有存储到内存中,而后再对内存中的数据进行各类处理。

例如,对于数据集S{1,2,3,4,5,6},要求S中元素的和,首先要把数据存储到内存中,而后再将内存中的数据相加。

当内存空间中有足够大的连续空间时,能够把数据连续的存放在内存中,各类编程语言中的数组通常都是按这种方式存储的(也可能有例外),如图1(b);当内存中只有一些离散的可用空间时,想连续存储数据就很是困难了,这时能想到的一种解决方式是移动内存中的数据,把离散的空间汇集成连续的一块大空间,如图1(c)所示,这样作固然也能够,可是这种状况由于可能要移动别人的数据,因此会存在一些困难,移动的过程当中也有可能会把一些别人的重要数据给丢失。另一种,不影响别人的数据存储方式是把数据集中的数据分开离散地存储到这些不连续空间中,如图(d)。这时为了能把数据集中的全部数据联系起来,须要在前一块数据的存储空间中记录下一块数据的地址,这样只要知道第一块内存空间的地址就能环环相扣地把数据集总体联系在一块儿了。C/C++中用指针实现的链表就是这种存储形式。

图内存分配

由上可知,内存中的存储形式能够分为连续存储和离散存储两种。所以,数据的物理存储结构就有连续存储和离散存储两种,它们对应了咱们一般所说的数组和链表,

8.2 数组和链表的区别

数组是将元素在内存中连续存储的;它的优势:由于数据是连续存储的,内存地址连续,因此在查找数据的时候效率比较高;它的缺点:在存储以前,咱们须要申请一块连续的内存空间,而且在编译的时候就必须肯定好它的空间的大小。在运行的时候空间的大小是没法随着你的须要进行增长和减小而改变的,当数据两比较大的时候,有可能会出现越界的状况,数据比较小的时候,又有可能会浪费掉内存空间。在改变数据个数时,增长、插入、删除数据效率比较低

链表是动态申请内存空间,不须要像数组须要提早申请好内存的大小,链表只需在用的时候申请就能够,根据须要来动态申请或者删除内存空间,对于数据增长和删除以及插入比数组灵活。还有就是链表中数据在内存中能够在任意的位置,经过应用来关联数据(就是经过存在元素的指针来联系)

8.3 链表和数组使用场景

数组应用场景:数据比较少;常常作的运算是按序号访问数据元素;数组更容易实现,任何高级语言都支持;构建的线性表较稳定。

链表应用场景:对线性表的长度或者规模难以估计;频繁作插入删除操做;构建动态性比较强的线性表。

参考博客:http://blog.csdn.net/u011277123/article/details/53908387

 

8.4 跟数组相关的面试题

用面向对象的方法求出数组中重复value的个数,按以下个数输出:

1出现:1次

3出现:2次

8出现:3次

2出现:4次

 

int[] arr = {1,4,1,4,2,5,4,5,8,7,8,77,88,5,4,9,6,2,4,1,5};

 

[if !supportLists]9. [endif]Java中ArrayList和Linkedlist区别?(2017-2-23)

ArrayList和Vector使用了数组的实现,能够认为ArrayList或者Vector封装了对内部数组的操做,好比向数组中添加,删除,插入新的元素或者数据的扩展和重定向。

LinkedList使用了循环双向链表数据结构。与基于数组的ArrayList相比,这是两种大相径庭的实现技术,这也决定了它们将适用于彻底不一样的工做场景。

LinkedList链表由一系列表项链接而成。一个表项老是包含3个部分:元素内容,前驱表和后驱表,如图所示:

 

在下图展现了一个包含3个元素的LinkedList的各个表项间的链接关系。在JDK的实现中,不管LikedList是否为空,链表内部都有一个header表项,它既表示链表的开始,也表示链表的结尾。表项header的后驱表项即是链表中第一个元素,表项header的前驱表项即是链表中最后一个元素。

 

[if !supportLists]10. [endif]List a=new ArrayList()和ArrayList a =new ArrayList()的区别?(2017-2-24)

List list = new ArrayList();这句建立了一个ArrayList的对象后把上溯到了List。此时它是一个List对象了,有些ArrayList有可是List没有的属性和方法,它就不能再用了。而ArrayList list=new ArrayList();建立一对象则保留了ArrayList的全部属性。 因此须要用到ArrayList独有的方法的时候不能用前者。实例代码以下:

[if !supportLists]1.[endif]List list = new ArrayList();

[if !supportLists]2.[endif]ArrayList arrayList = new ArrayList();

[if !supportLists]3.[endif]list.trimToSize(); //错误,没有该方法。

[if !supportLists]4.[endif]arrayList.trimToSize();   //ArrayList里有该方法。

 

[if !supportLists]11. [endif]要对集合更新操做时,ArrayList和LinkedList哪一个更适合?(2017-2-24)

1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。      2.若是集合数据是对于集合随机访问get和set,ArrayList绝对优于LinkedList,由于LinkedList要移动指针。       3.若是集合数据是对于集合新增和删除操做add和remove,LinedList比较占优点,由于ArrayList要移动数据。 

ArrayList和LinkedList 是两个集合类,用于存储一系列的对象引用(references)。例如咱们能够用ArrayList来存储一系列的String或者Integer。那 么ArrayList和LinkedList在性能上有什么差异呢?何时应该用ArrayList何时又该用LinkedList呢?

[if !supportLists]一.[endif]时间复杂度 首先一点关键的是,ArrayList的内部实现是基于基础的对象数组的,所以,它使用get方法访问列表中的任意一个元素时(random access),它的速度要比LinkedList快。LinkedList中的get方法是按照顺序从列表的一端开始检查,直到另一端。对LinkedList而言,访问列表中的某个指定元素没有更快的方法了。 假设咱们有一个很大的列表,它里面的元素已经排好序了,这个列表多是ArrayList类型的也多是LinkedList类型的,如今咱们对这个列表来进行二分查找(binary search),比较列表是ArrayList和LinkedList时的查询速度,看下面的程序: 

[if !supportLists]1.[endif]public class TestList{   

[if !supportLists]2.[endif]     public static final int N=50000;    //50000个数

[if !supportLists]3.[endif]     public static List values;     //要查找的集合

[if !supportLists]4.[endif]//放入50000个数给value;

[if !supportLists]5.[endif]     static{   

[if !supportLists]6.[endif]         Integer vals[]=new Integer[N];   

[if !supportLists]7.[endif]         Random r=new Random();   

[if !supportLists]8.[endif]         for(int i=0,currval=0;i

[if !supportLists]9.[endif]             vals=new Integer(currval);   

[if !supportLists]10.[endif]             currval+=r.nextInt(100)+1;   

[if !supportLists]11.[endif]         }   

[if !supportLists]12.[endif]         values=Arrays.asList(vals);   

[if !supportLists]13.[endif]     }   

[if !supportLists]14.[endif]//经过二分查找法查找

[if !supportLists]15.[endif]     static long timeList(List lst){   

[if !supportLists]16.[endif]         long start=System.currentTimeMillis();   

[if !supportLists]17.[endif]         for(int i=0;i

[if !supportLists]18.[endif]             int index=Collections.binarySearch(lst, values.get(i));   

[if !supportLists]19.[endif]             if(index!=i)   

[if !supportLists]20.[endif]System.out.println("***错误***");   

[if !supportLists]21.[endif]         }   

[if !supportLists]22.[endif]         return System.currentTimeMillis()-start;   

[if !supportLists]23.[endif]     }   

[if !supportLists]24.[endif]     public static void main(String args[])...{   

[if !supportLists]25.[endif]System.out.println("ArrayList消耗时间:"+timeList(new ArrayList(values)));   

[if !supportLists]26.[endif]System.out.println("LinkedList消耗时间:"+timeList(new LinkedList(values)));   

[if !supportLists]27.[endif]     }   

[if !supportLists]28.[endif]}   

获得的输出是:

[if !supportLists]1. [endif]ArrayList消耗时间:15    

[if !supportLists]2. [endif]LinkedList消耗时间:2596 

这个结果不是固定的,可是基本上ArrayList的时间要明显小于LinkedList的时间。所以在这种状况下不宜用LinkedList。二分查找法使用的随机访问(random access)策略,而LinkedList是不支持快速的随机访问的。对一个LinkedList作随机访问所消耗的时间与这个list的大小是成比例的。而相应的,在ArrayList中进行随机访问所消耗的时间是固定的。 这是否代表ArrayList老是比 LinkedList性能要好呢?这并不必定,在某些状况下LinkedList的表现要优于ArrayList,有些算法在LinkedList中实现 时效率更高。比方说,利用Collections.reverse方法对列表进行反转时,其性能就要好些。看这样一个例子,加入咱们有一个列表,要对其进行大量的插入和删除操做,在这种状况下LinkedList就是一个较好的选择。请看以下一个极端的例子,咱们重复的在一个列表的开端插入一个元素:

 

[if !supportLists]1.[endif]import java.util.*;   

[if !supportLists]2.[endif]public class ListDemo {   

[if !supportLists]3.[endif]     static final int N=50000;   

[if !supportLists]4.[endif]     static long timeList(List list){   

[if !supportLists]5.[endif]      long start=System.currentTimeMillis();   

[if !supportLists]6.[endif]      Object o = new Object();   

[if !supportLists]7.[endif]      for(int i=0;i

[if !supportLists]8.[endif]          list.add(0, o);   

[if !supportLists]9.[endif]      return System.currentTimeMillis()-start;   

[if !supportLists]10.[endif]     }   

[if !supportLists]11.[endif]     public static void main(String[] args) {   

[if !supportLists]12.[endif]System.out.println("ArrayList耗时:"+timeList(new ArrayList()));   

[if !supportLists]13.[endif]System.out.println("LinkedList耗时:"+timeList(new LinkedList()));   

[if !supportLists]14.[endif]     }   

[if !supportLists]15.[endif]}   

这时个人输出结果是

[if !supportLists]1. [endif]ArrayList耗时:2463 

[if !supportLists]2. [endif]LinkedList耗时:15 

[if !supportLists]二.[endif]空间复杂度在LinkedList中有一个私有的内部类,定义以下:

[if !supportLists]1.[endif]private static class Entry {   

[if !supportLists]2.[endif]         Object element;   

[if !supportLists]3.[endif]         Entry next;   

[if !supportLists]4.[endif]         Entry previous;   

[if !supportLists]5.[endif]     }   

每一个Entry对象reference列表 中的一个元素,同时还有在LinkedList中它的上一个元素和下一个元素。一个有1000个元素的LinkedList对象将有1000个连接在一块儿 的Entry对象,每一个对象都对应于列表中的一个元素。这样的话,在一个LinkedList结构中将有一个很大的空间开销,由于它要存储这1000个 Entity对象的相关信息。 ArrayList使用一个内置的数组来存 储元素,这个数组的起始容量是10.当数组须要增加时,新的容量按以下公式得到:新容量=(旧容量*3)/2+1,也就是说每一次容量大概会增加50%。 这就意味着,若是你有一个包含大量元素的ArrayList对象,那么最终将有很大的空间会被浪费掉,这个浪费是由ArrayList的工做方式自己形成 的。若是没有足够的空间来存放新的元素,数组将不得不被从新进行分配以便可以增长新的元素。对数组进行从新分配,将会致使性能急剧降低。若是咱们知道一个 ArrayList将会有多少个元素,咱们能够经过构造方法来指定容量。咱们还能够经过trimToSize方法在ArrayList分配完毕以后去掉浪 费掉的空间。

三.总结 ArrayList和LinkedList在性能上各有优缺点,都有各自所适用的地方,总的说来能够描述以下:  1.对ArrayList和 LinkedList而言,在列表末尾增长一个元素所花的开销都是固定的。对ArrayList而言,主要是在内部数组中增长一项,指向所添加的元素,偶 尔可能会致使对数组从新进行分配;而对LinkedList而言,这个开销是统一的,分配一个内部Entry对象。 2.在ArrayList的中间插入或删除一个元素意味着这个列表中剩余的元素都会被移动;而在LinkedList的中间插入或删除一个元素的开销是固定的。 3.LinkedList不支持高效的随机元素访问。 4.ArrayList的空间浪费主要体如今在list列表的结尾预留必定的容量空间,而LinkedList的空间花费则体如今它的每个元素都须要消耗至关的空间 能够这样说:当操做是在一列数据的后面添加数据而不是在前面或中间,而且须要随机地访问其中的元素时,使用ArrayList会提供比较好的性能;当你的操做是在一列数据的前面或中间添加或删除数据,而且按照顺序访问其中的元素时,就应该使用LinkedList了。

[if !supportLists]12. [endif]请用两个队列模拟堆栈结构(2017-2-24)

两个队列模拟一个堆栈,队列是先进先出,而堆栈是先进后出。模拟以下

队列a和b

[if !supportLists](1)[endif]入栈:a队列为空,b为空。例:则将”a,b,c,d,e”须要入栈的元素先放a中,a进栈为”a,b,c,d,e”

[if !supportLists](2)[endif]出栈:a队列目前的元素为”a,b,c,,d,e”。将a队列依次加入Arraylist集合a中。以倒序的方法,将a中的集合取出,放入b队列中,再将b队列出列。代码以下:

 

[if !supportLists]1.[endif] public static void main(String[] args) {

[if !supportLists]2.[endif] Queue queue = new LinkedList();  //a队列

[if !supportLists]3.[endif] Queue queue2=new LinkedList();   //b队列

[if !supportLists]4.[endif] ArrayList a=new ArrayList();     //arrylist集合是中间参数

[if !supportLists]5.[endif] //往a队列添加元素

[if !supportLists]6.[endif]         queue.offer("a");               

[if !supportLists]7.[endif]         queue.offer("b");

[if !supportLists]8.[endif]         queue.offer("c");

[if !supportLists]9.[endif]         queue.offer("d");

[if !supportLists]10.[endif]         queue.offer("e");

[if !supportLists]11.[endif] System.out.print("进栈:");

[if !supportLists]12.[endif]//a队列依次加入list集合之中

[if !supportLists]13.[endif]         for(String q : queue){

[if !supportLists]14.[endif]          a.add(q);

[if !supportLists]15.[endif]           System.out.print(q);       

[if !supportLists]16.[endif]         }

[if !supportLists]17.[endif]//以倒序的方法取出(a队列依次加入list集合)之中的值,加入b对列

[if !supportLists]18.[endif]         for(int i=a.size()-1;i>=0;i--){

[if !supportLists]19.[endif]          queue2.offer(a.get(i));    

[if !supportLists]20.[endif]         }  

[if !supportLists]21.[endif]//打印出栈队列

[if !supportLists]22.[endif]         System.out.println("");

[if !supportLists]23.[endif] System.out.print("出栈:");   

[if !supportLists]24.[endif]         for(String q : queue2){    

[if !supportLists]25.[endif]             System.out.print(q);

[if !supportLists]26.[endif]         }      

[if !supportLists]27.[endif]  }

打印结果为(遵循栈模式先进后出):

进栈:a b c d e

出栈:e d c b a

[if !supportLists]13. [endif]Collection和Map的集成体系(2017-11-14-lyq)

Collection:

 

Map:

 

[if !supportLists]14. [endif]Map中的key和value能够为null么?(2017-11-21-gxb)

HashMap对象的key、value值都可为null。

HahTable对象的key、value值均不可为null。

且二者的的key值均不能重复,若添加key相同的键值对,后面的value会自动覆盖前面的value,但不会报错。测试代码以下:

[if !supportLists]1. [endif]public class Test {  

[if !supportLists]2. [endif]  

[if !supportLists]3. [endif]    public static void main(String[] args) {  

[if !supportLists]4. [endif]Map map = new HashMap();//HashMap对象  

[if !supportLists]5. [endif]Map tableMap = new Hashtable();//HashTable对象  

[if !supportLists]6. [endif]  

[if !supportLists]7. [endif]        map.put(null, null);  

[if !supportLists]8. [endif]System.out.println("hashMap的[key]和[value]都可觉得null:" + map.get(null));  

[if !supportLists]9. [endif]  

[if !supportLists]10. [endif]        try {  

[if !supportLists]11. [endif]            tableMap.put(null, "3");  

[if !supportLists]12. [endif]            System.out.println(tableMap.get(null));  

[if !supportLists]13. [endif]        } catch (Exception e) {  

[if !supportLists]14. [endif]System.out.println("【ERROR】:hashTable的[key]不能为null");  

[if !supportLists]15. [endif]        }  

[if !supportLists]16. [endif]  

[if !supportLists]17. [endif]        try {  

[if !supportLists]18. [endif]            tableMap.put("3", null);  

[if !supportLists]19. [endif]            System.out.println(tableMap.get("3"));  

[if !supportLists]20. [endif]        } catch (Exception e) {  

[if !supportLists]21. [endif]System.out.println("【ERROR】:hashTable的[value]不能为null");  

[if !supportLists]22. [endif]        }  

[if !supportLists]23. [endif]    }  

[if !supportLists]24. [endif]  

[if !supportLists]25. [endif]}

运行结果:

hashMap的[key]和[value]都可觉得null:null 【ERROR】:hashTable的[key]不能为null 【ERROR】:hashTable的[value]不能为null

[if !supportLists]9、[endif]Java的多线程和并发库

对于Java程序员来讲,多线程在工做中的使用场景仍是比较常见的,而仅仅掌握了Java中的传统多线程机制,仍是不够的。在JDK5.0以后,Java增长的并发库中提供了不少优秀的API,在实际开发中用的比较多。所以在看具体的面试题以前咱们有必要对这部分知识作一个全面的了解。

(一)多线程基础知识--传统线程机制的回顾2017-12-11-wl

[if !supportLists]( 1 ) [endif]传统使用类Thread和接口Runnable实现

[if !supportLists]1. [endif]Thread子类覆盖的run方法中编写运行代码

方式一

new Thread(){

@Override

public void run(){

while(true){

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}.start();

 

[if !supportLists]2. [endif]传递给Thread对象的Runnable对象的run方法中编写代码

new Thread(new Runnable(){

public void run(){

while(true){

try {

Thread.sleep(2000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName());

}

}

}).start();

 

[if !supportLists]3. [endif]总结

 

查看Thread类的run()方法的源代码,能够看到其实这两种方式都是在调用Thread对象的run方法,若是Thread类的run方法没有被覆盖,而且为该Thread对象设置了一个Runnable对象,该run方法会调用Runnable对象的run方法

    /**

     * If this thread was constructed using a separate

     * Runnable run object, then that

     * Runnable object's run method is called;

     * otherwise, this method does nothing and returns.

     *

     * Subclasses of Thread should override this method.

     *

     * @see     #start()

     * @see     #stop()

     * @see     #Thread(ThreadGroup, Runnable, String)

     */

    @Override

    public void run() {

        if (target != null) {

            target.run();

        }

    }

[if !supportLists]( 2 ) [endif]实现时器Timer和TimerTask

Timer在实际开发中应用场景很少,通常来讲都会用其余第三方库来实现。但有时会在一些面试题中出现。下面咱们就针对一道面试题来使用Timer定时类。

[if !supportLists]1. [endif]请模拟写出双重定时器(面试题)

 

要求:使用定时器,间隔4秒执行一次,再间隔2秒执行一次,以此类推执行。

 

class TimerTastCus extends TimerTask{

@Override

public void run() {

count = (count +1)%2;

System.err.println("Boob boom ");

new Timer().schedule(new TimerTastCus(), 2000+2000*count);

}

}  

 

Timer  timer = new Timer();

timer.schedule(new TimerTastCus(), 2000+2000*count);

 

while (true) {

System.out.println(new  Date().getSeconds());

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

}

//PS:下面的代码中的count变量中

//此参数要使用在你匿名内部类中,使用final修饰就没法对其值进行修改,

//只能改成静态变量

private   static volatile int count  = 0;

[if !supportLists]( 3 ) [endif]线程互斥与同步

在引入多线程后,因为线程执行的异步性,会给系统形成混乱,特别是在急用临界资源时,如多个线程急用同一台打印机,会使打印结果交织在一块儿,难于区分。当多个线程急用共享变量,表格,链表时,可能会致使数据处理出错,所以线程同步的主要任务是使并发执行的各线程之间可以有效的共享资源和相互合做,从而使程序的执行具备可再现性。

当线程并发执行时,因为资源共享和线程协做,使用线程之间会存在如下两种制约关系。

[if !supportLists]1. [endif]间接相互制约。一个系统中的多个线程必然要共享某种系统资源,如共享CPU,共享I/O设备,所谓间接相互制约即源于这种资源共享,打印机就是最好的例子,线程A在使用打印机时,其它线程都要等待。

[if !supportLists]2. [endif]直接相互制约。这种制约主要是由于线程之间的合做,若有线程A将计算结果提供给线程B做进一步处理,那么线程B在线程A将数据送达以前都将处于阻塞状态。

间接相互制约能够称为互斥,直接相互制约能够称为同步,对于互斥能够这样理解,线程A和线程B互斥访问某个资源则它们之间就会产个顺序问题——要么线程A等待线程B操做完毕,要么线程B等待线程操做完毕,这其实就是线程的同步了。所以同步包括互斥,互斥实际上是一种特殊的同步。

下面咱们经过一道面试题来体会线程的交互。

要求:子线程运行执行10次后,主线程再运行5次。这样交替执行三遍

 

public static void main(String[] args) {

final Bussiness bussiness = new Bussiness();

//子线程

new Thread(new Runnable() {

@Override

public void run() {

for (int i = 0; i < 3; i++) {

bussiness.subMethod();

}

}

}).start();

//主线程

for (int i = 0; i < 3; i++) {

bussiness.mainMethod();

}

}

}

 

 

class Bussiness {

private boolean subFlag = true;

 

public synchronized void mainMethod() {

while (subFlag) {

try {

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

 

for (int i = 0; i < 5; i++) {

System.out.println(Thread.currentThread().getName()

+ " : main thread running loop count -- " + i);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

subFlag = true;

notify();

}

 

public synchronized void subMethod() {

while (!subFlag) {

try {

wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

}

 

for (int i = 0; i < 10; i++) {

System.err.println(Thread.currentThread().getName()

+ " : sub thread running loop count -- " + i);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

subFlag = false;

notify();

}

}

[if !supportLists]( 4 ) [endif]线程局部变量ThreadLocal

[if !supportLists]l [endif]ThreadLocal的做用和目的:用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另一份数据。

[if !supportLists]l [endif]每一个线程调用全局ThreadLocal对象的set方法,在set方法中,首先根据当前线程获取当前线程的ThreadLocalMap对象,而后往这个map中插入一条记录,key实际上是ThreadLocal对象,value是各自的set方法传进去的值。也就是每一个线程其实都有一份本身独享的ThreadLocalMap对象,该对象的Key是ThreadLocal对象,值是用户设置的具体值。在线程结束时能够调用ThreadLocal.remove()方法,这样会更快释放内存,不调用也能够,由于线程结束后也能够自动释放相关的ThreadLocal变量。

[if !supportLists]l [endif]ThreadLocal的应用场景:

[if !supportLists]Ø [endif]订单处理包含一系列操做:减小库存量、增长一条流水台帐、修改总帐,这几个操做要在同一个事务中完成,一般也即同一个线程中进行处理,若是累加公司应收款的操做失败了,则应该把前面的操做回滚,不然,提交全部操做,这要求这些操做使用相同的数据库链接对象,而这些操做的代码分别位于不一样的模块类中。

[if !supportLists]Ø [endif]银行转帐包含一系列操做:把转出账户的余额减小,把转入账户的余额增长,这两个操做要在同一个事务中完成,它们必须使用相同的数据库链接对象,转入和转出操做的代码分别是两个不一样的账户对象的方法。

[if !supportLists]Ø [endif]例如Strut2的ActionContext,同一段代码被不一样的线程调用运行时,该代码操做的数据是每一个线程各自的状态和数据,对于不一样的线程来讲,getContext方法拿到的对象都不相同,对同一个线程来讲,无论调用getContext方法多少次和在哪一个模块中getContext方法,拿到的都是同一个。

[if !supportLists]1. [endif]ThreadLocal的使用方式

[if !supportLists](1) [endif]在关联数据类中建立private static ThreadLocal

在下面的类中,私有静态 ThreadLocal 实例(serialNum)为调用该类的静态SerialNum.get() 方法的每一个线程维护了一个“序列号”,该方法将返回当前线程的序列号。(线程的序列号是在第一次调用SerialNum.get() 时分配的,并在后续调用中不会更改。)

public class SerialNum {  

    // The next serial number to be assigned  

    private static int nextSerialNum = 0;  

 

    private static ThreadLocal serialNum = new ThreadLocal() {  

        protected synchronized Object initialValue() {  

            return new Integer(nextSerialNum++);  

        }  

    };  

 

    public static int get() {  

        return ((Integer) (serialNum.get())).intValue();  

    }  

}  

 

另外一个例子,也是私有静态 ThreadLocal 实例:

public class ThreadContext {   private String userId;  private Long transactionId;   private static ThreadLocal threadLocal = new ThreadLocal(){     @Override        protected ThreadContext initialValue() {            return new ThreadContext();        }    };    public static ThreadContext get() {        return threadLocal.get();    }

    public String getUserId() {        return userId;    }    public void setUserId(String userId) {       this.userId = userId;    }    public Long getTransactionId() {       return transactionId;    }    public void setTransactionId(Long transactionId) {       this.transactionId = transactionId;    }}

补充:在JDK的API对ThreadLocal私有化的说明。并举例‘线程惟一标识符’UniqueThreadIdGenerator ,你们学习是能够结合官方API来学习。

[if !supportLists]2. [endif]在Util类中建立ThreadLocal

这是上面用法的扩展,即把ThreadLocal的建立放到工具类中。

public class HibernateUtil {    private static Log log = LogFactory.getLog(HibernateUtil.class);private static final SessionFactory sessionFactory;     //定义SessionFactory     static {        try {//经过默认配置文件hibernate.cfg.xml建立SessionFactory            sessionFactory = new Configuration().configure().buildSessionFactory();        } catch (Throwable ex) {log.error("初始化SessionFactory失败!", ex);            throw new ExceptionInInitializerError(ex);        }    }//建立线程局部变量session,用来保存Hibernate的Session    public static final ThreadLocal session = new ThreadLocal();     /***获取当前线程中的Session     * @return Session     * @throws HibernateException     */    public static Session currentSession() throws HibernateException {        Session s = (Session) session.get();//若是Session尚未打开,则新开一个Session        if (s == null) {            s = sessionFactory.openSession();session.set(s);         //将新开的Session保存到线程局部变量中        }        return s;    }     public static void closeSession() throws HibernateException {//获取线程局部变量,并强制转换为Session类型        Session s = (Session) session.get();        session.set(null);        if (s != null)            s.close();    }}

 

[if !supportLists]3. [endif]在Runnable中建立ThreadLocal

在线程类内部建立ThreadLocal,基本步骤以下:

[if !supportLists]①、[endif]在多线程的类(如ThreadDemo类)中,建立一个ThreadLocal对象threadXxx,用来保存线程间须要隔离处理的对象xxx。 

[if !supportLists]②、[endif]在ThreadDemo类中,建立一个获取要隔离访问的数据的方法getXxx(),在方法中判断,若ThreadLocal对象为null时候,应该new()一个隔离访问类型的对象,并强制转换为要应用的类型

[if !supportLists]③、[endif]在ThreadDemo类的run()方法中,经过调用getXxx()方法获取要操做的数据,这样能够保证每一个线程对应一个数据对象,在任什么时候刻都操做的是这个对象。

public class ThreadLocalTest implements Runnable{        ThreadLocal studenThreadLocal = new ThreadLocal();    @Override    public void run() {        String currentThreadName = Thread.currentThread().getName();        System.out.println(currentThreadName + " is running...");        Random random = new Random();        int age = random.nextInt(100);        System.out.println(currentThreadName + " is set age: "  + age);        Studen studen = getStudent(); //经过这个方法,为每一个线程都独立的new一个student对象,每一个线程的的student对象均可以设置不一样的值        studen.setAge(age);        System.out.println(currentThreadName + " is first get age: " + studen.getAge());        try {            Thread.sleep(500);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println( currentThreadName + " is second get age: " + studen.getAge());            }        private Studen getStudent() {        Studen studen = studenThreadLocal.get();        if (null == studen) {            studen = new Studen();            studenThreadLocal.set(studen);        }        return studen;    }    public static void main(String[] args) {        ThreadLocalTest t = new ThreadLocalTest();        Thread t1 = new Thread(t,"Thread A");        Thread t2 = new Thread(t,"Thread B");        t1.start();        t2.start();    }    }class Studen{    int age;    public int getAge() {        return age;    }    public void setAge(int age) {        this.age = age;    }    }

[if !supportLists]( 5 ) [endif]多线程共享数据

在Java传统线程机制中的共享数据方式,大体能够简单分两种状况:

[if !supportLists]Ø [endif]多个线程行为一致,共同操做一个数据源。也就是每一个线程执行的代码相同,能够使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,卖票系统就能够这么作。

[if !supportLists]Ø [endif]多个线程行为不一致,共同操做一个数据源。也就是每一个线程执行的代码不一样,这时候须要用不一样的Runnable对象。例如,银行存取款。

下面咱们经过两个示例代码来分别说明这两种方式。

[if !supportLists]1. [endif]多个线程行为一致共同操做一个数据

若是每一个线程执行的代码相同,能够使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就能够这么作。

/**

*共享数据类

**/

class ShareData{

private int num = 10 ;

public  synchronized void inc() {

num++;

System.out.println(Thread.currentThread().getName()+": invoke  inc method num =" + num);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

/**

*多线程类

**/

class RunnableCusToInc implements Runnable{

private ShareData  shareData;

public RunnableCusToInc(ShareData data) {

this.shareData = data;

}

@Override

public void run() {

for (int i = 0; i < 5; i++) {

shareData.inc();

}

}

}

 

/**

*测试方法

**/

public static void main(String[] args) {

ShareData  shareData = new ShareData();

for (int i = 0; i < 4; i++) {

new Thread(new  RunnableCusToInc(shareData),"Thread "+ i).start();

}

}

}

[if !supportLists]2. [endif]多个线程行为一致共同操做一个数据

若是每一个线程执行的代码不一样,这时候须要用不一样的Runnable对象,有以下两种方式来实现这些Runnable对象之间的数据共享:

[if !supportLists]1) [endif]将共享数据封装在另一个对象中,而后将这个对象逐一传递给各个Runnable对象。每一个线程对共享数据的操做方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操做的互斥和通讯。

public static void main(String[] args) {

ShareData  shareData = new ShareData();

for (int i = 0; i < 4; i++) {

if(i%2 == 0){

new Thread(new  RunnableCusToInc(shareData),"Thread "+ i).start();

}else{

new Thread(new  RunnableCusToDec(shareData),"Thread "+ i).start();

}

}

}

//封装共享数据类

class RunnableCusToInc implements Runnable{

 

//封装共享数据

private ShareData  shareData;

public RunnableCusToInc(ShareData data) {

this.shareData = data;

}

@Override

public void run() {

for (int i = 0; i < 5; i++) {

shareData.inc();

}

}

}

 

//封装共享数据类

class RunnableCusToDec implements Runnable{

 

//封装共享数据

private ShareData  shareData;

public RunnableCusToDec(ShareData data) {

this.shareData = data;

}

@Override

public void run() {

for (int i = 0; i < 5; i++) {

shareData.dec();

}

}

}

 

/**

*共享数据类

**/

class ShareData{

private int num = 10 ;

public  synchronized void inc() {

num++;

System.out.println(Thread.currentThread().getName()+": invoke  inc method num =" + num);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

 

[if !supportLists]2) [endif]将这些Runnable对象做为某一个类中的内部类,共享数据做为这个外部类中的成员变量,每一个线程对共享数据的操做方法也分配给外部类,以便实现对共享数据进行的各个操做的互斥和通讯,做为内部类的各个Runnable对象调用外部类的这些方法。

public static void main(String[] args) {

//公共数据

final ShareData  shareData = new ShareData();

for (int i = 0; i < 4; i++) {

if(i%2 == 0){

new Thread(new Runnable() {

@Override

public void run() {

for (int i = 0; i < 5; i++) {

shareData.inc();

}

}

},"Thread "+ i).start();

}else{

new Thread(new Runnable() {

@Override

public void run() {

for (int i = 0; i < 5; i++) {

shareData.dec();

}

}

},"Thread "+ i).start();

}

}

}

 

class ShareData{

private int num = 10 ;

public  synchronized void inc() {

num++;

System.out.println(Thread.currentThread().getName()+": invoke  inc method num =" + num);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

public synchronized void dec() {

num--;

System.err.println(Thread.currentThread().getName()+": invoke  dec  method num =" + num);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

 

补充:上面两种方式的组合:将共享数据封装在另一个对象中,每一个线程对共享数据的操做方法也分配到那个对象身上去完成,对象做为这个外部类中的成员变量或方法中的局部变量,每一个线程的Runnable对象做为外部类中的成员内部类或局部内部类。

 

总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通讯。

 

(二)多线程基础知识--线程并发库2017-12-11-wl

Java 5 添加了一个新的包到 Java 平台,java.util.concurrent 包。这个包包含有一系列可以让 Java 的并发编程变得更加简单轻松的类。在这个包被添加之前,你须要本身去动手实现本身的相关工具类。下面带你认识下java.util.concurrent包里的这些类,而后你能够尝试着如何在项目中使用它们。本文中将使用Java 6 版本,我不肯定这和 Java 5 版本里的是否有一些差别。我不会去解释关于 Java 并发的核心问题 – 其背后的原理,也就是说,若是你对那些东西感兴趣,参考《Java 并发指南》。

[if !supportLists]( 1 ) [endif]Java的线程并发库介绍

Java5的多线程并有两个大发库在java.util.concurrent包及子包中,子包主要的包有一下两个

[if !supportLists]1) [endif]java.util.concurrent包 (多线程并发库)

[if !supportLists]Ø [endif] java.util.concurrent 包含许多线程安全、测试良好、高性能的并发构建块。不客气地说,建立 java.util.concurrent 的目的就是要实现 Collection 框架对数据结构所执行的并发操做。经过提供一组可靠的、高性能并发构建块,开发人员能够提升并发类的线程安全、可伸缩性、性能、可读性和可靠性,后面、咱们会作介绍。

[if !supportLists]Ø [endif]若是一些类名看起来类似,多是由于java.util.concurrent 中的许多概念源自 Doug Lea 的 util.concurrent 库。

[if !supportLists]2) [endif]java.util.concurrent.atomic包 (多线程的原子性操做提供的工具类)

[if !supportLists]Ø [endif]查看atomic包文档页下面的介绍,它能够对多线程的基本数据、数组中的基本数据和对象中的基本数据进行多线程的操做(AtomicInteger、AtomicIntegerArray、AtomicIntegerFieldUpDater…)

[if !supportLists]Ø [endif]经过以下两个方法快速理解atomic包的意义:

[if !supportLists]n [endif]AtomicInteger类的boolean compareAndSet(expectedValue, updateValue);

[if !supportLists]n [endif]AtomicIntegerArray类的int addAndGet(int i, int delta);

[if !supportLists]Ø [endif]顺带解释volatile类型的做用,须要查看java语言规范。

[if !supportLists]n [endif]volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。(具备可见性)

[if !supportLists]n [endif]volatile没有原子性。

[if !supportLists]3) [endif]java.util.concurrent.lock包 (多线程的锁机制)

为锁和等待条件提供一个框架的接口和类,它不一样于内置同步和监视器。该框架容许更灵活地使用锁和条件。本包下有三大接口,下面简单介绍下:

[if !supportLists]Ø [endif]Lock接口:支持那些语义不一样(重入、公平等)的锁规则,能够在非阻塞式结构的上下文(包括hand-over-hand 和锁重排算法)中使用这些规则。主要的实现是ReentrantLock

[if !supportLists]Ø [endif]ReadWriteLock接口:以相似方式定义了一些读取者能够共享而写入者独占的锁。此包只提供了一个实现,即ReentrantReadWriteLock,由于它适用于大部分的标准用法上下文。但程序员能够建立本身的、适用于非标准要求的实现。

[if !supportLists]Ø [endif]Condition接口:描述了可能会与锁有关联的条件变量。这些变量在用法上与使用Object.wait 访问的隐式监视器相似,但提供了更强大的功能。须要特别指出的是,单个 Lock 可能与多个 Condition 对象关联。为了不兼容性问题,Condition 方法的名称与对应的 Object 版本中的不一样。

[if !supportLists]( 2 ) [endif]Java的并发库入门

下面咱们将分别介绍java.util.concurrent包下的经常使用类的使用。

[if !supportLists]1) [endif]java.util.concurrent 

      java.util.concurrent包描述:

在并发编程中很经常使用的实用工具类。此包包括了几个小的、已标准化的可扩展框架,以及一些提供有用功能的类。此包下有一些组件,其中包括:

[if !supportLists]l [endif]执行程序(线程池)

[if !supportLists]l [endif]并发队列

[if !supportLists]l [endif]同步器

[if !supportLists]l [endif]并发Collocation

下面咱们将java.util.concurrent包下的组件逐一简单介绍:

[if !supportLists]A. [endif]执行程序

[if !supportLists]Ø [endif]Executors线程池工厂类

首次咱们来讲下线程池的做用:

     线程池做用就是限制系统中执行线程的数量。     根据系统的环境状况,能够自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了形成系统拥挤效率不高。用线程池控制线程数量,其余线程 排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务须要运行时,若是线程 池中有等待的工做线程,就能够开始运行了;不然进入等待队列。

 

为何要用线程池:

[if !supportLists]l [endif]减小了建立和销毁线程的次数,每一个工做线程均可以被重复利用,可执行多个任务

[if !supportLists]l [endif]能够根据系统的承受能力,调整线程池中工做线线程的数目,防止由于由于消耗过多的内存,而把服务器累趴下(每一个线程须要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)

 

Executors详解:

Java里面线程池的顶级接口是Executor,可是严格意义上讲Executor并非一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。ThreadPoolExecutor是Executors类的底层实现。咱们先介绍下Executors。

线程池的基本思想仍是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样能够避免反复建立线程对象所带来的性能开销,节省了系统的资源。

Java5中并发库中,线程池建立线程大体能够分为下面三种:

//建立固定大小的线程池

ExecutorService fPool = Executors.newFixedThreadPool(3);

//建立缓存大小的线程池

ExecutorService cPool = Executors.newCachedThreadPool();

//建立单一的线程池

ExecutorService sPool = Executors.newSingleThreadExecutor();

 

下面咱们经过简单示例来分别说明:

[if !supportLists]l [endif]固定大小链接池

import java.util.concurrent.Executors;  

  import java.util.concurrent.ExecutorService;

  /**

  * Java线程:线程池- 

  *

  * @author Administrator 2009-11-4 23:30:44

  */

  public class Test {

     public static void main(String[] args) {  

      //建立一个可重用固定线程数的线程池  

      ExecutorService pool = Executors.newFixedThreadPool(2);  

      //建立实现了Runnable接口对象,Thread对象固然也实现了Runnable接口  

      Thread t1 = new MyThread();  

      Thread t2 = new MyThread();  

      Thread t3 = new MyThread();  

      Thread t4 = new MyThread();  

     Thread t5 = new MyThread();  

     //将线程放入池中进行执行  

     pool.execute(t1);  

     pool.execute(t2);  

     pool.execute(t3);  

     pool.execute(t4);

     pool.execute(t5);  

     //关闭线程池  

    pool.shutdown();  

    }  

  }

  class MyThread extends Thread{

    @Override  

    public void run() {  

      System.out.println(Thread.currentThread().getName()+"正在执行。。。");  

    }  

  }

 

运行结果:

pool-1-thread-1正在执行。。。    pool-1-thread-1正在执行。。。    pool-1-thread-2正在执行。。。   pool-1-thread-1正在执行。。。    pool-1-thread-2正在执行。。。 

从上面的运行来看,咱们Thread类都是在线程池中运行的,线程池在执行execute方法来执行Thread类中的run方法。无论execute执行几回,线程池始终都会使用2个线程来处理。不会再去建立出其余线程来处理run方法执行。这就是固定大小线程池。

[if !supportLists]l [endif]单任务链接池

咱们将上面的代码

//建立一个可重用固定线程数的线程池  

   ExecutorService pool = Executors.newFixedThreadPool(2);  

改成:

    //建立一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。

    ExecutorService pool = Executors.newSingleThreadExecutor(); 

 

 

运行结果:

pool-1-thread-1正在执行。。。    pool-1-thread-1正在执行。。。    pool-1-thread-1正在执行。。。   pool-1-thread-1正在执行。。。    pool-1-thread-1正在执行。。。

运行结果看出,单任务线程池在执行execute方法来执行Thread类中的run方法。无论execute执行几回,线程池始终都会使用单个线程来处理。

补充:在java的多线程中,一但线程关闭,就会成为死线程。关闭后死线程就没有办法在启动了。再次启动就会出现异常信息:Exception in thread "main" java.lang.IllegalThreadStateException。那么如何解决这个问题呢?

咱们这里就能够使用Executors.newSingleThreadExecutor()来再次启动一个线程。(面试)

[if !supportLists]l [endif]可变链接池

//建立一个可重用固定线程数的线程池  

   ExecutorService pool = Executors.newFixedThreadPool(2);  

改成:

    //建立一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。

ExecutorService pool = Executors.newCachedThreadPool(); 

 

 

运行结果:

pool-1-thread-5正在执行。。。    pool-1-thread-1正在执行。。。    pool-1-thread-4正在执行。。。    pool-1-thread-3正在执行。。。    pool-1-thread-2正在执行。。。 

运行结果看出,可变任务线程池在执行execute方法来执行Thread类中的run方法。这里execute执行屡次,线程池就会建立出多个线程来处理Thread类中run方法。全部咱们看到链接池会根据执行的状况,在程序运行时建立多个线程来处理,这里就是可变链接池的特色。

那么在上面的三种建立方式,Executors还能够在执行某个线程时,定时操做。那么下面咱们经过代码简单演示下。

[if !supportLists]l [endif]延迟链接池

 

import java.util.concurrent.Executors;  

  import java.util.concurrent.ScheduledExecutorService;

  import java.util.concurrent.TimeUnit;

  /**

  * Java线程:线程池- 

  *

  * @author Administrator 2009-11-4 23:30:44

  */

  public class Test {

  public static void main(String[] args) {

  //建立一个线程池,它可安排在给定延迟后运行命令或者按期地执行。  

  ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);

  //建立实现了Runnable接口对象,Thread对象固然也实现了Runnable接口  

  Thread t1 = new MyThread();

  Thread t2 = new MyThread();

  Thread t3 = new MyThread();

  Thread t4 = new MyThread();

  Thread t5 = new MyThread();

  //将线程放入池中进行执行  

  pool.execute(t1);

  pool.execute(t2);

  pool.execute(t3);

  //使用定时执行风格的方法

  pool.schedule(t4, 10, TimeUnit.MILLISECONDS);//t4和t5在10秒后执行

  pool.schedule(t5, 10, TimeUnit.MILLISECONDS);

  //关闭线程池  

  pool.shutdown();

  }

  }

  class MyThread extends Thread {

   @Override  

   public void run() {

     System.out.println(Thread.currentThread().getName() + "正在执行。。。");  

   }  

  }

 

运行结果:

pool-1-thread-1正在执行。。。   pool-1-thread-2正在执行。。。   pool-1-thread-1正在执行。。。   pool-1-thread-1正在执行。。。   pool-1-thread-2正在执行。。。 

[if !supportLists]Ø [endif]ExecutorService执行器服务

java.util.concurrent.ExecutorService 接口表示一个异步执行机制,使咱们可以在后台执行任务。所以一个 ExecutorService 很相似于一个线程池。实际上,存在于 java.util.concurrent 包里的 ExecutorService 实现就是一个线程池实现。

 ExecutorService例子:

如下是一个简单的ExecutorService 例子:

//线程工厂类建立出线程池

ExecutorService executorService = Executors.newFixedThreadPool(10); 

 

//执行一个线程任务

executorService.execute(new Runnable() { 

    public void run() { 

        System.out.println("Asynchronous task"); 

    } 

}); 

 

//线程池关闭

executorService.shutdown();

上面代码首先使用newFixedThreadPool() 工厂方法建立一个 ExecutorService。这里建立了一个十个线程执行任务的线程池。而后,将一个 Runnable 接口的匿名实现类传递给 execute() 方法。这将致使 ExecutorService 中的某个线程执行该 Runnable。这里能够当作一个任务分派,示例代码中的任务分派咱们能够理解为:

 

一个线程将一个任务委派给一个ExecutorService 去异步执行。

一旦该线程将任务委派给ExecutorService,该线程将继续它本身的执行,独立于该任务的执行。

以下图:

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 ExecutorService实现:

既然ExecutorService 是个接口,若是你想用它的话就得去使用它的实现类之一。

java.util.concurrent 包提供了 ExecutorService 接口的如下实现类:

[if !supportLists]l [endif]ThreadPoolExecutor

[if !supportLists]l [endif]ScheduledThreadPoolExecutor

 ExecutorService建立:

ExecutorService 的建立依赖于你使用的具体实现。可是你也能够使用 Executors 工厂类来建立 ExecutorService 实例。代码示例:

ExecutorService executorService1 = Executors.newSingleThreadExecutor();  //以前Executors已介绍

ExecutorService executorService2 = Executors.newFixedThreadPool(10); 

ExecutorService executorService3 = Executors.newScheduledThreadPool(10);

 ExecutorService使用:

有几种不一样的方式来将任务委托给ExecutorService 去执行:

[if !supportLists]l [endif]execute(Runnable)

[if !supportLists]l [endif]submit(Runnable)

[if !supportLists]l [endif]submit(Callable)

[if !supportLists]l [endif]invokeAny(…)

[if !supportLists]l [endif]invokeAll(…)

接下来咱们挨个看一下这些方法。

[if !supportLists]ü [endif]execute(Runnable)

execute(Runnable) 方法要求一个 java.lang.Runnable 对象,而后对它进行异步执行。如下是使用 ExecutorService 执行一个 Runnable 的示例:

//从Executors中得到ExecutorService

ExecutorService executorService = Executors.newSingleThreadExecutor(); 

//执行 ExecutorService 中的方法

executorService.execute(new Runnable() { 

    public void run() { 

        System.out.println("Asynchronous task"); 

    } 

}); 

//线程池关闭 

executorService.shutdown();

特色:没有办法得知被执行的Runnable 的执行结果。若是有须要的话你得使用一个 Callable(如下将作介绍)。

[if !supportLists]ü [endif]submit(Runnable)

submit(Runnable) 方法也要求一个 Runnable 实现类,但它返回一个 Future 对象。这个 Future 对象能够用来检查 Runnable 是否已经执行完毕。如下是 ExecutorService submit() 示例:

 

//从Executors中得到ExecutorService

ExecutorService executorService = Executors.newSingleThreadExecutor(); 

 

Future future = executorService.submit(new Runnable() { 

    public void run() { 

        System.out.println("Asynchronous task"); 

    } 

}); 

 

future.get();  //得到执行完run方法后的返回值,这里使用的Runnable,因此这里没有返回值,返回的是null。

executorService.shutdown();

[if !supportLists]ü [endif]submit(Runnable)

submit(Callable) 方法相似于 submit(Runnable) 方法,除了它所要求的参数类型以外。Callable 实例除了它的 call() 方法可以返回一个结果以外和一个 Runnable 很相像。Runnable.run() 不可以返回一个结果。Callable 的结果能够经过 submit(Callable) 方法返回的 Future 对象进行获取。

如下是一个ExecutorService Callable 示例:

//从Executors中得到ExecutorService

ExecutorService executorService = Executors.newSingleThreadExecutor(); 

 

Future future = executorService.submit(new Callable(){ 

    public Object call() throws Exception { 

        System.out.println("Asynchronous Callable"); 

        return "Callable Result"; 

    } 

}); 

 

System.out.println("future.get() = " + future.get());

executorService.shutdown();

 

输出:

Asynchronous Callable

future.get() = Callable Result

[if !supportLists]ü [endif]invokeAny()

invokeAny() 方法要求一系列的 Callable 或者其子接口的实例对象。调用这个方法并不会返回一个 Future,但它返回其中一个 Callable 对象的结果。没法保证返回的是哪一个 Callable 的结果 – 只能代表其中一个已执行结束。

若是其中一个任务执行结束(或者抛了一个异常),其余 Callable 将被取消。如下是示例代码:

 

ExecutorService executorService = Executors.newSingleThreadExecutor(); 

 

Set> callables = new HashSet>(); 

 

callables.add(new Callable() { 

    public String call() throws Exception { 

        return "Task 1"; 

    } 

}); 

callables.add(new Callable() { 

    public String call() throws Exception { 

        return "Task 2"; 

    } 

}); 

callables.add(new Callable() { 

    public String call() throws Exception { 

        return "Task 3"; 

    } 

}); 

 

String result = executorService.invokeAny(callables); 

 

System.out.println("result = " + result); 

 

executorService.shutdown();

上述代码将会打印出给定Callable 集合中的一个的执行结果。我本身试着执行了它几回,结果始终在变。有时是 “Task 1″,有时是 “Task 2″ 等等。

[if !supportLists]ü [endif]invokeAll()

invokeAll() 方法将调用你在集合中传给 ExecutorService 的全部 Callable 对象。invokeAll() 返回一系列的 Future 对象,经过它们你能够获取每一个 Callable 的执行结果。记住,一个任务可能会因为一个异常而结束,所以它可能没有"成功"。没法经过一个 Future 对象来告知咱们是两种结束中的哪种。

如下是一个代码示例:

ExecutorService executorService = Executors.newSingleThreadExecutor();  

 

Set> callables = new HashSet>();  

 

callables.add(new Callable() {  

    public String call() throws Exception {  

        return "Task 1";  

    }  

});  

callables.add(new Callable() {  

    public String call() throws Exception {  

        return "Task 2";  

    }  

});  

callables.add(new Callable() {  

    public String call() throws Exception {  

        return "Task 3";  

    }  

});  

 

List> futures = executorService.invokeAll(callables);  

 

for(Future future : futures){  

    System.out.println("future.get = " + future.get());  

}  

executorService.shutdown();  

 

输出结果:

future.get = Task 3

future.get = Task 1

future.get = Task 2

 Executors关闭:

使用shutdown和shutdownNow能够关闭线程池

二者的区别:

shutdown只是将空闲的线程interrupt() 了,shutdown()以前提交的任务能够继续执行直到结束。

shutdownNow 是interrupt全部线程, 所以大部分线程将马上被中断。之因此是大部分,而不是所有 ,是由于interrupt()方法能力有限。

[if !supportLists]Ø [endif]ThreadPoolExecutor线程池执行者

java.util.concurrent.ThreadPoolExecutor 是 ExecutorService 接口的一个实现。ThreadPoolExecutor 使用其内部池中的线程执行给定任务(Callable 或者 Runnable)。

ThreadPoolExecutor 包含的线程池可以包含不一样数量的线程。池中线程的数量由如下变量决定:

[if !supportLists]l [endif]corePoolSize

[if !supportLists]l [endif]maximumPoolSize

当一个任务委托给线程池时,若是池中线程数量低于corePoolSize,一个新的线程将被建立,即便池中可能尚有空闲线程。若是内部任务队列已满,并且有至少 corePoolSize 正在运行,可是运行线程的数量低于 maximumPoolSize,一个新的线程将被建立去执行该任务。

ThreadPoolExecutor 图解:

 

建立ThreadPoolExecutor:

int  corePoolSize  =    5;  

int  maxPoolSize   =   10;  

long keepAliveTime = 5000;  

 

ExecutorService threadPoolExecutor =  

        new ThreadPoolExecutor(  

                corePoolSize,  

                maxPoolSize,  

                keepAliveTime,  

                TimeUnit.MILLISECONDS,  

                new LinkedBlockingQueue()  

                );  

 

构造方法参数列表解释:

corePoolSize -池中所保存的线程数,包括空闲线程。

maximumPoolSize -池中容许的最大线程数。

keepAliveTime -当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。

unit - keepAliveTime参数的时间单位。

workQueue -执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。

 

[if !supportLists]Ø [endif]ScheduledPoolExecutor定时线程池执行者

java.util.concurrent.ScheduledExecutorService 是一个 ExecutorService, 它可以将任务延后执行,或者间隔固定时间屡次执行。 任务由一个工做者线程异步执行,而不是由提交任务给 ScheduledExecutorService 的那个线程执行。

ScheduledPoolExecutor例子:

ScheduledExecutorService scheduledExecutorService =  

        Executors.newScheduledThreadPool(5);  

 

ScheduledFuture scheduledFuture =  

    scheduledExecutorService.schedule(new Callable() {  

        public Object call() throws Exception {  

            System.out.println("Executed!");  

            return "Called!";  

        }  

    },  

    5,  

    TimeUnit.SECONDS);//5秒后执行

 

首先一个内置5 个线程的 ScheduledExecutorService 被建立。以后一个 Callable 接口的匿名类示例被建立而后传递给 schedule() 方法。后边的俩参数定义了 Callable 将在 5 秒钟以后被执行。

ScheduledExecutorService的实现:

ScheduledExecutorService 是一个接口,你要用它的话就得使用 java.util.concurrent 包里对它的某个实现类。ScheduledExecutorService 具备如下实现类:ScheduledThreadPoolExecutor

建立一个ScheduledExecutorService:

如何建立一个ScheduledExecutorService 取决于你采用的它的实现类。可是你也能够使用 Executors 工厂类来建立一个 ScheduledExecutorService 实例。好比:

ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);  

 

 

ScheduledExecutorService的使用:

一旦你建立了一个ScheduledExecutorService,你能够经过调用它的如下方法:

[if !supportLists]l [endif]schedule (Callable task, long delay, TimeUnit timeunit)

[if !supportLists]l [endif]schedule (Runnable task, long delay, TimeUnit timeunit)

[if !supportLists]l [endif]scheduleAtFixedRate (Runnable, long initialDelay, long period, TimeUnit timeunit)

[if !supportLists]l [endif]scheduleWithFixedDelay (Runnable, long initialDelay, long period, TimeUnit timeunit)

下面咱们就简单看一下这些方法。

[if !supportLists]ü [endif]schedule (Callable task, long delay, TimeUnit timeunit)

ScheduledExecutorService scheduledExecutorService =  

        Executors.newScheduledThreadPool(5);  

 

ScheduledFuture scheduledFuture =  

    scheduledExecutorService.schedule(new Callable() {  

        public Object call() throws Exception {  

            System.out.println("Executed!");  

            return "Called!";  

        }  

    },  

    5,  

    TimeUnit.SECONDS);  

 

System.out.println("result = " + scheduledFuture.get());  

 

scheduledExecutorService.shutdown(); 

 

输出结果:

Executed! result = Called!

[if !supportLists]ü [endif]schedule (Runnable task, long delay, TimeUnit timeunit)

这一方法规划一个任务将被按期执行。该任务将会在首个initialDelay 以后获得执行,而后每一个 period 时间以后重复执行。

若是给定任务的执行抛出了异常,该任务将再也不执行。若是没有任何异常的话,这个任务将会持续循环执行到ScheduledExecutorService 被关闭。

若是一个任务占用了比计划的时间间隔更长的时候,下一次执行将在当前执行结束执行才开始。计划任务在同一时间不会有多个线程同时执行。

[if !supportLists]ü [endif]scheduleAtFixedRate (Runnable, long initialDelay, long period, TimeUnit timeunit)

这一方法规划一个任务将被按期执行。该任务将会在首个initialDelay 以后获得执行,而后每一个 period 时间以后重复执行。

若是给定任务的执行抛出了异常,该任务将再也不执行。若是没有任何异常的话,这个任务将会持续循环执行到ScheduledExecutorService 被关闭。

若是一个任务占用了比计划的时间间隔更长的时候,下一次执行将在当前执行结束执行才开始。计划任务在同一时间不会有多个线程同时执行。

[if !supportLists]ü [endif]scheduleWithFixedDelay (Runnable, long initialDelay, long period, TimeUnit timeunit)

除了period 有不一样的解释以外这个方法和 scheduleAtFixedRate() 很是像。scheduleAtFixedRate() 方法中,period 被解释为前一个执行的开始和下一个执行的开始之间的间隔时间。而在本方法中,period 则被解释为前一个执行的结束和下一个执行的结束之间的间隔。所以这个延迟是执行结束之间的间隔,而不是执行开始之间的间隔。

ScheduledExecutorService的关闭:

正如ExecutorService,在你使用结束以后你须要把 ScheduledExecutorService 关闭掉。不然他将致使 JVM 继续运行,即便全部其余线程已经全被关闭。

你能够使用从ExecutorService 接口继承来的 shutdown() 或 shutdownNow() 方法将 ScheduledExecutorService 关闭。参见 ExecutorService 关闭部分以获取更多信息。

[if !supportLists]Ø [endif]ForkJoinPool合并和分叉(线程池)

ForkJoinPool 在 Java 7 中被引入。它和 ExecutorService 很类似,除了一点不一样。ForkJoinPool 让咱们能够很方便地把任务分裂成几个更小的任务,这些分裂出来的任务也将会提交给 ForkJoinPool。任务能够继续分割成更小的子任务,只要它还能分割。可能听起来有些抽象,所以本节中咱们将会解释 ForkJoinPool 是如何工做的,还有任务分割是如何进行的。

合并和分叉的解释:

在咱们开始看ForkJoinPool 以前咱们先来简要解释一下分叉和合并的原理。分叉和合并原理包含两个递归进行的步骤。两个步骤分别是分叉步骤和合并步骤。

分叉:

一个使用了分叉和合并原理的任务能够将本身分叉(分割)为更小的子任务,这些子任务能够被并发执行。以下图所示:

 

 

 

 

 

 

经过把本身分割成多个子任务,每一个子任务能够由不一样的CPU 并行执行,或者被同一个 CPU 上的不一样线程执行。只有当给的任务过大,把它分割成几个子任务才有意义。把任务分割成子任务有必定开销,所以对于小型任务,这个分割的消耗可能比每一个子任务并发执行的消耗还要大。

何时把一个任务分割成子任务是有意义的,这个界限也称做一个阀值。这要看每一个任务对有意义阀值的决定。很大程度上取决于它要作的工做的种类。

合并:

当一个任务将本身分割成若干子任务以后,该任务将进入等待全部子任务的结束之中。一旦子任务执行结束,该任务能够把全部结果合并到同一个结果。图示以下:

 

 

 

 

 

 

固然,并不是全部类型的任务都会返回一个结果。若是这个任务并不返回一个结果,它只需等待全部子任务执行完毕。也就不须要结果的合并啦。

因此咱们能够将ForkJoinPool 是一个特殊的线程池,它的设计是为了更好的配合 分叉-和-合并 任务分割的工做。ForkJoinPool 也在 java.util.concurrent 包中,其完整类名为 java.util.concurrent.ForkJoinPool。

建立一个ForkJoinPool:

你能够经过其构造子建立一个ForkJoinPool。做为传递给 ForkJoinPool 构造子的一个参数,你能够定义你指望的并行级别。并行级别表示你想要传递给 ForkJoinPool 的任务所需的线程或 CPU 数量。如下是一个 ForkJoinPool 示例:

//建立了一个并行级别为 4 的 ForkJoinPool

ForkJoinPool forkJoinPool = new ForkJoinPool(4);

 

提交任务到ForkJoinPool:

就像提交任务到ExecutorService 那样,把任务提交到 ForkJoinPool。你能够提交两种类型的任务。一种是没有任何返回值的(一个 “行动”),另外一种是有返回值的(一个”任务”)。这两种类型分别由 RecursiveAction 和 RecursiveTask 表示。接下来介绍如何使用这两种类型的任务,以及如何对它们进行提交。

RecursiveAction:

RecursiveAction 是一种没有任何返回值的任务。它只是作一些工做,好比写数据到磁盘,而后就退出了。一个 RecursiveAction 能够把本身的工做分割成更小的几块,这样它们能够由独立的线程或者 CPU 执行。你能够经过继承来实现一个 RecursiveAction。示例以下:

import java.util.ArrayList; 

import java.util.List; 

import java.util.concurrent.RecursiveAction; 

 

public class MyRecursiveAction extends RecursiveAction { 

 

    private long workLoad = 0; 

 

    public MyRecursiveAction(long workLoad) { 

        this.workLoad = workLoad; 

    } 

 

    @Override 

    protected void compute() { 

 

        //if work is above threshold, break tasks up into smaller tasks 

//翻译:若是工做超过门槛,把任务分解成更小的任务

        if(this.workLoad > 16) { 

            System.out.println("Splitting workLoad : " + this.workLoad); 

 

            List subtasks = 

                new ArrayList(); 

 

            subtasks.addAll(createSubtasks()); 

 

            for(RecursiveAction subtask : subtasks){ 

                subtask.fork(); 

            } 

 

        } else { 

            System.out.println("Doing workLoad myself: " + this.workLoad); 

        } 

    } 

 

    private List createSubtasks() { 

        List subtasks = 

            new ArrayList(); 

 

        MyRecursiveAction subtask1 = new MyRecursiveAction(this.workLoad / 2); 

        MyRecursiveAction subtask2 = new MyRecursiveAction(this.workLoad / 2); 

 

        subtasks.add(subtask1); 

        subtasks.add(subtask2); 

 

        return subtasks; 

    } 

 

}

例子很简单。MyRecursiveAction 将一个虚构的 workLoad 做为参数传给本身的构造子。若是 workLoad 高于一个特定阀值,该工做将被分割为几个子工做,子工做继续分割。若是 workLoad 低于特定阀值,该工做将由 MyRecursiveAction 本身执行。你能够这样规划一个 MyRecursiveAction 的执行:

//建立了一个并行级别为 4 的 ForkJoinPool

ForkJoinPool forkJoinPool = new ForkJoinPool(4);

 

//建立一个没有返回值的任务

MyRecursiveAction myRecursiveAction = new MyRecursiveAction(24); 

 

//ForkJoinPool执行任务

forkJoinPool.invoke(myRecursiveAction);

 

运行结果:

Splitting workLoad : 24

Doing workLoad myself: 12

RecursiveTask:

RecursiveTask 是一种会返回结果的任务。它能够将本身的工做分割为若干更小任务,并将这些子任务的执行结果合并到一个集体结果。能够有几个水平的分割和合并。如下是一个 RecursiveTask 示例:

import java.util.ArrayList; 

import java.util.List; 

import java.util.concurrent.RecursiveTask; 

 

 

public class MyRecursiveTask extends RecursiveTask { 

 

    private long workLoad = 0; 

 

    public MyRecursiveTask(long workLoad) { 

        this.workLoad = workLoad; 

    } 

 

    protected Long compute() { 

 

        //if work is above threshold, break tasks up into smaller tasks 

        if(this.workLoad > 16) { 

            System.out.println("Splitting workLoad : " + this.workLoad); 

 

            List subtasks = 

                new ArrayList(); 

            subtasks.addAll(createSubtasks()); 

 

            for(MyRecursiveTask subtask : subtasks){ 

                subtask.fork(); 

            } 

 

            long result = 0; 

            for(MyRecursiveTask subtask : subtasks) { 

                result += subtask.join(); 

            } 

            return result; 

 

        } else { 

            System.out.println("Doing workLoad myself: " + this.workLoad); 

            return workLoad * 3; 

        } 

    } 

 

    private List createSubtasks() { 

        List subtasks = 

        new ArrayList(); 

 

        MyRecursiveTask subtask1 = new MyRecursiveTask(this.workLoad / 2); 

        MyRecursiveTask subtask2 = new MyRecursiveTask(this.workLoad / 2); 

 

        subtasks.add(subtask1); 

        subtasks.add(subtask2); 

 

        return subtasks; 

    } 

}

除了有一个结果返回以外,这个示例和RecursiveAction 的例子很像。MyRecursiveTask 类继承自 RecursiveTask,这也就意味着它将返回一个 Long 类型的结果。

MyRecursiveTask 示例也会将工做分割为子任务,并经过 fork() 方法对这些子任务计划执行。此外,本示例还经过调用每一个子任务的 join() 方法收集它们返回的结果。子任务的结果随后被合并到一个更大的结果,并最终将其返回。对于不一样级别的递归,这种子任务的结果合并可能会发生递归。

你能够这样规划一个RecursiveTask:

//建立了一个并行级别为 4 的 ForkJoinPool

ForkJoinPool forkJoinPool = new ForkJoinPool(4);

//建立一个有返回值的任务

MyRecursiveTask myRecursiveTask = new MyRecursiveTask(128);

//线程池执行并返回结果

long mergedResult = forkJoinPool.invoke(myRecursiveTask);

 

System.out.println("mergedResult = " + mergedResult);

 

注意: ForkJoinPool.invoke() 方法的调用来获取最终执行结果的。

[if !supportLists]B. [endif]并发队列-阻塞队列

经常使用的并发队列有阻塞队列和非阻塞队列,前者使用锁实现,后者则使用CAS非阻塞算法实现。

PS:至于非阻塞队列是靠CAS非阻塞算法,在这里再也不介绍,你们只用知道,Java非阻塞队列是使用CAS算法来实现的就能够。感兴趣的童鞋能够维基网上自行学习.

下面咱们先介绍阻塞队列。

阻塞队列:

阻塞队列(BlockingQueue)是Java util.concurrent包下重要的数据结构,BlockingQueue提供了线程安全的队列访问方式:当阻塞队列进行插入数据时,若是队列已满,线程将会阻塞等待直到队列非满;从阻塞队列取数据时,若是队列已空,线程将会阻塞等待直到队列非空。并发包下不少高级同步类的实现都是基于BlockingQueue实现的。

[if !supportLists]Ø [endif]BlockingQueue阻塞队列

BlockingQueue 一般用于一个线程生产对象,而另一个线程消费这些对象的场景。下图是对这个原理的阐述:

 

 

 

 

 

 

 

 

一个线程往里边放,另一个线程从里边取的一个BlockingQueue。

一个线程将会持续生产新对象并将其插入到队列之中,直到队列达到它所能容纳的临界点。也就是说,它是有限的。若是该阻塞队列到达了其临界点,负责生产的线程将会在往里边插入新对象时发生阻塞。它会一直处于阻塞之中,直到负责消费的线程从队列中拿走一个对象。负责消费的线程将会一直从该阻塞队列中拿出对象。若是消费线程尝试去从一个空的队列中提取对象的话,这个消费线程将会处于阻塞之中,直到一个生产线程把一个对象丢进队列。

BlockingQueue的方法:

BlockingQueue 具备 4 组不一样的方法用于插入、移除以及对队列中的元素进行检查。若是请求的操做不能获得当即执行的话,每一个方法的表现也不一样。这些方法以下:

阻塞队列提供了四种处理方法:

方法\处理方式抛出异常返回特殊值一直阻塞超时退出

插入方法add(e)offer(e)put(e)offer(e,time,unit)

移除方法remove()poll()take()poll(time,unit)

检查方法element()peek()不可用不可用

四组不一样的行为方式解释:

抛异常:若是试图的操做没法当即执行,抛一个异常。

特定值:若是试图的操做没法当即执行,返回一个特定的值(经常是 true / false)。

阻塞:若是试图的操做没法当即执行,该方法调用将会发生阻塞,直到可以执行。

超时:若是试图的操做没法当即执行,该方法调用将会发生阻塞,直到可以执行,但等待时间不会超过给定值。返回一个特定值以告知该操做是否成功(典型的是true / false)。

没法向一个BlockingQueue 中插入 null。若是你试图插入 null,BlockingQueue 将会抛出一个 NullPointerException.

BlockingQueue的实现类:

BlockingQueue 是个接口,你须要使用它的实现之一来使用BlockingQueue,Java.util.concurrent包下具备如下 BlockingQueue 接口的实现类:

[if !supportLists]l [endif]ArrayBlockingQueue:ArrayBlockingQueue 是一个有界的阻塞队列,其内部实现是将对象放到一个数组里。有界也就意味着,它不可以存储无限多数量的元素。它有一个同一时间可以存储元素数量的上限。你能够在对其初始化的时候设定这个上限,但以后就没法对这个上限进行修改了(译者注:由于它是基于数组实现的,也就具备数组的特性:一旦初始化,大小就没法修改)。

[if !supportLists]l [endif]DelayQueue:DelayQueue 对元素进行持有直到一个特定的延迟到期。注入其中的元素必须实现 java.util.concurrent.Delayed 接口。

[if !supportLists]l [endif]LinkedBlockingQueue:LinkedBlockingQueue 内部以一个链式结构(连接节点)对其元素进行存储。若是须要的话,这一链式结构能够选择一个上限。若是没有定义上限,将使用 Integer.MAX_VALUE 做为上限。

[if !supportLists]l [endif]PriorityBlockingQueue:PriorityBlockingQueue 是一个无界的并发队列。它使用了和类 java.util.PriorityQueue 同样的排序规则。你没法向这个队列中插入 null 值。全部插入到 PriorityBlockingQueue 的元素必须实现 java.lang.Comparable 接口。所以该队列中元素的排序就取决于你本身的 Comparable 实现。

[if !supportLists]l [endif]SynchronousQueue:SynchronousQueue 是一个特殊的队列,它的内部同时只可以容纳单个元素。若是该队列已有一元素的话,试图向队列中插入一个新元素的线程将会阻塞,直到另外一个线程将该元素从队列中抽走。一样,若是该队列为空,试图向队列中抽取一个元素的线程将会阻塞,直到另外一个线程向队列中插入了一条新的元素。据此,把这个类称做一个队列显然是夸大其词了。它更多像是一个汇合点。

[if !supportLists]Ø [endif]ArrayBlockingQueue阻塞队列

ArrayBlockingQueue类图

 

 

 

如上图ArrayBlockingQueue内部有个数组items用来存放队列元素,putindex下标标示入队元素下标,takeIndex是出队下标,count统计队列元素个数,从定义可知道并无使用volatile修饰,这是由于访问这些变量使用都是在锁块内,并不存在可见性问题。另外有个独占锁lock用来对出入队操做加锁,这致使同时只有一个线程能够访问入队出队,另外notEmpty,notFull条件变量用来进行出入队的同步。

另外构造函数必须传入队列大小参数,因此为有界队列,默认是Lock为非公平锁。

public ArrayBlockingQueue(int capacity) {

        this(capacity, false);

  }

 

public ArrayBlockingQueue(int capacity, boolean fair) {

        if (capacity <= 0)

            throw new IllegalArgumentException();

        this.items = new Object[capacity];

        lock = new ReentrantLock(fair);

        notEmpty = lock.newCondition();

        notFull =  lock.newCondition();

}

ps:

所谓公平锁:就是在并发环境中,每一个线程在获取锁时会先查看此锁维护的等待队列,若是为空,或者当前线程线程是等待队列的第一个,就占有锁,不然就会加入到等待队列中,之后会按照FIFO的规则从队列中取到本身。

非公平锁:比较粗鲁,上来就直接尝试占有锁,若是尝试失败,就再采用相似公平锁那种方式

ArrayBlockingQueue方法

[if !supportLists]ü [endif]offer方法

在队尾插入元素,若是队列满则返回false,否者入队返回true。

public boolean offer(E e) {

 

//e为null,则抛出NullPointerException异常

    checkNotNull(e);

 

//获取独占锁

    final ReentrantLock lock = this.lock;

    lock.lock();

    try {

//若是队列满则返回false

        if (count == items.length)

            return false;

        else {

//否者插入元素

            insert(e);

            return true;

        }

    } finally {

//释放锁

        lock.unlock();

    }

}

 

 

private void insert(E x) {

 

//元素入队

    items[putIndex] = x;

 

//计算下一个元素应该存放的下标

    putIndex = inc(putIndex);

    ++count;

    notEmpty.signal();

}

 

//循环队列,计算下标

final int inc(int i) {

    return (++i == items.length) ? 0 : i;

}

这里因为在操做共享变量前加了锁,因此不存在内存不可见问题,加过锁后获取的共享变量都是从主内存获取的,而不是在CPU缓存或者寄存器里面的值,释放锁后修改的共享变量值会刷新会主内存中。

另外这个队列是使用循环数组实现,因此计算下一个元素存放下标时候有些特殊。另外insert后调用 notEmpty.signal();是为了激活调用notEmpty.await()阻塞后放入notEmpty条件队列中的线程。

[if !supportLists]ü [endif]Put操做

在队列尾部添加元素,若是队列满则等待队列有空位置插入后返回。

public void put(E e) throws InterruptedException {

    checkNotNull(e);

    final ReentrantLock lock = this.lock;

 

//获取可被中断锁

    lock.lockInterruptibly();

    try {

 

//若是队列满,则把当前线程放入notFull管理的条件队列

        while (count == items.length)

            notFull.await();

 

//插入元素

        insert(e);

    } finally {

        lock.unlock();

    }

}

须要注意的是若是队列满了那么当前线程会阻塞,知道出队操做调用了notFull.signal方法激活该线程。代码逻辑很简单,可是这里须要思考一个问题为啥调用lockInterruptibly方法而不是Lock方法。个人理解是由于调用了条件变量的await()方法,而await()方法会在中断标志设置后抛出InterruptedException异常后退出,因此还不如在加锁时候先看中断标志是否是被设置了,若是设置了直接抛出InterruptedException异常,就不用再去获取锁了。而后看了其余并发类里面凡是调用了await的方法获取锁时候都是使用的lockInterruptibly方法而不是Lock也验证了这个想法。

[if !supportLists]ü [endif]Poll操做

从队头获取并移除元素,队列为空,则返回null。

public E poll() {

    final ReentrantLock lock = this.lock;

    lock.lock();

    try {

//当前队列为空则返回null,否者

        return (count == 0) ? null : extract();

    } finally {

        lock.unlock();

    }

}

 

private E extract() {

    final Object[] items = this.items;

 

//获取元素值

    E x = this.cast(items[takeIndex]);

 

//数组中值值为null;

    items[takeIndex] = null;

 

//队头指针计算,队列元素个数减一

    takeIndex = inc(takeIndex);

    --count;

 

//发送信号激活notFull条件队列里面的线程

    notFull.signal();

    return x;

}

[if !supportLists]ü [endif]Take操做

从队头获取元素,若是队列为空则阻塞直到队列有元素。

public E take() throws InterruptedException {

    final ReentrantLock lock = this.lock;

    lock.lockInterruptibly();

    try {

 

//队列为空,则等待,直到队列有元素

        while (count == 0)

            notEmpty.await();

        return extract();

    } finally {

        lock.unlock();

    }

}

须要注意的是若是队列为空,当前线程会被挂起放到notEmpty的条件队列里面,直到入队操做执行调用notEmpty.signal后当前线程才会被激活,await才会返回。

[if !supportLists]ü [endif]Peek操做

返回队列头元素但不移除该元素,队列为空,返回null。

public E peek() {

    final ReentrantLock lock = this.lock;

    lock.lock();

    try {

//队列为空返回null,否者返回头元素

        return (count == 0) ? null : itemAt(takeIndex);

    } finally {

        lock.unlock();

    }

}

 

final E itemAt(int i) {

    return this.cast(items[i]);

}

[if !supportLists]ü [endif]Size操做

获取队列元素个数,很是精确由于计算size时候加了独占锁,其余线程不能入队或者出队或者删除元素。

public int size() {

    final ReentrantLock lock = this.lock;

    lock.lock();

    try {

        return count;

    } finally {

        lock.unlock();

    }

}

[if !supportLists]ü [endif]ArrayBlockingQueue小结

ArrayBlockingQueue经过使用全局独占锁实现同时只能有一个线程进行入队或者出队操做,这个锁的粒度比较大,有点相似在方法上添加synchronized的意味。其中offer,poll操做经过简单的加锁进行入队出队操做,而put,take则使用了条件变量实现若是队列满则等待,若是队列空则等待,而后分别在出队和入队操做中发送信号激活等待线程实现同步。另外相比LinkedBlockingQueue,ArrayBlockingQueue的size操做的结果是精确的,由于计算前加了全局锁。

ArrayBlockingQueue示例

需求:在多线程操做下,一个数组中最多只能存入3个元素。多放入不能够存入数组,或等待某线程对数组中某个元素取走才能放入,要求使用java的多线程来实现。(面试)

代码实现:

public class BlockingQueueTest {

public static void main(String[] args) {

final BlockingQueue queue = new ArrayBlockingQueue(3);

for(int i=0;i<2;i++){

new Thread(){

public void run(){

while(true){

try {

Thread.sleep((long)(Math.random()*1000));

System.out.println(Thread.currentThread().getName() + "准备放数据!");

queue.put(1);

System.out.println(Thread.currentThread().getName() + "已经放了数据," +

"队列目前有" + queue.size() + "个数据");

} catch (InterruptedException e) {

e.printStackTrace();

}

 

}

}

}.start();

}

new Thread(){

public void run(){

while(true){

try {

//将此处的睡眠时间分别改成100和1000,观察运行结果

Thread.sleep(100);

System.out.println(Thread.currentThread().getName() + "准备取数据!");

System.err.println(queue.take());

System.out.println(Thread.currentThread().getName() + "已经取走数据," +

"队列目前有" + queue.size() + "个数据");

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

}.start();

}

}

 

输出结果:

Thread-0准备放数据!

Thread-0已经放了数据,队列目前有1个数据

Thread-0准备放数据!

Thread-0已经放了数据,队列目前有2个数据

Thread-1准备放数据!

Thread-1已经放了数据,队列目前有3个数据

Thread-2准备取数据!

Thread-2已经取走数据,队列目前有3个数据

Thread-0准备放数据!

Thread-1准备放数据!

Thread-2准备取数据!

Thread-2已经取走数据,队列目前有3个数据

…………………

[if !supportLists]Ø [endif]LinkedBlockingQueue阻塞队列

LinkedBlockingQueue类图

LinkedBlockingQueue中也有两个Node分别用来存放首尾节点,而且里面有个初始值为0的原子变量count用来记录队列元素个数,另外里面有两个ReentrantLock的独占锁,分别用来控制元素入队和出队加锁,其中takeLock用来控制同时只有一个线程能够从队列获取元素,其余线程必须等待,putLock控制同时只能有一个线程能够获取锁去添加元素,其余线程必须等待。另外notEmpty和notFull用来实现入队和出队的同步。 另外因为出入队是两个非公平独占锁,因此能够同时又一个线程入队和一个线程出队,其实这个是个生产者-消费者模型,以下类图:

 

 

/**经过take取出进行加锁、取出 */

private final ReentrantLock takeLock = new ReentrantLock();

 

/**等待中的队列等待取出 */

private final Condition notEmpty = takeLock.newCondition();

 

/*经过put放置进行加锁、放置*/

private final ReentrantLock putLock = new ReentrantLock();

 

/**等待中的队列等待放置 */

private final Condition notFull = putLock.newCondition();

 

/*记录集合中的个数(计数器) */

private final AtomicInteger count = new AtomicInteger(0);

队列的容量:

//队列初始容量,Integer最大值

public static final int   MAX_VALUE = 0x7fffffff;

 

public LinkedBlockingQueue() {

    this(Integer.MAX_VALUE);

}

 

  public LinkedBlockingQueue(int capacity) {

    if (capacity <= 0) throw new IllegalArgumentException();

    this.capacity = capacity;

//初始化首尾节点

    last = head = new Node(null);

}

如图默认队列容量为0x7fffffff;用户也能够本身指定容量。

LinkedBlockingQueue方法

ps:下面介绍LinkedBlockingQueue用到不少Lock对象。详细能够查找Lock对象的介绍

[if !supportLists]ü [endif]带时间的Offer操做-生产者

在ArrayBlockingQueue中已经简单介绍了Offer()方法,LinkedBlocking的Offer方法相似,在此就不过多去介绍。此次咱们从介绍下带时间的Offer方法

public boolean offer(E e, long timeout, TimeUnit unit)

    throws InterruptedException {

 

//空元素抛空指针异常

    if (e == null) throw new NullPointerException();

    long nanos = unit.toNanos(timeout);

    int c = -1;

    final ReentrantLock putLock = this.putLock;

    final AtomicInteger count = this.count;

 

//获取可被中断锁,只有一个线程克获取

    putLock.lockInterruptibly();

    try {

 

//若是队列满则进入循环

        while (count.get() == capacity) {

//nanos<=0直接返回

            if (nanos <= 0)

                return false;

//否者调用await进行等待,超时则返回<=0(1)

            nanos = notFull.awaitNanos(nanos);

        }

//await在超时时间内返回则添加元素(2)

        enqueue(new Node(e));

        c = count.getAndIncrement();

 

//队列不满则激活其余等待入队线程(3)

        if (c + 1 < capacity)

            notFull.signal();

    } finally {

//释放锁

        putLock.unlock();

    }

 

//c==0说明队列里面有一个元素,这时候唤醒出队线程(4)

    if (c == 0)

        signalNotEmpty();

    return true;

}

 

private void enqueue(Node node) {  

    last = last.next = node;

}

 

    private void signalNotEmpty() {

        final ReentrantLock takeLock = this.takeLock;

        takeLock.lock();

        try {

            notEmpty.signal();

        } finally {

            takeLock.unlock();

        }

    }

[if !supportLists]ü [endif]带时间的poll操做-消费者

获取并移除队首元素,在指定的时间内去轮询队列看有没有首元素有则返回,否者超时后返回null。

public E poll(long timeout, TimeUnit unit) throws InterruptedException {

    E x = null;

    int c = -1;

    long nanos = unit.toNanos(timeout);

    final AtomicInteger count = this.count;

    final ReentrantLock takeLock = this.takeLock;

 

//出队线程获取独占锁

    takeLock.lockInterruptibly();

    try {

 

//循环直到队列不为空

        while (count.get() == 0) {

 

//超时直接返回null

            if (nanos <= 0)

                return null;

            nanos = notEmpty.awaitNanos(nanos);

        }

 

//出队,计数器减一

        x = dequeue();

        c = count.getAndDecrement();

 

//若是出队前队列不为空则发送信号,激活其余阻塞的出队线程

        if (c > 1)

            notEmpty.signal();

    } finally {

//释放锁

        takeLock.unlock();

    }

 

//当前队列容量为最大值-1则激活入队线程。

    if (c == capacity)

        signalNotFull();

    return x;

}

首先获取独占锁,而后进入循环当当前队列有元素才会退出循环,或者超时了,直接返回null。

超时前退出循环后,就从队列移除元素,而后计数器减去一,若是减去1前队列元素大于1则说明当前移除后队列还有元素,那么就发信号激活其余可能阻塞到当前条件信号的线程。

最后若是减去1前队列元素个数=最大值,那么移除一个后会腾出一个空间来,这时候能够激活可能存在的入队阻塞线程。

[if !supportLists]ü [endif]put操做-生产者

与带超时时间的poll相似不一样在于put时候若是当前队列满了它会一直等待其余线程调用notFull.signal才会被唤醒。

[if !supportLists]ü [endif]take操做-消费者

与带超时时间的poll相似不一样在于take时候若是当前队列空了它会一直等待其余线程调用notEmpty.signal()才会被唤醒。

[if !supportLists]ü [endif]size操做-消费者

当前队列元素个数,如代码直接使用原子变量count获取。

public int size() {

    return count.get();

}

[if !supportLists]ü [endif]peek操做

获取可是不移除当前队列的头元素,没有则返回null。

public E peek() {

//队列空,则返回null

        if (count.get() == 0)

            return null;

        final ReentrantLock takeLock = this.takeLock;

        takeLock.lock();

        try {

            Node first = head.next;

            if (first == null)

                return null;

            else

                return first.item;

        } finally {

            takeLock.unlock();

        }

}

[if !supportLists]ü [endif]remove操做

删除队列里面的一个元素,有则删除返回true,没有则返回false,在删除操做时候因为要遍历队列因此加了双重锁,也就是在删除过程当中不容许入队也不容许出队操做。

public boolean remove(Object o) {

    if (o == null) return false;

 

//双重加锁

    fullyLock();

    try {

 

//遍历队列找则删除返回true

        for (Node trail = head, p = trail.next;

             p != null;

             trail = p, p = p.next) {

            if (o.equals(p.item)) {

                unlink(p, trail);

                return true;

            }

        }

//找不到返回false

        return false;

    } finally {

//解锁

        fullyUnlock();

    }

}

 

void fullyLock() {

    putLock.lock();

    takeLock.lock();

}

 

void fullyUnlock() {

    takeLock.unlock();

    putLock.unlock();

}

 

void unlink(Node p, Node trail) {

 

    p.item = null;

    trail.next = p.next;

    if (last == p)

        last = trail;

//若是当前队列满,删除后,也不忘记最快的唤醒等待的线程

    if (count.getAndDecrement() == capacity)

        notFull.signal();

}

 

[if !supportLists]ü [endif]开源框架的使用

tomcat中任务队列TaskQueue。

类结构图:

 

 

 

 

 

 

 

 

 

可知TaskQueue继承了LinkedBlockingQueue而且泛化类型固定了为Runnalbe.重写了offer,poll,take方法。

tomcat中有个线程池ThreadPoolExecutor,在NIOEndPoint中当acceptor线程接受到请求后,会把任务放入队列,而后poller 线程从队列里面获取任务,而后就把任务放入线程池执行。这个ThreadPoolExecutor中的的一个参数就是TaskQueue。

先看看ThreadPoolExecutor的参数若是是普通LinkedBlockingQueue是怎么样的执行逻辑: 当调用线程池方法execute() 方法添加一个任务时:

[if !supportLists]l [endif]若是当前运行的线程数量小于corePoolSize,则建立新线程运行该任务

[if !supportLists]l [endif]若是当前运行的线程数量大于或等于corePoolSize,则将这个任务放入阻塞队列。

[if !supportLists]l [endif]若是当前队列满了,而且当前运行的线程数量小于maximumPoolSize,则建立新线程运行该任务;

[if !supportLists]l [endif]若是当前队列满了,而且当前运行的线程数量大于或等于maximumPoolSize,那么线程池将会抛出RejectedExecutionException异常。

若是线程执行完了当前任务,那么会去队列里面获取一个任务来执行,若是任务执行完了,而且当前线程数大于corePoolSize,那么会根据线程空闲时间keepAliveTime回收一些线程保持线程池corePoolSize个线程。

首先看下线程池中exectue添加任务时候的逻辑:

public void execute(Runnable command) {

    if (command == null)

        throw new NullPointerException();

 

//当前工做线程个数小于core个数则开新线程执行(1)

    int c = ctl.get();

    if (workerCountOf(c) < corePoolSize) {

        if (addWorker(command, true))

            return;

        c = ctl.get();

    }

//放入队列(2)

    if (isRunning(c) && workQueue.offer(command)) {

        int recheck = ctl.get();

        if (! isRunning(recheck) && remove(command))

            reject(command);

        else if (workerCountOf(recheck) == 0)

            addWorker(null, false);

    }

 

//若是队列满了则开新线程,可是个数要不超过最大值,超过则返回false

//而后执行reject handler(3)

    else if (!addWorker(command, false))

        reject(command);

}

可知当当前工做线程个数为corePoolSize后,若是在来任务会把任务添加到队列,队列满了或者入队失败了则开启新线程。

而后看看TaskQueue中重写的offer方法的逻辑:

public boolean offer(Runnable o) {

//若是parent为null则直接调用父类方法

    if (parent==null) return super.offer(o);

//若是当前线程池中线程个数达到最大,则无条件调用父类方法

    if (parent.getPoolSize() == parent.getMaximumPoolSize()) return super.offer(o);

//若是当前提交的任务小于当前线程池线程数,说明线程用不完,不必从新开线程

    if (parent.getSubmittedCount()<(parent.getPoolSize())) return super.offer(o);

//若是当前线程池线程个数>core个数可是小于最大个数,则开新线程代替放入队列

    if (parent.getPoolSize()

//到了这里,无条件调用父类

    return super.offer(o);

}

可知parent.getPoolSize()

LinkedBlockingQueue安全分析总结

仔细思考下阻塞队列是如何实现并发安全的维护队列链表的,先分析下简单的状况就是当队列里面有多个元素时候,因为同时只有一个线程(经过独占锁putLock实现)入队元素而且是操做last节点(,而同时只有一个出队线程(经过独占锁takeLock实现)操做head节点,因此不存在并发安全问题。

 

 

 

 

 

考虑当队列为空的时候队列状态为:

 

 

 

 

 

这时候假如一个线程调用了take方法,因为队列为空,因此count.get()==0因此当前线程会调用notEmpty.await()把本身挂起,而且放入notEmpty的条件队列,而且释放当前条件变量关联的经过takeLock.lockInterruptibly()获取的独占锁。因为释放了锁,因此这时候其余线程调用take时候就会经过takeLock.lockInterruptibly()获取独占锁,而后一样阻塞到notEmpty.await(),一样会被放入notEmpty的条件队列,也就说在队列为空的状况下可能会有多个线程由于调用take被放入了notEmpty的条件队列。

这时候若是有一个线程调用了put方法,那么就会调用enqueue操做,该操做会在last节点后面添加新元素而且设置last为新节点。而后count.getAndIncrement()先获取当前队列元个数为0保存到c,而后自增count为1,因为c==0因此调用signalNotEmpty激活notEmpty的条件队列里面的阻塞时间最长的线程,这时候take中调用notEmpty.await()的线程会被激活await内部会从新去获取独占锁获取成功则返回,否者被放入AQS的阻塞队列,若是获取成功,那么count.get() >0由于可能多个线程put了,因此调用dequeue从队列获取元素(这时候必定能够获取到),而后调用c = count.getAndDecrement() 把当前计数返回后并减去1,若是c>1 说明当前队列还有其余元素,那么就调用 notEmpty.signal()去激活 notEmpty的条件队列里面的其余阻塞线程。

考虑当队列满的时候:

当队列满的时候调用put方法时候,会因为notFull.await()当前线程被阻塞放入notFull管理的条件队列里面,同理可能会有多个调用put方法的线程都放到了notFull的条件队列里面。

这时候若是有一个线程调用了take方法,调用dequeue()出队一个元素,c = count.getAndDecrement();count值减一;c==capacity;如今队列有一个空的位置,因此调用signalNotFull()激活notFull条件队列里面等待最久的一个线程。

LinkedBlockingQueue简单示例

并发库中的BlockingQueue是一个比较好玩的类,顾名思义,就是阻塞队列。该类主要提供了两个方法put()和take(),前者将一个对象放到队列中,若是队列已经满了,就等待直到有空闲节点;后者从head取一个对象,若是没有对象,就等待直到有可取的对象。

下面的例子比较简单,一个读线程,用于将要处理的文件对象添加到阻塞队列中,另外四个写线程用于取出文件对象,为了模拟写操做耗时长的特色,特让线程睡眠一段随机长度的时间。另外,该Demo也使用到了线程池和原子整型 (AtomicInteger),AtomicInteger能够在并发状况下达到原子化更新,避免使用了synchronized,并且性能很是高。由 于阻塞队列的put和take操做会阻塞,为了使线程退出,特在队列中添加了一个“标识”,算法中也叫“哨兵”,当发现这个哨兵后,写线程就退出。

固然线程池也要显式退出了。

package concurrent;    import java.io.File;    import java.io.FileFilter;    import java.util.concurrent.BlockingQueue;    import java.util.concurrent.ExecutorService;    import java.util.concurrent.Executors;    import java.util.concurrent.LinkedBlockingQueue;    import java.util.concurrent.atomic.AtomicInteger;    public class TestBlockingQueue {      static long randomTime() {         return (long) (Math.random() * 1000);      }     public static void main(String[] args) {       //能容纳100个文件       final BlockingQueue queue = new LinkedBlockingQueue(100);      //线程池       final ExecutorService exec = Executors.newFixedThreadPool(5);       final File root = new File("F:\\JavaLib");       //完成标志       final File exitFile = new File("");       //读个数       final AtomicInteger rc = new AtomicInteger();       //写个数       final AtomicInteger wc = new AtomicInteger();       //读线程       Runnable read = new Runnable() {         public void run() {           scanFile(root);           scanFile(exitFile);         }       public void scanFile(File file) {        if (file.isDirectory()) {          File[] files = file.listFiles(new FileFilter() {            public boolean accept(File pathname) {              return pathname.isDirectory()                  || pathname.getPath().endsWith(".java");            }          });          for (File one : files)            scanFile(one);        } else {          try {            int index = rc.incrementAndGet();            System.out.println("Read0: " + index + " "                + file.getPath());            queue.put(file);          } catch (InterruptedException e) {          }        }      }    };    exec.submit(read);//四个写线程    for (int index = 0; index < 4; index++) {      // write thread      final int NO = index;      Runnable write = new Runnable() {        String threadName = "Write" + NO;        public void run() {          while (true) {            try {              Thread.sleep(randomTime());              int index = wc.incrementAndGet();              File file = queue.take();//队列已经无对象              if (file == exitFile) {//再次添加"标志",以让其余线程正常退出                queue.put(exitFile);                break;              }              System.out.println(threadName + ": " + index + " "                  + file.getPath());            } catch (InterruptedException e) {            }          }        }      };      exec.submit(write);    }    exec.shutdown();  }}

 

[if !supportLists]Ø [endif]PriorityBlockingQueue无界阻塞优先级队列

PriorityBlockingQueue是带优先级的无界阻塞队列,每次出队都返回优先级最高的元素,是二叉树最小堆的实现,研究过数组方式存放最小堆节点的都知道,直接遍历队列元素是无序的。

PriorityBlockingQueue类图结构

 

 

 

如图PriorityBlockingQueue内部有个数组queue用来存放队列元素,size用来存放队列元素个数,allocationSpinLockOffset是用来在扩容队列时候作cas的,目的是保证只有一个线程能够进行扩容。

因为这是一个优先级队列因此有个比较器comparator用来比较元素大小。lock独占锁对象用来控制同时只能有一个线程能够进行入队出队操做。notEmpty条件变量用来实现take方法阻塞模式。这里没有notFull 条件变量是由于这里的put操做是非阻塞的,为啥要设计为非阻塞的是由于这是无界队列。最后PriorityQueue q用来搞序列化的。

以下构造函数,默认队列容量为11,默认比较器为null;

private static final int DEFAULT_INITIAL_CAPACITY = 11;

 

public PriorityBlockingQueue() {

   this(DEFAULT_INITIAL_CAPACITY, null);

}

 

public PriorityBlockingQueue(int initialCapacity) {

    this(initialCapacity, null);

}

 

public PriorityBlockingQueue(int initialCapacity,

                                 Comparator comparator) {

        if (initialCapacity < 1)

            throw new IllegalArgumentException();

        this.lock = new ReentrantLock();

        this.notEmpty = lock.newCondition();

        this.comparator = comparator;

        this.queue = new Object[initialCapacity];

}

 

PriorityBlockingQueue方法

[if !supportLists]ü [endif]Offer操做

在队列插入一个元素,因为是无界队列,因此一直为成功返回true;

public boolean offer(E e) {

 

    if (e == null)

        throw new NullPointerException();

    final ReentrantLock lock = this.lock;

    lock.lock();

    int n, cap;

    Object[] array;

 

//若是当前元素个数>=队列容量,则扩容(1)

    while ((n = size) >= (cap = (array = queue).length))

        tryGrow(array, cap);

 

 

    try {

        Comparator cmp = comparator;

 

//默认比较器为null

        if (cmp == null)(2)

            siftUpComparable(n, e, array);

        else

//自定义比较器(3)

            siftUpUsingComparator(n, e, array, cmp);

 

//队列元素增长1,而且激活notEmpty的条件队列里面的一个阻塞线程

size = n + 1;(9)

        notEmpty.signal();

    } finally {

        lock.unlock();

    }

    return true;

}

主流程比较简单,下面看看两个主要函数

private void tryGrow(Object[] array, int oldCap) {

    lock.unlock(); //must release and then re-acquire main lock

    Object[] newArray = null;

 

//cas成功则扩容(4)

    if (allocationSpinLock == 0 &&

        UNSAFE.compareAndSwapInt(this, allocationSpinLockOffset,

                                 0, 1)) {

        try {

//oldGap<64则扩容新增oldcap+2,否者扩容50%,而且最大为MAX_ARRAY_SIZE

            int newCap = oldCap + ((oldCap < 64) ?

                                   (oldCap + 2) : // grow faster if small

                                   (oldCap >> 1));

            if (newCap - MAX_ARRAY_SIZE > 0) {    // possible overflow

                int minCap = oldCap + 1;

                if (minCap < 0 || minCap > MAX_ARRAY_SIZE)

                    throw new OutOfMemoryError();

                newCap = MAX_ARRAY_SIZE;

            }

            if (newCap > oldCap && queue == array)

                newArray = new Object[newCap];

        } finally {

            allocationSpinLock = 0;

        }

    }

 

//第一个线程cas成功后,第二个线程会进入这个地方,而后第二个线程让出cpu,尽可能让第一个线程执行下面点获取锁,可是这得不到确定的保证。(5)

    if (newArray == null) // back off if another thread is allocating

        Thread.yield();

    lock.lock();(6)

    if (newArray != null && queue == array) {

        queue = newArray;

        System.arraycopy(array, 0, newArray, 0, oldCap);

    }

}

tryGrow目的是扩容,这里要思考下为啥在扩容前要先释放锁,而后使用cas控制只有一个线程能够扩容成功。个人理解是为了性能,由于扩容时候是须要花时间的,若是这些操做时候还占用锁那么其余线程在这个时候是不能进行出队操做的,也不能进行入队操做,这大大下降了并发性。

因此在扩容前释放锁,这容许其余出队线程能够进行出队操做,可是因为释放了锁,因此也容许在扩容时候进行入队操做,这就会致使多个线程进行扩容会出现问题,因此这里使用了一个spinlock用cas控制只有一个线程能够进行扩容,失败的线程调用Thread.yield()让出cpu,目的意在让扩容线程扩容后优先调用lock.lock从新获取锁,可是这得不到必定的保证,有可能调用Thread.yield()的线程先获取了锁。

那copy元素数据到新数组为啥放到获取锁后面那?缘由应该是由于可见性问题,由于queue并无被volatile修饰。另外有可能在扩容时候进行了出队操做,若是直接拷贝可能看到的数组元素不是最新的。而经过调用Lock后,获取的数组则是最新的,而且在释放锁前 数组内容不会变化。

具体建堆算法:

private static void siftUpComparable(int k, T x, Object[] array) {

    Comparable key = (Comparable) x;

 

//队列元素个数>0则判断插入位置,否者直接入队(7)

    while (k > 0) {

        int parent = (k - 1) >>> 1;

        Object e = array[parent];

        if (key.compareTo((T) e) >= 0)

            break;

        array[k] = e;

        k = parent;

    }

    array[k] = key;(8)

}

下面用图说话模拟下过程:假设队列容量为2

[if !supportLists]· [endif]第一次offer(2)时候

 

 

 

 

执行(1)为false因此执行(2),因为k=n=size=0;因此执行(8)元素入队,然执行(9)size+1;如今队列状态:

 

 

[if !supportLists]· [endif]第二次offer(4)时候

执行(1)为false,因此执行(2)因为k=1,因此进入while循环,parent=0;e=2;key=4;key>e因此break;而后把4存到数据下标为1的地方,这时候队列状态为:

 

 

 

 

[if !supportLists]· [endif]第三次offer(4)时候

执行(1)为true,因此调用tryGrow,因为2<64因此newCap=2 + (2+2)=6;而后建立新数组并拷贝,而后调用siftUpComparable;k=2>0进入循环 parent=0;e=2;key=6;key>e因此break;而后把6放入下标为2的地方,如今队列状态:

 

 

 

[if !supportLists]· [endif]第四次offer(1)时候 

执行(1)为false,因此执行(2)因为k=3,因此进入while循环,parent=0;e=2;key=1; key

 

 

 

[if !supportLists]ü [endif]Poll操做

在队列头部获取并移除一个元素,若是队列为空,则返回null

public E poll() {

    final ReentrantLock lock = this.lock;

    lock.lock();

    try {

        return dequeue();

    } finally {

        lock.unlock();

    }

}

主要看dequeue

private E dequeue() {

 

//队列为空,则返回null

    int n = size - 1;

    if (n < 0)

        return null;

    else {

 

 

//获取队头元素(1)

        Object[] array = queue;

        E result = (E) array[0];

 

//获取对尾元素,并值null(2)

        E x = (E) array[n];

        array[n] = null;

 

        Comparator cmp = comparator;

if (cmp == null)//cmp=null则调用这个,把对尾元素位置插入到0位置,而且调整堆为最小堆(3)

            siftDownComparable(0, x, array, n);

        else

            siftDownUsingComparator(0, x, array, n, cmp);

size = n;(4)

        return result;

    }

}

 

private static void siftDownComparable(int k, T x, Object[] array,

                                            int n) {

     if (n > 0) {

         Comparable key = (Comparable)x;

         int half = n >>> 1;           // loop while a non-leaf

         while (k < half) {

             int child = (k << 1) + 1; // assume left child is least

Object c = array[child];(5)

int right = child + 1;(6)

             if (right < n &&

                 ((Comparable) c).compareTo((T) array[right]) > 0)(7)

                 c = array[child = right];

             if (key.compareTo((T) c) <= 0)(8)

                 break;

             array[k] = c;

             k = child;

         }

         array[k] = key;(9)

     }

 }

下面用图说话模拟下过程:

[if !supportLists]· [endif]第一次调用poll()

首先执行(1) result=1;而后执行(2)x=2;这时候队列状态

 

 

 

而后执行(3)后状态为:

 

 

 

执行(4)后的结果:

 

 

 

下面重点说说siftDownComparable这个屌屌的创建最小堆的算法:

首先说下思想,其中k一开始为0,x为数组里面最后一个元素,因为第0个元素为树根,被出队时候要被搞掉,因此建堆要从它的左右孩子节点找一个最小的值来当树根,子树根被搞掉后,会找子树的左右孩子最小的元素来代替,直到树节点为止,还不明白,不要紧,看图说话:假如当前队列元素:

 

 

 

 

那么对于树为:

 

 

这时候若是调用了poll();那么result=2;x=11;如今树为:

 

 

 

而后看leftChildVal = 4;rightChildVal = 6; 4<6;因此c=4;也就是获取根节点的左右孩子值小的那一个; 而后看11>4也就是key>c;而后把c放入树根,如今树为:

 

 

 

 

而后看根的左边孩子4为根的子树咱们要为这个字树找一个根节点。

看leftChildVal = 8;rightChildVal = 10; 8<10;因此c=8;也就是获取根节点的左右孩子值小的那一个; 而后看11>8也就是key>c;而后把c放入树根,如今树为:

 

 

这时候k=3;half=3因此推出循环,执行(9)后结果为:

 

 

 

 

 

这时候队列为:

 

 

[if !supportLists]ü [endif]Put操做

内部调用的offer,因为是无界队列,因此不须要阻塞

public void put(E e) {

    offer(e); // never need to block

}

[if !supportLists]ü [endif]Take操做

获取队列头元素,若是队列为空则阻塞。

public E take() throws InterruptedException {

    final ReentrantLock lock = this.lock;

    lock.lockInterruptibly();

    E result;

    try {

 

//若是队列为空,则阻塞,把当前线程放入notEmpty的条件队列

        while ( (result = dequeue()) == null)

            notEmpty.await();

    } finally {

        lock.unlock();

    }

    return result;

}

这里是阻塞实现,阻塞后直到入队操做调用notEmpty.signal 才会返回。

[if !supportLists]ü [endif]Size操做

获取队列元个数,因为加了独占锁因此返回结果是精确的

public int size() {

    final ReentrantLock lock = this.lock;

    lock.lock();

    try {

        return size;

    } finally {

        lock.unlock();

    }

}

PriorityBlockingQueue小结

PriorityBlockingQueue相似于ArrayBlockingQueue内部使用一个独占锁来控制同时只有一个线程能够进行入队和出队,另外前者只使用了一个notEmpty条件变量而没有notFull这是由于前者是无界队列,当put时候永远不会处于await因此也不须要被唤醒。

PriorityBlockingQueue始终保证出队的元素是优先级最高的元素,而且能够定制优先级的规则,内部经过使用一个二叉树最小堆算法来维护内部数组,这个数组是可扩容的,当当前元素个数>=最大容量时候会经过算法扩容。

值得注意的是为了不在扩容操做时候其余线程不能进行出队操做,实现上使用了先释放锁,而后经过cas保证同时只有一个线程能够扩容成功。

PriorityBlockingQueue示例

PriorityBlockingQueue类是JDK提供的优先级队列 自己是线程安全的 内部使用显示锁 保证线程安全。

PriorityBlockingQueue存储的对象必须是实现Comparable接口的 由于PriorityBlockingQueue队列会根据内部存储的每个元素的compareTo方法比较每一个元素的大小。这样在take出来的时候会根据优先级 将优先级最小的最早取出。

下面是示例代码

public static PriorityBlockingQueue queue = new PriorityBlockingQueue();  

 

public static void main(String[] args) {  

    queue.add(new User(1,"wu"));  

    queue.add(new User(5,"wu5"));  

    queue.add(new User(23,"wu23"));  

    queue.add(new User(55,"wu55"));  

    queue.add(new User(9,"wu9"));  

    queue.add(new User(3,"wu3"));  

    for (User user : queue) {  

        try {  

            System.out.println(queue.take().name);  

        } catch (InterruptedException e) {  

            e.printStackTrace();  

        }  

    }  

}  

 

//静态内部类  

static class User implements Comparable{  

 

    public User(int age,String name) {  

        this.age = age;  

        this.name = name;  

    }  

 

    int age;  

    String name;  

 

    @Override  

    public int compareTo(User o) {  

        return this.age > o.age ? -1 : 1;  

    }  

[if !supportLists]Ø [endif]SychronousQueue同步队列

SynchronousQueue是一个比较特别的队列,因为在线程池方面有所应用,为了更好的理解线程池的实现原理,此队列源码中充斥着大量的CAS语句,理解起来是有些难度的,为了方便往后回顾,本篇文章会以简洁的图形化方式展现该队列底层的实现原理。

SychronousQueue简单实用

经典的生产者-消费者模式,操做流程是这样的:

有多个生产者,能够并发生产产品,把产品置入队列中,若是队列满了,生产者就会阻塞;

有多个消费者,并发从队列中获取产品,若是队列空了,消费者就会阻塞;

以下面的示意图所示:

 

SynchronousQueue 也是一个队列来的,但它的特别之处在于它内部没有容器,一个生产线程,当它生产产品(即put的时候),若是当前没有人想要消费产品(即当前没有线程执行take),今生产线程必须阻塞,等待一个消费线程调用take操做,take操做将会唤醒该生产线程,同时消费线程会获取生产线程的产品(即数据传递),这样的一个过程称为一次配对过程(固然也能够先take后put,原理是同样的)。

咱们用一个简单的代码来验证一下,以下所示:

package com.concurrent;

import java.util.concurrent.SynchronousQueue;

public class SynchronousQueueDemo {

    public static void main(String[] args) throws InterruptedException {

        final SynchronousQueue queue = new SynchronousQueue();

 

        Thread putThread = new Thread(new Runnable() {

            @Override

            public void run() {

                System.out.println("put thread start");

                try {

                    queue.put(1);

                } catch (InterruptedException e) {

                }

                System.out.println("put thread end");

            }

        });

 

        Thread takeThread = new Thread(new Runnable() {

            @Override

            public void run() {

                System.out.println("take thread start");

                try {

                    System.out.println("take from putThread: " + queue.take());

                } catch (InterruptedException e) {

                }

                System.out.println("take thread end");

            }

        });

 

        putThread.start();

        Thread.sleep(1000);

        takeThread.start();

    }

}

一种输出结果以下:

put thread start

take thread start

take from putThread: 1

put thread end

take thread end

从结果能够看出,put线程执行queue.put(1) 后就被阻塞了,只有take线程进行了消费,put线程才能够返回。能够认为这是一种线程与线程间一对一传递消息的模型。

SychronousQueue实现原理

不像ArrayBlockingQueue、LinkedBlockingDeque之类的阻塞队列依赖AQS实现并发操做,SynchronousQueue直接使用CAS实现线程的安全访问。

队列的实现策略一般分为公平模式和非公平模式,接下来将分别进行说明。

公平模式下的模型:

公平模式下,底层实现使用的是TransferQueue这个内部队列,它有一个head和tail指针,用于指向当前正在等待匹配的线程节点。 初始化时,TransferQueue的状态以下:

 

接着咱们进行一些操做:

一、线程put1执行 put(1)操做,因为当前没有配对的消费线程,因此put1线程入队列,自旋一小会后睡眠等待,这时队列状态以下:

 

 

 

二、接着,线程put2执行了put(2)操做,跟前面同样,put2线程入队列,自旋一小会后睡眠等待,这时队列状态以下:

 

 

[if !supportLists]三、[endif]这时候,来了一个线程take1,执行了 take操做,因为tail指向put2线程,put2线程跟take1线程配对了(一put一take),这时take1线程不须要入队,可是请注意了,这时候,要唤醒的线程并非put2,而是put1。

为什么?你们应该知道咱们如今讲的是公平策略,所谓公平就是谁先入队了,谁就优先被唤醒,咱们的例子明显是put1应该优先被唤醒。至于读者可能会有一个疑问,明明是take1线程跟put2线程匹配上了,结果是put1线程被唤醒消费,怎么确保take1线程必定能够和次首节点(head.next)也是匹配的呢?其实你们能够拿个纸画一画,就会发现真的就是这样的。

公平策略总结下来就是:队尾匹配队头出队。

执行后put1线程被唤醒,take1线程的 take()方法返回了1(put1线程的数据),这样就实现了线程间的一对一通讯,这时候内部状态以下:

 

 

 

四、最后,再来一个线程take2,执行take操做,这时候只有put2线程在等候,并且两个线程匹配上了,线程put2被唤醒, take2线程take操做返回了2(线程put2的数据),这时候队列又回到了起点,以下所示:

 

 

以上即是公平模式下,SynchronousQueue的实现模型。总结下来就是:队尾匹配队头出队,先进先出,体现公平原则。

非公平模式下的模型:

咱们仍是使用跟公平模式下同样的操做流程,对比两种策略下有何不一样。非公平模式底层的实现使用的是TransferStack,一个栈,实现中用head指针指向栈顶,接着咱们看看它的实现模型:

一、线程put1执行 put(1)操做,因为当前没有配对的消费线程,因此put1线程入栈,自旋一小会后睡眠等待,这时栈状态以下:

 

[if !supportLists]二、[endif]接着,线程put2再次执行了put(2)操做,跟前面同样,put2线程入栈,自旋一小会后睡眠等待,这时栈状态以下:

 

 

[if !supportLists]三、[endif]这时候,来了一个线程take1,执行了take操做,这时候发现栈顶为put2线程,匹配成功,可是实现会先把take1线程入栈,而后take1线程循环执行匹配put2线程逻辑,一旦发现没有并发冲突,就会把栈顶指针直接指向 put1线程

 

步骤一:

 

 

 

 

 

 

 

步骤二:

[if !supportLists]四、[endif]最后,再来一个线程take2,执行take操做,这跟步骤3的逻辑基本是一致的,take2线程入栈,

而后在循环中匹配put1线程,最终所有匹配完毕,栈变为空,恢复初始状态,以下图所示:

步骤一:

 

步骤二:

 

能够从上面流程看出,虽然put1线程先入栈了,可是倒是后匹配,这就是非公平的由来。

SychronousQueue总结

SynchronousQueue因为其独有的线程一一配对通讯机制,在大部分日常开发中,可能都不太会用到,但线程池技术中会有所使用,因为内部没有使用AQS,而是直接使用CAS,因此代码理解起来会比较困难,但这并不妨碍咱们理解底层的实现模型,在理解了模型的基础上,有兴趣的话再查阅源码,就会有方向感,看起来也会比较容易,但愿本文有所借鉴意义。

[if !supportLists]Ø [endif]DeplayQueue延时无界阻塞队列

在谈到DelayQueue的使用和原理的时候,咱们首先介绍一下DelayQueue,DelayQueue是一个无界阻塞队列,只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的Delayed 元素。

DelayQueue阻塞队列在咱们系统开发中也经常会用到,例如:缓存系统的设计,缓存中的对象,超过了空闲时间,须要从缓存中移出;任务调度系统,可以准确的把握任务的执行时间。咱们可能须要经过线程处理不少时间上要求很严格的数据,若是使用普通的线程,咱们就须要遍历全部的对象,一个一个的检 查看数据是否过时等,首先这样在执行上的效率不会过高,其次就是这种设计的风格也大大的影响了数据的精度。一个须要12:00点执行的任务可能12:01 才执行,这样对数据要求很高的系统有更大的弊端。由此咱们能够使用DelayQueue。

下面将会对DelayQueue作一个介绍,而后举个例子。而且提供一个Delayed接口的实现和Sample代码。DelayQueue是一个BlockingQueue,其特化的参数是Delayed。(不了解BlockingQueue的同窗,先去了解BlockingQueue再看本文)

Delayed扩展了Comparable接口,比较的基准为延时的时间值,Delayed接口的实现类getDelay的返回值应为固定值(final)。DelayQueue内部是使用PriorityQueue实现的。

DelayQueue = BlockingQueue +PriorityQueue + Delayed

DelayQueue定义和原理

DelayQueue的关键元素BlockingQueue、PriorityQueue、Delayed。能够这么说,DelayQueue是一个使用优先队列(PriorityQueue)实现的BlockingQueue,优先队列的比较基准值是时间。

他们的基本定义以下

public interface Comparable {

    public int compareTo(T o);

}

public interface Delayed extends Comparable {

    long getDelay(TimeUnit unit);

}

public class DelayQueue implements BlockingQueue {

    private final PriorityQueue q = new PriorityQueue();

}

DelayQueue内部的实现使用了一个优先队列。当调用DelayQueue的offer方法时,把Delayed对象加入到优先队列q中。以下:

public boolean offer(E e) {

    final ReentrantLock lock = this.lock;

    lock.lock();

    try {

        E first = q.peek();

        q.offer(e);

        if (first == null || e.compareTo(first) < 0)

            available.signalAll();

        return true;

    } finally {

        lock.unlock();

    }

}

DelayQueue的take方法,把优先队列q的first拿出来(peek),若是没有达到延时阀值,则进行await处理。以下:

public E take() throws InterruptedException {

    final ReentrantLock lock = this.lock;

    lock.lockInterruptibly();

    try {

        for (;;) {

            E first = q.peek();

            if (first == null) {

                available.await();

            } else {

                long delay =  first.getDelay(TimeUnit.NANOSECONDS);

                if (delay > 0) {

                    long tl = available.awaitNanos(delay);

                } else {

                    E x = q.poll();

                    assert x != null;

                    if (q.size() != 0)

                        available.signalAll(); // wake up other takers

                    return x;

 

                }

            }

        }

    } finally {

        lock.unlock();

    }

}

DelayQueue实例应用

Ps:为了具备调用行为,存放到DelayDeque的元素必须继承Delayed接口。Delayed接口使对象成为延迟对象,它使存放在DelayQueue类中的对象具备了激活日期。该接口强制执行下列两个方法。

一下将使用Delay作一个缓存的实现。其中共包括三个类

[if !supportLists]n [endif]Pair

[if !supportLists]n [endif]DelayItem

[if !supportLists]n [endif]Cache

Pair类:

public class Pair {

    public K first;

 

    public V second;

 

    public Pair() {}

 

    public Pair(K first, V second) {

        this.first = first;

        this.second = second;

    }

}

一下是对Delay接口的实现:

import java.util.concurrent.Delayed;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.atomic.AtomicLong;

 

public class DelayItem implements Delayed {

    /** Base of nanosecond timings, to avoid wrapping */

    private static final long NANO_ORIGIN = System.nanoTime();

 

    /**

     * Returns nanosecond time offset by origin

     */

    final static long now() {

        return System.nanoTime() - NANO_ORIGIN;

    }

 

    /**

     * Sequence number to break scheduling ties, and in turn to guarantee FIFO order among tied

     * entries.

     */

    private static final AtomicLong sequencer = new AtomicLong(0);

 

    /** Sequence number to break ties FIFO */

    private final long sequenceNumber;

 

    /** The time the task is enabled to execute in nanoTime units */

    private final long time;

 

    private final T item;

 

    public DelayItem(T submit, long timeout) {

        this.time = now() + timeout;

        this.item = submit;

        this.sequenceNumber = sequencer.getAndIncrement();

    }

 

    public T getItem() {

        return this.item;

    }

 

    public long getDelay(TimeUnit unit) {

        long d = unit.convert(time - now(), TimeUnit.NANOSECONDS);

        return d;

    }

 

    public int compareTo(Delayed other) {

        if (other == this) // compare zero ONLY if same object

            return 0;

        if (other instanceof DelayItem) {

            DelayItem x = (DelayItem) other;

            long diff = time - x.time;

            if (diff < 0)

                return -1;

            else if (diff > 0)

                return 1;

            else if (sequenceNumber < x.sequenceNumber)

                return -1;

            else

                return 1;

        }

        long d = (getDelay(TimeUnit.NANOSECONDS) - other.getDelay(TimeUnit.NANOSECONDS));

        return (d == 0) ? 0 : ((d < 0) ? -1 : 1);

    }

}

如下是Cache的实现,包括了put和get方法

import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.ConcurrentMap;

import java.util.concurrent.DelayQueue;

import java.util.concurrent.TimeUnit;

import java.util.logging.Level;

import java.util.logging.Logger;

 

public class Cache {

    private static final Logger LOG = Logger.getLogger(Cache.class.getName());

 

    private ConcurrentMap cacheObjMap = new ConcurrentHashMap();

 

    private DelayQueue>> q = new DelayQueue>>();

 

    private Thread daemonThread;

 

    public Cache() {

 

        Runnable daemonTask = new Runnable() {

            public void run() {

                daemonCheck();

            }

        };

 

        daemonThread = new Thread(daemonTask);

        daemonThread.setDaemon(true);

        daemonThread.setName("Cache Daemon");

        daemonThread.start();

    }

 

    private void daemonCheck() {

 

        if (LOG.isLoggable(Level.INFO))

            LOG.info("cache service started.");

 

        for (;;) {

            try {

                DelayItem> delayItem = q.take();

                if (delayItem != null) {

//超时对象处理

                    Pair pair = delayItem.getItem();

                    cacheObjMap.remove(pair.first, pair.second); // compare and remove

                }

            } catch (InterruptedException e) {

                if (LOG.isLoggable(Level.SEVERE))

                    LOG.log(Level.SEVERE, e.getMessage(), e);

                break;

            }

        }

 

        if (LOG.isLoggable(Level.INFO))

            LOG.info("cache service stopped.");

    }

 

//添加缓存对象

    public void put(K key, V value, long time, TimeUnit unit) {

        V oldValue = cacheObjMap.put(key, value);

        if (oldValue != null)

            q.remove(key);

 

        long nanoTime = TimeUnit.NANOSECONDS.convert(time, unit);

        q.put(new DelayItem>(new Pair(key, value), nanoTime));

    }

 

    public V get(K key) {

        return cacheObjMap.get(key);

    }

测试main方法:

//测试入口函数

    public static void main(String[] args) throws Exception {

        Cache cache = new Cache();

        cache.put(1, "aaaa", 3, TimeUnit.SECONDS);

 

        Thread.sleep(1000 * 2);

        {

            String str = cache.get(1);

            System.out.println(str);

        }

 

        Thread.sleep(1000 * 2);

        {

            String str = cache.get(1);

            System.out.println(str);

        }

    }

 

输出结果为:

aaaa

null

咱们看到上面的结果,若是超过延时的时间,那么缓存中数据就会自动丢失,得到就为null。

[if !supportLists]C. [endif]并发(Collection)队列-非阻塞队列

[if !supportLists]Ø [endif]非阻塞队列

首先咱们要简单的理解下什么是非阻塞队列:

与阻塞队列相反,非阻塞队列的执行并不会被阻塞,不管是消费者的出队,仍是生产者的入队。

在底层,非阻塞队列使用的是CAS(compare andswap)来实现线程执行的非阻塞。

非阻塞队列简单操做

与阻塞队列相同,非阻塞队列中的经常使用方法,也是出队和入队。

入队方法:

[if !supportLists]n [endif]add():底层调用offer();

[if !supportLists]n [endif]offer():Queue接口继承下来的方法,实现队列的入队操做,不会阻碍线程的执行,插入成功返回true;

出队方法:

[if !supportLists]n [endif]poll():移动头结点指针,返回头结点元素,并将头结点元素出队;队列为空,则返回null;

[if !supportLists]n [endif]peek():移动头结点指针,返回头结点元素,并不会将头结点元素出队;队列为空,则返回null;

[if !supportLists]Ø [endif]非阻塞算法CAS

首先咱们须要了解悲观锁乐观锁

悲观锁:假定并发环境是悲观的,若是发生并发冲突,就会破坏一致性,因此要经过独占锁完全禁止冲突发生。有一个经典比喻,“若是你不锁门,那么捣蛋鬼就回闯入并搞得一团糟”,因此“你只能一次打开门放进一我的,才能时刻盯紧他”。乐观锁:假定并发环境是乐观的,即,虽然会有并发冲突,但冲突可发现且不会形成损害,因此,能够不加任何保护,等发现并发冲突后再决定放弃操做仍是重试。可类比的比喻为,“若是你不锁门,那么虽然捣蛋鬼会闯入,但他们一旦打算破坏你就能知道”,因此“你大能够放进全部人,等发现他们想破坏的时候再作决定”。

一般认为乐观锁的性能比悲观所更高,特别是在某些复杂的场景。这主要因为悲观锁在加锁的同时,也会把某些不会形成破坏的操做保护起来;而乐观锁的竞争则只发生在最小的并发冲突处,若是用悲观锁来理解,就是“锁的粒度最小”。但乐观锁的设计每每比较复杂,所以,复杂场景下仍是多用悲观锁。

首先保证正确性,有必要的话,再去追求性能。

CAS

乐观锁的实现每每须要硬件的支持,多数处理器都都实现了一个CAS指令,实现“Compare And Swap”的语义(这里的swap是“换入”,也就是set),构成了基本的乐观锁。

CAS包含3个操做数:

[if !supportLists]n [endif]须要读写的内存位置V

[if !supportLists]n [endif]进行比较的值A

[if !supportLists]n [endif]拟写入的新值B

当且仅当位置V的值等于A时,CAS才会经过原子方式用新值B来更新位置V的值;不然不会执行任何操做。不管位置V的值是否等于A,都将返回V原有的值。

一个有意思的事实是,“使用CAS控制并发”与“使用乐观锁”并不等价。CAS只是一种手段,既能够实现乐观锁,也能够实现悲观锁。乐观、悲观只是一种并发控制的策略。下文将分别用CAS实现悲观锁和乐观锁?

[if !supportLists]Ø [endif]ConcurrentLinkedQueue非阻塞无界链表队列

ConcurrentLinkedQueue是一个线程安全的队列,基于链表结构实现,是一个无界队列,理论上来讲队列的长度能够无限扩大。

与其余队列相同,ConcurrentLinkedQueue也采用的是先进先出(FIFO)入队规则,对元素进行排序。当咱们向队列中添加元素时,新插入的元素会插入到队列的尾部;而当咱们获取一个元素时,它会从队列的头部中取出。

由于ConcurrentLinkedQueue是链表结构,因此当入队时,插入的元素依次向后延伸,造成链表;而出队时,则从链表的第一个元素开始获取,依次递增;

不知道,我这样形容可否让你对链表的入队、出队产生一个大概的思路!

ConcurrentLinkedQuere简单示例

值得注意的是,在使用ConcurrentLinkedQueue时,若是涉及到队列是否为空的判断,切记不可以使用size()==0的作法,由于在size()方法中,是经过遍历整个链表来实现的,在队列元素不少的时候,size()方法十分消耗性能和时间,只是单纯的判断队列为空使用isEmpty()便可!!!

public class ConcurrentLinkedQueueTest {

 

    public static int threadCount = 10;

 

    public static ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();

 

    static class Offer implements Runnable {

        public void run() {

//不建议使用queue.size()==0,影响效率。能够使用!queue.isEmpty()

            if(queue.size()==0){

                String ele = new Random().nextInt(Integer.MAX_VALUE)+"";

                queue.offer(ele);

System.out.println("入队元素为"+ele);

            }

        }

    }

 

    static class Poll implements Runnable {

        public void run() {

            if(!queue.isEmpty()){

                String ele = queue.poll();

System.out.println("出队元素为"+ele);

            }

        }

    }

 

    public static void main(String[] agrs) {

        ExecutorService executorService = Executors.newFixedThreadPool(4);

        for(int x=0;x

            executorService.submit(new Offer());

            executorService.submit(new Poll());

        }

        executorService.shutdown();

    }

}

 

一种输出:

入队元素为313732926

出队元素为313732926

入队元素为812655435

出队元素为812655435

入队元素为1893079357

出队元素为1893079357

入队元素为1137820958

出队元素为1137820958

入队元素为1965962048

出队元素为1965962048

出队元素为685567162

入队元素为685567162

出队元素为1441081163

入队元素为1441081163

出队元素为1627184732

入队元素为1627184732

ConcurrentLinkedQuere类图

 

 

 

 

如图ConcurrentLinkedQueue中有两个volatile类型的Node节点分别用来存在列表的首尾节点,其中head节点存放链表第一个item为null的节点,tail则并非总指向最后一个节点。Node节点内部则维护一个变量item用来存放节点的值,next用来存放下一个节点,从而连接为一个单向无界列表。

public ConcurrentLinkedQueue() {

    head = tail = new Node(null);

}

如上代码初始化时候会构建一个item为NULL的空节点做为链表的首尾节点。ConcurrentLinkedQuere方法

[if !supportLists]ü [endif]Offer操做

offer操做是在链表末尾添加一个元素,下面看看实现原理。

public boolean offer(E e) {

//e为null则抛出空指针异常

    checkNotNull(e);

 

//构造Node节点构造函数内部调用unsafe.putObject,后面统一讲

    final Node newNode = new Node(e);

 

 

//从尾节点插入

    for (Node t = tail, p = t;;) {

 

        Node q = p.next;

 

//若是q=null说明p是尾节点则插入

        if (q == null) {

 

//cas插入(1)

            if (p.casNext(null, newNode)) {

//cas成功说明新增节点已经被放入链表,而后设置当前尾节点(包含head,1,3,5.。。个节点为尾节点)

                if (p != t) // hop two nodes at a time

                    casTail(t, newNode);  // Failure is OK.

                return true;

            }

            // Lost CAS race to another thread; re-read next

        }

        else if (p == q)//(2)

//多线程操做时候,因为poll时候会把老的head变为自引用,而后head的next变为新head,因此这里须要

//从新找新的head,由于新的head后面的节点才是激活的节点

            p = (t != (t = tail)) ? t : head;

        else

//寻找尾节点(3)

            p = (p != t && t != (t = tail)) ? t : q;

    }

}

从构造函数知道一开始有个item为null的哨兵节点,而且head和tail都是指向这个节点,而后当一个线程调用offer时候首先

 

如图首先查找尾节点,q==null,p就是尾节点,因此执行p.casNext经过cas设置p的next为新增节点,这时候p==t因此不从新设置尾节点为当前新节点。因为多线程能够调用offer方法,因此可能两个线程同时执行到了(1)进行cas,那么只有一个会成功(假如线程1成功了),成功后的链表为:

 

失败的线程会循环一次这时候指针为:

 

 

 

这时候会执行(3)因此p=q,而后在循环后指针位置为:

 

因此没有其余线程干扰的状况下会执行(1)执行cas把新增节点插入到尾部,没有干扰的状况下线程2 cas会成功,而后去更新尾节点tail,因为p!=t因此更新。这时候链表和指针为:

 

 

假如线程2cas时候线程3也在执行,那么线程3会失败,循环一次后,线程3的节点状态为:

 

这时候p!=t ;而且t的原始值为told,t的新值为tnew ,因此told!=tnew,因此 p=tnew=tail;

而后在循环一下后节点状态:

 

 

q==null因此执行(1)。

如今就差p==q这个分支尚未走,这个要在执行poll操做后才会出现这个状况。poll后会存在下面的状态

 

 

 

这个时候添加元素时候指针分布为:

 

因此会执行(2)分支 结果 p=head而后循环,循环后指针分布:

 

 

因此执行(1),而后p!=t因此设置tail节点。如今分布图:

 

自引用的节点会被垃圾回收掉。

[if !supportLists]ü [endif]add操做

add操做是在链表末尾添加一个元素,下面看看实现原理。其实内部调用的仍是offer

public boolean add(E e) {

    return offer(e);

}

[if !supportLists]ü [endif]poll操做

poll操做是在链表头部获取而且移除一个元素,下面看看实现原理。

public E poll() {

    restartFromHead:

 

//死循环

    for (;;) {

 

//死循环

        for (Node h = head, p = h, q;;) {

 

//保存当前节点值

            E item = p.item;

 

//当前节点有值则cas变为null(1)

            if (item != null && p.casItem(item, null)) {

//cas成功标志当前节点以及从链表中移除

if (p != h) //相似tail间隔2设置一次头节点(2)

                    updateHead(h, ((q = p.next) != null) ? q : p);

                return item;

            }

//当前队列为空则返回null(3)

            else if ((q = p.next) == null) {

                updateHead(h, p);

                return null;

            }

//自引用了,则从新找新的队列头节点(4)

            else if (p == q)

                continue restartFromHead;

            else//(5)

                p = q;

        }

    }

}

    final void updateHead(Node h, Node p) {

        if (h != p && casHead(h, p))

            h.lazySetNext(h);

    }

当队列为空时候:

 

可知执行(3)这时候有两种状况,第一没有其余线程添加元素时候(3)结果为true而后由于h!=p为false因此直接返回null。第二在执行q=p.next前,其余线程已经添加了一个元素到队列,这时候(3)返回false,而后执行(5)p=q,而后循环后节点分布:

 

 

这时候执行(1)分支,进行cas把当前节点值值为null,同时只有一个线程会成功,cas成功 标示该节点从队列中移除了,而后p!=h,调用updateHead方法,参数为h,p;h!=p因此把p变为当前链表head节点,而后h节点的next指向本身。如今状态为:

 

 

 

cas失败 后 会再次循环,这时候分布图为:

 

 

这时候执行(3)返回null.

如今还有个分支(4)没有执行过,那么何时会执行那?

 

 

这时候执行(1)分支,进行cas把当前节点值值为null,同时只有一个线程A会成功,cas成功 标示该节点从队列中移除了,而后p!=h,调用updateHead方法,假如执行updateHead前另一个线程B开始poll这时候它p指向为原来的head节点,而后当前线程A执行updateHead这时候B线程链表状态为:

 

因此会执行(4)从新跳到外层循环,获取当前head,如今状态为:

 

[if !supportLists]ü [endif]peek操做

peek操做是获取链表头部一个元素(只读取不移除),下面看看实现原理。代码与poll相似,只是少了castItem.而且peek操做会改变head指向,offer后head指向哨兵节点,第一次peek后head会指向第一个真的节点元素。

public E peek() {

    restartFromHead:

    for (;;) {

        for (Node h = head, p = h, q;;) {

            E item = p.item;

            if (item != null || (q = p.next) == null) {

                updateHead(h, p);

                return item;

            }

            else if (p == q)

                continue restartFromHead;

            else

                p = q;

        }

    }

}

[if !supportLists]ü [endif]size操做

获取当前队列元素个数,在并发环境下不是颇有用,由于使用CAS没有加锁因此从调用size函数到返回结果期间有可能增删元素,致使统计的元素个数不精确。

public int size() {

    int count = 0;

    for (Node p = first(); p != null; p = succ(p))

        if (p.item != null)

//最大返回Integer.MAX_VALUE

            if (++count == Integer.MAX_VALUE)

                break;

    return count;

}

 

//获取第一个队列元素(哨兵元素不算),没有则为null

Node first() {

    restartFromHead:

    for (;;) {

        for (Node h = head, p = h, q;;) {

            boolean hasItem = (p.item != null);

            if (hasItem || (q = p.next) == null) {

                updateHead(h, p);

                return hasItem ? p : null;

            }

            else if (p == q)

                continue restartFromHead;

            else

                p = q;

        }

    }

}

 

//获取当前节点的next元素,若是是自引入节点则返回真正头节点

final Node succ(Node p) {

    Node next = p.next;

    return (p == next) ? head : next;

}

[if !supportLists]ü [endif]remove操做

若是队列里面存在该元素则删除给元素,若是存在多个则删除第一个,并返回true,否者返回false

public boolean remove(Object o) {

 

//查找元素为空,直接返回false

    if (o == null) return false;

    Node pred = null;

    for (Node p = first(); p != null; p = succ(p)) {

        E item = p.item;

 

//相等则使用cas值null,同时一个线程成功,失败的线程循环查找队列中其余元素是否有匹配的。

        if (item != null &&

            o.equals(item) &&

            p.casItem(item, null)) {

 

//获取next元素

            Node next = succ(p);

 

//若是有前驱节点,而且next不为空则连接前驱节点到next,

            if (pred != null && next != null)

                pred.casNext(p, next);

            return true;

        }

        pred = p;

    }

    return false;

}

[if !supportLists]ü [endif]contains操做

判断队列里面是否含有指定对象,因为是遍历整个队列,因此相似size 不是那么精确,有可能调用该方法时候元素还在队列里面,可是遍历过程当中才把该元素删除了,那么就会返回false.

public boolean contains(Object o) {

    if (o == null) return false;

    for (Node p = first(); p != null; p = succ(p)) {

        E item = p.item;

        if (item != null && o.equals(item))

            return true;

    }

    return false;

}

ConcurrentLinkedQuere的offer方法有意思的问题

offer中有个 判断 t != (t = tail)假如 t=node1;tail=node2;而且node1!=node2那么这个判断是true仍是false那,答案是true,这个判断是看当前t是否是和tail相等,相等则返回true否者为false,可是不管结果是啥执行后t的值都是tail。

下面从字节码来分析下为啥。

[if !supportLists]· [endif]一个例子

public static void main(String[] args)  {

    int t = 2;

    int tail = 3;

    System.out.println(t != (t = tail));

}

结果为:true

[if !supportLists]· [endif]字节码文件

C:\Users\Simple\Desktop\TeacherCode\Crm_Test\build\classes\com\itheima\crm\util>javap -c Test001

警告:二进制文件Test001包含com.itheima.crm.util.Test001

Compiled from "Test001.java"

public class com.itheima.crm.util.Test001 {

  public com.itheima.crm.util.Test001();

    Code:

       0: aload_0

       1: invokespecial #8                  // Method java/lang/Object."":()V

       4: return

 

  public static void main(java.lang.String[]);

    Code:

       0: iconst_2

       1: istore_1

       2: iconst_3

       3: istore_2

       4: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;

       7: iload_1

       8: iload_2

       9: dup

      10: istore_1

      11: if_icmpeq     18

      14: iconst_1

      15: goto          19

      18: iconst_0

      19: invokevirtual #22                 // Method java/io/PrintStream.println:(Z)V

      22: return

}

咱们从上面标黄的字节码文件中分析

一开始栈为空:

[if !supportLists]· [endif]第0行指令做用是把值2入栈栈顶元素为2

[if !supportLists]· [endif]第1行指令做用是将栈顶int类型值保存到局部变量t中

 

[if !supportLists]· [endif]第2行指令做用是把值3入栈栈顶元素为3

[if !supportLists]· [endif]第3行指令做用是将栈顶int类型值保存到局部变量tail中。

 

[if !supportLists]· [endif]第4调用打印命令

[if !supportLists]· [endif]第7行指令做用是把变量t中的值入栈

 

[if !supportLists]· [endif]第8行指令做用是把变量tail中的值入栈

 

[if !supportLists]· [endif]如今栈里面的元素为三、2,而且3位于栈顶

[if !supportLists]· [endif]第9行指令做用是当前栈顶元素入栈,因此如今栈内容3,3,2

 

 

 

[if !supportLists]· [endif]第10行指令做用是把栈顶元素存放到t,如今栈内容3,2

[if !supportLists]· [endif]第11行指令做用是判断栈顶两个元素值,相等则跳转 18。因为如今栈顶严肃为3,2不相等因此返回true.

[if !supportLists]· [endif]第14行指令做用是把1入栈

[if !supportLists]· [endif]而后回头分析下!=是双目运算符,应该是首先把左边的操做数入栈,而后在去计算了右侧操做数。

ConcurrentLinkedQuere总结

ConcurrentLinkedQueue使用CAS非阻塞算法实现使用CAS解决了当前节点与next节点之间的安全连接和对当前节点值的赋值。因为使用CAS没有使用锁,因此获取size的时候有可能进行offer,poll或者remove操做,致使获取的元素个数不精确,因此在并发状况下size函数不是颇有用。另外第一次peek或者first时候会把head指向第一个真正的队列元素。

下面总结下如何实现线程安全的,可知入队出队函数都是操做volatile变量:head,tail。因此要保证队列线程安全只须要保证对这两个Node操做的可见性和原子性,因为volatile自己保证可见性,因此只须要看下多线程下若是保证对着两个变量操做的原子性。

对于offer操做是在tail后面添加元素,也就是调用tail.casNext方法,而这个方法是使用的CAS操做,只有一个线程会成功,而后失败的线程会循环一下,从新获取tail,而后执行casNext方法。对于poll也是这样的。

[if !supportLists]Ø [endif]ConcurrentHashMap非阻塞Hash集合

ConcurrentHashMap是Java并发包中提供的一个线程安全且高效的HashMap实现,ConcurrentHashMap在并发编程的场景中使用频率很是之高,本文就来分析下ConcurrentHashMap的实现原理,并对其实现原理进行分析。

ConcurrentLinkedQuere类图

 

ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap相似,是一种数组和链表结构, 一个Segment里包含一个HashEntry数组,每一个HashEntry是一个链表结构的元素, 每一个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先得到它对应的Segment锁。

 

ConcurrentLinkedQuere实现原理

众所周知,哈希表是中很是高效,复杂度为O(1)的数据结构,在Java开发中,咱们最多见到最频繁使用的就是HashMap和HashTable,可是在线程竞争激烈的并发场景中使用都不够合理。

HashMap :先说HashMap,HashMap是线程不安全的,在并发环境下,可能会造成环状链表(扩容时可能形成,具体缘由自行百度google或查看源码分析),致使get操做时,cpu空转,因此,在并发环境中使用HashMap是很是危险的。

HashTable : HashTable和HashMap的实现原理几乎同样,差异无非是1.HashTable不容许key和value为null;2.HashTable是线程安全的。可是HashTable线程安全的策略实现代价却太大了,简单粗暴,get/put全部相关操做都是synchronized的,这至关于给整个哈希表加了一把大锁,多线程访问时候,只要有一个线程访问或操做该对象,那其余线程只能阻塞,至关于将全部的操做串行化,在竞争激烈的并发场景中性能就会很是差。以下图

 

HashTable性能差主要是因为全部操做须要竞争同一把锁,而若是容器中有多把锁,每一把锁锁一段数据,这样在多线程访问时不一样段的数据时,就不会存在锁竞争了,这样即可以有效地提升并发效率。这就是ConcurrentHashMap所采用的"分段锁"思想

 

 

ConcurrentLinkedQuere源码解析

ConcurrentHashMap采用了很是精妙的"分段锁"策略,ConcurrentHashMap的主干是个Segment数组。

final Segment[] segments;

Segment继承了ReentrantLock,因此它就是一种可重入锁(ReentrantLock)。在ConcurrentHashMap,一个Segment就是一个子哈希表,Segment里维护了一个HashEntry数组,并发环境下,对于不一样Segment的数据进行操做是不用考虑锁竞争的。(就按默认的ConcurrentLeve为16来说,理论上就容许16个线程并发执行,有木有很酷)

因此,对于同一个Segment的操做才需考虑线程同步,不一样的Segment则无需考虑。Segment相似于HashMap,一个Segment维护着一个HashEntry数组

 transient volatile HashEntry[] table;

HashEntry是目前咱们提到的最小的逻辑处理单元了。一个ConcurrentHashMap维护一个Segment数组,一个Segment维护一个HashEntry数组。

 static final class HashEntry {

        final int hash;

        final K key;

        volatile V value;

        volatile HashEntry next;

//其余省略

}

咱们说Segment相似哈希表,那么一些属性就跟咱们以前提到的HashMap差不离,好比负载因子loadFactor,好比阈值threshold等等,看下Segment的构造方法

Segment(float lf, int threshold, HashEntry[] tab) {

this.loadFactor = lf;//负载因子

this.threshold = threshold;//阈值

this.table = tab;//主干数组即HashEntry数组

        }

咱们来看下ConcurrentHashMap的构造方法

  public ConcurrentHashMap(int initialCapacity,

                                float loadFactor, int concurrencyLevel) {

           if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)

               throw new IllegalArgumentException();

//MAX_SEGMENTS为1<<16=65536,也就是最大并发数为65536

           if (concurrencyLevel > MAX_SEGMENTS)

               concurrencyLevel = MAX_SEGMENTS;

//2的sshif次方等于ssize,例:ssize=16,sshift=4;ssize=32,sshif=5

          int sshift = 0;

//ssize为segments数组长度,根据concurrentLevel计算得出

          int ssize = 1;

          while (ssize < concurrencyLevel) {

              ++sshift;

              ssize <<= 1;

          }

//segmentShift和segmentMask这两个变量在定位segment时会用到,后面会详细讲

          this.segmentShift = 32 - sshift;

          this.segmentMask = ssize - 1;

          if (initialCapacity > MAXIMUM_CAPACITY)

              initialCapacity = MAXIMUM_CAPACITY;

//计算cap的大小,即Segment中HashEntry的数组长度,cap也必定为2的n次方.

          int c = initialCapacity / ssize;

          if (c * ssize < initialCapacity)

              ++c;

          int cap = MIN_SEGMENT_TABLE_CAPACITY;

          while (cap < c)

              cap <<= 1;

//建立segments数组并初始化第一个Segment,其他的Segment延迟初始化

          Segment s0 =

              new Segment(loadFactor, (int)(cap * loadFactor),

                               (HashEntry[])new HashEntry[cap]);

          Segment[] ss = (Segment[])new Segment[ssize];

          UNSAFE.putOrderedObject(ss, SBASE, s0);

          this.segments = ss;

      }

初始化方法有三个参数,若是用户不指定则会使用默认值,initialCapacity为16,loadFactor为0.75(负载因子,扩容时须要参考),concurrentLevel为16。

从上面的代码能够看出来,Segment数组的大小ssize是由concurrentLevel来决定的,可是却不必定等于concurrentLevel,ssize必定是大于或等于concurrentLevel的最小的2的次幂。好比:默认状况下concurrentLevel是16,则ssize为16;若concurrentLevel为14,ssize为16;若concurrentLevel为17,则ssize为32。为何Segment的数组大小必定是2的次幂?其实主要是便于经过按位与的散列算法来定位Segment的index。

其实,put方法对segment也会有所体现

 public V put(K key, V value) {

        Segment s;

//concurrentHashMap不容许key/value为空

        if (value == null)

            throw new NullPointerException();

//hash函数对key的hashCode从新散列,避免差劲的不合理的hashcode,保证散列均匀

        int hash = hash(key);

//返回的hash值无符号右移segmentShift位与段掩码进行位运算,定位segment

        int j = (hash >>> segmentShift) & segmentMask;

        if ((s = (Segment)UNSAFE.getObject          // nonvolatile; recheck

             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment

            s = ensureSegment(j);

        return s.put(key, hash, value, false);

  }

从源码看出,put的主要逻辑也就两步:

1.定位segment并确保定位的Segment已初始化

2.调用Segment的put方法。

Ps:关于segmentShift和segmentMask

  segmentShift和segmentMask这两个全局变量的主要做用是用来定位Segment,int j =(hash >>> segmentShift) & segmentMask。

  segmentMask:段掩码,假如segments数组长度为16,则段掩码为16-1=15;segments长度为32,段掩码为32-1=31。这样获得的全部bit位都为1,能够更好地保证散列的均匀性

segmentShift:2的sshift次方等于ssize,segmentShift=32-sshift。若segments长度为16,segmentShift=32-4=28;若segments长度为32,segmentShift=32-5=27。而计算得出的hash值最大为32位,无符号右移segmentShift,则意味着只保留高几位(其他位是没用的),而后与段掩码segmentMask位运算来定位Segment。

ConcurrentLinkedQuere方法

[if !supportLists]ü [endif]Get操做

 public V get(Object key) {

        Segment s;

        HashEntry[] tab;

        int h = hash(key);

        long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;//先定位Segment,再定位HashEntry

        if ((s = (Segment)UNSAFE.getObjectVolatile(segments, u)) != null &&

            (tab = s.table) != null) {

            for (HashEntry e = (HashEntry) UNSAFE.getObjectVolatile

                     (tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);

                 e != null; e = e.next) {

                K k;

                if ((k = e.key) == key || (e.hash == h && key.equals(k)))

                    return e.value;

            }

        }

        return null;

    }

get方法无需加锁,因为其中涉及到的共享变量都使用volatile修饰,volatile能够保证内存可见性,因此不会读取到过时数据。

  来看下concurrentHashMap代理到Segment上的put方法,Segment中的put方法是要加锁的。只不过是锁粒度细了而已。

[if !supportLists]ü [endif]Put操做

final V put(K key, int hash, V value, boolean onlyIfAbsent) {

            HashEntry node = tryLock() ? null :

scanAndLockForPut(key, hash, value);//tryLock不成功时会遍历定位到的HashEnry位置的链表(遍历主要是为了使CPU缓存链表),若找不到,则建立HashEntry。tryLock必定次数后(MAX_SCAN_RETRIES变量决定),则lock。若遍历过程当中,因为其余线程的操做致使链表头结点变化,则须要从新遍历。

            V oldValue;

            try {

                HashEntry[] tab = table;

int index = (tab.length - 1) & hash;//定位HashEntry,能够看到,这个hash值在定位Segment时和在Segment中定位HashEntry都会用到,只不过定位Segment时只用到高几位。

                HashEntry first = entryAt(tab, index);

                for (HashEntry e = first;;) {

                    if (e != null) {

                        K k;

                        if ((k = e.key) == key ||

                            (e.hash == hash && key.equals(k))) {

                            oldValue = e.value;

                            if (!onlyIfAbsent) {

                                e.value = value;

                                ++modCount;

                            }

                            break;

                        }

                        e = e.next;

                    }

                    else {

                        if (node != null)

                            node.setNext(first);

                        else

                            node = new HashEntry(hash, key, value, first);

                        int c = count + 1;              //若c超出阈值threshold,须要扩容并rehash。扩容后的容量是当前容量的2倍。这样能够最大程度避免以前散列好的entry从新散列,具体在另外一篇文章中有详细分析,不赘述。扩容并rehash的这个过程是比较消耗资源的。

                        if (c > threshold && tab.length < MAXIMUM_CAPACITY)

                            rehash(node);

                        else

                            setEntryAt(tab, index, node);

                        ++modCount;

                        count = c;

                        oldValue = null;

                        break;

                    }

                }

            } finally {

                unlock();

            }

            return oldValue;

        }

ConcurrentLinkedQuere总结

ConcurrentHashMap做为一种线程安全且高效的哈希表的解决方案,尤为其中的"分段锁"的方案,相比HashTable的全表锁在性能上的提高很是之大。本文对ConcurrentHashMap的实现原理进行了详细分析,并解读了部分源码,但愿能帮助到有须要的童鞋。

[if !supportLists]Ø [endif]ConcurrentSkipListMap非阻塞Hash跳表集合

你们都是知道TreeMap,它是使用树形结构的方式进行存储数据的线程不安全的Map集合(有序的哈希表),而且能够对Map中的Key进行排序,Key中存储的数据须要实现Comparator接口或使用CompareAble接口的子类来实现排序。

ConcurrentSkipListMap也是和TreeMap,它们都是有序的哈希表。可是,它们是有区别的:

第一,它们的线程安全机制不一样,TreeMap是非线程安全的,而ConcurrentSkipListMap是线程安全的。第二,ConcurrentSkipListMap是经过跳表实现的,而TreeMap是经过红黑树实现的。

那如今咱们须要知道说明是跳表。

什么是SkipList

Skip list(跳表)是一种能够代替平衡树的数据结构,默认是按照Key值升序的。Skip list让已排序的数据分布在多层链表中,以0-1随机数决定一个数据的向上攀升与否,经过“空间来换取时间”的一个算法,在每一个节点中增长了向前的指针,在插入、删除、查找时能够忽略一些不可能涉及到的结点,从而提升了效率。

从几率上保持数据结构的平衡比显示的保持数据结构平衡要简单的多。对于大多数应用,用Skip list要比用树算法相对简单。因为Skip list比较简单,实现起来会比较容易,虽然和平衡树有着相同的时间复杂度(O(logn)),可是skip list的常数项会相对小不少。Skip list在空间上也比较节省。一个节点平均只须要1.333个指针(甚至更少)。

下图为Skip list结构图(以7,14,21,32,37,71,85序列为例)

 

SkipList性质

[if !supportLists](1) [endif]由不少层结构组成,level是经过必定的几率随机产生的。(2) 每一层都是一个有序的链表,默认是升序,也能够根据建立映射时所提供的Comparator进行排序,具体取决于使用的构造方法。(3) 最底层(Level 1)的链表包含全部元素。(4) 若是一个元素出如今Level i 的链表中,则它在Level i 之下的链表也都会出现。(5) 每一个节点包含两个指针,一个指向同一链表中的下一个元素,一个指向下面一层的元素。

什么是ConcurrentSkipListMap

ConcurrentSkipListMap提供了一种线程安全的并发访问的排序映射表。内部是SkipList(跳表)结构实现,在理论上可以在O(log(n))时间内完成查找、插入、删除操做。 注意,调用ConcurrentSkipListMap的size时,因为多个线程能够同时对映射表进行操做,因此映射表须要遍历整个链表才能返回元素个数,这个操做是个O(log(n))的操做。

ConcurrentSkipListMap数据结构

ConcurrentSkipListMap的数据结构,以下图所示:

 

说明:能够看到ConcurrentSkipListMap的数据结构使用的是跳表,每个HeadIndex、Index结点都会包含一个对Node的引用,同一垂直方向上的Index、HeadIndex结点都包含了最底层的Node结点的引用。而且层级越高,该层级的结点(HeadIndex和Index)数越少。Node结点之间使用单链表结构。

ConcurrentSkipListMap源码分析

ConcurrentSkipListMap主要用到了Node和Index两种节点的存储方式,经过volatile关键字实现了并发的操做

static final class Node {  

        final K key;  

volatile Object value;//value值  

volatile Node next;//next引用  

        ……  

}  

static class Index {  

        final Node node;  

final Index down;//downy引用  

volatile Index right;//右边引用  

       ……  

}  

[if !supportLists]ü [endif]ConcurrentSkipListMap查找操做

经过SkipList的方式进行查找操做:(下图以“查找91”进行说明:)

 

 

 

红色虚线,表示查找的路径,蓝色向右箭头表示right引用;黑色向下箭头表示down引用;

下面就是源码中的实现(get方法是经过doGet方法来时实现的)

public V get(Object key) {  

      return doGet(key);  

 }  

//doGet的实现  

private V doGet(Object okey) {  

        Comparable key = comparable(okey);  

        Node bound = null;  

Index q = head;//把头结点做为当前节点的前驱节点  

Index r = q.right;//前驱节点的右节点做为当前节点  

        Node n;  

        K k;  

        int c;  

for (;;) {//遍历  

            Index d;  

//依次遍历right节点  

            if (r != null && (n = r.node) != bound && (k = n.key) != null) {  

if ((c = key.compareTo(k)) > 0) {//因为key都是升序排列的,全部当前关键字大于所要

查找的key时继续向右遍历  

                    q = r;  

                    r = r.right;  

                    continue;  

                } else if (c == 0) {  

//若是找到了相等的key节点,则返回该Node的value若是value为空多是其余并发delete

致使的,因而经过另外一种

//遍历findNode的方式再查找  

                    Object v = n.value;  

                    return (v != null)? (V)v : getUsingFindNode(key);  

                } else  

                    bound = n;  

            }  

//若是一个链表中right没能找到key对应的value,则调整到其down的引用处继续查找  

            if ((d = q.down) != null) {  

                q = d;  

                r = d.right;  

            } else  

                break;  

        }  

//若是经过上面的遍历方式,还没能找到key对应的value,再经过Node.next的方式进行查找  

        for (n = q.node.next;  n != null; n = n.next) {  

            if ((k = n.key) != null) {  

                if ((c = key.compareTo(k)) == 0) {  

                    Object v = n.value;  

                    return (v != null)? (V)v : getUsingFindNode(key);  

                } else if (c < 0)  

                    break;  

            }  

        }  

        return null;  

    }  

[if !supportLists]ü [endif]ConcurrentSkipListMap删除操做

经过SkipList的方式进行删除操做:(下图以“删除23”进行说明:)

 

红色虚线,表示查找的路径,蓝色向右箭头表示right引用;黑色向下箭头表示down引用;

下面就是源码中的实现(remove方法是经过doRemove方法来时实现的)

//remove操做,经过doRemove实现,把全部level中出现关键字key的地方都delete掉  

public V remove(Object key) {  

        return doRemove(key, null);  

 }  

 final V doRemove(Object okey, Object value) {  

        Comparable key = comparable(okey);  

        for (;;) {  

Node b = findPredecessor(key);//获得key的前驱(就是比key小的最大节点)  

Node n = b.next;//前驱节点的next引用  

for (;;) {//遍历  

if (n == null)//若是next引用为空,直接返回  

                    return null;  

                Node f = n.next;  

if (n != b.next)                    //若是两次得到的b.next不是相同的Node,就跳转

到第一层循环从新得到b和n  

                    break;  

                Object v = n.value;  

if (v == null) {                    //当n被其余线程delete的时候,其value==null,此时作辅助处理,并从新获取b和n  

                    n.helpDelete(b, f);  

                    break;  

                }  

if (v == n || b.value == null)      //当其前驱被delet的时候直接跳出,从新获取b和n  

                    break;  

                int c = key.compareTo(n.key);  

                if (c < 0)  

                    return null;  

if (c > 0) {//当key较大时就继续遍历  

                    b = n;  

                    n = f;  

                    continue;  

                }  

                if (value != null && !value.equals(v))  

                    return null;  

                if (!n.casValue(v, null))  

                    break;  

if (!n.appendMarker(f) || !b.casNext(n, f))//casNext方法就是经过比较和设置b(前驱)

的next节点的方式来实现删除操做  

findNode(key);                  //经过尝试findNode的方式继续find  

                else {  

                    findPredecessor(key);           // Clean index  

if (head.right == null)   //若是head的right引用为空,则表示不存在该level  

                        tryReduceLevel();  

                }  

                return (V)v;  

            }  

        }  

    }  

[if !supportLists]ü [endif]ConcurrentSkipListMap插入操做

经过SkipList的方式进行插入操做:(下图以“添加55”的两种状况,进行说明:)

 

在level=2(该level存在)的状况下添加55的图示:只需在level<=2的合适位置插入55便可

 

在level=4(该level不存在,图示level4是新建的)的状况下添加55的状况:首先新建level4,而后在level<=4的合适位置插入55。

下面就是源码中的实现(put方法是经过doPut方法来时实现的)

//put操做,经过doPut实现  

 public V put(K key, V value) {  

        if (value == null)  

            throw new NullPointerException();  

        return doPut(key, value, false);  

 }  

private V doPut(K kkey, V value, boolean onlyIfAbsent) {  

        Comparable key = comparable(kkey);  

        for (;;) {  

Node b = findPredecessor(key);//前驱  

            Node n = b.next;  

//定位的过程就是和get操做类似  

            for (;;) {  

                if (n != null) {  

                    Node f = n.next;  

if (n != b.next) //先后值不一致的状况下,跳转到第一层循环从新得到b和n  

                        break;;  

                    Object v = n.value;  

if (v == null) {               // n被delete的状况下  

                        n.helpDelete(b, f);  

                        break;  

                    }  

if (v == n || b.value == null) // b被delete的状况,从新获取b和n  

                        break;  

                    int c = key.compareTo(n.key);  

                    if (c > 0) {  

                        b = n;  

                        n = f;  

                        continue;  

                    }  

                    if (c == 0) {  

                        if (onlyIfAbsent || n.casValue(v, value))  

                            return (V)v;  

                        else  

                            break; // restart if lost race to replace value  

                    }  

                    // else c < 0; fall through  

                }  

                Node z = new Node(kkey, value, n);  

                if (!b.casNext(n, z))  

                    break;         // restart if lost race to append to b  

int level = randomLevel();//获得一个随机的level做为该key-value插入的最高level  

                if (level > 0)  

insertIndex(z, level);//进行插入操做  

                return null;  

            }  

        }  

    }  

 

 /** 

*得到一个随机的level值 

     */  

    private int randomLevel() {  

        int x = randomSeed;  

        x ^= x << 13;  

        x ^= x >>> 17;  

        randomSeed = x ^= x << 5;  

        if ((x & 0x8001) != 0) // test highest and lowest bits  

            return 0;  

        int level = 1;  

        while (((x >>>= 1) & 1) != 0) ++level;  

        return level;  

    }  

//执行插入操做:如上图所示,有两种可能的状况:  

//1.当level存在时,对level<=n都执行insert操做  

//2.当level不存在(大于目前的最大level)时,首先添加新的level,而后在执行操做1   

private void insertIndex(Node z, int level) {  

        HeadIndex h = head;  

        int max = h.level;  

if (level <= max) {//状况1  

            Index idx = null;  

for (int i = 1; i <= level; ++i)//首先获得一个包含1~level个级别的down关系的链表,

最后的inx为最高level  

                idx = new Index(z, idx, null);  

addIndex(idx, h, level);//把最高level的idx传给addIndex方法  

} else { //状况2 增长一个新的级别  

            level = max + 1;  

            Index[] idxs = (Index[])new Index[level+1];  

            Index idx = null;  

for (int i = 1; i <= level; ++i)//该步骤和状况1相似  

                idxs[i] = idx = new Index(z, idx, null);  

            HeadIndex oldh;  

            int k;  

            for (;;) {  

                oldh = head;  

                int oldLevel = oldh.level;  

                if (level <= oldLevel) { // lost race to add level  

                    k = level;  

                    break;  

                }  

                HeadIndex newh = oldh;  

                Node oldbase = oldh.node;  

                for (int j = oldLevel+1; j <= level; ++j)  

newh = new HeadIndex(oldbase, newh, idxs[j], j);//建立新的  

                if (casHead(oldh, newh)) {  

                    k = oldLevel;  

                    break;  

                }  

            }  

            addIndex(idxs[k], oldh, k);  

        }  

    }  

/** 

*在1~indexlevel层中插入数据  

     */  

    private void addIndex(Index idx, HeadIndex h, int indexLevel) {  

//  insertionLevel表明要插入的level,该值会在indexLevel~1间遍历一遍  

        int insertionLevel = indexLevel;  

        Comparable key = comparable(idx.node.key);  

        if (key == null) throw new NullPointerException();  

//和get操做相似,不一样的就是查找的同时在各个level上加入了对应的key  

        for (;;) {  

            int j = h.level;  

            Index q = h;  

            Index r = q.right;  

            Index t = idx;  

            for (;;) {  

                if (r != null) {  

                    Node n = r.node;  

                    // compare before deletion check avoids needing recheck  

                    int c = key.compareTo(n.key);  

                    if (n.value == null) {  

                        if (!q.unlink(r))  

                            break;  

                        r = q.right;  

                        continue;  

                    }  

                    if (c > 0) {  

                        q = r;  

                        r = r.right;  

                        continue;  

                    }  

                }  

if (j == insertionLevel) {//在该层level中执行插入操做  

                    // Don't insert index if node already deleted  

                    if (t.indexesDeletedNode()) {  

                        findNode(key); // cleans up  

                        return;  

                    }  

if (!q.link(r, t))//执行link操做,其实就是inset的实现部分  

                        break; // restart  

                    if (--insertionLevel == 0) {  

                        // need final deletion check before return  

                        if (t.indexesDeletedNode())  

                            findNode(key);  

                        return;  

                    }  

                }  

if (--j >= insertionLevel && j < indexLevel)//key移动到下一层level  

                    t = t.down;  

                q = q.down;  

                r = q.right;  

            }  

        }  

    }  

ConcurrentLinkedQuere示例

下面咱们看下面示例输出的结果

import java.util.*;import java.util.concurrent.*;

/*

*   ConcurrentSkipListMap是“线程安全”的哈希表,而TreeMap是非线程安全的。

 *

*下面是“多个线程同时操做而且遍历map”的示例

*   (01)当map是ConcurrentSkipListMap对象时,程序能正常运行。

*   (02)当map是TreeMap对象时,程序会产生ConcurrentModificationException异常。

 *

 */public class ConcurrentSkipListMapDemo1 {

 

// TODO: map是TreeMap对象时,程序会出错。

    //private static Map map = new TreeMap();

    private static Map map = new ConcurrentSkipListMap();

    public static void main(String[] args) {

 

//同时启动两个线程对map进行操做!

        new MyThread("a").start();

        new MyThread("b").start();

    }

 

    private static void printAll() {

        String key, value;

        Iterator iter = map.entrySet().iterator();

        while(iter.hasNext()) {

            Map.Entry entry = (Map.Entry)iter.next();

            key = (String)entry.getKey();

            value = (String)entry.getValue();

            System.out.print("("+key+", "+value+"), ");

        }

        System.out.println();

    }

 

    private static class MyThread extends Thread {

        MyThread(String name) {

            super(name);

        }

        @Override

        public void run() {

                int i = 0;

            while (i++ < 6) {

// “线程名” + "序号"

                String val = Thread.currentThread().getName()+i;

                map.put(val, "0");

//经过“Iterator”遍历map。               

 printAll();

            }

        }

    }

}

 

某一次的运行结果:

(a1, 0), (a1, 0), (b1, 0), (b1, 0),

(a1, 0), (b1, 0), (b2, 0),

(a1, 0), (a1, 0), (a2, 0), (a2, 0), (b1, 0), (b1, 0), (b2, 0), (b2, 0), (b3, 0),

(b3, 0), (a1, 0),

(a2, 0), (a3, 0), (a1, 0), (b1, 0), (a2, 0), (b2, 0), (a3, 0), (b3, 0), (b1, 0), (b4, 0),

(b2, 0), (a1, 0), (b3, 0), (a2, 0), (b4, 0),

(a3, 0), (a1, 0), (a4, 0), (a2, 0), (b1, 0), (a3, 0), (b2, 0), (a4, 0), (b3, 0), (b1, 0), (b4, 0), (b2, 0), (b5, 0),

(b3, 0), (a1, 0), (b4, 0), (a2, 0), (b5, 0),

(a3, 0), (a1, 0), (a4, 0), (a2, 0), (a5, 0), (a3, 0), (b1, 0), (a4, 0), (b2, 0), (a5, 0), (b3, 0), (b1, 0), (b4, 0), (b2, 0), (b5, 0), (b3, 0), (b6, 0),

(b4, 0), (a1, 0), (b5, 0), (a2, 0), (b6, 0),

(a3, 0), (a4, 0), (a5, 0), (a6, 0), (b1, 0), (b2, 0), (b3, 0), (b4, 0), (b5, 0), (b6, 0),

 

结果说明

示例程序中,启动两个线程(线程a和线程b)分别对ConcurrentSkipListMap进行操做。以线程a而言,它会先获取“线程名”+“序号”,而后将该字符串做为key,将“0”做为value,插入到ConcurrentSkipListMap中;接着,遍历并输出ConcurrentSkipListMap中的所有元素。 线程b的操做和线程a同样,只不过线程b的名字和线程a的名字不一样。 当map是ConcurrentSkipListMap对象时,程序能正常运行。若是将map改成TreeMap时,程序会产生ConcurrentModificationException异常。

[if !supportLists]2) [endif]java.util.concurrent.atomic 

[if !supportLists]Ø [endif]AtomicBoolean原子性布尔

AtomicBoolean是java.util.concurrent.atomic包下的原子变量,这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具备排他性,即当某个线程进入方法,执行其中的指令时,不会被其余线程打断,而别的线程就像自旋锁同样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另外一个线程进入,这只是一种逻辑上的理解。其实是借助硬件的相关指令来实现的,不会阻塞线程(或者说只是在硬件级别上阻塞了)。

AtomicBoolean,在这个Boolean值的变化的时候不容许在之间插入,保持操做的原子性。

下面将解释重点方法并举例:

boolean compareAndSet(expectedValue, updateValue)这个方法主要两个做用:  

[if !supportLists]1. [endif]比较AtomicBoolean和expect的值,若是一致,执行方法内的语句。其实就是一个if语句

[if !supportLists]2. [endif]把AtomicBoolean的值设成update,比较最要的是这两件事是一鼓作气的,这连个动做之间不会被打断,任何内部或者外部的语句都不可能在两个动做之间运行。为多线程的控制提供了解决的方案

下面咱们从代码上解释:

首先咱们看下在不使用AtomicBoolean状况下,代码的运行状况:

package zmx.atomic.test;  

 

import java.util.concurrent.TimeUnit;  

 

public class BarWorker implements Runnable {  

     //静态变量 

     private static boolean exists = false;    

 

     private String name;    

 

     public BarWorker(String name) {     

          this.name = name;    

     }    

 

     @Override    

     public void run() {     

         if (!exists) {    

                 try {    

                  TimeUnit.SECONDS.sleep(1);    

                 } catch (InterruptedException e1) {    

                  // do nothing    

                 }    

                 exists = true;    

                 System.out.println(name + " enter");    

                 try {    

                  System.out.println(name + " working");    

                  TimeUnit.SECONDS.sleep(2);    

                 } catch (InterruptedException e) {    

                  // do nothing    

                 }    

                 System.out.println(name + " leave");    

                 exists = false;    

        } else {    

         System.out.println(name + " give up");    

        }    

 

    }   

 

 

     public static void main(String[] args) {  

 

         BarWorker bar1 = new BarWorker("bar1");  

         BarWorker bar2 = new BarWorker("bar2");  

         new Thread(bar1).start();  

         new Thread(bar2).start();       

    } 

 

运行结果:

bar1 enter  

bar2 enter  

bar1 working  

bar2 working  

bar1 leave  

bar2 leave 

从上面的运行结果咱们可看到,两个线程运行时,都对静态变量exists同时作操做,并无保证exists静态变量的原子性,也就是一个线程在对静态变量exists进行操做到时候,其余线程必须等待或不做为。等待一个线程操做完后,才能对其进行操做。

下面咱们将静态变量使用AtomicBoolean来进行操做

package zmx.atomic.test;  

 

import java.util.concurrent.TimeUnit;  

import java.util.concurrent.atomic.AtomicBoolean;  

 

public class BarWorker2 implements Runnable {  

    //静态变量使用AtomicBoolean 进行操做

    private static AtomicBoolean exists = new AtomicBoolean(false);    

 

 

     private String name;    

 

     public BarWorker2(String name) {     

          this.name = name;    

     }    

 

     @Override    

     public void run() {     

         if (exists.compareAndSet(false, true)) {    

 

             System.out.println(name + " enter");    

             try {    

                  System.out.println(name + " working");    

                  TimeUnit.SECONDS.sleep(2);    

             } catch (InterruptedException e) {    

                  // do nothing    

             }    

             System.out.println(name + " leave");    

             exists.set(false);      

        } else {    

             System.out.println(name + " give up");    

        }    

 

    }   

     public static void main(String[] args) {  

         BarWorker2 bar1 = new BarWorker2("bar1");  

         BarWorker2 bar2 = new BarWorker2("bar2");  

         new Thread(bar1).start();  

         new Thread(bar2).start();  

    }  

}  

 

运行结果:

bar1 enter  

bar1 working  

bar2 give up  

bar1 leave  

能够从上面的运行结果看出仅仅一个线程进行工做,由于exists.compareAndSet(false, true)提供了原子性操做,比较和赋值操做组成了一个原子操做, 中间不会提供可乘之机。使得一个线程操做,其余线程等待或不做为。

下面咱们简单介绍下AtomicBoolean的API

建立一个AtomicBoolean

你能够这样建立一个AtomicBoolean:

AtomicBoolean atomicBoolean = new AtomicBoolean();

以上示例新建了一个默认值为false 的 AtomicBoolean。若是你想要为 AtomicBoolean 实例设置一个显式的初始值,那么你能够将初始值传给 AtomicBoolean 的构造子:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);

得到 AtomicBoolean的值

你能够经过使用get() 方法来获取一个 AtomicBoolean 的值。示例以下:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);

boolean value = atomicBoolean.get();

设置AtomicBoolean的值

你能够经过使用set() 方法来设置一个 AtomicBoolean 的值。示例以下:

AtomicBoolean atomicBoolean = new AtomicBoolean(true);

atomicBoolean.set(false);

以上代码执行后AtomicBoolean 的值为 false。

交换AtomicBoolean的值

你能够经过getAndSet() 方法来交换一个 AtomicBoolean 实例的值。getAndSet() 方法将返回 AtomicBoolean 当前的值,并将为 AtomicBoolean 设置一个新值。示例以下:

AtomicBoolean atomicBoolean = new AtomicBoolean(true); 

boolean oldValue = atomicBoolean.getAndSet(false);

以上代码执行后oldValue 变量的值为 true,atomicBoolean 实例将持有 false 值。代码成功将 AtomicBoolean 当前值 ture 交换为 false。

比较并设置AtomicBoolean的值

compareAndSet() 方法容许你对 AtomicBoolean 的当前值与一个指望值进行比较,若是当前值等于指望值的 话,将会对AtomicBoolean 设定一个新值。compareAndSet() 方法是原子性的,所以在同一时间以内有单个 线程执行它。所以compareAndSet() 方法可被用于一些相似于锁的同步的简单实现。如下是一个 compareAndSet() 示例:

AtomicBoolean atomicBoolean = new AtomicBoolean(true); 

 

boolean expectedValue = true; 

boolean newValue      = false; 

 

boolean wasNewValueSet = atomicBoolean.compareAndSet( 

    expectedValue, newValue);

本示例对AtomicBoolean 的当前值与 true 值进行比较,若是相等,将 AtomicBoolean 的值更新为 false

[if !supportLists]Ø [endif]AtomicInteger原子性整型

AtomicInteger,一个提供原子操做的Integer的类。在Java语言中,++i和i++操做并非线程安全的,在使用的时候,不可避免的会用到synchronized关键字。而AtomicInteger则经过一种线程安全的加减操做接口。

咱们先来看看AtomicInteger给咱们提供了什么方法:

public final int get() //获取当前的值

public final int getAndSet(int newValue)//获取当前的值,并设置新的值

public final int getAndIncrement()//获取当前的值,并自增

public final int getAndDecrement() //获取当前的值,并自减

public final int getAndAdd(int delta)  //获取当前的值,并加上预期的值

下面经过两个简单的例子来看一下AtomicInteger 的优点在哪: 普通线程同步:

class Test2 {

        private volatile int count = 0;

 

        public synchronized void increment() {

count++; //若要线程安全执行执行count++,须要加锁

        }

 

        public int getCount() {

                  return count;

        }

}

 

使用AtomicInteger:

class Test2 {

        private AtomicInteger count = new AtomicInteger();

 

        public void increment() {

                  count.incrementAndGet();

        }

       //使用AtomicInteger以后,不须要加锁,也能够实现线程安全。

       public int getCount() {

                return count.get();

        }

}

从上面的例子中咱们能够看出:使用AtomicInteger是很是的安全的.并且由于AtomicInteger由硬件提供原子操做指令实现的。在非激烈竞争的状况下,开销更小,速度更快。AtomicInteger是使用非阻塞算法来实现并发控制的。AtomicInteger的关键域只有一下3个:

// setup to use Unsafe.compareAndSwapInt for updates

private static final Unsafe unsafe = Unsafe.getUnsafe();

private static final long valueOffset;

static {    

         try {        

       valueOffset=

unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));   

        } catch (Exception ex) {

               throw new Error(ex);

        }

    }

private volatile int value;

这里,unsafe是java提供的得到对对象内存地址访问的类,注释已经清楚的写出了,它的做用就是在更新操做时提供“比较并替换”的做用。实际上就是AtomicInteger中的一个工具。valueOffset是用来记录value自己在内存的便宜地址的,这个记录,也主要是为了在更新操做在内存中找到value的位置,方便比较。 注意:value是用来存储整数的时间变量,这里被声明为volatile,就是为了保证在更新操做时,当前线程能够拿到value最新的值(并发环境下,value可能已经被其余线程更新了)。

优势:最大的好处就是能够避免多线程的优先级倒置和死锁状况的发生,提高在高并发处理下的性能。

下面咱们简单介绍下AtomicInteger的API

建立一个AtomicInteger

建立一个AtomicInteger 示例以下:

AtomicInteger atomicInteger = new AtomicInteger();

本示例将建立一个初始值为0 的 AtomicInteger。若是你想要建立一个给定初始值的 AtomicInteger,你能够这样:

AtomicInteger atomicInteger = new AtomicInteger(123);

本示例将123 做为参数传给 AtomicInteger 的构造子,它将设置 AtomicInteger 实例的初始值为 123。

 

得到AtomicInteger的值

你能够使用get() 方法获取 AtomicInteger 实例的值。示例以下:

AtomicInteger atomicInteger = new AtomicInteger(123);

int theValue = atomicInteger.get();

 

设置AtomicInteger的值

你能够经过set() 方法对 AtomicInteger 的值进行从新设置。如下是 AtomicInteger.set() 示例:

AtomicInteger atomicInteger = new AtomicInteger(123);

atomicInteger.set(234);

以上示例建立了一个初始值为123 的 AtomicInteger,而在第二行将其值更新为 234。

比较并设置AtomicInteger的值

AtomicInteger 类也经过了一个原子性的 compareAndSet() 方法。这一方法将 AtomicInteger 实例的当前值与指望值进行比较,若是两者相等,为 AtomicInteger 实例设置一个新值。AtomicInteger.compareAndSet() 代码示例:

AtomicInteger atomicInteger = new AtomicInteger(123); 

int expectedValue = 123; 

int newValue      = 234; 

atomicInteger.compareAndSet(expectedValue, newValue);

本示例首先新建一个初始值为123 的 AtomicInteger 实例。而后将 AtomicInteger 与指望值 123 进行比较,若是相等,将 AtomicInteger 的值更新为 234。

增长AtomicInteger的值

AtomicInteger 类包含有一些方法,经过它们你能够增长 AtomicInteger 的值,并获取其值。这些方法以下:

public final int addAndGet(int addValue)//在原来的数值上增长新的值,并返回新值

public final int getAndIncrement()//获取当前的值,并自增

public final int incrementAndget() //自减,并得到自减后的值

public final int getAndAdd(int delta)  //获取当前的值,并加上预期的值

第一个addAndGet() 方法给 AtomicInteger 增长了一个值,而后返回增长后的值。getAndAdd() 方法为 AtomicInteger 增长了一个值,但返回的是增长之前的 AtomicInteger 的值。具体使用哪个取决于你的应用场景。如下是这两种方法的示例:

AtomicInteger atomicInteger = new AtomicInteger(); 

System.out.println(atomicInteger.getAndAdd(10)); 

System.out.println(atomicInteger.addAndGet(10));

本示例将打印出0 和 20。例子中,第二行拿到的是加 10 以前的 AtomicInteger 的值。加 10 以前的值是 0。第三行将 AtomicInteger 的值再加 10,并返回加操做以后的值。该值如今是为 20。你固然也能够使用这俩方法为 AtomicInteger 添加负值。结果实际是一个减法操做。getAndIncrement() 和 incrementAndGet() 方法相似于 getAndAdd() 和 addAndGet(),但每次只将 AtomicInteger 的值加 1。

减少AtomicInteger的值

AtomicInteger 类还提供了一些减少 AtomicInteger 的值的原子性方法。这些方法是:

public final int decrementAndGet()

public final int getAndDecrement()

decrementAndGet() 将 AtomicInteger 的值减一,并返回减一后的值。getAndDecrement() 也将 AtomicInteger 的值减一,但它返回的是减一以前的值。

[if !supportLists]Ø [endif]AtomicIntegerArray原子性整型数组

java.util.concurrent.atomic.AtomicIntegerArray类提供了能够以原子方式读取和写入的底层int数组的操做,还包含高级原子操做。 AtomicIntegerArray支持对底层int数组变量的原子操做。 它具备获取和设置方法,如在变量上的读取和写入。 也就是说,一个集合与同一变量上的任何后续get相关联。 原子compareAndSet方法也具备这些内存一致性功能。

AtomicIntegerArray本质上是对int[]类型的封装。使用Unsafe类经过CAS的方式控制int[]在多线程下的安全性。它提供了如下几个核心API:

//得到数组第i个下标的元素

public final int get(int i)

//得到数组的长度

public final int length()

//将数组第i个下标设置为newValue,并返回旧的值

public final int getAndSet(int i, int newValue)

//进行CAS操做,若是第i个下标的元素等于expect,则设置为update,设置成功返回true

public final boolean compareAndSet(int i, int expect, int update)

//将第i个下标的元素加1

public final int getAndIncrement(int i)

//将第i个下标的元素减1

public final int getAndDecrement(int i)

//将第i个下标的元素增长delta(delta能够是负数)

public final int getAndAdd(int i, int delta)

下面给出一个简单的示例,展现AtomicIntegerArray使用:

public class AtomicIntegerArrayDemo {

     static AtomicIntegerArray arr = new AtomicIntegerArray(10);

    public static class AddThread implements Runnable{

         public void run(){

            for(int k=0;k<10000;k++)

                arr.getAndIncrement(k%arr.length());

         }

     }

     public static void main(String[] args) throws InterruptedException {

         Thread[] ts=new Thread[10];

         for(int k=0;k<10;k++){

             ts[k]=new Thread(new AddThread());

         }

         for(int k=0;k<10;k++){ts[k].start();}

         for(int k=0;k<10;k++){ts[k].join();}

         System.out.println(arr);

     }

 }

 

输出结果:

[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]

上述代码第2行,申明了一个内含10个元素的数组。第3行定义的线程对数组内10个元素进行累加操做,每一个元素各加1000次。第11行,开启10个这样的线程。所以,能够预测,若是线程安全,数组内10个元素的值必然都是10000。反之,若是线程不安全,则部分或者所有数值会小于10000。

[if !supportLists]Ø [endif]AtomicLong、AtomicLongArray原子性整型数组

AtomicLong、AtomicLongArray的API跟AtomicInteger、AtomicIntegerArray在使用方法都是差很少的。区别在于用前者是使用原子方式更新的long值和long数组,后者是使用原子方式更新的Integer值和Integer数组。二者的相同处在于它们此类确实扩展了Number,容许那些处理基于数字类的工具和实用工具进行统一访问。在实际开发中,它们分别用于不一样的场景。这个就具体状况具体分析了,下面将举例说明AtomicLong的使用场景(使用AtomicLong生成自增加ID),其余就不在过多介绍。

import java.util.ArrayList;

import java.util.Collections;

import java.util.HashSet;

import java.util.List;

import java.util.Set;

import java.util.concurrent.atomic.AtomicLong;

 

public class AtomicLongTest {

 

    /**

     * @param args

     */

    public static void main(String[] args) {

        final AtomicLong orderIdGenerator = new AtomicLong(0);

        final List orders = Collections

                .synchronizedList(new ArrayList());

 

        for (int i = 0; i < 10; i++) {

            Thread orderCreationThread = new Thread(new Runnable() {

                public void run() {

                    for (int i = 0; i < 10; i++) {

                        long orderId = orderIdGenerator.incrementAndGet();

                        Item order = new Item(Thread.currentThread().getName(),

                                orderId);

                        orders.add(order);

                    }

                }

            });

            orderCreationThread.setName("Order Creation Thread " + i);

            orderCreationThread.start();

        }

        try {

            Thread.sleep(1000);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        Set orderIds = new HashSet();

        for (Item order : orders) {

            orderIds.add(order.getID());

             System.out.println("Order name:" + order.getItemName()

+"----"+"Order id:" + order.getID());

        }

    }

}

 

class Item {

    String itemName;

    long id;

 

    Item(String n, long id) {

        this.itemName = n;

        this.id = id;

    }

 

    public String getItemName() {

        return itemName;

    }

 

    public long getID() {

        return id;

    }

}

 

输出:

Order name:Order Creation Thread 0----Order id:1

Order name:Order Creation Thread 1----Order id:2

Order name:Order Creation Thread 0----Order id:4

Order name:Order Creation Thread 1----Order id:5

Order name:Order Creation Thread 3----Order id:3

Order name:Order Creation Thread 0----Order id:7

Order name:Order Creation Thread 1----Order id:6

........

Order name:Order Creation Thread 2----Order id:100

从运行结果咱们看到,无论是哪一个线程。它们得到的ID是不会重复的,保证的ID生成的原子性,避免了线程安全上的问题。

[if !supportLists]3) [endif]java.util.concurrent.lock包 

待续...

(三)多线程面试题

[if !supportLists]1. [endif]多线程建立方式2017-11-23-wzz

(1)、继承Thread类:但Thread本质上也是实现了Runnable接口的一个实例,它表明一个线程的实例,而且,启动线程的惟一方法就是经过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,经过本身的类直接extend Thread,并复写run()方法,就能够启动新线程并执行本身定义的run()方法。例如:继承Thread类实现多线程,并在合适的地方启动线程

[if !supportLists]1.[endif]public class MyThread extends Thread {  

[if !supportLists]2.[endif]  public void run() {

[if !supportLists]3.[endif]  System.out.println("MyThread.run()");

[if !supportLists]4.[endif]  }

[if !supportLists]5.[endif]}

 

[if !supportLists]6.[endif]MyThread myThread1 = new MyThread();  

[if !supportLists]7.[endif]MyThread myThread2 = new MyThread();  

[if !supportLists]8.[endif]myThread1.start();  

[if !supportLists]9.[endif]myThread2.start();

 

(2)、实现Runnable接口的方式实现多线程,而且实例化Thread,传入本身的Thread实例,调用run( )方法

[if !supportLists]1.[endif]public class MyThread implements Runnable {  

[if !supportLists]2.[endif]  public void run() {

[if !supportLists]3.[endif]  System.out.println("MyThread.run()");

[if !supportLists]4.[endif]  }

[if !supportLists]5.[endif]}

[if !supportLists]6.[endif]MyThread myThread = new MyThread();  

[if !supportLists]7.[endif]Thread thread = new Thread(myThread);  

[if !supportLists]8.[endif]thread.start();

[if !supportLists](3)[endif]、使用ExecutorService、Callable、Future实现有返回结果的多线程:ExecutorService、Callable、Future这个对象实际上都是属于Executor框架中的功能类。想要详细了解Executor框架的能够访问http://www.javaeye.com/topic/366591,这里面对该框架作了很详细的解释。返回结果的线程是在JDK1.5中引入的新特征,确实很实用,有了这种特征我就不须要再为了获得返回值而大费周折了,并且即使实现了也可能漏洞百出。可返回值的任务必须实现Callable接口,相似的,无返回值的任务必须Runnable接口。执行Callable任务后,能够获取一个Future的对象,在该对象上调用get就能够获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就能够实现传说中有返回结果的多线程了。下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问题能够直接使用。代码以下:

[if !supportLists]1.[endif]import java.util.concurrent.*;  

[if !supportLists]2.[endif]import java.util.Date;  

[if !supportLists]3.[endif]import java.util.List;  

[if !supportLists]4.[endif]import java.util.ArrayList;  

[if !supportLists]5.[endif]  

[if !supportLists]6.[endif]/**

[if !supportLists]7.[endif]*有返回值的线程

[if !supportLists]8.[endif]*/  

[if !supportLists]9.[endif]@SuppressWarnings("unchecked")  

[if !supportLists]10.[endif]public class Test {  

[if !supportLists]11.[endif]public static void main(String[] args) throws ExecutionException,  

[if !supportLists]12.[endif]    InterruptedException {  

[if !supportLists]13.[endif]System.out.println("----程序开始运行----");  

[if !supportLists]14.[endif]   Date date1 = new Date();  

[if !supportLists]15.[endif]  

[if !supportLists]16.[endif]   int taskSize = 5;  

[if !supportLists]17.[endif]//建立一个线程池  

[if !supportLists]18.[endif]   ExecutorService pool = Executors.newFixedThreadPool(taskSize);  

[if !supportLists]19.[endif]//建立多个有返回值的任务  

[if !supportLists]20.[endif]   List list = new ArrayList();  

[if !supportLists]21.[endif]   for (int i = 0; i < taskSize; i++) {  

[if !supportLists]22.[endif]    Callable c = new MyCallable(i + " ");  

[if !supportLists]23.[endif]//执行任务并获取Future对象  

[if !supportLists]24.[endif]    Future f = pool.submit(c);  

[if !supportLists]25.[endif]    // System.out.println(">>>" + f.get().toString());  

[if !supportLists]26.[endif]    list.add(f);  

[if !supportLists]27.[endif]   }  

[if !supportLists]28.[endif]//关闭线程池  

[if !supportLists]29.[endif]   pool.shutdown();  

[if !supportLists]30.[endif]  

[if !supportLists]31.[endif]//获取全部并发任务的运行结果  

[if !supportLists]32.[endif]   for (Future f : list) {  

[if !supportLists]33.[endif]//从Future对象上获取任务的返回值,并输出到控制台  

[if !supportLists]34.[endif]    System.out.println(">>>" + f.get().toString());  

[if !supportLists]35.[endif]   }  

[if !supportLists]36.[endif]  

[if !supportLists]37.[endif]   Date date2 = new Date();  

[if !supportLists]38.[endif]System.out.println("----程序结束运行----,程序运行时间【"  

[if !supportLists]39.[endif]+ (date2.getTime() - date1.getTime()) + "毫秒】");  

[if !supportLists]40.[endif]}  

[if !supportLists]41.[endif]}  

[if !supportLists]42.[endif]  

[if !supportLists]43.[endif]class MyCallable implements Callable {  

[if !supportLists]44.[endif]private String taskNum;  

[if !supportLists]45.[endif]  

[if !supportLists]46.[endif]MyCallable(String taskNum) {  

[if !supportLists]47.[endif]   this.taskNum = taskNum;  

[if !supportLists]48.[endif]}  

[if !supportLists]49.[endif]  

[if !supportLists]50.[endif]public Object call() throws Exception {  

[if !supportLists]51.[endif]System.out.println(">>>" + taskNum + "任务启动");  

[if !supportLists]52.[endif]   Date dateTmp1 = new Date();  

[if !supportLists]53.[endif]   Thread.sleep(1000);  

[if !supportLists]54.[endif]   Date dateTmp2 = new Date();  

[if !supportLists]55.[endif]   long time = dateTmp2.getTime() - dateTmp1.getTime();  

[if !supportLists]56.[endif]System.out.println(">>>" + taskNum + "任务终止");  

[if !supportLists]57.[endif]return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";  

[if !supportLists]58.[endif]}  

[if !supportLists]59.[endif]}  

[if !supportLists]2. [endif]在java中wait和sleep方法的不一样?

最大的不一样是在等待时wait会释放锁,而sleep一直持有锁。wait一般被用于线程间交互,sleep一般被用于暂停执行。

[if !supportLists]3. [endif]synchronized和volatile关键字的做用

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰以后,那么就具有了两层语义:

  1)保证了不一样线程对这个变量进行操做时的可见性,即一个线程修改了某个变量的值,这新值对其余线程来讲是当即可见的。

  2)禁止进行指令重排序。

volatile本质是在告诉jvm当前变量在寄存器(工做内存)中的值是不肯定的,须要从主存中读取;

synchronized则是锁定当前变量,只有当前线程能够访问该变量,其余线程被阻塞住。

[if !supportLists]1.[endif]volatile仅能使用在变量级别;

synchronized则能够使用在变量、方法、和类级别的

[if !supportLists]2.[endif]volatile仅能实现变量的修改可见性,并不能保证原子性;

synchronized则能够保证变量的修改可见性和原子性

[if !supportLists]3.[endif]volatile不会形成线程的阻塞;

synchronized可能会形成线程的阻塞。

[if !supportLists]4.[endif]volatile标记的变量不会被编译器优化;

synchronized标记的变量能够被编译器优化

[if !supportLists]4. [endif]分析线程并发访问代码解释缘由

[if !supportLists]1. [endif]public class Counter {

[if !supportLists]2. [endif] private volatile int count = 0;

[if !supportLists]3. [endif] public void inc(){

[if !supportLists]4. [endif] try {

[if !supportLists]5. [endif] Thread.sleep(3);

[if !supportLists]6. [endif] } catch (InterruptedException e) {

[if !supportLists]7. [endif] e.printStackTrace();

[if !supportLists]8. [endif] }

[if !supportLists]9. [endif] count++;

[if !supportLists]10. [endif] }

[if !supportLists]11. [endif] @Override

[if !supportLists]12. [endif] public String toString() {

[if !supportLists]13. [endif] return "[count=" + count + "]";

[if !supportLists]14. [endif] }

[if !supportLists]15. [endif]}

[if !supportLists]16. [endif]//---------------------------------华丽的分割线-----------------------------

[if !supportLists]17. [endif]public class VolatileTest {

[if !supportLists]18. [endif] public static void main(String[] args) {

[if !supportLists]19. [endif] final Counter counter = new Counter();

[if !supportLists]20. [endif] for(int i=0;i<1000;i++){

[if !supportLists]21. [endif] new Thread(new Runnable() {

[if !supportLists]22. [endif] @Override

[if !supportLists]23. [endif] public void run() {

[if !supportLists]24. [endif] counter.inc();

[if !supportLists]25. [endif] }

[if !supportLists]26. [endif] }).start();

[if !supportLists]27. [endif] }

[if !supportLists]28. [endif] System.out.println(counter);

[if !supportLists]29. [endif] }

[if !supportLists]30. [endif]}

上面的代码执行完后输出的结果肯定为1000吗?

答案是不必定,或者不等于1000。这是为何吗?

在java 的内存模型中每个线程运行时都有一个线程栈,线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先经过对象的引用找到对应在堆内存的变量的值,而后把堆内存变量的具体值load到线程本地内存中,创建一个变量副本,以后线程就再也不和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,在修改完以后的某一个时刻(线程退出以前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。

也就是说上面主函数中开启了1000个子线程,每一个线程都有一个变量副本,每一个线程修改变量只是临时修改了本身的副本,当线程结束时再将修改的值写入在主内存中,这样就出现了线程安全问题。所以结果就不可能等于1000了,通常都会小于1000。

上面的解释用一张图表示以下:

(图片来自网络,非本人所绘)

[if !supportLists]5. [endif]什么是线程池,如何使用?

线程池就是事先将多个线程对象放到一个容器中,当使用的时候就不用new线程而是直接去池中拿线程便可,节省了开辟子线程的时间,提升的代码执行效率。

在JDK的java.util.concurrent.Executors中提供了生成多种线程池的静态方法。

[if !supportLists]1. [endif]ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();

[if !supportLists]2. [endif]ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(4);

[if !supportLists]3. [endif]ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(4);

[if !supportLists]4. [endif]ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();

而后调用他们的execute方法便可。

[if !supportLists]6. [endif]经常使用的线程池有哪些?(2017-11-23-wzz

newSingleThreadExecutor:建立一个单线程的线程池,此线程池保证全部任务的执行顺序按照任务的提交顺序执行。

newFixedThreadPool:建立固定大小的线程池,每次提交一个任务就建立一个线程,直到线程达到线程池的最大大小。

newCachedThreadPool:建立一个可缓存的线程池,此线程池不会对线程池大小作限制,线程池大小彻底依赖于操做系统(或者说JVM)可以建立的最大线程大小。

newScheduledThreadPool:建立一个大小无限的线程池,此线程池支持定时以及周期性执行任务的需求。

newSingleThreadExecutor:建立一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

[if !supportLists]7. [endif]请叙述一下您对线程池的理解?(2015-11-25)

(若是问到了这样的问题,能够展开的说一下线程池如何用、线程池的好处、线程池的启动策略)

合理利用线程池可以带来三个好处。

第一:下降资源消耗。经过重复利用已建立的线程下降线程建立和销毁形成的消耗。

第二:提升响应速度。当任务到达时,任务能够不须要等到线程建立就能当即执行。

第三:提升线程的可管理性。线程是稀缺资源,若是无限制的建立,不只会消耗系统资源,还会下降系统的稳定性,使用线程池能够进行统一的分配,调优和监控。

[if !supportLists]8. [endif]线程池的启动策略?(2015-11-25)

官方对线程池的执行过程描述以下:

[if !supportLists]26. [endif] /*

[if !supportLists]27. [endif]         * Proceed in 3 steps:

[if !supportLists]28. [endif]         *

[if !supportLists]29. [endif]         * 1. If fewer than corePoolSize threads are running, try to

[if !supportLists]30. [endif]         * start a new thread with the given command as its first

[if !supportLists]31. [endif]         * task.  The call to addWorker atomically checks runState and

[if !supportLists]32. [endif]         * workerCount, and so prevents false alarms that would add

[if !supportLists]33. [endif]         * threads when it shouldn't, by returning false.

[if !supportLists]34. [endif]         *

[if !supportLists]35. [endif]         * 2. If a task can be successfully queued, then we still need

[if !supportLists]36. [endif]         * to double-check whether we should have added a thread

[if !supportLists]37. [endif]         * (because existing ones died since last checking) or that

[if !supportLists]38. [endif]         * the pool shut down since entry into this method. So we

[if !supportLists]39. [endif]         * recheck state and if necessary roll back the enqueuing if

[if !supportLists]40. [endif]         * stopped, or start a new thread if there are none.

[if !supportLists]41. [endif]         *

[if !supportLists]42. [endif]         * 3. If we cannot queue task, then we try to add a new

[if !supportLists]43. [endif]         * thread.  If it fails, we know we are shut down or saturated

[if !supportLists]44. [endif]         * and so reject the task.

[if !supportLists]45. [endif]         */

一、线程池刚建立时,里面没有一个线程。任务队列是做为参数传进来的。不过,就算队列里面有任务,线程池也不会立刻执行它们。

二、当调用execute() 方法添加一个任务时,线程池会作以下判断:

     a. 若是正在运行的线程数量小于 corePoolSize,那么立刻建立线程运行这个任务;

  b. 若是正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。

  c. 若是这时候队列满了,并且正在运行的线程数量小于 maximumPoolSize,那么仍是要建立线程运行这个任务;

  d. 若是队列满了,并且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。

三、当一个线程完成任务时,它会从队列中取下一个任务来执行。

四、当一个线程无事可作,超过必定的时间(keepAliveTime)时,线程池会判断,若是当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。因此线程池的全部任务完成后,它最终会收缩到 corePoolSize 的大小。

 

[if !supportLists]9. [endif]如何控制某个方法容许并发访问线程的个数?(2015-11-30)

[if !supportLists]1. [endif]package com.yange;

[if !supportLists]2. [endif]

[if !supportLists]3. [endif]import java.util.concurrent.Semaphore;

[if !supportLists]4. [endif]/**

[if !supportLists]5. [endif] *

[if !supportLists]6. [endif] * @author wzy 2015-11-30

[if !supportLists]7. [endif] *

[if !supportLists]8. [endif] */

[if !supportLists]9. [endif]public class SemaphoreTest {

[if !supportLists]10. [endif] /*

[if !supportLists]11. [endif]* permits the initial number of permits available. This value may be negative,

[if !supportLists]12. [endif]in which case releases must occur before any acquires will be granted.

[if !supportLists]13. [endif]fair true if this semaphore will guarantee first-in first-out granting of

[if !supportLists]14. [endif]permits under contention, else false

[if !supportLists]15. [endif]*/

[if !supportLists]16. [endif] static Semaphore semaphore = new Semaphore(5,true);

[if !supportLists]17. [endif] public static void main(String[] args) {

[if !supportLists]18. [endif] for(int i=0;i<100;i++){

[if !supportLists]19. [endif] new Thread(new Runnable() {

[if !supportLists]20. [endif]

[if !supportLists]21. [endif] @Override

[if !supportLists]22. [endif] public void run() {

[if !supportLists]23. [endif] test();

[if !supportLists]24. [endif] }

[if !supportLists]25. [endif] }).start();

[if !supportLists]26. [endif] }

[if !supportLists]27. [endif]

[if !supportLists]28. [endif] }

[if !supportLists]29. [endif]

[if !supportLists]30. [endif] public static void test(){

[if !supportLists]31. [endif] try {

[if !supportLists]32. [endif]//申请一个请求

[if !supportLists]33. [endif] semaphore.acquire();

[if !supportLists]34. [endif] } catch (InterruptedException e1) {

[if !supportLists]35. [endif] e1.printStackTrace();

[if !supportLists]36. [endif] }

[if !supportLists]37. [endif] System.out.println(Thread.currentThread().getName()+"进来了");

[if !supportLists]38. [endif] try {

[if !supportLists]39. [endif] Thread.sleep(1000);

[if !supportLists]40. [endif] } catch (InterruptedException e) {

[if !supportLists]41. [endif] e.printStackTrace();

[if !supportLists]42. [endif] }

[if !supportLists]43. [endif] System.out.println(Thread.currentThread().getName()+"走了");

[if !supportLists]44. [endif]//释放一个请求

[if !supportLists]45. [endif] semaphore.release();

[if !supportLists]46. [endif] }

[if !supportLists]47. [endif]}

能够使用Semaphore控制,第16行的构造函数建立了一个Semaphore对象,而且初始化了5个信号。这样的效果是控件test方法最多只能有5个线程并发访问,对于5个线程时就排队等待,走一个来一下。第33行,请求一个信号(消费一个信号),若是信号被用完了则等待,第45行释放一个信号,释放的信号新的线程就能够使用了。

[if !supportLists]10. [endif]三个线程a、b、c并发运行,b,c须要a线程的数据怎么实现(上海3期学员提供)

根据问题的描述,我将问题用如下代码演示,ThreadA、ThreadB、ThreadC,ThreadA用于初始化数据num,只有当num初始化完成以后再让ThreadB和ThreadC获取到初始化后的变量num。

分析过程以下:

考虑到多线程的不肯定性,所以咱们不能确保ThreadA就必定先于ThreadB和ThreadC前执行,就算ThreadA先执行了,咱们也没法保证ThreadA何时才能将变量num给初始化完成。所以咱们必须让ThreadB和ThreadC去等待ThreadA完成任何后发出的消息。

如今须要解决两个难题,一是让ThreadB和ThreadC等待ThreadA先执行完,二是ThreadA执行完以后给ThreadB和ThreadC发送消息。

解决上面的难题我能想到的两种方案,一是使用纯Java API的Semaphore类来控制线程的等待和释放,二是使用Android提供的Handler消息机制。

[if !supportLists]1. [endif]package com.example;

[if !supportLists]2. [endif]/**

[if !supportLists]3. [endif]*三个线程a、b、c并发运行,b,c须要a线程的数据怎么实现(上海3期学员提供)

[if !supportLists]4. [endif] *

[if !supportLists]5. [endif] */

[if !supportLists]6. [endif]public class ThreadCommunication {

[if !supportLists]7. [endif] private static int num;//定义一个变量做为数据

[if !supportLists]8. [endif]

[if !supportLists]9. [endif] public static void main(String[] args) {

[if !supportLists]10. [endif]

[if !supportLists]11. [endif] Thread threadA = new Thread(new Runnable() {

[if !supportLists]12. [endif]

[if !supportLists]13. [endif] @Override

[if !supportLists]14. [endif] public void run() {

[if !supportLists]15. [endif] try {

[if !supportLists]16. [endif] //模拟耗时操做以后初始化变量num

[if !supportLists]17. [endif] Thread.sleep(1000);

[if !supportLists]18. [endif] num = 1;

[if !supportLists]19. [endif]

[if !supportLists]20. [endif] } catch (InterruptedException e) {

[if !supportLists]21. [endif] e.printStackTrace();

[if !supportLists]22. [endif] }

[if !supportLists]23. [endif] }

[if !supportLists]24. [endif] });

[if !supportLists]25. [endif] Thread threadB = new Thread(new Runnable() {

[if !supportLists]26. [endif]

[if !supportLists]27. [endif] @Override

[if !supportLists]28. [endif] public void run() {

[if !supportLists]29. [endif] System.out.println(Thread.currentThread().getName()+"获取到num的值为:"+num);

[if !supportLists]30. [endif] }

[if !supportLists]31. [endif] });

[if !supportLists]32. [endif] Thread threadC = new Thread(new Runnable() {

[if !supportLists]33. [endif]

[if !supportLists]34. [endif] @Override

[if !supportLists]35. [endif] public void run() {

[if !supportLists]36. [endif] System.out.println(Thread.currentThread().getName()+"获取到num的值为:"+num);

[if !supportLists]37. [endif] }

[if !supportLists]38. [endif] });

[if !supportLists]39. [endif] //同时开启3个线程

[if !supportLists]40. [endif] threadA.start();

[if !supportLists]41. [endif] threadB.start();

[if !supportLists]42. [endif] threadC.start();

[if !supportLists]43. [endif]

[if !supportLists]44. [endif] }

[if !supportLists]45. [endif]}

[if !supportLists]46. [endif]

解决方案一:

[if !supportLists]1. [endif]public class ThreadCommunication {

[if !supportLists]2. [endif] private static int num;

[if !supportLists]3. [endif] /**

[if !supportLists]4. [endif] *定义一个信号量,该类内部维持了多个线程锁,能够阻塞多个线程,释放多个线程,

[if !supportLists]5. [endif]线程的阻塞和释放是经过permit概念来实现的

[if !supportLists]6. [endif] *线程经过semaphore.acquire()方法获取permit,若是当前semaphore有permit则分配给该线程,

[if !supportLists]7. [endif]若是没有则阻塞该线程直到semaphore

[if !supportLists]8. [endif] *调用release()方法释放permit。

[if !supportLists]9. [endif] *构造函数中参数:permit(容许) 个数,

[if !supportLists]10. [endif]  */

[if !supportLists]11. [endif] private static Semaphore semaphore = new Semaphore(0);

[if !supportLists]12. [endif] public static void main(String[] args) {

[if !supportLists]13. [endif]

[if !supportLists]14. [endif] Thread threadA = new Thread(new Runnable() {

[if !supportLists]15. [endif]

[if !supportLists]16. [endif] @Override

[if !supportLists]17. [endif] public void run() {

[if !supportLists]18. [endif] try {

[if !supportLists]19. [endif] //模拟耗时操做以后初始化变量num

[if !supportLists]20. [endif] Thread.sleep(1000);

[if !supportLists]21. [endif] num = 1;

[if !supportLists]22. [endif] //初始化完参数后释放两个permit

[if !supportLists]23. [endif] semaphore.release(2);

[if !supportLists]24. [endif]

[if !supportLists]25. [endif] } catch (InterruptedException e) {

[if !supportLists]26. [endif] e.printStackTrace();

[if !supportLists]27. [endif] }

[if !supportLists]28. [endif] }

[if !supportLists]29. [endif] });

[if !supportLists]30. [endif] Thread threadB = new Thread(new Runnable() {

[if !supportLists]31. [endif]

[if !supportLists]32. [endif] @Override

[if !supportLists]33. [endif] public void run() {

[if !supportLists]34. [endif] try {

[if !supportLists]35. [endif] //获取permit,若是semaphore没有可用的permit则等待,若是有则消耗一个

[if !supportLists]36. [endif] semaphore.acquire();

[if !supportLists]37. [endif] } catch (InterruptedException e) {

[if !supportLists]38. [endif] e.printStackTrace();

[if !supportLists]39. [endif] }

[if !supportLists]40. [endif] System.out.println(Thread.currentThread().getName()+"获取到num的值为:"+num);

[if !supportLists]41. [endif] }

[if !supportLists]42. [endif] });

[if !supportLists]43. [endif] Thread threadC = new Thread(new Runnable() {

[if !supportLists]44. [endif]

[if !supportLists]45. [endif] @Override

[if !supportLists]46. [endif] public void run() {

[if !supportLists]47. [endif] try {

[if !supportLists]48. [endif] //获取permit,若是semaphore没有可用的permit则等待,若是有则消耗一个

[if !supportLists]49. [endif] semaphore.acquire();

[if !supportLists]50. [endif] } catch (InterruptedException e) {

[if !supportLists]51. [endif] e.printStackTrace();

[if !supportLists]52. [endif] }

[if !supportLists]53. [endif] System.out.println(Thread.currentThread().getName()+"获取到num的值为:"+num);

[if !supportLists]54. [endif] }

[if !supportLists]55. [endif] });

[if !supportLists]56. [endif] //同时开启3个线程

[if !supportLists]57. [endif] threadA.start();

[if !supportLists]58. [endif] threadB.start();

[if !supportLists]59. [endif] threadC.start();

[if !supportLists]60. [endif]

[if !supportLists]61. [endif] }

[if !supportLists]62. [endif]}

[if !supportLists]11. [endif]同一个类中的2个方法都加了同步锁,多个线程能同时访问同一个类中的这两个方法吗?(2017-2-24)

这个问题须要考虑到Lock与synchronized 两种实现锁的不一样情形。由于这种状况下使用Lock 和synchronized 会有大相径庭的结果。Lock能够让等待锁的线程响应中断,Lock获取锁,以后须要释放锁。以下代码,多个线程不可访问同一个类中的2个加了Lock锁的方法。

[if !supportLists]63. [endif]package com;

[if !supportLists]64. [endif]import java.util.concurrent.locks.Lock;

[if !supportLists]65. [endif]import java.util.concurrent.locks.ReentrantLock;

[if !supportLists]66. [endif]public class qq {

[if !supportLists]67. [endif]

[if !supportLists]68. [endif]     private int count = 0;

[if !supportLists]69. [endif]private Lock lock = new ReentrantLock();//设置lock锁

[if !supportLists]70. [endif]//方法1

[if !supportLists]71. [endif]    public Runnable run1 = new Runnable(){

[if !supportLists]72. [endif]        public void run() {

[if !supportLists]73. [endif]lock.lock();  //加锁

[if !supportLists]74. [endif]                while(count < 1000) {

[if !supportLists]75. [endif]                    try {     

[if !supportLists]76. [endif]//打印是否执行该方法

[if !supportLists]77. [endif]                        System.out.println(Thread.currentThread().getName() + " run1: "+count++);

[if !supportLists]78. [endif]                    } catch (Exception e) {

[if !supportLists]79. [endif]                        e.printStackTrace();

[if !supportLists]80. [endif]                    }

[if !supportLists]81. [endif]                }

[if !supportLists]82. [endif]            }

[if !supportLists]83. [endif]            lock.unlock();

[if !supportLists]84. [endif]        }};

[if !supportLists]85. [endif]//方法2

[if !supportLists]86. [endif]    public Runnable run2 = new Runnable(){

[if !supportLists]87. [endif]            public void run() {

[if !supportLists]88. [endif]               lock.lock();

[if !supportLists]89. [endif]                    while(count < 1000) {

[if !supportLists]90. [endif]                        try {

[if !supportLists]91. [endif]                            System.out.println(Thread.currentThread().getName() +

[if !supportLists]92. [endif]                             " run2: "+count++);

[if !supportLists]93. [endif]                        } catch (Exception e) {

[if !supportLists]94. [endif]                            e.printStackTrace();

[if !supportLists]95. [endif]                        }

[if !supportLists]96. [endif]                    }

[if !supportLists]97. [endif]                lock.unlock();

[if !supportLists]98. [endif]            }};

[if !supportLists]99. [endif]             

[if !supportLists]100. [endif]   

[if !supportLists]101. [endif]     

[if !supportLists]102. [endif]    public static void main(String[] args) throws InterruptedException {

[if !supportLists]103. [endif]     qq t = new qq();   //建立一个对象

[if !supportLists]104. [endif]new Thread(t.run1).start();//获取该对象的方法1

[if !supportLists]105. [endif]

[if !supportLists]106. [endif]new Thread(t.run2).start();//获取该对象的方法2

[if !supportLists]107. [endif]    }

[if !supportLists]108. [endif]}

结果是:

Thread-0 run1: 0

Thread-0 run1: 1

Thread-0 run1: 2

Thread-0 run1: 3

Thread-0 run1: 4

Thread-0 run1: 5

Thread-0 run1: 6

........

而synchronized却不行,使用synchronized时,当咱们访问同一个类对象的时候,是同一把锁,因此能够访问该对象的其余synchronized方法。代码以下:

[if !supportLists]1.[endif]package com;

[if !supportLists]2.[endif]import java.util.concurrent.locks.Lock;

[if !supportLists]3.[endif]import java.util.concurrent.locks.ReentrantLock;

[if !supportLists]4.[endif]public class qq {

[if !supportLists]5.[endif] private int count = 0;

[if !supportLists]6.[endif]    private Lock lock = new ReentrantLock();

[if !supportLists]7.[endif]    public Runnable run1 = new Runnable(){

[if !supportLists]8.[endif]        public void run() {

[if !supportLists]9.[endif]synchronized(this) {  //设置关键字synchronized,以当前类为锁

[if !supportLists]10.[endif]                while(count < 1000) {

[if !supportLists]11.[endif]                    try {

[if !supportLists]12.[endif]//打印是否执行该方法

[if !supportLists]13.[endif]                        System.out.println(Thread.currentThread().getName() + " run1: "+count++);

[if !supportLists]14.[endif]                    } catch (Exception e) {

[if !supportLists]15.[endif]                        e.printStackTrace();

[if !supportLists]16.[endif]                    }

[if !supportLists]17.[endif]                }

[if !supportLists]18.[endif]            }

[if !supportLists]19.[endif]        }};    

[if !supportLists]20.[endif]    public Runnable run2 = new Runnable(){

[if !supportLists]21.[endif]            public void run() {

[if !supportLists]22.[endif]                synchronized(this) {

[if !supportLists]23.[endif]                    while(count < 1000) {

[if !supportLists]24.[endif]                        try {

[if !supportLists]25.[endif]                            System.out.println(Thread.currentThread().getName()

[if !supportLists]26.[endif]                             + " run2: "+count++);

[if !supportLists]27.[endif]                        } catch (Exception e) {

[if !supportLists]28.[endif]                            e.printStackTrace();

[if !supportLists]29.[endif]                        }

[if !supportLists]30.[endif]                    }

[if !supportLists]31.[endif]                }

[if !supportLists]32.[endif]            }};

[if !supportLists]33.[endif]    public static void main(String[] args) throws InterruptedException {

[if !supportLists]34.[endif]       qq t = new qq(); //建立一个对象

[if !supportLists]35.[endif]new Thread(t.run1).start(); //获取该对象的方法1

[if !supportLists]36.[endif]new Thread(t.run2).start(); //获取该对象的方法2

[if !supportLists]37.[endif]    }

[if !supportLists]38.[endif]}

结果为:

Thread-1 run2: 0

Thread-1 run2: 1

Thread-1 run2: 2

Thread-0 run1: 0

Thread-0 run1: 4 Thread-0 run1: 5 Thread-0 run1: 6

     ......

 

[if !supportLists]12. [endif]什么状况下致使线程死锁,遇到线程死锁该怎么解决?(2017-2-24)

11.1 死锁的定义:所谓死锁是指多个线程因竞争资源而形成的一种僵局(互相等待),若无外力做用,这些进程都将没法向前推动。

11.2 死锁产生的必要条件:

互斥条件:线程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某资源仅为一个线程所占有。此时如有其余线程请求该资源,则请求线程只能等待。

不剥夺条件:线程所得到的资源在未使用完毕以前,不能被其余线程强行夺走,即只能由得到该资源的线程本身来释放(只能是主动释放)。

请求和保持条件:线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其余线程占有,此时请求进程被阻塞,但对本身已得到的资源保持不放。

循环等待条件:存在一种线程资源的循环等待链,链中每个线程已得到的资源同时被链中下一个线程所请求。即存在一个处于等待状态的线程集合{Pl, P2, ..., pn},其中Pi等待的资源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的资源被P0占有,如图2-15所示。

 

 

11.3 产生死锁的一个例子

[if !supportLists]1.[endif]package itheima.com;

[if !supportLists]2.[endif]/**  

[if !supportLists]3.[endif]*一个简单的死锁类  

[if !supportLists]4.[endif]*当DeadLock类的对象flag==1时(td1),先锁定o1,睡眠500毫秒  

[if !supportLists]5.[endif]*而td1在睡眠的时候另外一个flag==0的对象(td2)线程启动,先锁定o2,睡眠500毫秒  

[if !supportLists]6.[endif]* td1睡眠结束后须要锁定o2才能继续执行,而此时o2已被td2锁定;  

[if !supportLists]7.[endif]* td2睡眠结束后须要锁定o1才能继续执行,而此时o1已被td1锁定;  

[if !supportLists]8.[endif]* td一、td2相互等待,都须要获得对方锁定的资源才能继续执行,从而死锁。  

[if !supportLists]9.[endif]*/    

[if !supportLists]10.[endif]public class DeadLock implements Runnable {    

[if !supportLists]11.[endif]    public int flag = 1;    

[if !supportLists]12.[endif]//静态对象是类的全部对象共享的    

[if !supportLists]13.[endif]    private static Object o1 = new Object(), o2 = new Object();    

[if !supportLists]14.[endif]    public void run() {    

[if !supportLists]15.[endif]        System.out.println("flag=" + flag);    

[if !supportLists]16.[endif]        if (flag == 1) {    

[if !supportLists]17.[endif]            synchronized (o1) {    

[if !supportLists]18.[endif]                try {    

[if !supportLists]19.[endif]                    Thread.sleep(500);    

[if !supportLists]20.[endif]                } catch (Exception e) {    

[if !supportLists]21.[endif]                    e.printStackTrace();    

[if !supportLists]22.[endif]                }    

[if !supportLists]23.[endif]                synchronized (o2) {    

[if !supportLists]24.[endif]                    System.out.println("1");    

[if !supportLists]25.[endif]                }    

[if !supportLists]26.[endif]            }    

[if !supportLists]27.[endif]        }    

[if !supportLists]28.[endif]        if (flag == 0) {    

[if !supportLists]29.[endif]            synchronized (o2) {    

[if !supportLists]30.[endif]                try {    

[if !supportLists]31.[endif]                    Thread.sleep(500);    

[if !supportLists]32.[endif]                } catch (Exception e) {    

[if !supportLists]33.[endif]                    e.printStackTrace();    

[if !supportLists]34.[endif]                }    

[if !supportLists]35.[endif]                synchronized (o1) {    

[if !supportLists]36.[endif]                    System.out.println("0");    

[if !supportLists]37.[endif]                }    

[if !supportLists]38.[endif]            }    

[if !supportLists]39.[endif]        }    

[if !supportLists]40.[endif]    }      

[if !supportLists]41.[endif]    public static void main(String[] args) {       

[if !supportLists]42.[endif]        DeadLock td1 = new DeadLock();    

[if !supportLists]43.[endif]        DeadLock td2 = new DeadLock();    

[if !supportLists]44.[endif]        td1.flag = 1;    

[if !supportLists]45.[endif]        td2.flag = 0;    

[if !supportLists]46.[endif]//td1,td2都处于可执行状态,但JVM线程调度先执行哪一个线程是不肯定的。    

[if !supportLists]47.[endif]//td2的run()可能在td1的run()以前运行    

[if !supportLists]48.[endif]        new Thread(td1).start();    

[if !supportLists]49.[endif]        new Thread(td2).start();    

[if !supportLists]50.[endif]    }    

[if !supportLists]51.[endif]}  

11.4 如何避免死锁

在有些状况下死锁是能够避免的。两种用于避免死锁的技术:

[if !supportLists]1)[endif]加锁顺序(线程按照必定的顺序加锁)

[if !supportLists]1.[endif]package itheima.com;

[if !supportLists]2.[endif]public class DeadLock {    

[if !supportLists]3.[endif]    public int flag = 1;    

[if !supportLists]4.[endif]//静态对象是类的全部对象共享的    

[if !supportLists]5.[endif]    private static Object o1 = new Object(), o2 = new Object();    

[if !supportLists]6.[endif]    public void money(int flag) {

[if !supportLists]7.[endif]     this.flag=flag;

[if !supportLists]8.[endif]     if( flag ==1){

[if !supportLists]9.[endif]          synchronized (o1) {    

[if !supportLists]10.[endif]                 try {    

[if !supportLists]11.[endif]                     Thread.sleep(500);    

[if !supportLists]12.[endif]                 } catch (Exception e) {    

[if !supportLists]13.[endif]                     e.printStackTrace();    

[if !supportLists]14.[endif]                 }    

[if !supportLists]15.[endif]                 synchronized (o2) {    

[if !supportLists]16.[endif]System.out.println("当前的线程是"+

[if !supportLists]17.[endif]                     Thread.currentThread().getName()+" "+"flag的值"+"1");    

[if !supportLists]18.[endif]                 }    

[if !supportLists]19.[endif]             }        

[if !supportLists]20.[endif]       }        

[if !supportLists]21.[endif]        if(flag ==0){

[if !supportLists]22.[endif]         synchronized (o2) {    

[if !supportLists]23.[endif]                try {    

[if !supportLists]24.[endif]                    Thread.sleep(500);    

[if !supportLists]25.[endif]                } catch (Exception e) {    

[if !supportLists]26.[endif]                    e.printStackTrace();    

[if !supportLists]27.[endif]                }    

[if !supportLists]28.[endif]                synchronized (o1) {    

[if !supportLists]29.[endif]System.out.println("当前的线程是"+

[if !supportLists]30.[endif]                     Thread.currentThread().getName()+" "+"flag的值"+"0");    

[if !supportLists]31.[endif]                }    

[if !supportLists]32.[endif]            }      

[if !supportLists]33.[endif]        }

[if !supportLists]34.[endif]    }    

[if !supportLists]35.[endif]    

[if !supportLists]36.[endif]    public static void main(String[] args) {               

[if !supportLists]37.[endif]        final DeadLock td1 = new DeadLock();    

[if !supportLists]38.[endif]        final DeadLock td2 = new DeadLock();        

[if !supportLists]39.[endif]        td1.flag = 1;    

[if !supportLists]40.[endif]        td2.flag = 0;    

[if !supportLists]41.[endif]//td1,td2都处于可执行状态,但JVM线程调度先执行哪一个线程是不肯定的。    

[if !supportLists]42.[endif]//td2的run()可能在td1的run()以前运行    

[if !supportLists]43.[endif]        final Thread t1=new Thread(new Runnable(){

[if !supportLists]44.[endif] public void run() {

[if !supportLists]45.[endif] td1.flag = 1;

[if !supportLists]46.[endif] td1.money(1);

[if !supportLists]47.[endif] }        

[if !supportLists]48.[endif]        });

[if !supportLists]49.[endif]        t1.start();

[if !supportLists]50.[endif]        Thread t2= new Thread(new Runnable(){

[if !supportLists]51.[endif] public void run() {

[if !supportLists]52.[endif] // TODO Auto-generated method stub

[if !supportLists]53.[endif] try {

[if !supportLists]54.[endif] //让t2等待t1执行完

[if !supportLists]55.[endif] t1.join();//核心代码,让t1执行完后t2才会执行

[if !supportLists]56.[endif] } catch (InterruptedException e) {

[if !supportLists]57.[endif] // TODO Auto-generated catch block

[if !supportLists]58.[endif] e.printStackTrace();

[if !supportLists]59.[endif] }

[if !supportLists]60.[endif] td2.flag = 0;

[if !supportLists]61.[endif] td1.money(0);

[if !supportLists]62.[endif] }     

[if !supportLists]63.[endif]        });

[if !supportLists]64.[endif]        t2.start();

[if !supportLists]65.[endif]    }    

[if !supportLists]66.[endif]}   

结果:

当前的线程是Thread-0 flag的值1

当前的线程是Thread-1 flag的值0

[if !supportLists]2)[endif]加锁时限(线程尝试获取锁的时候加上必定的时限,超过期限则放弃对该锁的请求,并释放本身占有的锁)

[if !supportLists]1.[endif] package itheima.com;

[if !supportLists]2.[endif]import java.util.concurrent.TimeUnit;

[if !supportLists]3.[endif]import java.util.concurrent.locks.Lock;

[if !supportLists]4.[endif]import java.util.concurrent.locks.ReentrantLock;

[if !supportLists]5.[endif]public class DeadLock {    

[if !supportLists]6.[endif]    public int flag = 1;    

[if !supportLists]7.[endif]//静态对象是类的全部对象共享的    

[if !supportLists]8.[endif]    private static Object o1 = new Object(), o2 = new Object();    

[if !supportLists]9.[endif]    public void money(int flag) throws InterruptedException {   

[if !supportLists]10.[endif]     this.flag=flag;

[if !supportLists]11.[endif]     if( flag ==1){

[if !supportLists]12.[endif]          synchronized (o1) {                     

[if !supportLists]13.[endif]                     Thread.sleep(500);               

[if !supportLists]14.[endif]                 synchronized (o2) {    

[if !supportLists]15.[endif]System.out.println("当前的线程是"+

[if !supportLists]16.[endif]                     Thread.currentThread().getName()+" "+"flag的值"+"1");    

[if !supportLists]17.[endif]                 }    

[if !supportLists]18.[endif]             }        

[if !supportLists]19.[endif]       }        

[if !supportLists]20.[endif]        if(flag ==0){

[if !supportLists]21.[endif]         synchronized (o2) {   

[if !supportLists]22.[endif]                    Thread.sleep(500);       

[if !supportLists]23.[endif]                synchronized (o1) {    

[if !supportLists]24.[endif]System.out.println("当前的线程是"+

[if !supportLists]25.[endif]                     Thread.currentThread().getName()+" "+"flag的值"+"0");    

[if !supportLists]26.[endif]                }    

[if !supportLists]27.[endif]            }      

[if !supportLists]28.[endif]        }

[if !supportLists]29.[endif]    }    

[if !supportLists]30.[endif]    

[if !supportLists]31.[endif]    public static void main(String[] args) {    

[if !supportLists]32.[endif]     final Lock lock = new ReentrantLock();  

[if !supportLists]33.[endif]        final DeadLock td1 = new DeadLock();    

[if !supportLists]34.[endif]        final DeadLock td2 = new DeadLock();        

[if !supportLists]35.[endif]        td1.flag = 1;    

[if !supportLists]36.[endif]        td2.flag = 0;    

[if !supportLists]37.[endif]//td1,td2都处于可执行状态,但JVM线程调度先执行哪一个线程是不肯定的。    

[if !supportLists]38.[endif]//td2的run()可能在td1的run()以前运行    

[if !supportLists]39.[endif]        

[if !supportLists]40.[endif]        final Thread t1=new Thread(new Runnable(){

[if !supportLists]41.[endif] public void run() {

[if !supportLists]42.[endif] // TODO Auto-generated method stub

[if !supportLists]43.[endif] String tName = Thread.currentThread().getName();

[if !supportLists]44.[endif]

[if !supportLists]45.[endif] td1.flag = 1;

[if !supportLists]46.[endif] try {  

[if !supportLists]47.[endif]//获取不到锁,就等5秒,若是5秒后仍是获取不到就返回false  

[if !supportLists]48.[endif]                    if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {  

[if !supportLists]49.[endif]                     System.out.println(tName + "获取到锁!");  

[if !supportLists]50.[endif]                    } else {  

[if !supportLists]51.[endif]                     System.out.println(tName + "获取不到锁!");  

[if !supportLists]52.[endif]                        return;  

[if !supportLists]53.[endif]                    }  

[if !supportLists]54.[endif]                } catch (Exception e) {  

[if !supportLists]55.[endif]                    e.printStackTrace();  

[if !supportLists]56.[endif]                }  

[if !supportLists]57.[endif]                  

[if !supportLists]58.[endif]                try {                         

[if !supportLists]59.[endif]                 td1.money(1);                     

[if !supportLists]60.[endif]                } catch (Exception e) {  

[if !supportLists]61.[endif]                 System.out.println(tName + "出错了!!!");  

[if !supportLists]62.[endif]                } finally {  

[if !supportLists]63.[endif]System.out.println("当前的线程是"+Thread.currentThread().getName()+"释放锁!!");  

[if !supportLists]64.[endif]                    lock.unlock();  

[if !supportLists]65.[endif]                }  

[if !supportLists]66.[endif] }        

[if !supportLists]67.[endif]        });

[if !supportLists]68.[endif]        t1.start();

[if !supportLists]69.[endif]        Thread t2= new Thread(new Runnable(){

[if !supportLists]70.[endif] public void run() {

[if !supportLists]71.[endif] String tName = Thread.currentThread().getName();

[if !supportLists]72.[endif] // TODO Auto-generated method stub

[if !supportLists]73.[endif] td1.flag = 1;

[if !supportLists]74.[endif] try {  

[if !supportLists]75.[endif]//获取不到锁,就等5秒,若是5秒后仍是获取不到就返回false  

[if !supportLists]76.[endif]                    if (lock.tryLock(5000, TimeUnit.MILLISECONDS)) {  

[if !supportLists]77.[endif]                     System.out.println(tName + "获取到锁!");  

[if !supportLists]78.[endif]                    } else {  

[if !supportLists]79.[endif]                     System.out.println(tName + "获取不到锁!");  

[if !supportLists]80.[endif]                        return;  

[if !supportLists]81.[endif]                    }  

[if !supportLists]82.[endif]                } catch (Exception e) {  

[if !supportLists]83.[endif]                    e.printStackTrace();  

[if !supportLists]84.[endif]                }  

[if !supportLists]85.[endif]                try {  

[if !supportLists]86.[endif]                 td2.money(0);  

[if !supportLists]87.[endif]                } catch (Exception e) {  

[if !supportLists]88.[endif]                 System.out.println(tName + "出错了!!!");

[if !supportLists]89.[endif]                } finally {  

[if !supportLists]90.[endif]System.out.println("当前的线程是"+Thread.currentThread().getName()+"释放锁!!");  

[if !supportLists]91.[endif]                    lock.unlock();  

[if !supportLists]92.[endif]                }

[if !supportLists]93.[endif] }  

[if !supportLists]94.[endif]        });

[if !supportLists]95.[endif]        t2.start();  

[if !supportLists]96.[endif]    }    

[if !supportLists]97.[endif]}   

打印结果:

Thread-0获取到锁!

当前的线程是Thread-0 flag的值1

当前的线程是Thread-0释放锁!!

Thread-1获取到锁!

当前的线程是Thread-1 flag的值0

当前的线程是Thread-1释放锁!!

 

[if !supportLists]13. [endif]Java中多线程间的通讯怎么实现?(2017-2-24)

线程通讯的方式:

[if !supportLists]1.[endif]共享变量

线程间通讯能够经过发送信号,发送信号的一个简单方式是在共享对象的变量里设置信号值。线程A在一个同步块里设置boolean型成员变量hasDataToProcess为true,线程B也在同步块里读取hasDataToProcess这个成员变量。这个简单的例子使用了一个持有信号的对象,并提供了set和get方法:

[if !supportLists]1.[endif]package itheima.com;

[if !supportLists]2.[endif]public class MySignal{

[if !supportLists]3.[endif]//共享的变量

[if !supportLists]4.[endif] private boolean hasDataToProcess=false;

[if !supportLists]5.[endif]//取值

[if !supportLists]6.[endif] public boolean getHasDataToProcess() {

[if !supportLists]7.[endif] return hasDataToProcess;

[if !supportLists]8.[endif] }

[if !supportLists]9.[endif]//存值

[if !supportLists]10.[endif] public void setHasDataToProcess(boolean hasDataToProcess) {

[if !supportLists]11.[endif] this.hasDataToProcess = hasDataToProcess;

[if !supportLists]12.[endif] }

[if !supportLists]13.[endif] public static void main(String[] args){

[if !supportLists]14.[endif]//同一个对象

[if !supportLists]15.[endif] final MySignal my=new MySignal();

[if !supportLists]16.[endif]//线程1设置hasDataToProcess值为true

[if !supportLists]17.[endif] final Thread t1=new Thread(new Runnable(){

[if !supportLists]18.[endif] public void run() {

[if !supportLists]19.[endif] my.setHasDataToProcess(true);

[if !supportLists]20.[endif] }

[if !supportLists]21.[endif] });

[if !supportLists]22.[endif] t1.start();

[if !supportLists]23.[endif]//线程2取这个值hasDataToProcess

[if !supportLists]24.[endif] Thread t2=new Thread(new Runnable(){

[if !supportLists]25.[endif] public void run() {

[if !supportLists]26.[endif] try {

[if !supportLists]27.[endif]//等待线程1完成而后取值

[if !supportLists]28.[endif] t1.join();

[if !supportLists]29.[endif] } catch (InterruptedException e) {

[if !supportLists]30.[endif] e.printStackTrace();

[if !supportLists]31.[endif] }

[if !supportLists]32.[endif] my.getHasDataToProcess();

[if !supportLists]33.[endif] System.out.println("t1改变之后的值:" + my.isHasDataToProcess());

[if !supportLists]34.[endif] }

[if !supportLists]35.[endif] });

[if !supportLists]36.[endif] t2.start();

[if !supportLists]37.[endif]}

[if !supportLists]38.[endif]}

结果:

t1改变之后的值:true

[if !supportLists]2.[endif]wait/notify机制

以资源为例,生产者生产一个资源,通知消费者就消费掉一个资源,生产者继续生产资源,消费者消费资源,以此循环。代码以下:

[if !supportLists]1.[endif]package itheima.com;

[if !supportLists]2.[endif]//资源类

[if !supportLists]3.[endif] class Resource{  

[if !supportLists]4.[endif]    private String name;  

[if !supportLists]5.[endif]    private int count=1;  

[if !supportLists]6.[endif]    private boolean flag=false;  

[if !supportLists]7.[endif]    public synchronized void set(String name){  

[if !supportLists]8.[endif]     //生产资源

[if !supportLists]9.[endif]        while(flag) {

[if !supportLists]10.[endif]            try{

[if !supportLists]11.[endif]             //线程等待。消费者消费资源

[if !supportLists]12.[endif]             wait();

[if !supportLists]13.[endif]             }catch(Exception e){}  

[if !supportLists]14.[endif]        }

[if !supportLists]15.[endif]        this.name=name+"---"+count++;  

[if !supportLists]16.[endif]System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);  

[if !supportLists]17.[endif]        flag=true;  

[if !supportLists]18.[endif]//唤醒等待中的消费者

[if !supportLists]19.[endif]        this.notifyAll();

[if !supportLists]20.[endif]        }  

[if !supportLists]21.[endif]    public synchronized void out(){

[if !supportLists]22.[endif]     //消费资源

[if !supportLists]23.[endif]        while(!flag) {

[if !supportLists]24.[endif]         //线程等待,生产者生产资源

[if !supportLists]25.[endif]            try{wait();}catch(Exception e){}  

[if !supportLists]26.[endif]        }

[if !supportLists]27.[endif]System.out.println(Thread.currentThread().getName()+"...消费者..."+this.name);  

[if !supportLists]28.[endif]        flag=false;  

[if !supportLists]29.[endif]//唤醒生产者,生产资源

[if !supportLists]30.[endif]        this.notifyAll();

[if !supportLists]31.[endif]        }  

[if !supportLists]32.[endif]}  

[if !supportLists]33.[endif]//生产者

[if !supportLists]34.[endif] class Producer implements Runnable{  

[if !supportLists]35.[endif]     private Resource res;  

[if !supportLists]36.[endif]     Producer(Resource res){  

[if !supportLists]37.[endif]         this.res=res;  

[if !supportLists]38.[endif]     }  

[if !supportLists]39.[endif] //生产者生产资源

[if !supportLists]40.[endif]     public void run(){  

[if !supportLists]41.[endif]         while(true){  

[if !supportLists]42.[endif] res.set("商品");  

[if !supportLists]43.[endif]         }  

[if !supportLists]44.[endif]     }  

[if !supportLists]45.[endif] }  

[if !supportLists]46.[endif]//消费者消费资源

[if !supportLists]47.[endif] class Consumer implements Runnable{  

[if !supportLists]48.[endif]     private Resource res;  

[if !supportLists]49.[endif]     Consumer(Resource res){  

[if !supportLists]50.[endif]         this.res=res;  

[if !supportLists]51.[endif]     }  

[if !supportLists]52.[endif]     public void run(){  

[if !supportLists]53.[endif]         while(true){  

[if !supportLists]54.[endif]             res.out();  

[if !supportLists]55.[endif]         }  

[if !supportLists]56.[endif]     }  

[if !supportLists]57.[endif] }  

[if !supportLists]58.[endif]public class ProducerConsumerDemo{  

[if !supportLists]59.[endif]    public static void main(String[] args){  

[if !supportLists]60.[endif]        Resource r=new Resource();  

[if !supportLists]61.[endif]        Producer pro=new Producer(r);  

[if !supportLists]62.[endif]        Consumer con=new Consumer(r);  

[if !supportLists]63.[endif]        Thread t1=new Thread(pro);  

[if !supportLists]64.[endif]        Thread t2=new Thread(con);  

[if !supportLists]65.[endif]        t1.start();  

[if !supportLists]66.[endif]        t2.start();  

[if !supportLists]67.[endif]    }  

[if !supportLists]68.[endif]}

[if !supportLists]14. [endif]线程和进程的区别(2017-11-23-wzz

进程:具备必定独立功能的程序关于某个数据集合上的一次运行活动,是操做系统进行资源分配和调度的一个独立单位。

线程:是进程的一个实体,是cpu调度和分派的基本单位,是比进程更小的能够独立运行的基本单位。

特色:线程的划分尺度小于进程,这使多线程程序拥有高并发性,进程在运行时各自内存单元相互独立,线程之间内存共享,这使多线程编程能够拥有更好的性能和用户体验

注意:多线程编程对于其它程序是不友好的,占据大量cpu资源。

[if !supportLists]15. [endif]请说出同步线程及线程调度相关的方法?(2017-11-23-wzz

wait():使一个线程处于等待(阻塞)状态,而且释放所持有的对象的锁;

sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;

notify():唤醒一个处于等待状态的线程,固然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM肯定唤醒哪一个线程,并且与优先级无关;

notityAll():唤醒全部处于等待状态的线程,该方法并非将对象的锁给全部线程,而是让它们竞争,只有得到锁的线程才能进入就绪状态;

注意:java 5 经过Lock接口提供了显示的锁机制,Lock接口中定义了加锁(lock()方法)和解锁(unLock()方法),加强了多线程编程的灵活性及对线程的协调

[if !supportLists]16. [endif]启动一个线程是调用run()方法仍是start()方法?(2017-11-23-wzz

启动一个线程是调用start()方法,使线程所表明的虚拟处理机处于可运行状态,这意味着它能够由JVM 调度并执行,这并不意味着线程就会当即运行。

run()方法是线程启动后要进行回调(callback)的方法。

[if !supportLists]10、[endif]Java内部类

[if !supportLists]1. [endif]静态嵌套类(Static Nested Class) 和内部类(Inner Class)的不一样?(2017-11-16-wl)

静态嵌套类:Static Nested Class 是被声明为静态(static)的内部类,它能够不依赖于外部类实例被实例化。

内部类:须要在外部类实例化后才能实例化,其语法看起来挺诡异的。

[if !supportLists]2. [endif]下面的代码哪些地方会产生编译错误?(2017-11-16-wl)

[if !supportLists]1.[endif]class Outer {

[if !supportLists]2.[endif]

[if !supportLists]3.[endif]class Inner {}

[if !supportLists]4.[endif]

[if !supportLists]5.[endif]public static void foo() { new Inner(); }

[if !supportLists]6.[endif]

[if !supportLists]7.[endif]public void bar() { new Inner(); }

[if !supportLists]8.[endif]

[if !supportLists]9.[endif]public static void main(String[] args) {

[if !supportLists]10.[endif]new Inner();

[if !supportLists]11.[endif]}

[if !supportLists]12.[endif]}

 

注意:Java 中非静态内部类对象的建立要依赖其外部类对象,上面的面试题中 foo 和 main 方法都是静态方法,静态方法中没有 this,也就是说没有所谓的外部类对象,所以没法建立内部类对象,若是要在静态方法中建立内部类对象,能够这样作

[if !supportLists]1.[endif]new Outer().new Inner();

[if !supportLists]第三章 [endif]JavaSE高级

[if !supportLists]1、[endif]Java中的反射

[if !supportLists]1. [endif]说说你对Java中反射的理解

Java中的反射首先是可以获取到Java中要反射类的字节码,获取字节码有三种方法,1.Class.forName(className) 2.类名.class 3.this.getClass()。而后将字节码中的方法,变量,构造函数等映射成相应的Method、Filed、Constructor等类,这些类提供了丰富的方法能够被咱们所使用。

[if !supportLists]2、[endif]Java中的动态代理

[if !supportLists]1. [endif]写一个ArrayList的动态代理类(笔试题)

[if !supportLists]1. [endif]final List list = new ArrayList();

[if !supportLists]2. [endif]

[if !supportLists]3. [endif] List proxyInstance =

[if !supportLists]4. [endif](List)Proxy.newProxyInstance(list.getClass().getClassLoader(),

[if !supportLists]5. [endif]list.getClass().getInterfaces(),

[if !supportLists]6. [endif]new InvocationHandler() {

[if !supportLists]7. [endif]

[if !supportLists]8. [endif] @Override

[if !supportLists]9. [endif] public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

[if !supportLists]10. [endif] return method.invoke(list, args);

[if !supportLists]11. [endif] }

[if !supportLists]12. [endif] });

[if !supportLists]13. [endif] proxyInstance.add("你好");

[if !supportLists]14. [endif] System.out.println(list);

[if !supportLists]2. [endif]动静态代理的区别,什么场景使用?(2015-11-25)

静态代理一般只代理一个类,动态代理是代理一个接口下的多个实现类。

静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。

动态代理是实现JDK里的InvocationHandler接口的invoke方法,但注意的是代理的是接口,也就是你的业务类必需要实现接口,经过Proxy里的newProxyInstance获得代理对象。

还有一种动态代理CGLIB,代理的是类,不须要业务类继承接口,经过派生的子类来实现代理。经过在运行时,动态修改字节码达到修改类的目的。

AOP编程就是基于动态代理实现的,好比著名的Spring框架、Hibernate框架等等都是动态代理的使用例子。

[if !supportLists]3、[endif]Java中的设计模式&回收机制

[if !supportLists]1. [endif]你所知道的设计模式有哪些

Java中通常认为有23种设计模式,咱们不须要全部的都会,可是其中经常使用的几种设计模式应该去掌握。下面列出了全部的设计模式。须要掌握的设计模式我单独列出来了,固然能掌握的越多越好。

整体来讲设计模式分为三大类:

建立型模式,共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

结构型模式,共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

[if !supportLists]2. [endif]单例设计模式

最好理解的一种设计模式,分为懒汉式和饿汉式。

饿汉式:

[if !supportLists]1. [endif]public class Singleton {

[if !supportLists]2. [endif] //直接建立对象

[if !supportLists]3. [endif] public static Singleton instance = new Singleton();

[if !supportLists]4. [endif]

[if !supportLists]5. [endif] //私有化构造函数

[if !supportLists]6. [endif] private Singleton() {

[if !supportLists]7. [endif] }

[if !supportLists]8. [endif]

[if !supportLists]9. [endif] //返回对象实例

[if !supportLists]10. [endif] public static Singleton getInstance() {

[if !supportLists]11. [endif] return instance;

[if !supportLists]12. [endif] }

[if !supportLists]13. [endif]}

 

懒汉式:

[if !supportLists]1. [endif]public class Singleton {

[if !supportLists]2. [endif] //声明变量

[if !supportLists]3. [endif] private static volatile Singleton singleton = null;

[if !supportLists]4. [endif]

[if !supportLists]5. [endif] //私有构造函数

[if !supportLists]6. [endif] private Singleton() {

[if !supportLists]7. [endif] }

[if !supportLists]8. [endif]

[if !supportLists]9. [endif] //提供对外方法

[if !supportLists]10. [endif] public static Singleton getInstance() {

[if !supportLists]11. [endif] if (singleton == null) {

[if !supportLists]12. [endif] synchronized (Singleton.class) {

[if !supportLists]13. [endif] if (singleton == null) {

[if !supportLists]14. [endif] singleton = new Singleton();

[if !supportLists]15. [endif] }

[if !supportLists]16. [endif] }

[if !supportLists]17. [endif] }

[if !supportLists]18. [endif] return singleton;

[if !supportLists]19. [endif] }

[if !supportLists]20. [endif]}

 

 

[if !supportLists]3. [endif]工厂设计模式

工厂模式分为工厂方法模式和抽象工厂模式。

工厂方法模式

工厂方法模式分为三种:普通工厂模式,就是创建一个工厂类,对实现了同一接口的一些类进行实例的建立。

多个工厂方法模式,是对普通工厂方法模式的改进,在普通工厂方法模式中,若是传递的字符串出错,则不能正确建立对象,而多个工厂方法模式是提供多个工厂方法,分别建立对象。

静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不须要建立实例,直接调用便可。

普通工厂模式

[if !supportLists]1. [endif]public interface Sender {

[if !supportLists]2. [endif] public void Send();

[if !supportLists]3. [endif]}

[if !supportLists]4. [endif]public class MailSender implements Sender {

[if !supportLists]5. [endif]

[if !supportLists]6. [endif] @Override

[if !supportLists]7. [endif] public void Send() {

[if !supportLists]8. [endif] System.out.println("this is mail sender!");

[if !supportLists]9. [endif] }

[if !supportLists]10. [endif]}

[if !supportLists]11. [endif]public class SmsSender implements Sender {

[if !supportLists]12. [endif]

[if !supportLists]13. [endif] @Override

[if !supportLists]14. [endif] public void Send() {

[if !supportLists]15. [endif] System.out.println("this is sms sender!");

[if !supportLists]16. [endif] }

[if !supportLists]17. [endif]}

[if !supportLists]18. [endif]public class SendFactory {

[if !supportLists]19. [endif] public Sender produce(String type) {

[if !supportLists]20. [endif] if ("mail".equals(type)) {

[if !supportLists]21. [endif] return new MailSender();

[if !supportLists]22. [endif] } else if ("sms".equals(type)) {

[if !supportLists]23. [endif] return new SmsSender();

[if !supportLists]24. [endif] } else {

[if !supportLists]25. [endif] System.out.println("请输入正确的类型!");

[if !supportLists]26. [endif] return null;

[if !supportLists]27. [endif] }

[if !supportLists]28. [endif] }

[if !supportLists]29. [endif]}

多个工厂方法模式

该模式是对普通工厂方法模式的改进,在普通工厂方法模式中,若是传递的字符串出错,则不能正确建立对象,而多个工厂方法模式是提供多个工厂方法,分别建立对象。

[if !supportLists]1. [endif]public class SendFactory {

[if !supportLists]2. [endif] public Sender produceMail(){  

[if !supportLists]3. [endif]        return new MailSender();  

[if !supportLists]4. [endif]    }  

[if !supportLists]5. [endif]      

[if !supportLists]6. [endif]    public Sender produceSms(){  

[if !supportLists]7. [endif]        return new SmsSender();  

[if !supportLists]8. [endif]    }  

[if !supportLists]9. [endif]}  

[if !supportLists]10. [endif]

[if !supportLists]11. [endif]public class FactoryTest {

[if !supportLists]12. [endif] public static void main(String[] args) {

[if !supportLists]13. [endif] SendFactory factory = new SendFactory();

[if !supportLists]14. [endif] Sender sender = factory.produceMail();

[if !supportLists]15. [endif] sender.send();

[if !supportLists]16. [endif] }

[if !supportLists]17. [endif]}

静态工厂方法模式,将上面的多个工厂方法模式里的方法置为静态的,不须要建立实例,直接调用便可。

 

[if !supportLists]1. [endif]public class SendFactory {

[if !supportLists]2. [endif] public static Sender produceMail(){  

[if !supportLists]3. [endif]        return new MailSender();  

[if !supportLists]4. [endif]    }  

[if !supportLists]5. [endif]      

[if !supportLists]6. [endif]    public static Sender produceSms(){  

[if !supportLists]7. [endif]        return new SmsSender();  

[if !supportLists]8. [endif]    }  

[if !supportLists]9. [endif]}  

[if !supportLists]10. [endif]

[if !supportLists]11. [endif]

[if !supportLists]12. [endif]public class FactoryTest {

[if !supportLists]13. [endif] public static void main(String[] args) {

[if !supportLists]14. [endif] Sender sender = SendFactory.produceMail();

[if !supportLists]15. [endif] sender.send();

[if !supportLists]16. [endif] }

[if !supportLists]17. [endif]}

 

抽象工厂模式

工厂方法模式有一个问题就是,类的建立依赖工厂类,也就是说,若是想要拓展程序,必须对工厂类进行修改,这违背了闭包原则,因此,从设计角度考虑,有必定的问题,如何解决?就用到抽象工厂模式,建立多个工厂类,这样一旦须要增长新的功能,直接增长新的工厂类就能够了,不须要修改以前的代码。

[if !supportLists]1. [endif]public interface Provider {

[if !supportLists]2. [endif] public Sender produce();

[if !supportLists]3. [endif]}

[if !supportLists]4. [endif]-------------------------------------------------------------------------------------

[if !supportLists]5. [endif]public interface Sender {

[if !supportLists]6. [endif] public void send();

[if !supportLists]7. [endif]}

[if !supportLists]8. [endif]-------------------------------------------------------------------------------------

[if !supportLists]9. [endif]public class MailSender implements Sender {

[if !supportLists]10. [endif]

[if !supportLists]11. [endif] @Override

[if !supportLists]12. [endif] public void send() {

[if !supportLists]13. [endif] System.out.println("this is mail sender!");

[if !supportLists]14. [endif] }

[if !supportLists]15. [endif]}

[if !supportLists]16. [endif]-------------------------------------------------------------------------------------

[if !supportLists]17. [endif]public class SmsSender implements Sender {

[if !supportLists]18. [endif]

[if !supportLists]19. [endif] @Override

[if !supportLists]20. [endif] public void send() {

[if !supportLists]21. [endif] System.out.println("this is sms sender!");

[if !supportLists]22. [endif] }

[if !supportLists]23. [endif]}

[if !supportLists]24. [endif]-------------------------------------------------------------------------------------

[if !supportLists]25. [endif]public class SendSmsFactory implements Provider {

[if !supportLists]26. [endif]

[if !supportLists]27. [endif] @Override

[if !supportLists]28. [endif] public Sender produce() {

[if !supportLists]29. [endif] return new SmsSender();

[if !supportLists]30. [endif] }

[if !supportLists]31. [endif]}

 

[if !supportLists]1. [endif]public class SendMailFactory implements Provider {

[if !supportLists]2. [endif]

[if !supportLists]3. [endif] @Override

[if !supportLists]4. [endif] public Sender produce() {

[if !supportLists]5. [endif] return new MailSender();

[if !supportLists]6. [endif] }

[if !supportLists]7. [endif]}

[if !supportLists]8. [endif]-------------------------------------------------------------------------------------

[if !supportLists]9. [endif]public class Test {

[if !supportLists]10. [endif] public static void main(String[] args) {

[if !supportLists]11. [endif] Provider provider = new SendMailFactory();

[if !supportLists]12. [endif] Sender sender = provider.produce();

[if !supportLists]13. [endif] sender.send();

[if !supportLists]14. [endif] }

[if !supportLists]15. [endif]}

 

[if !supportLists]4. [endif]建造者模式(Builder)

工厂类模式提供的是建立单个类的模式,而建造者模式则是将各类产品集中起来进行管理,用来建立复合对象,所谓复合对象就是指某个类具备不一样的属性,其实建造者模式就是前面抽象工厂模式和最后的Test结合起来获得的。

[if !supportLists]1. [endif]public class Builder {

[if !supportLists]2. [endif] private List list = new ArrayList();

[if !supportLists]3. [endif]

[if !supportLists]4. [endif] public void produceMailSender(int count) {

[if !supportLists]5. [endif] for (int i = 0; i < count; i++) {

[if !supportLists]6. [endif] list.add(new MailSender());

[if !supportLists]7. [endif] }

[if !supportLists]8. [endif] }

[if !supportLists]9. [endif]

[if !supportLists]10. [endif] public void produceSmsSender(int count) {

[if !supportLists]11. [endif] for (int i = 0; i < count; i++) {

[if !supportLists]12. [endif] list.add(new SmsSender());

[if !supportLists]13. [endif] }

[if !supportLists]14. [endif] }

[if !supportLists]15. [endif]}

 

[if !supportLists]1. [endif]public class Builder {

[if !supportLists]2. [endif] private List list = new ArrayList();

[if !supportLists]3. [endif]

[if !supportLists]4. [endif] public void produceMailSender(int count) {

[if !supportLists]5. [endif] for (int i = 0; i < count; i++) {

[if !supportLists]6. [endif] list.add(new MailSender());

[if !supportLists]7. [endif] }

[if !supportLists]8. [endif] }

[if !supportLists]9. [endif]

[if !supportLists]10. [endif] public void produceSmsSender(int count) {

[if !supportLists]11. [endif] for (int i = 0; i < count; i++) {

[if !supportLists]12. [endif] list.add(new SmsSender());

[if !supportLists]13. [endif] }

[if !supportLists]14. [endif] }

[if !supportLists]15. [endif]}

 

[if !supportLists]1. [endif]public class TestBuilder {

[if !supportLists]2. [endif] public static void main(String[] args) {

[if !supportLists]3. [endif] Builder builder = new Builder();

[if !supportLists]4. [endif] builder.produceMailSender(10);

[if !supportLists]5. [endif] }

[if !supportLists]6. [endif]}

[if !supportLists]5. [endif]适配器设计模式

适配器模式将某个类的接口转换成客户端指望的另外一个接口表示,目的是消除因为接口不匹配所形成的类的兼容性问题。主要分为三类:类的适配器模式、对象的适配器模式、接口的适配器模式。

类的适配器模式

 

[if !supportLists]1. [endif]public class Source {

[if !supportLists]2. [endif] public void method1() {

[if !supportLists]3. [endif] System.out.println("this is original method!");

[if !supportLists]4. [endif] }

[if !supportLists]5. [endif]}

[if !supportLists]6. [endif]-------------------------------------------------------------

[if !supportLists]7. [endif]public interface Targetable {

[if !supportLists]8. [endif] /*与原类中的方法相同 */

[if !supportLists]9. [endif] public void method1();

[if !supportLists]10. [endif] /*新类的方法 */

[if !supportLists]11. [endif] public void method2();

[if !supportLists]12. [endif]}

[if !supportLists]13. [endif]public class Adapter extends Source implements Targetable {

[if !supportLists]14. [endif] @Override

[if !supportLists]15. [endif] public void method2() {

[if !supportLists]16. [endif] System.out.println("this is the targetable method!");

[if !supportLists]17. [endif] }

[if !supportLists]18. [endif]}

[if !supportLists]19. [endif]public class AdapterTest {

[if !supportLists]20. [endif] public static void main(String[] args) {

[if !supportLists]21. [endif] Targetable target = new Adapter();

[if !supportLists]22. [endif] target.method1();

[if !supportLists]23. [endif] target.method2();

[if !supportLists]24. [endif] }

[if !supportLists]25. [endif]}

 

对象的适配器模式

基本思路和类的适配器模式相同,只是将Adapter类做修改,此次不继承Source类,而是持有Source类的实例,以达到解决兼容性的问题。

[if !supportLists]1. [endif]public class Wrapper implements Targetable {

[if !supportLists]2. [endif] private Source source;

[if !supportLists]3. [endif]

[if !supportLists]4. [endif] public Wrapper(Source source) {

[if !supportLists]5. [endif] super();

[if !supportLists]6. [endif] this.source = source;

[if !supportLists]7. [endif] }

[if !supportLists]8. [endif]

[if !supportLists]9. [endif] @Override

[if !supportLists]10. [endif] public void method2() {

[if !supportLists]11. [endif] System.out.println("this is the targetable method!");

[if !supportLists]12. [endif] }

[if !supportLists]13. [endif]

[if !supportLists]14. [endif] @Override

[if !supportLists]15. [endif] public void method1() {

[if !supportLists]16. [endif] source.method1();

[if !supportLists]17. [endif] }

[if !supportLists]18. [endif]}

[if !supportLists]19. [endif]--------------------------------------------------------------

[if !supportLists]20. [endif]public class AdapterTest {  

[if !supportLists]21. [endif]   

[if !supportLists]22. [endif]     public static void main(String[] args) {  

[if !supportLists]23. [endif]         Source source = new Source();  

[if !supportLists]24. [endif]         Targetable target = new Wrapper(source);  

[if !supportLists]25. [endif]         target.method1();  

[if !supportLists]26. [endif]         target.method2();  

[if !supportLists]27. [endif]     }  

[if !supportLists]28. [endif] }  

 

接口的适配器模式

接口的适配器是这样的:有时咱们写的一个接口中有多个抽象方法,当咱们写该接口的实现类时,必须实现该接口的全部方法,这明显有时比较浪费,由于并非全部的方法都是咱们须要的,有时只须要某一些,此处为了解决这个问题,咱们引入了接口的适配器模式,借助于一个抽象类,该抽象类实现了该接口,实现了全部的方法,而咱们不和原始的接口打交道,只和该抽象类取得联系,因此咱们写一个类,继承该抽象类,重写咱们须要的方法就行。

[if !supportLists]6. [endif]装饰模式(Decorator)

顾名思义,装饰模式就是给一个对象增长一些新的功能,并且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例。

[if !supportLists]1. [endif]public interface Sourceable {

[if !supportLists]2. [endif] public void method();

[if !supportLists]3. [endif]}

[if !supportLists]4. [endif]----------------------------------------------------

[if !supportLists]5. [endif]public class Source implements Sourceable {

[if !supportLists]6. [endif] @Override

[if !supportLists]7. [endif] public void method() {

[if !supportLists]8. [endif] System.out.println("the original method!");

[if !supportLists]9. [endif] }

[if !supportLists]10. [endif]}

[if !supportLists]11. [endif]----------------------------------------------------

[if !supportLists]12. [endif]public class Decorator implements Sourceable {

[if !supportLists]13. [endif] private Sourceable source;

[if !supportLists]14. [endif] public Decorator(Sourceable source) {

[if !supportLists]15. [endif] super();

[if !supportLists]16. [endif] this.source = source;

[if !supportLists]17. [endif] }

[if !supportLists]18. [endif]

[if !supportLists]19. [endif] @Override

[if !supportLists]20. [endif] public void method() {

[if !supportLists]21. [endif] System.out.println("before decorator!");

[if !supportLists]22. [endif] source.method();

[if !supportLists]23. [endif] System.out.println("after decorator!");

[if !supportLists]24. [endif] }

[if !supportLists]25. [endif]}

[if !supportLists]26. [endif]----------------------------------------------------

[if !supportLists]27. [endif]public class DecoratorTest {

[if !supportLists]28. [endif] public static void main(String[] args) {

[if !supportLists]29. [endif] Sourceable source = new Source();

[if !supportLists]30. [endif] Sourceable obj = new Decorator(source);

[if !supportLists]31. [endif] obj.method();

[if !supportLists]32. [endif] }

[if !supportLists]33. [endif]}

 

[if !supportLists]7. [endif]策略模式(strategy)

策略模式定义了一系列算法,并将每一个算法封装起来,使他们能够相互替换,且算法的变化不会影响到使用算法的客户。须要设计一个接口,为一系列实现类提供统一的方法,多个实现类实现该接口,设计一个抽象类(无关紧要,属于辅助类),提供辅助函数。策略模式的决定权在用户,系统自己提供不一样算法的实现,新增或者删除算法,对各类算法作封装。所以,策略模式多用在算法决策系统中,外部用户只须要决定用哪一个算法便可。

[if !supportLists]1. [endif]public interface ICalculator {

[if !supportLists]2. [endif] public int calculate(String exp);

[if !supportLists]3. [endif]}

[if !supportLists]4. [endif]---------------------------------------------------------

[if !supportLists]5. [endif]public class Minus extends AbstractCalculator implements ICalculator {

[if !supportLists]6. [endif]

[if !supportLists]7. [endif] @Override

[if !supportLists]8. [endif] public int calculate(String exp) {

[if !supportLists]9. [endif] int arrayInt[] = split(exp, "-");

[if !supportLists]10. [endif] return arrayInt[0] - arrayInt[1];

[if !supportLists]11. [endif] }

[if !supportLists]12. [endif]}

[if !supportLists]13. [endif]---------------------------------------------------------

[if !supportLists]14. [endif]public class Plus extends AbstractCalculator implements ICalculator {

[if !supportLists]15. [endif]

[if !supportLists]16. [endif] @Override

[if !supportLists]17. [endif] public int calculate(String exp) {

[if !supportLists]18. [endif] int arrayInt[] = split(exp, "\\+");

[if !supportLists]19. [endif] return arrayInt[0] + arrayInt[1];

[if !supportLists]20. [endif] }

[if !supportLists]21. [endif]}

[if !supportLists]22. [endif]--------------------------------------------------------

[if !supportLists]23. [endif]public class AbstractCalculator {

[if !supportLists]24. [endif] public int[] split(String exp, String opt) {

[if !supportLists]25. [endif] String array[] = exp.split(opt);

[if !supportLists]26. [endif] int arrayInt[] = new int[2];

[if !supportLists]27. [endif] arrayInt[0] = Integer.parseInt(array[0]);

[if !supportLists]28. [endif] arrayInt[1] = Integer.parseInt(array[1]);

[if !supportLists]29. [endif] return arrayInt;

[if !supportLists]30. [endif] }

[if !supportLists]31. [endif]}

 

[if !supportLists]1. [endif]public class StrategyTest {

[if !supportLists]2. [endif] public static void main(String[] args) {

[if !supportLists]3. [endif] String exp = "2+8";

[if !supportLists]4. [endif] ICalculator cal = new Plus();

[if !supportLists]5. [endif] int result = cal.calculate(exp);

[if !supportLists]6. [endif] System.out.println(result);

[if !supportLists]7. [endif] }

[if !supportLists]8. [endif]}

 

[if !supportLists]8. [endif]观察者模式(Observer)

观察者模式很好理解,相似于邮件订阅和RSS订阅,当咱们浏览一些博客或wiki时,常常会看到RSS图标,就这的意思是,当你订阅了该文章,若是后续有更新,会及时通知你。其实,简单来说就一句话:当一个对象变化时,其它依赖该对象的对象都会收到通知,而且随着变化!对象之间是一种一对多的关系。

[if !supportLists]1. [endif]public interface Observer {

[if !supportLists]2. [endif] public void update();

[if !supportLists]3. [endif]}

[if !supportLists]4. [endif]

[if !supportLists]5. [endif]public class Observer1 implements Observer {

[if !supportLists]6. [endif] @Override

[if !supportLists]7. [endif] public void update() {

[if !supportLists]8. [endif]  System.out.println("observer1 has received!");  

[if !supportLists]9. [endif] }

[if !supportLists]10. [endif]}

[if !supportLists]11. [endif]

[if !supportLists]12. [endif]public class Observer2 implements Observer {

[if !supportLists]13. [endif] @Override

[if !supportLists]14. [endif] public void update() {

[if !supportLists]15. [endif]  System.out.println("observer2 has received!");  

[if !supportLists]16. [endif] }

[if !supportLists]17. [endif]}

[if !supportLists]18. [endif]

[if !supportLists]19. [endif]public interface Subject {

[if !supportLists]20. [endif] /*增长观察者*/  

[if !supportLists]21. [endif]    public void add(Observer observer);  

[if !supportLists]22. [endif]      

[if !supportLists]23. [endif]/*删除观察者*/  

[if !supportLists]24. [endif]    public void del(Observer observer);  

25./*通知全部的观察者*/  

[if !supportLists]1. [endif]    public void notifyObservers();  

[if !supportLists]2. [endif]      

[if !supportLists]3. [endif]/*自身的操做*/  

[if !supportLists]4. [endif]    public void operation();

[if !supportLists]5. [endif]}

[if !supportLists]6. [endif]

[if !supportLists]7. [endif]public abstract class AbstractSubject implements Subject {

[if !supportLists]8. [endif]

[if !supportLists]9. [endif] private Vector vector = new Vector();

[if !supportLists]10. [endif]

[if !supportLists]11. [endif] @Override

[if !supportLists]12. [endif] public void add(Observer observer) {

[if !supportLists]13. [endif] vector.add(observer);

[if !supportLists]14. [endif] }

[if !supportLists]15. [endif]

[if !supportLists]16. [endif] @Override

[if !supportLists]17. [endif] public void del(Observer observer) {

[if !supportLists]18. [endif] vector.remove(observer);

[if !supportLists]19. [endif] }

[if !supportLists]20. [endif]

[if !supportLists]21. [endif] @Override

[if !supportLists]22. [endif] public void notifyObservers() {

[if !supportLists]23. [endif] Enumeration enumo = vector.elements();

[if !supportLists]24. [endif] while (enumo.hasMoreElements()) {

[if !supportLists]25. [endif] enumo.nextElement().update();

[if !supportLists]26. [endif] }

[if !supportLists]27. [endif] }

[if !supportLists]28. [endif]}

[if !supportLists]29. [endif]

[if !supportLists]30. [endif]public class MySubject extends AbstractSubject {

[if !supportLists]31. [endif]

[if !supportLists]32. [endif] @Override

[if !supportLists]33. [endif] public void operation() {

[if !supportLists]34. [endif] System.out.println("update self!");  

[if !supportLists]35. [endif]        notifyObservers();

[if !supportLists]36. [endif] }

[if !supportLists]37. [endif]}

[if !supportLists]38. [endif]

[if !supportLists]39. [endif]public class ObserverTest {

[if !supportLists]40. [endif] public static void main(String[] args) {

[if !supportLists]41. [endif] Subject sub = new MySubject();

[if !supportLists]42. [endif] sub.add(new Observer1());

[if !supportLists]43. [endif] sub.add(new Observer2());

[if !supportLists]44. [endif] sub.operation();

[if !supportLists]45. [endif] }

[if !supportLists]46. [endif]}

[if !supportLists]9. [endif]JVM垃圾回收机制和常见算法

理论上来说Sun公司只定义了垃圾回收机制规则而不局限于其实现算法,所以不一样厂商生产的虚拟机采用的算法也不尽相同。

GC(Garbage Collector)在回收对象前首先必须发现那些无用的对象,如何去发现定位这些无用的对象?经常使用的搜索算法以下:

[if !supportLists]1)[endif]引用计数器算法(废弃)

引用计数器算法是给每一个对象设置一个计数器,当有地方引用这个对象的时候,计数器+1,当引用失效的时候,计数器-1,当计数器为0的时候,JVM就认为对象再也不被使用,是“垃圾”了。

引用计数器实现简单,效率高;可是不能解决循环引用问问题(A对象引用B对象,B对象又引用A对象,可是A,B对象已不被任何其余对象引用),同时每次计数器的增长和减小都带来了不少额外的开销,因此在JDK1.1以后,这个算法已经再也不使用了。

[if !supportLists]2)[endif]根搜索算法(使用)

根搜索算法是经过一些“GC Roots”对象做为起点,从这些节点开始往下搜索,搜索经过的路径成为引用链(Reference Chain),当一个对象没有被GC Roots的引用链链接的时候,说明这个对象是不可用的。

 

 

 

 

 

 

 

GC Roots对象包括:

a) 虚拟机栈(栈帧中的本地变量表)中的引用的对象。

b) 方法区域中的类静态属性引用的对象。

c) 方法区域中常量引用的对象。

d) 本地方法栈中JNI(Native方法)的引用的对象。

经过上面的算法搜索到无用对象以后,就是回收过程,回收算法以下:

[if !supportLists]1)[endif]标记—清除算法(Mark-Sweep)(DVM使用的算法)

标记—清除算法包括两个阶段:“标记”和“清除”。在标记阶段,肯定全部要回收的对象,并作标记。清除阶段紧随标记阶段,将标记阶段肯定不可用的对象清除。标记—清除算法是基础的收集算法,标记和清除阶段的效率不高,并且清除后回产生大量的不连续空间,这样当程序须要分配大内存对象时,可能没法找到足够的连续空间。

 

[if !supportLists]2)[endif]复制算法(Copying)

复制算法是把内存分红大小相等的两块,每次使用其中一块,当垃圾回收的时候,把存活的对象复制到另外一块上,而后把这块内存整个清理掉。复制算法实现简单,运行效率高,可是因为每次只能使用其中的一半,形成内存的利用率不高。如今的JVM用复制方法收集新生代,因为新生代中大部分对象(98%)都是朝生夕死的,因此两块内存的比例不是1:1(大概是8:1)。

 

[if !supportLists]3)[endif]标记—整理算法(Mark-Compact)

标记—整理算法和标记—清除算法同样,可是标记—整理算法不是把存活对象复制到另外一块内存,而是把存活对象往内存的一端移动,而后直接回收边界之外的内存。标记—整理算法提升了内存的利用率,而且它适合在收集对象存活时间较长的老年代。

 

[if !supportLists]4)[endif]分代收集(Generational Collection)

分代收集是根据对象的存活时间把内存分为新生代和老年代,根据各个代对象的存活特色,每一个代采用不一样的垃圾回收算法。新生代采用复制算法,老年代采用标记—整理算法。垃圾算法的实现涉及大量的程序细节,并且不一样的虚拟机平台实现的方法也各不相同。

[if !supportLists]10. [endif]谈谈JVM的内存结构和内存分配

[if !supportLists]a)[endif] Java内存模型

Java虚拟机将其管辖的内存大体分三个逻辑部分:方法区(Method Area)、Java栈和Java堆。

    一、方法区是静态分配的,编译器将变量绑定在某个存储位置上,并且这些绑定不会在运行时改变。

常数池,源代码中的命名常量、String常量和static变量保存在方法区。

    二、Java Stack是一个逻辑概念,特色是后进先出。一个栈的空间多是连续的,也多是不连续的。

最典型的Stack应用是方法的调用,Java虚拟机每调用一次方法就建立一个方法帧(frame),退出该方法则对应的  方法帧被弹出(pop)。栈中存储的数据也是运行时肯定的。

    三、Java堆分配(heap allocation)意味着以随意的顺序,在运行时进行存储空间分配和收回的内存管理模型。

堆中存储的数据经常是大小、数量和生命期在编译时没法肯定的。Java对象的内存老是在heap中分配。

咱们天天都在写代码,天天都在使用JVM的内存。

[if !supportLists]b)[endif] java内存分配

     一、基础数据类型直接在栈空间分配;

     二、方法的形式参数,直接在栈空间分配,当方法调用完成后从栈空间回收;

     三、引用数据类型,须要用new来建立,既在栈空间分配一个地址空间,又在堆空间分配对象的类变量;

     四、方法的引用参数,在栈空间分配一个地址空间,并指向堆空间的对象区,当方法调用完后从栈空间回收;

     五、局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间马上被回收,堆空间区域等待GC回收;

     六、方法调用时传入的实际参数,先在栈空间分配,在方法调用完成后从栈空间释放;

     七、字符串常量在 DATA 区域分配 ,this 在堆空间分配;

     八、数组既在栈空间分配数组名称, 又在堆空间分配数组实际的大小!

[if !supportLists]11. [endif]Java中引用类型都有哪些?(重要)

Java中对象的引用分为四种级别,这四种级别由高到低依次为:强引用、软引用、弱引用和虚引用。

强引用(StrongReference)

这个就很少说,咱们写代码每天在用的就是强引用。若是一个对象被被人拥有强引用,那么垃圾回收器毫不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具备强引用的对象来解决内存不足问题。

Java的对象是位于heap中的,heap中对象有强可及对象、软可及对象、弱可及对象、虚可及对象和不可到达对象。应用的强弱顺序是强、软、弱、和虚。对于对象是属于哪一种可及的对象,由他的最强的引用决定。以下代码:

[if !supportLists]1. [endif]      String abc=new String("abc");  //1       

[if !supportLists]2. [endif] SoftReference softRef=new SoftReference(abc);  //2       

[if !supportLists]3. [endif] WeakReference weakRef = new WeakReference(abc); //3       

[if !supportLists]4. [endif] abc=null; //4       

[if !supportLists]5. [endif] softRef.clear();//5

 

第一行在heap堆中建立内容为“abc”的对象,并创建abc到该对象的强引用,该对象是强可及的。

第二行和第三行分别创建对heap中对象的软引用和弱引用,此时heap中的abc对象已经有3个引用,显然此时abc对象还是强可及的。

第四行以后heap中对象再也不是强可及的,变成软可及的。

第五行执行以后变成弱可及的。

软引用(SoftReference)

若是一个对象只具备软引用,那么若是内存空间足够,垃圾回收器就不会回收它,若是内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就能够被程序使用。软引用可用来实现内存敏感的高速缓存。

软引用能够和一个引用队列(ReferenceQueue)联合使用,若是软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

软引用是主要用于内存敏感的高速缓存。在jvm报告内存不足以前会清除全部的软引用,这样以来gc就有可能收集软可及的对象,可能解决内存吃紧问题,避免内存溢出。何时会被收集取决于gc的算法和gc运行时可用内存的大小。当gc决定要收集软引用时执行如下过程,以上面的softRef为例:

    1 首先将softRef的referent(abc)设置为null,再也不引用heap中的new String("abc")对象。

    2 将heap中的new String("abc")对象设置为可结束的(finalizable)。

    3 当heap中的new String("abc")对象的finalize()方法被运行并且该对象占用的内存被释放, softRef被添加到它的ReferenceQueue(若是有的话)中。

注意:对ReferenceQueue软引用和弱引用能够有可无,可是虚引用必须有。

被Soft Reference 指到的对象,即便没有任何 Direct Reference,也不会被清除。一直要到 JVM 内存不足且没有Direct Reference 时才会清除,SoftReference 是用来设计 object-cache 之用的。如此一来 SoftReference 不但能够把对象 cache 起来,也不会形成内存不足的错误 (OutOfMemoryError)。

弱引用(WeakReference)

若是一个对象只具备弱引用,那该类就是无关紧要的对象,由于只要该对象被gc扫描到了随时都会把它干掉。弱引用与软引用的区别在于:只具备弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程当中,一旦发现了只具备弱引用的对象,无论当前内存空间足够与否,都会回收它的内存。不过,因为垃圾回收器是一个优先级很低的线程,所以不必定会很快发现那些只具备弱引用的对象。

弱引用能够和一个引用队列(ReferenceQueue)联合使用,若是弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

虚引用(PhantomReference)

"虚引用"顾名思义,就是形同虚设,与其余几种引用都不一样,虚引用并不会决定对象的生命周期。若是一个对象仅持有虚引用,那么它就和没有任何引用同样,在任什么时候候均可能被垃圾回收。虚引用主要用来跟踪对象被垃圾回收的活动。

虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,若是发现它还有虚引用,就会在回收对象的内存以前,把这个虚引用加入到与之关联的引用队列中。程序能够经过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序若是发现某个虚引用已经被加入到引用队列,那么就能够在所引用的对象的内存被回收以前采起必要的行动。

创建虚引用以后经过get方法返回结果始终为null,经过源代码你会发现,虚引用通向会把引用的对象写进referent,只是get方法返回结果为null。先看一下和gc交互的过程再说一下他的做用。

  1 不把referent设置为null, 直接把heap中的new String("abc")对象设置为可结束的(finalizable)。

  2 与软引用和弱引用不一样, 先把PhantomRefrence对象添加到它的ReferenceQueue中.而后在释放虚可及的对象。

[if !supportLists]12. [endif]heap和stack有什么区别(2017-2-23)

从如下几个方面阐述堆(heap)和栈(stack)的区别。

1. 申请方式

stack:由系统自动分配。例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间

heap:须要程序员本身申请,并指明大小,在c中malloc函数,对于Java须要手动new Object()的形式开辟

2. 申请后系统的响应

stack:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,不然将报异常提示栈溢出。

heap:首先应该知道操做系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,

会遍历该链表,寻找第一个空间大于所申请空间的堆结点,而后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。另外,因为找到的堆结点的大小不必定正好等于申请的大小,系统会自动的将多余的那部分从新放入空闲链表中。

3. 申请大小的限制

stack:栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就肯定的常数),若是申请的空间超过栈的剩余空间时,将提示overflow。所以,能从栈得到的空间较小。

heap:堆是向高地址扩展的数据结构,是不连续的内存区域。这是因为系统是用链表来存储的空闲内存地址的,天然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。因而可知,堆得到的空间比较灵活,也比较大。

4. 申请效率的比较:

stack:由系统自动分配,速度较快。但程序员是没法控制的。

heap:由new分配的内存,通常速度比较慢,并且容易产生内存碎片,不过用起来最方便。

5. heap和stack中的存储内容

stack: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,而后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,而后是函数中的局部变量。注意静态变量是不入栈的。

当本次函数调用结束后,局部变量先出栈,而后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

heap:通常是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。      

6. 数据结构层面的区别

还有就是数据结构方面的堆和栈,这些都是不一样的概念。这里的堆实际上指的就是(知足堆性质的)优先队列的一种数据结构,第1个元素有最高的优先权;栈实际上就是知足先进后出的性质的数学或数据结构。

虽然堆栈,堆栈的说法是连起来叫,可是他们仍是有很大区别的,连着叫只是因为历史的缘由。

7. 拓展知识(Java中堆栈的应用)

 1). 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不一样,Java自动管理栈和堆,程序员不能直接地设置栈或堆。

2). 栈的优点是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是肯定的,缺少灵活性。另外,栈数据能够共享,详见第3点。堆的优点是能够动态地分配内存大小,生存期也没必要事先告诉编译器,Java的垃圾回收器会自动收走这些再也不使用的数据。但缺点是,因为要在运行时动态分配内存,存取速度较慢。

3). Java中的数据类型有两种。

  一种是基本类型(primitive types), 共有8种,即int, short, long, byte, float, double, boolean, char(注意,并无string的基本类型)。这种类型的定义是经过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量(自动变量:只在定义它们的时候才建立,在定义它们的函数返回时系统回收变量所占存储空间。对这些变量存储空间的分配和回收是由系统自动完成的。)。值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并无类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,因为大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的缘由,就存在于栈中。

  另外,栈有一个很重要的特殊性,就是存在栈中的数据能够共享。假设咱们同时定义

  int a = 3;

  int b = 3;

  编译器先处理int a = 3;首先它会在栈中建立一个变量为a的引用,而后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,而后将a指向3的地址。接着处理int b = 3;在建立完b的引用变量后,因为在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的状况。

  特别注意的是,这种字面值的引用与类对象的引用不一样。假定两个类对象的引用同时指向一个对象,若是一个对象引用变量修改了这个对象的内部状态,那么另外一个对象引用变量也即刻反映出这个变化。相反,经过字面值的引用来修改其值,不会致使另外一个指向此字面值的引用的值也跟着改变的状况。如上例,咱们定义完a与 b的值后,再令a=4;那么,b不会等于4,仍是等于3。在编译器内部,遇到a=4;时,它就会从新搜索栈中是否有4的字面值,若是没有,从新开辟地址存放4的值;若是已经有了,则直接将a指向这个地址。所以a值的改变不会影响到b的值。

  另外一种是包装类数据,如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据所有存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据须要动态建立,所以比较灵活,但缺点是要占用更多的时间。

4).每一个JVM的线程都有本身的私有的栈空间,随线程建立而建立,java的stack存放的是frames,java的stack和c的不一样,只是存放本地变量,返回值和调用方法,不容许直接push和pop frames ,由于frames 多是有heap分配的,因此java的stack分配的内存不须要是连续的。java的heap是全部线程共享的,堆存放全部 runtime data ,里面是全部的对象实例和数组,heap是JVM启动时建立。

  5). String是一个特殊的包装类数据。便可以用String str = new String("abc");的形式来建立,也能够用String str = "abc";的形式来建立(做为对比,在JDK 5.0以前,你从未见过Integer i = 3;的表达式,由于类与字面值是不能通用的,除了String。而在JDK 5.0中,这种表达式是能够的!由于编译器在后台进行Integer i = new Integer(3)的转换)。前者是规范的类的建立过程,即在Java中,一切都是对象,而对象是类的实例,所有经过new()的形式来建立。那为何在String str = "abc";中,并无经过new()来建立实例,是否是违反了上述原则?其实没有。

5.1). 关于String str = "abc"的内部工做。Java内部将此语句转化为如下几个步骤:

  (1)先定义一个名为str的对String类的对象引用变量:String str;

  (2)在栈中查找有没有存放值为"abc"的地址,若是没有,则开辟一个存放字面值为"abc"的地址,接着建立一个新的String类的对象o,并将o 的字符串值指向这个地址,并且在栈中这个地址旁边记下这个引用的对象o。若是已经有了值为"abc"的地址,则查找对象o,并返回o的地址。

  (3)将str指向对象o的地址。

  值得注意的是,通常String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值倒是保存了一个指向存在栈中数据的引用!

为了更好地说明这个问题,咱们能够经过如下的几个代码进行验证。

  String str1 = "abc";

  String str2 = "abc";

  System.out.println(str1==str2);   //true

  注意,咱们这里并不用str1.equals(str2);的方式,由于这将比较两个字符串的值是否相等。==号,根据JDK的说明,只有在两个引用都指向了同一个对象时才返回真值。而咱们在这里要看的是,str1与str2是否都指向了同一个对象。

  结果说明,JVM建立了两个引用str1和str2,但只建立了一个对象,并且两个引用都指向了这个对象。

  咱们再来更进一步,将以上代码改为:

  String str1 = "abc";

  String str2 = "abc";

  str1 = "bcd";

  System.out.println(str1 + "," + str2);   //bcd, abc

  System.out.println(str1==str2);   //false

  这就是说,赋值的变化致使了类对象引用的变化,str1指向了另一个新对象!而str2仍旧指向原来的对象。上例中,当咱们将str1的值改成"bcd"时,JVM发如今栈中没有存放该值的地址,便开辟了这个地址,并建立了一个新的对象,其字符串的值指向这个地址。

  事实上,String类被设计成为不可改变(immutable)的类。若是你要改变其值,能够,但JVM在运行时根据新值悄悄建立了一个新对象,而后将这个对象的地址返回给原来类的引用。这个建立过程虽然说是彻底自动进行的,但它毕竟占用了更多的时间。在对时间要求比较敏感的环境中,会带有必定的不良影响。

  再修改原来代码:

  String str1 = "abc";

  String str2 = "abc";

  str1 = "bcd";

  String str3 = str1;

  System.out.println(str3);   //bcd

  String str4 = "bcd";

  System.out.println(str1 == str4);   //true

  str3 这个对象的引用直接指向str1所指向的对象(注意,str3并无建立新对象)。当str1改完其值后,再建立一个String的引用str4,并指向因str1修改值而建立的新的对象。能够发现,这回str4也没有建立新的对象,从而再次实现栈中数据的共享。

  咱们再接着看如下的代码。

  String str1 = new String("abc");

  String str2 = "abc";

  System.out.println(str1==str2);   //false

  建立了两个引用。建立了两个对象。两个引用分别指向不一样的两个对象。

  以上两段代码说明,只要是用new()来新建对象的,都会在堆中建立,并且其字符串是单独存值的,即便与栈中的数据相同,也不会与栈中的数据共享。

  6). 数据类型包装类的值不可修改。不只仅是String类的值不可修改,全部的数据类型包装类都不能更改其内部的值。

  7). 结论与建议:

  (1)咱们在使用诸如String str = "abc";的格式定义类时,老是想固然地认为,咱们建立了String类的对象str。担忧陷阱!对象可能并无被建立!惟一能够确定的是,指向 String类的引用被建立了。至于这个引用究竟是否指向了一个新的对象,必须根据上下文来考虑,除非你经过new()方法来显要地建立一个新的对象。所以,更为准确的说法是,咱们建立了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。清醒地认识到这一点对排除程序中难以发现的bug是颇有帮助的。

  (2)使用String str = "abc";的方式,能够在必定程度上提升程序的运行速度,由于JVM会自动根据栈中数据的实际状况来决定是否有必要建立新对象。而对于String str = new String("abc");的代码,则一律在堆中建立新对象,而无论其字符串值是否相等,是否有必要建立新对象,从而加剧了程序的负担。这个思想应该是享元模式的思想,但JDK的内部在这里实现是否应用了这个模式,不得而知。

  (3)当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。

  (4)因为String类的immutable性质,当String变量须要常常变换其值时,应该考虑使用StringBuffer类,以提升程序效率。

若是java不能成功分配heap的空间,将抛出OutOfMemoryError。

[if !supportLists]13. [endif]解释内存中的栈 (stack) 、堆 (heap) 和方法区 (method area) 的用法(2017-11-12-wl)

一般咱们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用JVM中的栈空间;而经过new关键字和构造器建立的对象则放在堆空间,堆是垃圾收集器管理的主要区域,因为如今的垃圾收集器都采用分代收集算法,因此堆空间还能够细分为新生代和老生代,再具体一点能够分为 Eden、Survivor(又可分为 From Survivor 和 To Survivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT 编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的 100、"hello"和常量都是放在常量池中,常量池是方法区的一部分。栈空间操做起来最快可是栈很小,一般大量的对象都是放在堆空间,栈和堆的大小均可以经过 JVM 的启动参数来进行调整,栈空间用光了会引起 StackOverflowError,而堆和常量池空间不足则会引起 OutOfMemoryError。

String str = new String("hello");

上面的语句中变量str 放在栈上,用 new 建立出来的字符串对象放在堆上,而"hello"这个字面量是放在方法区的。

[if !supportLists]4、[endif]Java的类加载器(2015-12-2)

[if !supportLists]1. [endif]Java的类加载器的种类都有哪些?

一、根类加载器(Bootstrap) --C++写的 ,看不到源码

二、扩展类加载器(Extension) --加载位置 :jre\lib\ext中

三、系统(应用)类加载器(System\App)  --加载位置 :classpath中

四、自定义加载器(必须继承ClassLoader)

[if !supportLists]2. [endif]类何时被初始化?

1)建立类的实例,也就是new一个对象

2)访问某个类或接口的静态变量,或者对该静态变量赋值

3)调用类的静态方法

4)反射(Class.forName("com.lyj.load"))

5)初始化一个类的子类(会首先初始化子类的父类)

6)JVM启动时标明的启动类,即文件名和类名相同的那个类

只有这6中状况才会致使类的类的初始化。

类的初始化步骤:

        1)若是这个类尚未被加载和连接,那先进行加载和连接

        2)假如这个类存在直接父类,而且这个类尚未被初始化(注意:在一个类加载器中,类只能初始化一次),那就初始化直接的父类(不适用于接口)

         3)加入类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。

[if !supportLists]3. [endif]Java类加载体系之ClassLoader双亲委托机制 (2017-2-24)

java是一种类型安全的语言,它有四类称为安全沙箱机制的安全机制来保证语言的安全性,这四类安全沙箱分别是:

[if !supportLists]1) [endif]类加载体系

[if !supportLists]2) [endif].class文件检验器

[if !supportLists]3) [endif]内置于Java虚拟机(及语言)的安全特性

[if !supportLists]4) [endif]安全管理器及Java API

主要讲解类的加载体系:

java程序中的 .java文件编译完会生成 .class文件,而 .class文件就是经过被称为类加载器的ClassLoader加载的,而ClassLoder在加载过程当中会使用“双亲委派机制”来加载 .class文件,先上图:

 

BootStrapClassLoader:启动类加载器,该ClassLoader是jvm在启动时建立的,用于加载 $JAVA_HOME$/jre/lib下面的类库(或者经过参数-Xbootclasspath指定)。因为启动类加载器涉及到虚拟机本地实现细节,开发者没法直接获取到启动类加载器的引用,因此不能直接经过引用进行操做。

ExtClassLoader:扩展类加载器,该ClassLoader是在sun.misc.Launcher里做为一个内部类ExtClassLoader定义的(即 sun.misc.Launcher$ExtClassLoader),ExtClassLoader会加载 $JAVA_HOME/jre/lib/ext下的类库(或者经过参数-Djava.ext.dirs指定)。

AppClassLoader:应用程序类加载器,该ClassLoader一样是在sun.misc.Launcher里做为一个内部类AppClassLoader定义的(即 sun.misc.Launcher$AppClassLoader),AppClassLoader会加载java环境变量CLASSPATH所指定的路径下的类库,而CLASSPATH所指定的路径能够经过System.getProperty("java.class.path")获取;固然,该变量也能够覆盖,能够使用参数-cp,例如:java -cp 路径 (能够指定要执行的class目录)。

CustomClassLoader:自定义类加载器,该ClassLoader是指咱们自定义的ClassLoader,好比tomcat的StandardClassLoader属于这一类;固然,大部分状况下使用AppClassLoader就足够了。

前面谈到了ClassLoader的几类加载器,而ClassLoader使用双亲委派机制来加载class文件的。ClassLoader的双亲委派机制是这样的(这里先忽略掉自定义类加载器CustomClassLoader):

1)当AppClassLoader加载一个class时,它首先不会本身去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

2)当ExtClassLoader加载一个class时,它首先也不会本身去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

3)若是BootStrapClassLoader加载失败(例如在$JAVA_HOME$/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

4)若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,若是AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

下面贴下ClassLoader的loadClass(String name, boolean resolve)的源码:

[if !supportLists]1.[endif]protected synchronized Class loadClass(String name, boolean resolve)

[if !supportLists]2.[endif] throws ClassNotFoundException {

[if !supportLists]3.[endif] // 首先找缓存是否有class

[if !supportLists]4.[endif] Class c = findLoadedClass(name);

[if !supportLists]5.[endif] if (c == null) {

[if !supportLists]6.[endif]//没有判断有没有父类

[if !supportLists]7.[endif] try {

[if !supportLists]8.[endif] if (parent != null) {

[if !supportLists]9.[endif]//有的话,用父类递归获取class

[if !supportLists]10.[endif] c = parent.loadClass(name, false);

[if !supportLists]11.[endif] } else {

[if !supportLists]12.[endif]//没有父类。经过这个方法来加载

[if !supportLists]13.[endif] c = findBootstrapClassOrNull(name);

[if !supportLists]14.[endif] }

[if !supportLists]15.[endif] } catch (ClassNotFoundException e) {

[if !supportLists]16.[endif] // ClassNotFoundException thrown if class not found

[if !supportLists]17.[endif] // from the non-null parent class loader

[if !supportLists]18.[endif] }

[if !supportLists]19.[endif] if (c == null) {

[if !supportLists]20.[endif] // 若是仍是没有找到,调用findClass(name)去找这个类

[if !supportLists]21.[endif] c = findClass(name);

[if !supportLists]22.[endif] }

[if !supportLists]23.[endif] }

[if !supportLists]24.[endif] if (resolve) {

[if !supportLists]25.[endif] resolveClass(c);

[if !supportLists]26.[endif] }

[if !supportLists]27.[endif] return c;

[if !supportLists]28.[endif] }

代码很明朗:首先找缓存(findLoadedClass),没有的话就判断有没有parent,有的话就用parent来递归的loadClass,然而ExtClassLoader并无设置parent,则会经过findBootstrapClassOrNull来加载class,而findBootstrapClassOrNull则会经过JNI方法”private native Class findBootstrapClass(String name)“来使用BootStrapClassLoader来加载class。

而后若是parent未找到class,则会调用findClass来加载class,findClass是一个protected的空方法,能够覆盖它以便自定义class加载过程。

另外,虽然ClassLoader加载类是使用loadClass方法,可是鼓励用 ClassLoader 的子类重写 findClass(String),而不是重写loadClass,这样就不会覆盖了类加载默认的双亲委派机制。

双亲委派托机制为何安全

举个例子,ClassLoader加载的class文件来源不少,好比编译器编译生成的class、或者网络下载的字节码。而一些来源的class文件是不可靠的,好比我能够自定义一个java.lang.Integer类来覆盖jdk中默认的Integer类,例以下面这样:

[if !supportLists]1. [endif]package java.lang;

[if !supportLists]2. [endif]public class Integer {

[if !supportLists]3. [endif] public Integer(int value) {

[if !supportLists]4. [endif] System.exit(0);

[if !supportLists]5. [endif] }

[if !supportLists]6. [endif]}

初始化这个Integer的构造器是会退出JVM,破坏应用程序的正常进行,若是使用双亲委派机制的话该Integer类永远不会被调用,觉得委托BootStrapClassLoader加载后会加载JDK中的Integer类而不会加载自定义的这个,能够看下下面这测试个用例:

[if !supportLists]1.[endif]public static void main(String... args) {

[if !supportLists]2.[endif] Integer i = new Integer(1);

[if !supportLists]3.[endif] System.err.println(i);

[if !supportLists]4.[endif] }

 

执行时JVM并未在new Integer(1)时退出,说明未使用自定义的Integer,因而就保证了安全性。

[if !supportLists]4. [endif]描述一下JVM加载class (2017-11-15-wl)

JVM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中的类加载器是一个重要的Java 运行时系统组件,它负责在运行时查找和装入类文件中的类。

因为Java的跨平台性,通过编译的 Java 源程序并非一个可执行程序,而是一个或多个类文件。当Java程序须要使用某个类时,JVM会确保这个类已经被加载、链接(验证、准备和解析)和初始化。类的加载是指把类的.class 文件中的数据读入到内存中,一般是建立一个字节数组读入.class 文件,而后产生与所加载类对应的 Class 对象。加载完成后,Class 对象还不完整,因此此时的类还不可用。当类被加载后就进入链接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。最后 JVM 对类进行初始化,包括:

若是类存在直接的父类而且这个类尚未被初始化,那么就先初始化父类;

若是类中存在初始化语句,就依次执行这些初始化语句。类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader 的子类)。

从Java 2(JDK 1.2)开始,类加载过程采起了父亲委托机制(PDM)。PDM 更好的保证了 Java 平台的安全性,在该机制中,JVM 自带的 Bootstrap是根加载器,其余的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java 程序提供对 Bootstrap 的引用。

下面是关于几个类加载器的说明:

•  Bootstrap:通常用本地代码实现,负责加载 JVM 基础核心类库(rt.jar);

•  Extension:从 java.ext.dirs 系统属性所指定的目录中加载类库,它的父加载器是 Bootstrap;

•  System:又叫应用类加载器,其父类是 Extension。它是应用最普遍的类加载器。它从环境变量classpath

或者系统属性java.class.path 所指定的目录中记载类,是用户自定义加载器的默认父加载器。

[if !supportLists]5. [endif]得到一个类对象有哪些方式?(2017-11-23-wzz)

类型.class,例如:String.class

对象.getClass(),例如:”hello”.getClass()

Class.forName(),例如:Class.forName(“java.lang.String”)

 

[if !supportLists]5、[endif]JVM基础知识2017-11-16-wl

[if !supportLists]1. [endif]既然有GC机制,为何还会有内存泄露的状况(2017-11-16-wl)

理论上Java 由于有垃圾回收机制(GC)不会存在内存泄露问题(这也是 Java 被普遍使用于服务器端编程的一个重要缘由)。然而在实际开发中,可能会存在无用但可达的对象,这些对象不能被 GC 回收,所以也会致使内存泄露的发生。

例如hibernate 的 Session(一级缓存)中的对象属于持久态,垃圾回收器是不会回收这些对象的,然而这些对象中可能存在无用的垃圾对象,若是不及时关闭(close)或清空(flush)一级缓存就可能致使内存泄露。

下面例子中的代码也会致使内存泄露。

 

[if !supportLists]1. [endif]import java.util.Arrays;

[if !supportLists]2. [endif]import java.util.EmptyStackException;

[if !supportLists]3. [endif]public class MyStack {

[if !supportLists]4. [endif] private T[] elements;

[if !supportLists]5. [endif] private int size = 0;

[if !supportLists]6. [endif] private static final int INIT_CAPACITY = 16;

[if !supportLists]7. [endif] public MyStack() {

[if !supportLists]8. [endif]      elements = (T[]) new Object[INIT_CAPACITY];

[if !supportLists]9. [endif] }

[if !supportLists]10. [endif] public void push(T elem) {

[if !supportLists]11. [endif] ensureCapacity();

[if !supportLists]12. [endif] elements[size++] = elem;

[if !supportLists]13. [endif] }

[if !supportLists]14. [endif] public T pop() {

[if !supportLists]15. [endif] if(size == 0)throw new EmptyStackException();

[if !supportLists]16. [endif]  return elements[--size];

[if !supportLists]17. [endif]}

[if !supportLists]18. [endif] private void ensureCapacity() {

[if !supportLists]19. [endif] if(elements.length == size) {

[if !supportLists]20. [endif]   elements = Arrays.copyOf(elements, 2 * size + 1);

[if !supportLists]21. [endif] }

[if !supportLists]22. [endif]}

[if !supportLists]23. [endif]}

 

上面的代码实现了一个栈(先进后出(FILO))结构,乍看之下彷佛没有什么明显的问题,它甚至能够经过你编写的各类单元测试。然而其中的 pop 方法却存在内存泄露的问题,当咱们用 pop 方法弹出栈中的对象时,该对象不会被看成垃圾回收,即便使用栈的程序再也不引用这些对象,由于栈内部维护着对这些对象的过时引用(obsolete reference)。在支持垃圾回收的语言中,内存泄露是很隐蔽的,这种内存泄露其实就是无心识的对象保持。若是一个对象引用被无心识的保留起来了,那么垃圾回收器不会处理这个对象,也不会处理该对象引用的其余对象,即便这样的对象只有少数几个,也可能会致使不少的对象被排除在垃圾回收以外,从而对性能形成重大影响,极端状况下会引起 Disk Paging (物理内存与硬盘的虚拟内存交换数据),甚至形成 OutOfMemoryError。

[if !supportLists]6、[endif]GC基础知识2017-11-16-wl

[if !supportLists]1. [endif]Java中为何会有GC机制呢?(2017-11-16-wl)

Java中为何会有GC机制呢?

[if !supportLists]· [endif]安全性考虑;-- for security.

[if !supportLists]· [endif]减小内存泄露;-- erase memory leak in some degree.

[if !supportLists]· [endif]减小程序员工做量。-- Programmers don't worry about memory releasing.

[if !supportLists]2. [endif]对于Java的GC哪些内存须要回收(2017-11-16-wl)

内存运行时JVM会有一个运行时数据区来管理内存。它主要包括5大部分:程序计数器(Program Counter Register)、虚拟机栈(VM Stack)、本地方法栈(Native Method Stack)、方法区(Method Area)、堆(Heap).

而其中程序计数器、虚拟机栈、本地方法栈是每一个线程私有的内存空间,随线程而生,随线程而亡。例如栈中每个栈帧中分配多少内存基本上在类结构肯定是哪一个时就已知了,所以这3个区域的内存分配和回收都是肯定的,无需考虑内存回收的问题。

但方法区和堆就不一样了,一个接口的多个实现类须要的内存可能不同,咱们只有在程序运行期间才会知道会建立哪些对象,这部份内存的分配和回收都是动态的,GC主要关注的是这部份内存。

总而言之,GC主要进行回收的内存是JVM中的方法区和堆;

[if !supportLists]3. [endif]Java的GC何时回收垃圾(2017-11-16-wl)

在面试中常常会碰到这样一个问题(事实上笔者也碰到过):如何判断一个对象已经死去?

很容易想到的一个答案是:对一个对象添加引用计数器。每当有地方引用它时,计数器值加1;当引用失效时,计数器值减1.而当计数器的值为0时这个对象就不会再被使用,判断为已死。是否是简单又直观。然而,很遗憾。这种作法是错误的!为何是错的呢?事实上,用引用计数法确实在大部分状况下是一个不错的解决方案,而在实际的应用中也有很多案例,但它却没法解决对象之间的循环引用问题。好比对象A中有一个字段指向了对象B,而对象B中也有一个字段指向了对象A,而事实上他们俩都再也不使用,但计数器的值永远都不可能为0,也就不会被回收,而后就发生了内存泄露。

因此,正确的作法应该是怎样呢? 在Java,C#等语言中,比较主流的断定一个对象已死的方法是:可达性分析(Reachability Analysis).全部生成的对象都是一个称为"GC Roots"的根的子树。从GC Roots开始向下搜索,搜索所通过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链能够到达时,就称这个对象是不可达的(不可引用的),也就是能够被GC回收了。

 

不管是引用计数器仍是可达性分析,断定对象是否存活都与引用有关!那么,如何定义对象的引用呢?

咱们但愿给出这样一类描述:当内存空间还够时,可以保存在内存中;若是进行了垃圾回收以后内存空间仍旧很是紧张,则能够抛弃这些对象。因此根据不一样的需求,给出以下四种引用,根据引用类型的不一样,GC回收时也会有不一样的操做:

1)强引用(Strong Reference):Object obj = new Object();只要强引用还存在,GC永远不会回收掉被引用的对象。

2)软引用(Soft Reference):描述一些还有用但非必需的对象。在系统将会发生内存溢出以前,会把这些对象列入回收范围进行二次回收(即系统将会发生内存溢出了,才会对他们进行回收。)

弱引用(Weak Reference):程度比软引用还要弱一些。这些对象只能生存到下次GC以前。当GC工做时,不管内存是否足够都会将其回收(即只要进行GC,就会对他们进行回收。)

虚引用(Phantom Reference):一个对象是否存在虚引用,彻底不会对其生存时间构成影响。

关于方法区中须要回收的是一些废弃的常量和无用的类。

1.废弃的常量的回收。这里看引用计数就能够了。没有对象引用该常量就能够放心的回收了。

2.无用的类的回收。什么是无用的类呢?

A.该类全部的实例都已经被回收。也就是Java堆中不存在该类的任何实例;

B.加载该类的ClassLoader已经被回收;

C.该类对应的java.lang.Class对象没有任何地方被引用,没法在任何地方经过反射访问该类的方法。

总而言之:

对于堆中的对象,主要用可达性分析判断一个对象是否还存在引用,若是该对象没有任何引用就应该被回收。而根据咱们实际对引用的不一样需求,又分红了4中引用,每种引用的回收机制也是不一样的。

对于方法区中的常量和类,当一个常量没有任何对象引用它,它就能够被回收了。而对于类,若是能够断定它为无用类,就能够被回收了。

[if !supportLists]7、[endif]Java8的新特性以及使用(2017-12-02-wl

[if !supportLists]1. [endif]经过10个示例来初步认识Java8中的lambda表达式(2017-12-02-wl)

我我的对Java 8发布很是激动,尤为是lambda表达式和流API。愈来愈多的了解它们,我能写出更干净的代码。虽然一开始并非这样。第一次看到用lambda表达式写出来的Java代码时,我对这种神秘的语法感到很是失望,认为它们把Java搞得不可读,但我错了。花了一天时间作了一些lambda表达式和流API示例的练习后,我开心的看到了更清晰的Java代码。这有点像学习泛型,第一次见的时候我很讨厌它。我甚至继续使用老版Java 1.4来处理集合,直到有一天,朋友跟我介绍了使用泛型的好处(才意识到它的好处)。因此基本立场就是,不要畏惧lambda表达式以及方法引用的神秘语法,作几回练习,从集合类中提取、过滤数据以后,你就会喜欢上它。下面让咱们开启学习Java 8 lambda表达式的学习之旅吧~

本小节中先不说lambda表达的含义和繁琐的概念。咱们先从最简单的示例来介绍java8中的lambda表达式

例一、用lambda表达式实现Runnable

// Java 8以前:

new Thread(new Runnable() {

    @Override

    public void run() {

      System.out.println("Before Java8, too much code for too little to do");

    }

}).start();

 

//Java 8方式:

new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();

 

输出:

too much code, for too little to do

Lambda expression rocks !!

这个例子向咱们展现了Java 8 lambda表达式的语法。你能够使用lambda写出以下代码:

(params) -> expression

(params) -> statement

(params) -> { statements }

例如,若是你的方法不对参数进行修改、重写,只是在控制台打印点东西的话,那么能够这样写:

() -> System.out.println("Hello Lambda Expressions");

若是你的方法接收两个参数,那么能够写成以下这样:

(int even, int odd) -> even + odd

顺便提一句,一般都会把lambda表达式内部变量的名字起得短一些。这样能使代码更简短,放在同一行。因此,在上述代码中,变量名选用a、b或者x、y会比even、odd要好。

例二、使用Java 8 lambda表达式进行事件处理

若是你用过Swing API编程,你就会记得怎样写事件监听代码。这又是一个旧版本简单匿名类的经典用例,但如今能够不这样了。你能够用lambda表达式写出更好的事件监听代码,以下所示:

// Java 8以前:

JButton show =  new JButton("Show");

show.addActionListener(new ActionListener() {

    @Override

    public void actionPerformed(ActionEvent e) {

    System.out.println("Event handling without lambda expression is boring");

    }

});

 

// Java 8方式:

show.addActionListener((e) -> {

    System.out.println("Light, Camera, Action !! Lambda expressions Rocks");

});

Java开发者常用匿名类的另外一个地方是为 Collections.sort() 定制 Comparator。在Java 8中,你能够用更可读的lambda表达式换掉丑陋的匿名类。我把这个留作练习,应该不难,能够按照我在使用lambda表达式实现 Runnable 和 ActionListener 的过程当中的套路来作。

例三、使用Java 8 lambda表达式进行事件处理  使用lambda表达式对列表进行迭代

若是你使过几年Java,你就知道针对集合类,最多见的操做就是进行迭代,并将业务逻辑应用于各个元素,例如处理订单、交易和事件的列表。因为Java是命令式语言,Java 8以前的全部循环代码都是顺序的,便可以对其元素进行并行化处理。若是你想作并行过滤,就须要本身写代码,这并非那么容易。经过引入lambda表达式和默认方法,将作什么和怎么作的问题分开了,这意味着Java集合如今知道怎样作迭代,并能够在API层面对集合元素进行并行处理。下面的例子里,我将介绍如何在使用lambda或不使用lambda表达式的状况下迭代列表。你能够看到列表如今有了一个 forEach()  方法,它能够迭代全部对象,并将你的lambda代码应用在其中。

// Java 8以前:

List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");

for (String feature : features) {

    System.out.println(feature);

}

 

// Java 8以后:

List features = Arrays.asList("Lambdas", "Default Method", "Stream API", "Date and Time API");

features.forEach(n -> System.out.println(n));

 

//使用Java 8的方法引用更方便,方法引用由::双冒号操做符标示,

//看起来像C++的做用域解析运算符

features.forEach(System.out::println);

 

输出:

Lambdas

Default Method

Stream API

Date and Time API

列表循环的最后一个例子展现了如何在Java 8中使用方法引用(method reference)。你能够看到C++里面的双冒号、范围解析操做符如今在Java 8中用来表示方法引用。

例四、使用lambda表达式和函数式接口Predicate

除了在语言层面支持函数式编程风格,Java 8也添加了一个包,叫作 java.util.function。它包含了不少类,用来支持Java的函数式编程。其中一个即是Predicate,使用 java.util.function.Predicate 函数式接口以及lambda表达式,能够向API方法添加逻辑,用更少的代码支持更多的动态行为。下面是Java 8 Predicate 的例子,展现了过滤集合数据的多种经常使用方法。Predicate接口很是适用于作过滤。

public static void main(args[]){

    List languages = Arrays.asList("Java", "Scala", "C++", "Haskell", "Lisp");

 

    System.out.println("Languages which starts with J :");

    filter(languages, (str)->str.startsWith("J"));

 

    System.out.println("Languages which ends with a ");

    filter(languages, (str)->str.endsWith("a"));

 

    System.out.println("Print all languages :");

    filter(languages, (str)->true);

 

    System.out.println("Print no language : ");

    filter(languages, (str)->false);

 

    System.out.println("Print language whose length greater than 4:");

    filter(languages, (str)->str.length() > 4);

}

 

public static void filter(List names, Predicate condition) {

    for(String name: names)  {

        if(condition.test(name)) {

            System.out.println(name + " ");

        }

    }

}

 

 

// filter更好的办法--filter方法改进

public static void filter(List names, Predicate condition) {

    names.stream().filter((name) -> (condition.test(name))).forEach((name) -> {

        System.out.println(name + " ");

    });

}

 

能够看到,Stream API的过滤方法也接受一个Predicate,这意味着能够将咱们定制的 filter() 方法替换成写在里面的内联代码,这就是lambda表达式的魔力。另外,Predicate接口也容许进行多重条件的测试,下个例子将要讲到。

例五、如何在lambda表达式中加入Predicate

上个例子说到,java.util.function.Predicate 容许将两个或更多的 Predicate 合成一个。它提供相似于逻辑操做符AND和OR的方法,名字叫作and()、or()和xor(),用于将传入 filter() 方法的条件合并起来。例如,要获得全部以J开始,长度为四个字母的语言,能够定义两个独立的 Predicate 示例分别表示每个条件,而后用 Predicate.and() 方法将它们合并起来,以下所示:

//甚至能够用and()、or()和xor()逻辑函数来合并Predicate,

//例如要找到全部以J开始,长度为四个字母的名字,你能够合并两个Predicate并传入

Predicate startsWithJ = (n) -> n.startsWith("J");

Predicate fourLetterLong = (n) -> n.length() == 4;

names.stream()

    .filter(startsWithJ.and(fourLetterLong))

    .forEach((n) -> System.out.print("nName, which starts with 'J' and four letter long is : " + n));

相似地,也能够使用or() 和 xor() 方法。本例着重介绍了以下要点:可按须要将 Predicate 做为单独条件而后将其合并起来使用。简而言之,你能够以传统Java命令方式使用 Predicate 接口,也能够充分利用lambda表达式达到事半功倍的效果。

例六、Java 8中使用lambda表达式的Map和Reduce示例

本例介绍最广为人知的函数式编程概念map。它容许你将对象进行转换。例如在本例中,咱们将 costBeforeTax 列表的每一个元素转换成为税后的值。咱们将 x -> x*x lambda表达式传到 map() 方法,后者将其应用到流中的每个元素。而后用 forEach() 将列表元素打印出来。使用流API的收集器类,能够获得全部含税的开销。有 toList() 这样的方法将 map 或任何其余操做的结果合并起来。因为收集器在流上作终端操做,所以以后便不能重用流了。你甚至能够用流API的 reduce() 方法将全部数字合成一个,下一个例子将会讲到。

//不使用lambda表达式为每一个订单加上12%的税

List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);

for (Integer cost : costBeforeTax) {

    double price = cost + .12*cost;

    System.out.println(price);

}

 

//使用lambda表达式

List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);

costBeforeTax.stream().map((cost) -> cost + .12*cost).forEach(System.out::println);

在上面例子中,能够看到map将集合类(例如列表)元素进行转换的。还有一个 reduce() 函数能够将全部值合并成一个。Map和Reduce操做是函数式编程的核心操做,由于其功能,reduce 又被称为折叠操做。另外,reduce 并非一个新的操做,你有可能已经在使用它。SQL中相似 sum()、avg() 或者 count() 的汇集函数,实际上就是 reduce 操做,由于它们接收多个值并返回一个值。流API定义的 reduceh() 函数能够接受lambda表达式,并对全部值进行合并。IntStream这样的类有相似 average()、count()、sum() 的内建方法来作 reduce 操做,也有mapToLong()、mapToDouble() 方法来作转换。这并不会限制你,你能够用内建方法,也能够本身定义。在这个Java 8的Map Reduce示例里,咱们首先对全部价格应用 12% 的VAT,而后用 reduce() 方法计算总和。

//为每一个订单加上12%的税

//老方法:

List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);

double total = 0;

for (Integer cost : costBeforeTax) {

    double price = cost + .12*cost;

    total = total + price;

}

System.out.println("Total : " + total);

 

//新方法:

List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);

double bill = costBeforeTax.stream().map((cost) -> cost + .12*cost).reduce((sum, cost) -> sum + cost).get();

System.out.println("Total : " + bill);

例七、经过过滤建立一个String列表

过滤是Java开发者在大规模集合上的一个经常使用操做,而如今使用lambda表达式和流API过滤大规模数据集合是惊人的简单。流提供了一个 filter() 方法,接受一个 Predicate 对象,便可以传入一个lambda表达式做为过滤逻辑。下面的例子是用lambda表达式过滤Java集合,将帮助理解。

//建立一个字符串列表,每一个字符串长度大于2

List costBeforeTax = Arrays.asList("abc","bcd","defg","jk");

List filtered = strList.stream().filter(x -> x.length()> 2).collect(Collectors.toList());

System.out.printf("Original List : %s, filtered list : %s %n", strList, filtered);

 

 

输出:

Original List : [abc, , bcd, , defg, jk], filtered list : [abc, bcd, defg]

另外,关于filter() 方法有个常见误解。在现实生活中,作过滤的时候,一般会丢弃部分,但使用filter()方法则是得到一个新的列表,且其每一个元素符合过滤原则。

例八、对列表的每一个元素应用函数

咱们一般须要对列表的每一个元素使用某个函数,例如逐一乘以某个数、除以某个数或者作其它操做。这些操做都很适合用map() 方法,能够将转换逻辑以lambda表达式的形式放在 map() 方法里,就能够对集合的各个元素进行转换了,以下所示。

//将字符串换成大写并用逗号连接起来

List G7 = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.","Canada");

String G7Countries = G7.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", "));

System.out.println(G7Countries);

 

输出:

USA, JAPAN, FRANCE, GERMANY, ITALY, U.K., CANADA

例九、复制不一样的值,建立一个子列表

本例展现了如何利用流的distinct() 方法来对集合进行去重

//用全部不一样的数字建立一个正方形列表

List numbers = Arrays.asList(9, 10, 3, 4, 7, 3, 4);

List distinct = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());

System.out.printf("Original List : %s,  Square Without duplicates : %s %n", numbers, distinct);

 

输出:

Original List : [9, 10, 3, 4, 7, 3, 4],  Square Without duplicates : [81, 100, 9, 16, 49]

例十、计算集合元素的最大值、最小值、总和以及平均值

IntStream、LongStream 和 DoubleStream 等流的类中,有个很是有用的方法叫作 summaryStatistics() 。能够返回 IntSummaryStatistics、LongSummaryStatistics 或者 DoubleSummaryStatistic s,描述流中元素的各类摘要数据。在本例中,咱们用这个方法来计算列表的最大值和最小值。它也有 getSum() 和 getAverage() 方法来得到列表的全部元素的总和及平均值。

//获取数字的个数、最小值、最大值、总和以及平均值

List primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);

IntSummaryStatistics stats = primes.stream().mapToInt((x) -> x).summaryStatistics();

System.out.println("Highest prime number in List : " + stats.getMax());

System.out.println("Lowest prime number in List : " + stats.getMin());

System.out.println("Sum of all prime numbers : " + stats.getSum());

System.out.println("Average of all prime numbers : " + stats.getAverage());

 

输出:

Highest prime number in List : 29

Lowest prime number in List : 2

Sum of all prime numbers : 129

Average of all prime numbers : 12.9

Java 8的10个lambda表达式,这对于新手来讲是个合适的任务量,你可能须要亲自运行示例程序以便掌握。试着修改要求建立本身的例子,达到快速学习的目的。

补充:从例子中咱们能够能够看到,之前写的匿名内部类都用了lambda表达式代替了。那么,咱们简单谈谈“lambda表达式&匿名内部类”

二者不用:

[if !supportLists]1. [endif]关键字this

[if !supportLists](1) [endif]匿名内部类中的this表明匿名类

[if !supportLists](2) [endif]Lambda表达式中的this表明lambda表达式的类

[if !supportLists]2. [endif]编译方式不一样

[if !supportLists](1) [endif]匿名内部类中会编译成一个.class文件,文件命名方式为:主类+$+(1,2,3.......)

[if !supportLists](2) [endif]Java编译器将lambda表达式编译成类的私有方法。使用了Java 7的 invokedynamic 字节码指令来动态绑定这个方法

[if !supportLists]2. [endif]Java8中的lambda表达式要点(2017-12-02-wl)

经过面10个小示例中学习,咱们下面说下lambda表达式的6个要点

要点1:lambda表达式的使用位置

预约义使用了@Functional 注释的函数式接口,自带一个抽象函数的方法,或者SAM(Single Abstract Method 单个抽象方法)类型。这些称为lambda表达式的目标类型,能够用做返回类型,或lambda目标代码的参数。例如,若一个方法接收Runnable、Comparable或者 Callable 接口,都有单个抽象方法,能够传入lambda表达式。相似的,若是一个方法接受声明于 java.util.function 包内的接口,例如 Predicate、Function、Consumer 或 Supplier,那么能够向其传lambda表达式。

要点2:lambda表达式和方法引用

lambda表达式内能够使用方法引用,仅当该方法不修改lambda表达式提供的参数。本例中的lambda表达式能够换为方法引用,由于这仅是一个参数相同的简单方法调用。

list.forEach(n -> System.out.println(n)); 

list.forEach(System.out::println);  //使用方法引用

然而,若对参数有任何修改,则不能使用方法引用,而需键入完整地lambda表达式,以下所示:

list.forEach((String s) -> System.out.println("*" + s + "*"));

事实上,能够省略这里的lambda参数的类型声明,编译器能够从列表的类属性推测出来。

要点3:lambda表达式内部引用资源

lambda内部能够使用静态、非静态和局部变量,这称为lambda内的变量捕获。

要点4:lambda表达式也成闭包

Lambda表达式在Java中又称为闭包或匿名函数,因此若是有同事把它叫闭包的时候,不用惊讶。

要点5:lambda表达式的编译方式

Lambda方法在编译器内部被翻译成私有方法,并派发 invokedynamic 字节码指令来进行调用。能够使用JDK中的 javap 工具来反编译class文件。使用 javap -p 或 javap -c -v 命令来看一看lambda表达式生成的字节码。大体应该长这样:

private static java.lang.Object lambda$0(java.lang.String);

要点6:lambda表达式的限制

lambda表达式有个限制,那就是只能引用 final 或 final 局部变量,这就是说不能在lambda内部修改定义在域外的变量。

List primes = Arrays.asList(new Integer[]{2, 3,5,7});

int factor = 2;

primes.forEach(element -> { factor++; });

 

 

Error:

Compile time error : "local variables referenced from a lambda expression must be final or effectively final"

另外,只是访问它而不做修改是能够的,以下所示:

List primes = Arrays.asList(new Integer[]{2, 3,5,7});

int factor = 2;

primes.forEach(element -> { System.out.println(factor*element); });

 

输出:

4

6

10

14

所以,它看起来更像不可变闭包,相似于Python。

[if !supportLists]3. [endif]Java8中的Optional类的解析(2017-12-02-wl)

身为一名Java程序员,你们可能都有这样的经历:调用一个方法获得了返回值却不能直接将返回值做为参数去调用别的方法。咱们首先要判断这个返回值是否为null,只有在非空的前提下才能将其做为其余方法的参数。这正是一些相似Guava的外部API试图解决的问题。一些JVM编程语言好比Scala、Ceylon等已经将对在核心API中解决了这个问题。在个人前一篇文章中,介绍了Scala是如何解决了这个问题。

新版本的Java,好比Java 8引入了一个新的Optional类。Optional类的Javadoc描述以下:

这是一个能够为null的容器对象。若是值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

下面会逐个探讨Optional类包含的方法,并经过一两个示例展现如何使用。

方法1:Optional.of()

做用:为非null的值建立一个Optional。

说明:of方法经过工厂方法建立Optional类。须要注意的是,建立对象时传入的参数不能为null。若是传入参数为null,则抛出NullPointerException 。

//调用工厂方法建立Optional实例

Optional name = Optional.of("Sanaulla");

//传入参数为null,抛出NullPointerException.

Optional someNull = Optional.of(null);

方法2:Optional.ofNullable()

做用:为指定的值建立一个Optional,若是指定的值为null,则返回一个空的Optional。

说明:ofNullable与of方法类似,惟一的区别是能够接受参数为null的状况。

//下面建立了一个不包含任何值的Optional实例

//例如,值为'null'

Optional empty = Optional.ofNullable(null);

方法3:Optional.isPresent()

做用:判断预期值是否存在

说明:若是值存在返回true,不然返回false。

//isPresent方法用来检查Optional实例中是否包含值

Optional name = Optional.of("Sanaulla");

if (name.isPresent()) {

//在Optional实例内调用get()返回已存在的值

System.out.println(name.get());//输出Sanaulla

}

方法4:Optional.get()

做用:若是Optional有值则将其返回,不然抛出NoSuchElementException。

说明:上面的示例中,get方法用来获得Optional实例中的值。下面咱们看一个抛出NoSuchElementException的例子

//执行下面的代码会输出:No value present

try {

       Optional empty = Optional.ofNullable(null);

//在空的Optional实例上调用get(),抛出NoSuchElementException

  System.out.println(empty.get());

} catch (NoSuchElementException ex) {

  System.out.println(ex.getMessage());

}

方法5:Optional.ifPresent()

做用:若是Optional实例有值则为其调用consumer,不然不作处理

说明:要理解ifPresent方法,首先须要了解Consumer类。简答地说,Consumer类包含一个抽象方法。该抽象方法对传入的值进行处理,但没有返回值。Java8支持不用接口直接经过lambda表达式传入参数,若是Optional实例有值,调用ifPresent()能够接受接口段或lambda表达式

//ifPresent方法接受lambda表达式做为参数。

//lambda表达式对Optional的值调用consumer进行处理。

Optional name = Optional.of("Sanaulla");

name.ifPresent((value) -> {

  System.out.println("The length of the value is: " + value.length());

});

方法7:Optional.orElse()

做用:若是有值则将其返回,不然返回指定的其它值。

说明:若是Optional实例有值则将其返回,不然返回orElse方法传入的参数。示例以下:

 

Optional name = Optional.of("Sanaulla");

Optional someNull = Optional.of(null);

//若是值不为null,orElse方法返回Optional实例的值。

//若是为null,返回传入的消息。

//输出:There is no value present!

System.out.println(empty.orElse("There is no value present!"));

//输出:Sanaulla

System.out.println(name.orElse("There is some value!"));

方法8:Optional.orElseGet()

做用:若是有值则将其返回,不然返回指定的其它值。

说明:orElseGet与orElse方法相似,区别在于获得的默认值。orElse方法将传入的字符串做为默认值,orElseGet方法能够接受Supplier接口的实现用来生成默认值

Optional name = Optional.of("Sanaulla");

Optional someNull = Optional.of(null);

 

//orElseGet与orElse方法相似,区别在于orElse传入的是默认值,

//orElseGet能够接受一个lambda表达式生成默认值。

//输出:Default Value

System.out.println(empty.orElseGet(() -> "Default Value"));

//输出:Sanaulla

System.out.println(name.orElseGet(() -> "Default Value"));

方法9:Optional.orElseThrow()

做用:若是有值则将其返回,不然抛出supplier接口建立的异常。

说明:在orElseGet方法中,咱们传入一个Supplier接口。然而,在orElseThrow中咱们能够传入一个lambda表达式或方法,若是值不存在来抛出异常

try {

Optional empty= Optional.of(null);

 

//orElseThrow与orElse方法相似。与返回默认值不一样,

//orElseThrow会抛出lambda表达式或方法生成的异常

  empty.orElseThrow(ValueAbsentException::new);

} catch (Throwable ex) {

//输出: No value present in the Optional instance

  System.out.println(ex.getMessage());

}

 

ValueAbsentException定义以下:

class ValueAbsentException extends Throwable {

 

  public ValueAbsentException() {

    super();

  }

 

  public ValueAbsentException(String msg) {

    super(msg);

  }

 

  @Override

  public String getMessage() {

    return "No value present in the Optional instance";

  }

}

方法10:Optional.map()

做用:若是有值,则对其执行调用mapping函数获得返回值。若是返回值不为null,则建立包含mapping返回值的Optional做为map方法返回值,不然返回空Optional。

说明:map方法用来对Optional实例的值执行一系列操做。经过一组实现了Function接口的lambda表达式传入操做。

Optional name = Optional.of("Sanaulla");

//map方法执行传入的lambda表达式参数对Optional实例的值进行修改。

//为lambda表达式的返回值建立新的Optional实例做为map方法的返回值。

Optional upperName = name.map((value) -> value.toUpperCase());

System.out.println(upperName.orElse("No value found"));

方法11:Optional.flatMap()

做用:若是有值,为其执行mapping函数返回Optional类型返回值,不然返回空Optional。flatMap与map(Funtion)方法相似,区别在于flatMap中的mapper返回值必须是Optional。调用结束时,flatMap不会对结果用Optional封装。

说明:flatMap方法与map方法相似,区别在于mapping函数的返回值不一样。map方法的mapping函数返回值能够是任何类型T,而flatMap方法的mapping函数必须是Optional。

Optional name = Optional.of("Sanaulla");

//flatMap与map(Function)很是相似,区别在于传入方法的lambda表达式的返回类型。

//map方法中的lambda表达式返回值能够是任意类型,在map函数返回以前会包装为Optional。

//但flatMap方法中的lambda表达式返回值必须是Optionl实例。

upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));

System.out.println(upperName.orElse("No value found"));//输出SANAULLA

方法12:Optional.filter()

做用:若是有值而且知足断言条件返回包含该值的Optional,不然返回空Optional。

说明:filter个方法经过传入限定条件对Optional实例的值进行过滤。这里能够传入一个lambda表达式。对于filter函数咱们应该传入实现了Predicate接口的lambda表达式。

Optional name = Optional.of("Sanaulla");

//filter方法检查给定的Option值是否知足某些条件。

//若是知足则返回同一个Option实例,不然返回空Optional。

Optional longName = name.filter((value) -> value.length() > 6);

System.out.println(longName.orElse("The name is less than 6 characters"));//输出Sanaulla

 

//另外一个例子是Optional值不知足filter指定的条件。

Optional anotherName = Optional.of("Sana");

Optional shortName = anotherName.filter((value) -> value.length() > 6);

//输出:name长度不足6字符

System.out.println(shortName.orElse("The name is less than 6 characters"));

总结:Optional方法

以上,咱们介绍了Optional类的各个方法。下面经过一个完整的示例对用法集中展现。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

public class OptionalDemo {

 

  public static void main(String[] args) {

//建立Optional实例,也能够经过方法返回值获得。

    Optional name = Optional.of("Sanaulla");

 

//建立没有值的Optional实例,例如值为'null'

    Optional empty = Optional.ofNullable(null);

 

//isPresent方法用来检查Optional实例是否有值。

    if (name.isPresent()) {

//调用get()返回Optional值。

      System.out.println(name.get());

    }

 

    try {

//在Optional实例上调用get()抛出NoSuchElementException。

      System.out.println(empty.get());

    } catch (NoSuchElementException ex) {

      System.out.println(ex.getMessage());

    }

 

//ifPresent方法接受lambda表达式参数。

//若是Optional值不为空,lambda表达式会处理并在其上执行操做。

    name.ifPresent((value) -> {

      System.out.println("The length of the value is: " + value.length());

    });

 

//若是有值orElse方法会返回Optional实例,不然返回传入的错误信息。

    System.out.println(empty.orElse("There is no value present!"));

    System.out.println(name.orElse("There is some value!"));

 

//orElseGet与orElse相似,区别在于传入的默认值。

//orElseGet接受lambda表达式生成默认值。

    System.out.println(empty.orElseGet(() -> "Default Value"));

    System.out.println(name.orElseGet(() -> "Default Value"));

 

    try {

//orElseThrow与orElse方法相似,区别在于返回值。

//orElseThrow抛出由传入的lambda表达式/方法生成异常。

      empty.orElseThrow(ValueAbsentException::new);

    } catch (Throwable ex) {

      System.out.println(ex.getMessage());

    }

 

//map方法经过传入的lambda表达式修改Optonal实例默认值。

//lambda表达式返回值会包装为Optional实例。

    Optional upperName = name.map((value) -> value.toUpperCase());

    System.out.println(upperName.orElse("No value found"));

 

//flatMap与map(Funtion)很是类似,区别在于lambda表达式的返回值。

//map方法的lambda表达式返回值能够是任何类型,可是返回值会包装成Optional实例。

//可是flatMap方法的lambda返回值老是Optional类型。

    upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));

    System.out.println(upperName.orElse("No value found"));

 

//filter方法检查Optiona值是否知足给定条件。

//若是知足返回Optional实例值,不然返回空Optional。

    Optional longName = name.filter((value) -> value.length() > 6);

    System.out.println(longName.orElse("The name is less than 6 characters"));

 

//另外一个示例,Optional值不知足给定条件。

    Optional anotherName = Optional.of("Sana");

    Optional shortName = anotherName.filter((value) -> value.length() > 6);

    System.out.println(shortName.orElse("The name is less than 6 characters"));

 

  }

 

}

 

[if !supportLists]8、[endif]在开发中遇到过内存溢出么?缘由有哪些?解决方法有哪些?(2017-11-23-gxb)

引发内存溢出的缘由有不少种,常见的有如下几种:

  1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据;

  2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;

  3.代码中存在死循环或循环产生过多重复的对象实体;

  4.使用的第三方软件中的BUG;

  5.启动参数内存值设定的太小;

内存溢出的解决方案:

      第一步,修改JVM启动参数,直接增长内存。(-Xms,-Xmx参数必定不要忘记加。)

  第二步,检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。

  第三步,对代码进行走查和分析,找出可能发生内存溢出的位置。

重点排查如下几点:

  1.检查对数据库查询中,是否有一次得到所有数据的查询。通常来讲,若是一次取十万条记录到内存,就可能引发内存溢出。这个问题比较隐蔽,在上线前,数据库中数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引发内存溢出。所以对于数据库查询尽可能采用分页的方式查询。

  2.检查代码中是否有死循环或递归调用。

  3.检查是否有大循环重复产生新对象实体。

  4.检查对数据库查询中,是否有一次得到所有数据的查询。通常来讲,若是一次取十万条记录到内存,就可能引发内存溢出。这个问题比较隐蔽,在上线前,数据库中   数据较少,不容易出问题,上线后,数据库中数据多了,一次查询就有可能引发内存溢出。所以对于数据库查询尽可能采用分页的方式查询。

  5.检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

  第四步,使用内存查看工具动态查看内存使用状况。

[if !supportLists]第四章 [endif]JavaWEB 基础

1、JDBC技术

[if !supportLists]1. [endif]说下原生jdbc操做数据库流程?(2017-11-25-wzz)

第一步:Class.forName()加载数据库链接驱动;

第二步:DriverManager.getConnection()获取数据链接对象;

第三步:根据SQL获取sql会话对象,有2种方式 Statement、PreparedStatement ;

第四步:执行SQL处理结果集,执行SQL前若是有参数值就设置参数值setXXX();

第五步:关闭结果集、关闭会话、关闭链接。

详细代码请看(封装):http://blog.csdn.net/qq_29542611/article/details/52426006

 

[if !supportLists]2. [endif]什么要使用PreparedStatement?(2017-11-25-wzz)

一、 PreparedStatement接口继承Statement, PreparedStatement 实例包含已编译的 SQL 语句,因此其执行速度要快于 Statement 对象。 

二、做为 Statement 的子类,PreparedStatement 继承了 Statement 的全部功能。三种方法  execute、 executeQuery 和 executeUpdate 已被更改以使之再也不须要参数 

三、在JDBC应用中,在任什么时候候都不要使用Statement,缘由以下:

  1、代码的可读性和可维护性.Statement须要不断地拼接,而PreparedStatement不会。

  2、PreparedStatement尽最大可能提升性能.DB有缓存机制,相同的预编译语句再次被调用不会再次须要编译。

  3、最重要的一点是极大地提升了安全性.Statement容易被SQL注入,而PreparedStatementc传入的内容不会和sql语句发生任何匹配关系。

[if !supportLists]3. [endif]关系数据库中链接池的机制是什么?(2017-12-6-lyq)

前提:为数据库链接创建一个缓冲池。

1:从链接池获取或建立可用链接

2:使用完毕以后,把链接返回给链接池

3:在系统关闭前,断开全部链接并释放链接占用的系统资源

4:可以处理无效链接,限制链接池中的链接总数不低于或者不超过某个限定值。

其中有几个概念须要你们理解:

最小链接数是链接池一直保持的数据链接。若是应用程序对数据库链接的使用量不大,将会有大量的数据库链接资源被浪费掉。

最大链接数是链接池能申请的最大链接数。若是数据链接请求超过此数,后面的数据链接请求将被加入到等待队列中,这会影响以后的数据库操做。

若是最小链接数与最大链接数相差太大,那么,最早的链接请求将会获利,以后超过最小链接数量的链接请求等价于创建一个新的数据库链接。不过,这些大于最小链接数的数据库链接在使用完不会立刻被释放,它将被放到链接池中等待重复使用或是空闲超时后被释放。

上面的解释,能够这样理解:数据库池链接数量一直保持一个很多于最小链接数的数量,当数量不够时,数据库会建立一些链接,直到一个最大链接数,以后链接数据库就会等待。

 

3、Http协议

[if !supportLists]1. [endif]http的长链接和短链接(2017-11-14-lyq)

HTTP协议有HTTP/1.0版本和HTTP/1.1版本。HTTP1.1默认保持长链接(HTTP persistent connection,也翻译为持久链接),数据传输完成了保持TCP链接不断开(不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据;相反的就是短链接。

在HTTP/1.0中,默认使用的是短链接。也就是说,浏览器和服务器每进行一次HTTP操做,就创建一次链接,任务结束就中断链接。从HTTP/1.1起,默认使用的是长链接,用以保持链接特性。

[if !supportLists]2. [endif]HTTP/1.1与HTTP/1.0的区别(2017-11-21-wzy)

参考原文:http://blog.csdn.net/forgotaboutgirl/article/details/6936982

 

[if !supportLists]1 [endif]可扩展性

[if !supportLists]a) [endif]HTTP/1.1 在消息中增长版本号,用于兼容性判断。

[if !supportLists]b) [endif]HTTP/1.1增长了OPTIONS方法,它容许客户端获取一个服务器支持的方法列表。

[if !supportLists]c) [endif]为了与将来的协议规范兼容,HTTP/1.1在请求消息中包含了Upgrade头域,经过该头域,客户端能够让服务器知道它可以支持的其它备用通讯协议,服务器能够据此进行协议切换,使用备用协议与客户端进行通讯。

[if !supportLists]2 [endif]缓存

在HTTP/1.0中,使用Expire头域来判断资源的fresh或stale,并使用条件请求(conditional request)来判断资源是否仍有效。HTTP/1.1在1.0的基础上加入了一些cache的新特性,当缓存对象的Age超过Expire时变为stale对象,cache不须要直接抛弃stale对象,而是与源服务器进行从新激活(revalidation)。

[if !supportLists]3 [endif]带宽优化

HTTP/1.0中,存在一些浪费带宽的现象,例如客户端只是须要某个对象的一部分,而服务器却将整个对象送过来了。例如,客户端只须要显示一个文档的部份内容,又好比下载大文件时须要支持断点续传功能,而不是在发生断连后不得不从新下载完整的包。

HTTP/1.1中在请求消息中引入了range头域,它容许只请求资源的某个部分。在响应消息中Content-Range头域声明了返回的这部分对象的偏移值和长度。若是服务器相应地返回了对象所请求范围的内容,则响应码为206(Partial Content),它能够防止Cache将响应误觉得是完整的一个对象。

另一种状况是请求消息中若是包含比较大的实体内容,但不肯定服务器是否可以接收该请求(如是否有权限),此时若贸然发出带实体的请求,若是被拒绝也会浪费带宽。

HTTP/1.1加入了一个新的状态码100(Continue)。客户端事先发送一个只带头域的请求,若是服务器由于权限拒绝了请求,就回送响应码401(Unauthorized);若是服务器接收此请求就回送响应码100,客户端就能够继续发送带实体的完整请求了。注意,HTTP/1.0的客户端不支持100响应码。但能够让客户端在请求消息中加入Expect头域,并将它的值设置为100-continue。

节省带宽资源的一个很是有效的作法就是压缩要传送的数据。Content-Encoding是对消息进行端到端(end-to-end)的编码,它多是资源在服务器上保存的固有格式(如jpeg图片格式);在请求消息中加入Accept-Encoding头域,它能够告诉服务器客户端可以解码的编码方式。

[if !supportLists]4 [endif]长链接

HTTP/1.0规定浏览器与服务器只保持短暂的链接,浏览器的每次请求都须要与服务器创建一个TCP链接,服务器完成请求处理后当即断开TCP链接,服务器不跟踪每一个客户也不记录过去的请求。此外,因为大多数网页的流量都比较小,一次TCP链接不多能经过slow-start区,不利于提升带宽利用率。

HTTP 1.1支持长链接(PersistentConnection)和请求的流水线(Pipelining)处理,在一个TCP链接上能够传送多个HTTP请求和响应,减小了创建和关闭链接的消耗和延迟。例如:一个包含有许多图像的网页文件的多个请求和应答能够在一个链接中传输,但每一个单独的网页文件的请求和应答仍然须要使用各自的链接。

HTTP 1.1还容许客户端不用等待上一次请求结果返回,就能够发出下一次请求,但服务器端必须按照接收到客户端请求的前后顺序依次回送响应结果,以保证客户端可以区分出每次请求的响应内容,这样也显著地减小了整个下载过程所须要的时间。

[if !supportLists]5 [endif]消息传递

HTTP消息中能够包含任意长度的实体,一般它们使用Content-Length来给出消息结束标志。可是,对于不少动态产生的响应,只能经过缓冲完整的消息来判断消息的大小,但这样作会加大延迟。若是不使用长链接,还能够经过链接关闭的信号来断定一个消息的结束。

HTTP/1.1中引入了Chunkedtransfer-coding来解决上面这个问题,发送方将消息分割成若干个任意大小的数据块,每一个数据块在发送时都会附上块的长度,最后用一个零长度的块做为消息结束的标志。这种方法容许发送方只缓冲消息的一个片断,避免缓冲整个消息带来的过载。

在HTTP/1.0中,有一个Content-MD5的头域,要计算这个头域须要发送方缓冲完整个消息后才能进行。而HTTP/1.1中,采用chunked分块传递的消息在最后一个块(零长度)结束以后会再传递一个拖尾(trailer),它包含一个或多个头域,这些头域是发送方在传递完全部块以后再计算出值的。发送方会在消息中包含一个Trailer头域告诉接收方这个拖尾的存在。

[if !supportLists]6 [endif]Host头域

在HTTP1.0中认为每台服务器都绑定一个惟一的IP地址,所以,请求消息中的URL并无传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上能够存在多个虚拟主机(Multi-homed Web Servers),而且它们共享一个IP地址。

HTTP1.1的请求消息和响应消息都应支持Host头域,且请求消息中若是没有Host头域会报告一个错误(400 Bad Request)。此外,服务器应该接受以绝对路径标记的资源请求。

[if !supportLists]7 [endif]错误提示

HTTP/1.0中只定义了16个状态响应码,对错误或警告的提示不够具体。HTTP/1.1引入了一个Warning头域,增长对错误或警告信息的描述。

此外,在HTTP/1.1中新增了24个状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。

[if !supportLists]3. [endif]http常见的状态码有哪些?(2017-11-23-wzz)

200 OK      //客户端请求成功

301 Moved Permanently(永久移除),请求的URL已移走。Response中应该包含一个Location URL, 说明资源如今所处的位置

302 found 重定向

400 Bad Request  //客户端请求有语法错误,不能被服务器所理解

401 Unauthorized //请求未经受权,这个状态代码必须和WWW-Authenticate报头域一块儿使用 

403 Forbidden  //服务器收到请求,可是拒绝提供服务

404 Not Found  //请求资源不存在,eg:输入了错误的URL

500 Internal Server Error //服务器发生不可预期的错误

503 Server Unavailable  //服务器当前不能处理客户端的请求,一段时间后可能恢复正常

[if !supportLists]4. [endif]GET和POST的区别?(2017-11-23-wzz)

从表面现像上面看GET和POST的区别:

1. GET请求的数据会附在URL以后(就是把数据放置在HTTP协议头中),以?分割URL和传输数据,参数之间以&相连,如:login.action?name=zhagnsan&password=123456。POST把提交的数据则放置在是HTTP包的包体中。

2. GET方式提交的数据最多只能是1024字节,理论上POST没有限制,可传较大量的数据。其实这样说是错误的,不许确的:

“GET方式提交的数据最多只能是1024字节",由于GET是经过URL提交数据,那么GET可提交的数据量就跟URL的长度有直接关系了。而实际上,URL不存在参数上限的问题,HTTP协议规范没有对URL长度进行限制。这个限制是特定的浏览器及服务器对它的限制。IE对URL长度的限制是2083字节(2K+35)。对于其余浏览器,如Netscape、FireFox等,理论上没有长度限制,其限制取决于操做系统的支持。

3.POST的安全性要比GET的安全性高。注意:这里所说的安全性和上面GET提到的“安全”不是同个概念。上面“安全”的含义仅仅是不做数据修改,而这里安全的含义是真正的Security的含义,好比:经过GET提交数据,用户名和密码将明文出如今URL上,由于(1)登陆页面有可能被浏览器缓存,(2)其余人查看浏览器的历史纪录,那么别人就能够拿到你的帐号和密码了,除此以外,使用GET提交数据还可能会形成Cross-site request forgery攻击。

Get是向服务器发索取数据的一种请求,而Post是向服务器提交数据的一种请求,在FORM(表单)中,Method默认为"GET",实质上,GET和POST只是发送机制不一样,并非一个取一个发!

参考原文:https://www.cnblogs.com/hyddd/archive/2009/03/31/1426026.html

 

[if !supportLists]5. [endif]http中重定向和请求转发的区别?(2017-11-23-wzz)

本质区别:转发是服务器行为,重定向是客户端行为。

重定向特色:两次请求,浏览器地址发生变化,能够访问本身web以外的资源,传输的数据会丢失。

请求转发特色:一次强求,浏览器地址不变,访问的是本身自己的web资源,传输的数据不会丢失。

4、CookieSession

[if !supportLists]1. [endif]Cookie和Session的区别(2017-11-15-lyq)

Cookie是web服务器发送给浏览器的一块信息,浏览器会在本地一个文件中给每一个web服务器存储cookie。之后浏览器再给特定的web服务器发送请求时,同时会发送全部为该服务器存储的cookie。

Session是存储在web服务器端的一块信息。session对象存储特定用户会话所需的属性及配置信息。当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。

Cookie和session的不一样点:

一、不管客户端作怎样的设置,session都可以正常工做。当客户端禁用cookie时将没法使用cookie。

二、在存储的数据量方面:session可以存储任意的java对象,cookie只能存储String类型的对象。

[if !supportLists]2. [endif]session共享怎么作的(分布式如何实现session共享)?

参考原文:http://blog.csdn.net/sxiaobei/article/details/57086489

 

问题描述:一个用户在登陆成功之后会把用户信息存储在session当中,这时session所在服务器为server1,那么用户在session失效以前若是再次使用app,那么可能会被路由到server2,这时问题来了,server没有该用户的session,因此须要用户从新登陆,这时的用户体验会很是很差,因此咱们想如何实现多台server之间共享session,让用户状态得以保存。

一、服务器实现的session复制或session共享,这类型的共享session是和服务器紧密相关的,好比webSphere或JBOSS在搭建集群时候能够配置实现session复制或session共享,可是这种方式有一个致命的缺点,就是很差扩展和移植,好比咱们更换服务器,那么就要修改服务器配置。

二、利用成熟的技术作session复制,好比12306使用的gemfire,好比常见的内存数据库如redis或memorycache,这类方案虽然比较普适,可是严重依赖于第三方,这样当第三方服务器出现问题的时候,那么将是应用的灾难。

三、将session维护在客户端,很容易想到就是利用cookie,可是客户端存在风险,数据不安全,并且能够存放的数据量比较小,因此将session维护在客户端还要对session中的信息加密。

咱们实现的方案能够说是第二种方案和第三种方案的合体,能够利用gemfire实现session复制共享,还能够将session维护在redis中实现session共享,同时能够将session维护在客户端的cookie中,可是前提是数据要加密。这三种方式能够迅速切换,而不影响应用正常执行。咱们在实践中,首选gemfire或者redis做为session共享的载体,一旦session不稳定出现问题的时候,能够紧急切换cookie维护session做为备用,不影响应用提供服务。

这里主要讲解redis和cookie方案,gemfire比较复杂你们能够自行查看gemfire工做原理。利用redis作session共享,首先须要与业务逻辑代码解耦,否则session共享将没有意义,其次支持动态切换到客户端cookie模式。redis的方案是,重写服务器中的HttpSession和HttpServletRequest,首先实现HttpSession接口,重写session的全部方法,将session以hash值的方式存在redis中,一个session的key就是sessionID,setAtrribute重写以后就是更新redis中的数据,getAttribute重写以后就是获取redis中的数据,等等须要将HttpSession的接口一一实现。

实现了HttpSesson,那么咱们先将该session类叫作MySession(固然实践中不是这么命名的),当MySession出现以后问题才开始,怎么能在不影响业务逻辑代码的状况下,还能让本来的request.getSession()获取到的是MySession,而不是服务器原生的session。这里,我决定重写服务器的HttpServletRequet,这里先称为MyRequest,可是这可不是单纯的重写,我须要在原生的request基础上重写,因而我决定在filter中,实现request的偷梁换柱,个人思路是这样的,MyRequest的构建器,必须以request做为参数,因而我在filter中将服务器原生的request(也有多是框架封装过的request),当作参数new出来一个MyRequest,而且MyRequest也实现了HttpServletRequest接口,其实就是对原生request的一个加强,这里主要重写了几个request的方法,可是最重要的是重写了request.getSession(),写到这里你们应该都明白为何重写这个方法了吧,固然是为了获取MySession,因而这样就在filter中,偷偷的将原生的request换成MyRequest了,而后再将替换过的request传入chan.doFilter(),这样filter时候的代码都使用的是MyRequest了,同时对业务代码是透明的,业务代码获取session的方法仍然是request.getSession(),但其实获取到的已是MySession了,这样对session的操做已经变成了对redis的操做。这样实现的好处有两个,第一开发人员不须要对session共享作任何关注,session共享对用户是透明的;第二,filter是可配置的,经过filter的方式能够将session共享作成一项可插拔的功能,没有任何侵入性。

这个时候已经实现了一套可插拔的session共享的框架了,可是咱们想到若是redis服务出了问题,这时咱们该怎么办呢,因而咱们延续redis的想法,想到能够将session维护在客户端内(加密的cookie),固然实现方法仍是同样的,咱们重写HttpSession接口,实现其全部方法,好比setAttribute就是写入cookie,getAttribute就是读取cookie,咱们能够将重写的session称做MySession2,这时怎么让开发人员透明的获取到MySession2呢,实现方法仍是在filter内偷梁换柱,在MyRequest加一个判断,读取sessionType配置,若是sessionType是redis的,那么getSession的时候获取到的是MySession,若是sessionType是coolie的,那么getSession的时候获取到的是MySession2,以此类推,用一样的方法就能够获取到MySession 3,4,5,6等等。

这样两种方式都有了,那么咱们怎实现两种session共享方式的快速切换呢,刚刚我提到一个sessionType,这是用来决定获取到session的类型的,只要变换sessionType就能实现两种session共享方式的切换,可是sessionType必须对全部的服务器都是一致的,若是不一致那将会出现比较严重的问题,咱们目前是将sessionType维护在环境变量里,若是要切换sessionType就要重启每一台服务器,完成session共享的转换,可是当服务器太多的时候将是一种灾难。并且重启服务意味着服务的中断,因此这样的方式只适合服务器规模比较小,并且用户量比较少的状况,当服务器太多的时候,务必须要一种协调技术,可以让服务器可以及时获取切换的通知。基于这样的缘由,咱们选用zookeeper做为配置平台,每一台服务器都会订阅zookeeper上的配置,当咱们切换sessionType以后,全部服务器都会订阅到修改以后的配置,那么切换就会当即生效,固然可能会有短暂的时间延迟,但这是能够接受的。

[if !supportLists]3. [endif]在单点登陆中,若是cookie被禁用了怎么办?(2017-11-23-gxb)

单点登陆的原理是后端生成一个session ID,而后设置到 cookie,后面的全部请求浏览器都会带上 cookie,而后服务端从 cookie 里获取 session ID,再查询到用户信息。因此,保持登陆的关键不是 cookie,而是经过 cookie 保存和传输的 session ID,其本质是能获取用户信息的数据。除了 cookie,还一般使用 HTTP 请求头来传输。可是这个请求头浏览器不会像 cookie 同样自动携带,须要手工处理。

 

5、jsp技术

[if !supportLists]1. [endif]什么是jsp,什么是Servlet?jsp和Servlet有什么区别?(2017-11-23-wzz)

jsp本质上就是一个Servlet,它是Servlet的一种特殊形式(由SUN公司推出),每一个jsp页面都是一个servlet实例。

Servlet是由Java提供用于开发web服务器应用程序的一个组件,运行在服务端,由servlet容器管理,用来生成动态内容。一个servlet实例是实现了特殊接口Servlet的Java类,全部自定义的servlet均必须实现Servlet接口。

区别:

jsp是html页面中内嵌的Java代码,侧重页面显示;

Servlet是html代码和Java代码分离,侧重逻辑控制,mvc设计思想中jsp位于视图层,servlet位于控制层

 Jsp运行机制:以下图

 

JVM只能识别Java类,并不能识别jsp代码!web容器收到以.jsp为扩展名的url请求时,会将访问请求交给tomcat中jsp引擎处理,每一个jsp页面第一次被访问时,jsp引擎将jsp代码解释为一个servlet源程序,接着编译servlet源程序生成.class文件,再有web容器servlet引擎去装载执行servlet程序,实现页面交互。

[if !supportLists]2. [endif]jsp有哪些域对象和内置对象及他们的做用?(2017-11-25-wzz)

四大域对象:

(1)pageContext  page域-指当前页面,在当前jsp页面有效,跳到其它页面失效

(2)request request域-指一次请求范围内有效,从http请求到服务器处理结束,返回响应的整个过程。在这个过程当中使用forward(请求转发)方式跳转多个jsp,在这些页面里你均可以使用这个变量

(3)session session域-指当前会话有效范围,浏览器从打开到关闭过程当中,转发、重定向都可以使用

(4)application context域-指只能在同一个web中使用,服务器未关闭或者重启,数据就有效

九大内置对象:

 生命周期做用域使用状况

Request一次请求只在Jsp页面内有效用于接受经过HTTP协议传送到服务器的数据(包括头信息、系统信息、请求方式以及请求参数等)。

Reponse一次响应只在Jsp页面内有效表示服务器端对客户端的回应。主要用于设置头信息、跳转、Cookie等

Session从存入数据开始,默认闲置30分钟后失效会话内有效用于存储特定的用户会话所需的信息

Outhttp://www.cnblogs.com/leirenyuan/p/6016063.html 用于在Web浏览器内输出信息,而且管理应用服务器上的输出缓冲区

PageContext详细了解:

http://www.cnblogs.com/leirenyuan/p/6016063.html

 用于存取其余隐含对象,如request、reponse、session、application 等对象。(实际上,pageContext对象提供了对JSP页面全部的对象及命名空间的访问。

Pagehttp://www.cnblogs.com/leirenyuan/p/6016063.html page对象表明JSP自己(对应this),只有在JSP页面内才是合法的

Exceptionhttp://www.cnblogs.com/leirenyuan/p/6016063.html 显示异常信息,必须在page 指令中设定< %@ page isErrorPage="true" %>才能使用,在通常的JSP页面中使用该对象将没法编译JSP文件

Application服务器启动发送第一个请求时就产生了Application对象,直到服务器关闭。 用于存储和访问来自任何页面的变量

全部的用户分享一个 Application 对象

Confighttp://www.cnblogs.com/leirenyuan/p/6016063.html 取得服务器的配置信息

 

6、XML技术

[if !supportLists]1. [endif]什么是xml,使用xml的优缺点,xml的解析器有哪几种,分别有什么区别?(2017-11-25-wzz)

xml是一种可扩展性标记语言,支持自定义标签(使用前必须预约义)使用DTD和XML Schema标准化XML结构。

具体了解xml详见:http://www.importnew.com/10839.html

 

 

优势:用于配置文件,格式统一,符合标准;用于在互不兼容的系统间交互数据,共享数据方便;

缺点:xml文件格式复杂,数据传输占流量,服务端和客户端解析xml文件占用大量资源且不易维护

Xml经常使用解析器有2种,分别是:DOM和SAX;

主要区别在于它们解析xml文档的方式不一样。使用DOM解析,xml文档以DOM

树形结构加载入内存,而SAX采用的是事件模型,

详细区别:https://wenku.baidu.com/view/fc3fb5610b1c59eef8c7b410.html

 

 

[if !supportLists]第五章 [endif]JavaWEB高级

[if !supportLists]1、[endif]FilterListener

未完待续......

2、AJAX

[if !supportLists]1. [endif]谈谈你对ajax的认识?(2017-11-23-wzz)

Ajax是一种建立交互式网页应用的的网页开发技术;Asynchronous JavaScript and XML”的缩写。

Ajax的优点:

经过异步模式,提高了用户体验。

优化了浏览器和服务器之间的传输,减小没必要要的数据往返,减小了带宽占用。

Ajax引擎在客户端运行,承担了一部分原本由服务器承担的工做,从而减小了大用户量下的服务器负载。

Ajax的最大特色:

能够实现局部刷新,在不更新整个页面的前提下维护数据,提高用户体验度。

注意:

ajax在实际项目开发中使用率很是高(牢固掌握),针对ajax的详细描述:http://www.jb51.net/article/93258.htm

 

[if !supportLists]2. [endif]jsonp原理(2017-11-21-gxb)

JavaScript是一种在Web开发中常用的前端动态脚本技术。在JavaScript中,有一个很重要的安全性限制,被称为“Same-Origin Policy”(同源策略)。这一策略对于JavaScript代码可以访问的页面内容作了很重要的限制,即JavaScript只能访问与包含它的文档在同一域下的内容。

JavaScript这个安全策略在进行多iframe或多窗口编程、以及Ajax编程时显得尤其重要。根据这个策略,在baidu.com下的页面中包含的JavaScript代码,不能访问在google.com域名下的页面内容;甚至不一样的子域名之间的页面也不能经过JavaScript代码互相访问。对于Ajax的影响在于,经过XMLHttpRequest实现的Ajax请求,不能向不一样的域提交请求,例如,在abc.example.com下的页面,不能向def.example.com提交Ajax请求,等等。

然而,当进行一些比较深刻的前端编程的时候,不可避免地须要进行跨域操做,这时候“同源策略”就显得过于苛刻。JSONP跨域GET请求是一个经常使用的解决方案,下面咱们来看一下JSONP跨域是如何实现的,而且探讨下JSONP跨域的原理。

jsonp的最基本的原理是:动态添加一个标签,使用script标签的src属性没有跨域的限制的特色实现跨域。首先在客户端注册一个callback, 而后把callback的名字传给服务器。此时,服务器先生成 json 数据。 而后以 javascript 语法的方式,生成一个function , function 名字就是传递上来的参数 jsonp。最后将 json 数据直接以入参的方式,放置到 function 中,这样就生成了一段 js 语法的文档,返回给客户端。

客户端浏览器,解析script标签,并执行返回的 javascript 文档,此时数据做为参数,传入到了客户端预先定义好的 callback 函数里。

参考资料:http://www.nowamagic.net/librarys/veda/detail/224

 

3、Linux

[if !supportLists]1. [endif]说一下经常使用的Linux命令

列出文件列表:ls 【参数 -a -l】

建立目录和移除目录:mkdir  rmdir

用于显示文件后几行内容:tail

打包:tar -xvf

打包并压缩:tar -zcvf

查找字符串:grep

显示当前所在目录:pwd

建立空文件:touch

编辑器:vim  vi

列出文件列表:ls 【参数 -a -l】

建立目录和移除目录:mkdir  rmdir

用于显示文件后几行内容:tail

打包:tar -xvf

打包并压缩:tar -zcvf

查找字符串:grep

显示当前所在目录:pwd

建立空文件:touch

编辑器:vim  vi

[if !supportLists]2. [endif]Linux中如何查看日志?(2017-11-21-gxb)

 动态打印日志信息:tail –f 日志文件

参考资料:https://www.cnblogs.com/zdz8207/p/linux-log-tail-cat-tac.html

 

[if !supportLists]3. [endif]Linux怎么关闭进程(2017-11-21-gxb)

一般用ps 查看进程PID ,用kill命令终止进程。

ps 命令用于查看当前正在运行的进程。

  grep 是搜索

  例如:ps -ef | grep java

  表示查看全部进程里CMD是java的进程信息。

  ps -aux | grep java

  -aux 显示全部状态

kill 命令用于终止进程。

  例如:kill -9 [PID]

  -9表示强迫进程当即中止。

[if !supportLists]4、[endif]常见的前端框架有哪些

[if !supportLists]1. [endif]EasyUI(2017-11-23-lyq)

EasyUI是一种基于jQuery的用户界面插件集合。easyui为建立现代化,互动,JavaScript应用程序,提供必要的功能。使用easyui你不须要写不少代码,你只须要经过编写一些简单HTML标记,就能够定义用户界面。优点:开源免费,页面也还说的过去。

easyUI入门:

页面引入必要的js和css样式文件,文件引入顺序为:

[if !supportLists]1. [endif]

[if !supportLists]2. [endif]

[if !supportLists]3. [endif]

[if !supportLists]4. [endif]

[if !supportLists]5. [endif]

[if !supportLists]6. [endif]

[if !supportLists]7. [endif]

[if !supportLists]8. [endif]

[if !supportLists]9. [endif]                                                                    

[if !supportLists]10. [endif]   

而后在页面写easyUI代码就行,easyUI提供了不少样式:

 

示例以下:

 

实现代码以下:

[if !supportLists]1. [endif]

[if !supportLists]2. [endif]

[if !supportLists]3. [endif]

[if !supportLists]4. [endif]

[if !supportLists]5. [endif] Basic Dialog - jQuery EasyUI Demo

[if !supportLists]6. [endif]

[if !supportLists]7. [endif]

[if !supportLists]8. [endif]

[if !supportLists]9. [endif]

[if !supportLists]10. [endif]

[if !supportLists]11. [endif]

[if !supportLists]12. [endif]

[if !supportLists]13. [endif]

Basic Dialog

[if !supportLists]14. [endif]

Click below button to open or close dialog.

[if !supportLists]15. [endif]

[if !supportLists]16. [endif] Open

[if !supportLists]17. [endif] Close

[if !supportLists]18. [endif]

[if !supportLists]19. [endif]

[if !supportLists]20. [endif] The dialog content.

[if !supportLists]21. [endif]

[if !supportLists]22. [endif]

[if !supportLists]23. [endif]

[if !supportLists]24. [endif]                                                                 

[if !supportLists]2. [endif]MiniUI(2017-11-23-lyq)

基于jquery 的框架,开发的界面功能都很丰富。jQuery MiniUI - 快速开发 WebUI。它能缩短开发时间,减小代码量,使开发者更专一于业务和服务端,轻松实现界面开发,带来绝佳的用户体验。使用 MiniUI,开发者能够快速建立 Ajax 无刷新、 B/S 快速录入数据、 CRUD、 Master-Detail、菜单工具栏、弹出面板、布局导航、数据验证、分页表格、树、树形表格等典型 WEB 应用系统界面。缺点:收费,没有源码,基于这个开发若是想对功能作扩展就须要找他们的团队进行升级!

提供如下几大类控件:

表格控件

树形控件

布局控件:标题面板、弹出面板、折叠分割器、布局器、表单布局器等

导航控件:分页导航器、导航菜单、选项卡、菜单、工具栏等。

表单控件:多选输入框、弹出选择框、文本输入框、数字输入框、日期选择框、下拉选择框、下拉树形选择框、下拉表格选择框、文件上传控件、多选框、列表框、多选框组、单选框组、按钮等

富文本编辑器

图表控件:柱状图、饼图、线形图、双轴图等。

技术亮点:

快速开发:使用Html配置界面,减小80%界面代码量。

易学易用:简单的API设计,能够独立、组合使用控件。

性能优化:内置数据懒加载、低内存开销、快速界面布局等机制。

丰富控件:包含表格、树、数据验证、布局导航等超过50个控件。

超强表格:提供锁定列、多表头、分页排序、行过滤、数据汇总、单元格编辑、详细行、Excel导出等功能。

第三方兼容:与ExtJS、jQuery、YUI、Dojo等任意第三方控件无缝集成。

浏览器兼容:支持IE6+、FireFox、Chrome等。

跨平台支持:支持Java、.NET、PHP等。

示例以下:

 

实现代码以下:

[if !supportLists]1. [endif]

[if !supportLists]2. [endif]    showTreeIcon="true" textField="text" idField="id"

[if !supportLists]3. [endif]    allowDrag="true" allowDrop="true"

[if !supportLists]4. [endif]    >

[if !supportLists]5. [endif]

[if !supportLists]1. [endif]jQueryUI(2017-11-23-lyq)

jQuery UI 是一套 jQuery 的页面 UI 插件,包含不少种经常使用的页面空间,例如 Tabs(如本站首页右上角部分) 、拉帘效果(本站首页左上角)、对话框、拖放效果、日期选择、颜色选择、数据排序、窗体大小调整等等很是多的内容。

技术亮点:

简单易用:继承jQuery 简易使用特性,提供高度抽象接口,短时间改善网站易用性。

开源免费:采用MIT & GPL 双协议受权,轻松知足自由产品至企业产品各类受权需求。

普遍兼容:兼容各主流桌面浏览器。包括IE 6+、Firefox 2+、Safari 3+、Opera 9+、Chrome 1+。

轻便快捷:组件间相对独立,可按需加载,避免浪费带宽拖慢网页打开速度。

标准先进:支持WAI-ARIA,经过标准 XHTML 代码提供渐进加强,保证低端环境可访问性。

美观多变:提供近20 种预设主题,并可自定义多达 60 项可配置样式规则,提供 24 种背景纹理选择。

度娘上搜jQueryUI的api,其用法与easyUI、MiniUI都大同小异,此处将再也不举例。

[if !supportLists]2. [endif]Vue.js(2017-11-23-lyq)

参考原文:https://cn.vuejs.org/v2/guide/

 

Vue.js (读音 /vjuː/,相似于 view) 是一套构建用户界面的渐进式框架。与其余重量级框架不一样的是,Vue 采用自底向上增量开发的设计。Vue 的核心库只关注视图层,它不只易于上手,还便于与第三方库或既有项目整合。另外一方面,当与单文件组件和 Vue 生态系统支持的库结合使用时,Vue 也彻底可以为复杂的单页应用程序提供驱动。

Vue.js起步:

引入相应文件:

[if !supportLists]1. [endif]

声明式渲染:

Vue.js 的核心是一个容许采用简洁的模板语法来声明式的将数据渲染进 DOM 的系统:

[if !supportLists]1. [endif]

[if !supportLists]2. [endif]

[if !supportLists]3. [endif]  {{ message }}

[if !supportLists]4. [endif]

 

 

[if !supportLists]5. [endif]var app = new Vue({

[if !supportLists]6. [endif]  el: '#app',

[if !supportLists]7. [endif]  data: {

[if !supportLists]8. [endif]    message: 'Hello Vue!'

[if !supportLists]9. [endif]  }

[if !supportLists]10. [endif]})

经过浏览器查看效果图为:

 

建立vue实例:

每一个Vue 应用都是经过 Vue 函数建立一个新的 Vue 实例开始的,当建立一个 Vue 实例时,你能够传入一个选项对象。能够使用这些选项来建立你想要的行为。

[if !supportLists]1. [endif]

[if !supportLists]2. [endif]var vm = new Vue({

[if !supportLists]3. [endif]//选项

[if !supportLists]4. [endif]})

实例生命周期:

每一个Vue 实例在被建立以前都要通过一系列的初始化过程。例如须要设置数据监听、编译模板、挂载实例到 DOM、在数据变化时更新 DOM 等。同时在这个过程当中也会运行一些叫作生命周期钩子的函数,给予用户机会在一些特定的场景下添加他们本身的代码。好比created 钩子能够用来在一个实例被建立以后执行代码:

[if !supportLists]1. [endif]

[if !supportLists]2. [endif]new Vue({

[if !supportLists]3. [endif]  data: {

[if !supportLists]4. [endif]    a: 1

[if !supportLists]5. [endif]  },

[if !supportLists]6. [endif]  created: function () {

[if !supportLists]7. [endif]// `this`指向 vm 实例

[if !supportLists]8. [endif]    console.log('a is: ' + this.a)

[if !supportLists]9. [endif]  }

[if !supportLists]10. [endif]})

[if !supportLists]11. [endif]// => "a is: 1"

 

[if !supportLists]3. [endif]AngularJS (2017-11-23-lyq)

参考原文:http://www.angularjs.net.cn/api/

 

AngularJS 是google 开发者设计的一个前端开发框架,它是由是由JavaScript 编写的一个JS框架。一般它是用来在静态网页构建动态应用不足而设计的。

AngularJS 特色以下:

 一、 数据绑定: AngularJS是数据双向绑定。

 二、 MVVM(Model-View-ViewModel)模式:  Model 简单数据对象,View 视图(如HTML,JSP等),ViewModel是用来提供数据和方法,和View 进行交互。这种设计模式使得代码解耦合。

 三、依赖注入:AngularJS支持注入方式把须要的对象,方法等注入到指定的对象中。

 四、 指令: AngularJS内部自带各类经常使用指令,同时也支持开发者自定义指令。

 五、HTML模板和扩展HTML: AngularJS能够定义与HTML兼容的自定义模板。

AngularJS 的Api:

AngularJS提供了不少功能丰富的组件,处理核心的ng组件外,还扩展了不少经常使用的功能组件,如ngRoute(路由),ngAnimate(动画),ngTouch(移动端操做)等,只须要引入相应的头文件,并依赖注入你的工做模块,则可以使用。

ng (core module):AngularJS的默认模块,包含AngularJS的全部核心组件。

指令(directive)这是指令的核心集合,你能够在你的模板代码中使用它们来构建一个AngularJS应用。

一些例子包括:ngClick,ngInclude,ngRepeat,等等

服务(service)这是服务的核心集合,依赖注入后可在你的应用中使用。

一些例子包括: $compile,$http,$location,等等

过滤器(filter)在组件模块中的核心过滤器是用来转换模板数据。

一些例子包括: filter,date,currency,lowercase,uppercase等等

全局函数(function)核心的全局函数做为angularjs对象。这些核心功能在您的应用程序的原生的JavaScript操做有用。

一些例子包括: angular.copy(),angular.equals(),angular.element()等等

ngRoute:AgularJS的路由模块,你能使用ngRoute结合"#"定义你的地址访问。引入angular-route.js文件,而后在你当前的工做模块依赖注入ngRoute模块。

服务(service)下面这些服务用做AngularJS的路由管理。

$routeParams- 解析返回路由中带有的参数

$route- 用于构建各个路由的url、view、controller这三者的关系

$routeProvider- 提供路由配置

指令(Directive)指令ngView提供不一样路由模板插入的视图层

ngAnimate:AngularJS的动画模块,使用ngAnimate各类核心指令能为你的应用程序提供动画效果。动画可以使用css或者JavaScript回调函数。引入angular-animate.js文件,而后在你当前的工做模块依赖注入ngAnimate模块。

服务(service)使用$animate来在你的指令代码中触发动画操做。

CSS-based animations按照nganimate的CSS命名结构参考CSS转换/关键帧动画在AngularJS。

一旦定义,动画能够经过引用CSS在HTML模板代码触发。

JS-based animations使用module.animation()来注册一个 JavaScript动画。

一旦注册,动画能够经过引用CSS在HTML模板代码触发。

ngAria:帮助制做AngularJS 自定义组件的新模块。引入angular-aria.js 文件,而后在你当前的工做模块依赖注入ngAria模块。

服务(service)这是服务的核心集合,依赖注入后可在你的应用中使用。

一些例子包括: ngClick, ngInclude, ngRepeat等等

ngResource:AngularJS的动画模块,使用ngAnimate各类核心指令能为你的应用程序提供动画效果。动画可以使用css或者JavaScript回调函数。引入angular-resource.js 文件,而后在你当前的工做模块依赖注入ngResource模块。

服务(service)这是服务的核心集合,依赖注入后可在你的应用中使用。

一些例子包括: ngClick, ngInclude, ngRepeat等等

ngCookies:ngCookies模块提供了一个方便的包用于读取和写入浏览器的cookies。

引入angular-cookies.js 文件,而后在你当前的工做模块依赖注入ngCookies模块。

服务(service)这是服务的核心集合,依赖注入后可在你的应用中使用。

一些例子包括: ngClick, ngInclude, ngRepeat等等

ngTouch:AngularJS的动画模块,使用ngAnimate各类核心指令能为你的应用程序提供动画效果。动画可以使用css或者JavaScript回调函数。引入angular-touch.js文件,而后在你当前的工做模块依赖注入ngTouch模块。

服务(service)这是服务的核心集合,依赖注入后可在你的应用中使用。

一些例子包括: ngClick, ngInclude, ngRepeat等等

ngSanitize:使用ngSanitize可安全地解析和在你的应用程序中操做HTML数据。

引入angular-sanitize.js 文件,而后在你当前的工做模块依赖注入ngSanitize模块。

服务(service)这是服务的核心集合,依赖注入后可在你的应用中使用。

一些例子包括: ngClick, ngInclude, ngRepeat等等

 

ngMock:AngularJS的动画模块,使用ngAnimate各类核心指令能为你的应用程序提供动画效果。动画可以使用css或者JavaScript回调函数。引入angular-animate.js 文件,而后在你当前的工做模块依赖注入ngMock模块。

服务(service)这是服务的核心集合,依赖注入后可在你的应用中使用。

一些例子包括: ngClick, ngInclude, ngRepeat等等

 

[if !supportLists]第六章 [endif]数据库

[if !supportLists]1、[endif]Mysql

[if !supportLists]1. [endif]SQL的select语句完整的执行顺序(2017-11-15-lyq)

SQL Select语句完整的执行顺序:

一、from子句组装来自不一样数据源的数据;

二、where子句基于指定的条件对记录行进行筛选;

三、group by子句将数据划分为多个分组;

四、使用汇集函数进行计算;

五、使用having子句筛选分组;

六、计算全部的表达式;

七、select 的字段;

八、使用order by对结果集进行排序。

SQL语言不一样于其余编程语言的最明显特征是处理代码的顺序。在大多数据库语言中,代码按编码顺序被处理。但在SQL语句中,第一个被处理的子句式FROM,而不是第一出现的SELECT。SQL查询处理的步骤序号:

(1) FROM

(2) JOIN

(3) ON

(4) WHERE

(5) GROUP BY

(6) WITH {CUBE | ROLLUP}

(7) HAVING

(8) SELECT

(9) DISTINCT

(9) ORDER BY

(10)

以上每一个步骤都会产生一个虚拟表,该虚拟表被用做下一个步骤的输入。这些虚拟表对调用者(客户端应用程序或者外部查询)不可用。只有最后一步生成的表才会会给调用者。若是没有在查询中指定某一个子句,将跳过相应的步骤。

逻辑查询处理阶段简介:

[if !supportLists]一、 [endif]FROM:对FROM子句中的前两个表执行笛卡尔积(交叉联接),生成虚拟表VT1。

[if !supportLists]二、 [endif]ON:对VT1应用ON筛选器,只有那些使为真才被插入到TV2。

[if !supportLists]三、 [endif]OUTER (JOIN):若是指定了OUTER JOIN(相对于CROSS JOIN或INNER JOIN),保留表中未找到匹配的行将做为外部行添加到VT2,生成TV3。若是FROM子句包含两个以上的表,则对上一个联接生成的结果表和下一个表重复执行步骤1到步骤3,直处处理完全部的表位置。

[if !supportLists]四、 [endif]WHERE:对TV3应用WHERE筛选器,只有使为true的行才插入TV4。

[if !supportLists]五、 [endif]GROUP BY:按GROUP BY子句中的列列表对TV4中的行进行分组,生成TV5。

[if !supportLists]六、 [endif]CUTE|ROLLUP:把超组插入VT5,生成VT6。

[if !supportLists]七、 [endif]HAVING:对VT6应用HAVING筛选器,只有使为true的组插入到VT7。

[if !supportLists]八、 [endif]SELECT:处理SELECT列表,产生VT8。

[if !supportLists]九、 [endif]DISTINCT:将重复的行从VT8中删除,产品VT9。

[if !supportLists]十、 [endif]ORDER BY:将VT9中的行按ORDER BY子句中的列列表顺序,生成一个游标(VC10)。

[if !supportLists]十一、 [endif]TOP:从VC10的开始处选择指定数量或比例的行,生成表TV11,并返回给调用者。

where子句中的条件书写顺序

[if !supportLists]2. [endif]SQL之聚合函数(2017-11-15-lyq)

聚合函数是对一组值进行计算并返回单一的值的函数,它常常与select语句中的group by子句一同使用。

[if !supportLists]a. [endif]avg():返回的是指定组中的平均值,空值被忽略。

[if !supportLists]b. [endif]count():返回的是指定组中的项目个数。

[if !supportLists]c. [endif]max():返回指定数据中的最大值。

[if !supportLists]d. [endif]min():返回指定数据中的最小值。

[if !supportLists]e. [endif]sum():返回指定数据的和,只能用于数字列,空值忽略。

[if !supportLists]f. [endif]group by():对数据进行分组,对执行完group by以后的组进行聚合函数的运算,计算每一组的值。最后用having去掉不符合条件的组,having子句中的每个元素必须出如今select列表中(只针对于mysql)。

[if !supportLists]3. [endif]SQL之链接查询(左链接和右链接的区别)(2017-11-15-lyq)

外链接:

左链接(左外链接):以左表做为基准进行查询,左表数据会所有显示出来,右表若是和左表匹配的数据则显示相应字段的数据,若是不匹配则显示为null。

右链接(右外链接):以右表做为基准进行查询,右表数据会所有显示出来,左表若是和右表匹配的数据则显示相应字段的数据,若是不匹配则显示为null。

全链接:先以左表进行左外链接,再以右表进行右外链接。

内链接:

显示表之间有链接匹配的全部行。

[if !supportLists]4. [endif]SQL之sql注入(2017-11-15-lyq)

经过在Web表单中输入(恶意)SQL语句获得一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。举例:当执行的sql为 select * from user where username =“admin” or “a”=“a”时,sql语句恒成立,参数admin毫无心义。

防止sql注入的方式:

[if !supportLists]1. [endif]预编译语句:如,select * from user where username = ?,sql语句语义不会发生改变,sql语句中变量用?表示,即便传递参数时为“admin  or‘a’= ‘a’”,也会把这总体当作一个字符创去查询。

[if !supportLists]2. [endif]Mybatis框架中的mapper方式中的 # 也能很大程度的防止sql注入($没法防止sql注入)。

[if !supportLists]5. [endif]Mysql性能优化(2017-11-15-lyq)

[if !supportLists]一、[endif]当只要一行数据时使用limit 1

查询时若是已知会获得一条数据,这种状况下加上limit 1 会增长性能。由于mysql数据库引擎会在找到一条结果中止搜索,而不是继续查询下一条是否符合标准直到全部记录查询完毕。

[if !supportLists]二、[endif]选择正确的数据库引擎

Mysql中有两个引擎MyISAM和InnoDB,每一个引擎有利有弊。

MyISAM适用于一些大量查询的应用,但对于有大量写功能的应用不是很好。甚至你只须要update一个字段整个表都会被锁起来。而别的进程就算是读操做也不行要等到当前update操做完成以后才能继续进行。另外,MyISAM对于select count(*)这类操做是超级快的。

InnoDB的趋势会是一个很是复杂的存储引擎,对于一些小的应用会比MyISAM还慢,可是支持“行锁”,因此在写操做比较多的时候会比较优秀。而且,它支持不少的高级应用,例如:事物。

[if !supportLists]3. [endif]用not exists代替not in

Not exists用到了链接可以发挥已经创建好的索引的做用,not in不能使用索引。Not in是最慢的方式要同每条记录比较,在数据量比较大的操做红不建议使用这种方式。

[if !supportLists]4. [endif]对操做符的优化,尽可能不采用不利于索引的操做符

如:in    not in    is null    is not null    <>  等

某个字段总要拿来搜索,为其创建索引:

Mysql中能够利用alter table语句来为表中的字段添加索引,语法为:alter table 代表 add index (字段名);

[if !supportLists]6. [endif]必看sql面试题(学生表_课程表_成绩表_教师表)(2017-11-25-wzz)

给你们推荐一篇很是好的博客,该博客中收集了最多见的Mysql常见面试题和笔试题。

博客连接:http://www.cnblogs.com/qixuejia/p/3637735.html

 

[if !supportLists]7. [endif]Mysql数据库架构图(2017-11-25-wzz)

 

MyISAM和InnoDB是最多见的两种存储引擎,特色以下。

MyISAM存储引擎

MyISAM是MySQL官方提供默认的存储引擎,其特色是不支持事务、表锁和全文索引,对于一些OLAP(联机分析处理)系统,操做速度快。

每一个MyISAM在磁盘上存储成三个文件。文件名都和表名相同,扩展名分别是.frm(存储表定义)、.MYD (MYData,存储数据)、.MYI (MYIndex,存储索引)。这里特别要注意的是MyISAM不缓存数据文件,只缓存索引文件。

InnoDB存储引擎

InnoDB存储引擎支持事务,主要面向OLTP(联机事务处理过程)方面的应用,其特色是行锁设置、支持外键,并支持相似于Oracle的非锁定读,即默认状况下读不产生锁。InnoDB将数据放在一个逻辑表空间中(相似Oracle)。InnoDB经过多版本并发控制来得到高并发性,实现了ANSI标准的4种隔离级别,默认为Repeatable,使用一种被称为next-key locking的策略避免幻读。

对于表中数据的存储,InnoDB采用相似Oracle索引组织表Clustered的方式进行存储。

InnoDB 存储引擎提供了具备提交、回滚和崩溃恢复能力的事务安全。可是对比Myisam的存储引擎,InnoDB 写的处理效率差一些而且会占用更多的磁盘空间以保留数据和索引。

InnoDB体系架构

 

 

[if !supportLists]8. [endif]Mysql架构器中各个模块都是什么?(2017-11-25-wzz)

(1)、链接管理与安全验证是什么?

每一个客户端都会创建一个与服务器链接的线程,服务器会有一个线程池来管理这些 链接;若是客户端须要链接到MYSQL数据库还须要进行验证,包括用户名、密码、 主机信息等。

(2)、解析器是什么?

解析器的做用主要是分析查询语句,最终生成解析树;首先解析器会对查询语句的语法进行分析,分析语法是否有问题。还有解析器会查询缓存,若是在缓存中有对应的语句,就返回查询结果不进行接下来的优化执行操做。前提是缓存中的数据没有被修改,固然若是被修改了也会被清出缓存。

(3)、优化器怎么用?

优化器的做用主要是对查询语句进行优化操做,包括选择合适的索引,数据的读取方式,包括获取查询的开销信息,统计信息等,这也是为何图中会有优化器指向存储引擎的箭头。以前在别的文章没有看到优化器跟存储引擎之间的关系,在这里我我的的理解是由于优化器须要经过存储引擎获取查询的大体数据和统计信息。

(4)、执行器是什么?

执行器包括执行查询语句,返回查询结果,生成执行计划包括与存储引擎的一些处理操做。

[if !supportLists]9. [endif]Mysql存储引擎有哪些?(2017-11-25-wzz)

(1)、InnoDB存储引擎

InnoDB是事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和外键,InnoDB是默认的MySQL引擎。

(2)、MyISAM存储引擎

MyISAM基于ISAM存储引擎,并对其进行扩展。它是在Web、数据仓储和其余应用环境下最常使用的存储引擎之一。MyISAM拥有较高的插入、查询速度,但不支持事物。

(3)、MEMORY存储引擎

MEMORY存储引擎将表中的数据存储到内存中,未查询和引用其余表数据提供快速访问。

(4)、NDB存储引擎

DB存储引擎是一个集群存储引擎,相似于Oracle的RAC,但它是Share Nothing的架构,所以能提供更高级别的高可用性和可扩展性。NDB的特色是数据所有放在内存中,所以经过主键查找很是快。

关于NDB,有一个问题须要注意,它的链接(join)操做是在MySQL数据库层完成,不是在存储引擎层完成,这意味着,复杂的join操做须要巨大的网络开销,查询速度会很慢。

[if !supportLists](5)[endif]、Memory (Heap) 存储引擎

Memory存储引擎(以前称为Heap)将表中数据存放在内存中,若是数据库重启或崩溃,数据丢失,所以它很是适合存储临时数据。

[if !supportLists](6)[endif]、Archive存储引擎

正如其名称所示,Archive很是适合存储归档数据,如日志信息。它只支持INSERT和SELECT操做,其设计的主要目的是提供高速的插入和压缩功能。

[if !supportLists](7)[endif]、Federated存储引擎

Federated存储引擎不存放数据,它至少指向一台远程MySQL数据库服务器上的表,很是相似于Oracle的透明网关。

(8)、Maria存储引擎

Maria存储引擎是新开发的引擎,其设计目标是用来取代原有的MyISAM存储引擎,从而成为MySQL默认的存储引擎。

上述引擎中,InnoDB是事务安全的存储引擎,设计上借鉴了不少Oracle的架构思想,通常而言,在OLTP应用中,InnoDB应该做为核心应用表的首先存储引擎。InnoDB是由第三方的Innobase Oy公司开发,现已被Oracle收购,创始人是Heikki Tuuri,芬兰赫尔辛基人,和著名的Linux创始人Linus是校友。

[if !supportLists]10. [endif]MySQL事务介绍(2017-11-25-wzz)

MySQL和其它的数据库产品有一个很大的不一样就是事务由存储引擎所决定,例如MYISAM,MEMORY,ARCHIVE都不支持事务,事务就是为了解决一组查询要么所有执行成功,要么所有执行失败。

MySQL事务默认是采起自动提交的模式,除非显示开始一个事务。

SHOW VARIABLES LIKE 'AUTOCOMMIT';

 

 

修改自动提交模式,0=OFF,1=ON

注意:修改自动提交对非事务类型的表是无效的,由于它们自己就没有提交和回滚的概念,还有一些命令是会强制自动提交的,好比DLL命令、lock tables等。

 

SET AUTOCOMMIT=OFF或 SET AUTOCOMMIT=0

10.1事务的四大特征是什么?

数据库事务transanction正确执行的四个基本要素。ACID,原子性(Atomicity)、一致性(Correspondence)、隔离性(Isolation)、持久性(Durability)。

(1)原子性:整个事务中的全部操做,要么所有完成,要么所有不完成,不可能停滞在中间某个环节。事务在执行过程当中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务历来没有执行过同样。

(2)一致性:在事务开始以前和事务结束之后,数据库的完整性约束没有被破坏。

(3)隔离性:隔离状态执行事务,使它们好像是系统在给定时间内执行的惟一操做。若是有两个事务,运行在相同的时间内,执行 相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操做间的混淆, 必须串行化或序列化请求,使得在同一时间仅有一个请求用于同一数据。

(4)持久性:在事务完成之后,该事务所对数据库所做的更改便持久的保存在数据库之中,并不会被回滚。

10.2 Mysql中四种隔离级别分别是什么?

隔离级别脏读不可重复读幻读

Read uncommitted(读未提交)是是是

Read committed(读已提交)否是是

Repeatable read(可重复读)否否是

Serializable(串行读)否否否

 

 

 

 

读未提交(READ UNCOMMITTED):未提交读隔离级别也叫读脏,就是事务能够读取其它事务未提交的数据。

读已提交(READ COMMITTED):在其它数据库系统好比SQL Server默认的隔离级别就是提交读,已提交读隔离级别就是在事务未提交以前所作的修改其它事务是不可见的。

可重复读(REPEATABLE READ):保证同一个事务中的屡次相同的查询的结果是一致的,好比一个事务一开始查询了一条记录而后过了几秒钟又执行了相同的查询,保证两次查询的结果是相同的,可重复读也是mysql的默认隔离级别。

可串行化(SERIALIZABLE):可串行化就是保证读取的范围内没有新的数据插入,好比事务第一次查询获得某个范围的数据,第二次查询也一样获得了相同范围的数据,中间没有新的数据插入到该范围中。

 

[if !supportLists]11. [endif]MySQL怎么建立存储过程(2017-11-25-wzz)

MySQL存储过程是从MySQL5.0开始增长的新功能。存储过程的优势有一箩筐。不过最主要的仍是执行效率和SQL 代码封装。特别是 SQL 代码封装功能,若是没有存储过程,在外部程序访问数据库时,要组织不少 SQL 语句。特别是业务逻辑复杂的时候,一大堆的 SQL 和条件夹杂在代码中,让人毛骨悚然。如今有了 MySQL 存储过程,业务逻辑能够封装存储过程当中,这样不只容易维护,并且执行效率也高。

1、建立MySQL存储过程

下面代码建立了一个叫pr_add 的MySQL 存储过程,这个MySQL 存储过程有两个int 类型的输入参数 “a”、“b”,返回这两个参数的和。

1)drop procedure if exists pr_add;  (备注:若是存在pr_add的存储过程,则先删掉)

2)计算两个数之和(备注:实现计算两个整数之和的功能)

create procedure pr_add   (   a int,   b int   )   begin    declare c int; 

if a is null then   set a = 0;   

end if;   

if b is null then   set b = 0;   

end if;   

set c = a + b; 

select c as sum; 

[if !supportLists]2、[endif]调用MySQL 存储过程

call pr_add(10, 20);

[if !supportLists]12. [endif]MySQL触发器怎么写?(2017-11-25-wzz)

MySQL包含对触发器的支持。触发器是一种与表操做有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操做事件触发表上的触发器的执行。

在MySQL中,建立触发器语法以下:

CREATE TRIGGER trigger_name

trigger_time

trigger_event ON tbl_name

FOR EACH ROW

trigger_stmt

其中:

trigger_name:标识触发器名称,用户自行指定;

trigger_time:标识触发时机,取值为 BEFORE 或 AFTER;

trigger_event:标识触发事件,取值为 INSERT、UPDATE 或 DELETE;

tbl_name:标识创建触发器的表名,即在哪张表上创建触发器;

trigger_stmt:触发器程序体,能够是一句SQL语句,或者用 BEGIN 和 END 包含的多条语句。

因而可知,能够创建6种触发器,即:BEFORE INSERT、BEFORE UPDATE、BEFORE DELETE、AFTER INSERT、AFTER UPDATE、AFTER DELETE。

另外有一个限制是不能同时在一个表上创建2个相同类型的触发器,所以在一个表上最多创建6个触发器。

假设系统中有两个表:

1)班级表 class(班级号 classID, 班内学生数 stuCount)

2)学生表 student(学号 stuID, 所属班级号 classID)

要建立触发器来使班级表中的班内学生数随着学生的添加自动更新,代码以下:

create trigger tri_stuInsert after insert

on student for each row

begin

declare c int;

set c = (select stuCount from class where classID=new.classID);

update class set stuCount = c + 1 where classID = new.classID;

查看触发器:

和查看数据库(show databases;)查看表格(show tables;)同样,查看触发器的语法以下:

SHOW TRIGGERS [FROM schema_name];

其中,schema_name 即 Schema 的名称,在 MySQL 中 Schema 和 Database 是同样的,也就是说,能够指定数据库名,这样就没必要先“USE database_name;”了。

删除触发器:

和删除数据库、删除表格同样,删除触发器的语法以下:

DROP TRIGGER [IF EXISTS] [schema_name.]trigger_name

[if !supportLists]13. [endif]MySQL语句优化(2017-11-26-wzz)

13.1 where子句中能够对字段进行null值判断吗?

能够,好比select id from t where num is null 这样的sql也是能够的。可是最好不要给数据库留NULL,尽量的使用 NOT NULL填充数据库。不要觉得 NULL 不须要空间,好比:char(100) 型,在字段创建时,空间就固定了,无论是否插入值(NULL也包含在内),都是占用100个字符的空间的,若是是varchar 这样的变长字段,null不占用空间。能够在num上设置默认值0,确保表中num列没有null值,而后这样查询:select id from t where num = 0。

13.2 select * from admin left join log on admin.admin_id = log.admin_id where log.admin_id>10如何优化?

优化为:select * from (select * from admin where admin_id>10) T1 lef join log on T1.admin_id = log.admin_id。

使用JOIN 时候,应该用小的结果驱动大的结果(left join 左边表结果尽可能小若是有条件应该放到左边先处理,right join 同理反向),同时尽可能把牵涉到多表联合的查询拆分多个query(多个连表查询效率低,容易到以后锁表和阻塞)。

13.3 limit的基数比较大时使用between

例如:select * from admin order by admin_id limit 100000,10

优化为:select * from admin where admin_id between 100000 and 100010 order by admin_id。

13.4尽可能避免在列上作运算,这样致使索引失效

例如:select * from admin where year(admin_time)>2014

优化为:select * from admin where admin_time> '2014-01-01′

[if !supportLists]14. [endif]MySQL中文乱码问题完美解决方案(2017-12-07-lwl)

解决乱码的核心思想是统一编码。咱们在使用MySQL建数据库和建表时应尽可能使用统一的编码,强烈推荐的是utf8编码,由于该编码几乎能够兼容世界上全部的字符。

数据库在安装的时候能够设置默认编码,在安装时就必定要设置为utf8编码。设置以后再建立的数据库和表若是不指定编码,默认都会使用utf8编码,省去了不少麻烦。

数据库软件安装好以后能够经过以下命令查看默认编码:

[if !supportLists]一、[endif]查询数据库软件使用的默认编码格式

show variables like“%colla%”;

show varables like“%char%”

 

其中collation,表明了字符串排序(比较)的规则,若是值是utf8_general_ci,表明使用utf8字符集大小写不敏感的天然方式比较。

若是character_set的值不为utf8,那么能够使用以下命令修改成utf8。

[if !supportLists]二、[endif]修改数据库默认编码为utf8

SET character_set_client='utf8';

SET character_set_connection='utf8';

SET character_set_results='utf8';

若是不想设置数据库软件的全局默认编码,也能够单独修改或者设置某个具体数据库的编码也能够单独修改或设置某个数据库中某个表的编码。

[if !supportLists]三、[endif]建立数据库的时候指定使用utf8编码

CREATE DATABASE `test`

CHARACTER SET 'utf8'

COLLATE 'utf8_general_ci';

[if !supportLists]四、[endif]建立表的时候指定使用utf8编码

CREATE TABLE `database_user` (

`ID` varchar(40) NOT NULL default '',

`UserID` varchar(40) NOT NULL default '',

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

若是数据库已经建立好了,能够使用show database 数据库名;和 show create table 表名;查看一下数据库和表的字符集是否为utf8,若是不是则在命令行下面能够用以下命令,将数据库和表编码修改成utf8.

[if !supportLists]五、[endif]修改具体某数据库或表的编码

ALTER DATABASE `db_name` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

ALTER TABLE `tb_name` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;

 

[if !supportLists]15. [endif]如何提升MySQL的安全性(2017-12-8-lwl)

1.若是MySQL客户端和服务器端的链接须要跨越并经过不可信任的网络,那么须要使用ssh隧道来加密该链接的通讯。

2.使用set password语句来修改用户的密码,先“mysql -u root”登录数据库系统,而后“mysql> update mysql.user set password=password(’newpwd’)”,最后执行“flush privileges”。

3.MySQL须要提防的攻击有,防偷听、篡改、回放、拒绝服务等,不涉及可用性和容错方面。对全部的链接、查询、其余操做使用基于ACL(ACL(访问控制列表)是一种路由器配置和控制网络访问的一种有力的工具,它可控制路由器应该容许或拒绝数据包经过,可监控流量,可自上向下检查网络的安全性,可检查和过滤数据和限制没必要要的路由更新,所以让网络资源节约成本的ACL配置技术在生活中愈来愈普遍应用。)即访问控制列表的安全措施来完成。

4.设置除了root用户外的其余任何用户不容许访问mysql主数据库中的user表;

5.使用grant和revoke语句来进行用户访问控制的工做;

6.不要使用明文密码,而是使用md5()和sha1()等单向的哈系函数来设置密码;

7.不要选用字典中的字来作密码;

8.采用防火墙能够去掉50%的外部危险,让数据库系统躲在防火墙后面工做,或放置在DMZ(DMZ是英文“demilitarized zone”的缩写,隔离区,它是为了解决安装防火墙后外部网络的访问用户不能访问内部网络服务器的问题,而设立的一个非安全系统与安全系统之间的缓冲区。)区域中;

9.从因特网上用nmap来扫描3306端口,也可用telnet server_host 3306的方法测试,不容许从非信任网络中访问数据库服务器的3306号tcp端口,须要在防火墙或路由器上作设定;

10.服务端要对SQL进行预编译,避免SQL注入攻击,例如where id=234,别人却输入where id=234 or 1=1。

11.在传递数据给mysql时检查一下大小;

12.应用程序链接到数据库时应该使用通常的用户账号,开放少数必要的权限给该用户;

13.学会使用tcpdump和strings工具来查看传输数据的安全性,例如tcpdump -l -i eth0 -w -src or dst port 3306 strings。以普通用户来启动mysql数据库服务;

14.确信在mysql目录中只有启动数据库服务的用户才能够对文件有读和写的权限;

15.不准将process或super权限付给非管理用户,该mysqladmin processlist能够列举出当前执行的查询文本;super权限可用于切断客户端链接、改变服务器运行参数状态、控制拷贝复制数据库的服务器;

16.若是不相信dns服务公司的服务,能够在主机名称容许表中只设置ip数字地址;

17.使用max_user_connections变量来使mysqld服务进程,对一个指定账户限定链接数;

18.grant语句也支持资源控制选项;

19.启动mysqld服务进程的安全选项开关,–local-infile=0或1,如果0则客户端程序就没法使用local load data了,赋权的一个例子grant insert(user) on mysql.user to ‘user_name’@'host_name’;若使用–skip-grant-tables系统将对任何用户的访问不作任何访问控制,但能够用 mysqladmin flush-privileges或mysqladmin reload来开启访问控制;默认状况是show databases语句对全部用户开放,能够用–skip-show-databases来关闭掉。

23.碰到error 1045(28000) access denied for user ‘root’@'localhost’ (using password:no)错误时,你须要从新设置密码,具体方法是:先用–skip-grant-tables参数启动mysqld,而后执行 mysql -u root mysql,mysql>update user set password=password(’newpassword’) where user=’root’;mysql>flush privileges;,最后从新启动mysql就能够了。

[if !supportLists]2、[endif]Oracle

[if !supportLists]1. [endif]什么是存储过程,使用存储过程的好处?(2017-11-25-wzz)

存储过程(Stored Procedure )是一组为了完成特定功能的SQL 语句集,经编译后存储在数据库中。用户经过指定存储过程的名字并给出参数(若是该存储过程带有参数)来执行它。存储过程是数据库中的一个重要对象,任何一个设计良好的数据库应用程序都应该用到存储过程。

优势:

(1)容许模块化程序设计,就是说只须要建立一次过程,之后在程序中就能够调用该过程任意次。

(2)容许更快执行,若是某操做须要执行大量 SQL 语句或重复执行,存储过程比 SQL 语句执行的要快。

(3)减小网络流量,例如一个须要数百行的 SQL 代码的操做有一条执行语句完成,不须要在网络中发

送数百行代码。

(4) 更好的安全机制,对于没有权限执行存储过程的用户,也可受权他们执行存储过程。

存储过程的具体使用详见:http://www.cnblogs.com/yank/p/4235609.html

 

[if !supportLists]2. [endif]Oracle存储过程怎么建立?(2017-11-25-wzz)

存储过程建立语法:

create or replace procedure存储过程名(param1 in type,param2 out type)

as

变量1类型(值范围);

变量2类型(值范围);

Begin

Select count(*) into变量1 from 表A where列名=param1;

If (判断条件) then

Select列名 into 变量2 from 表A where列名=param1;

Dbms_output。Put_line(‘打印信息’);

Elsif (判断条件) then

Dbms_output。Put_line(‘打印信息’);

    Else

Raise异常名(NO_DATA_FOUND);

    End if;

Exception

    When others then

       Rollback;

End;

注意事项:

1. 存储过程参数不带取值范围,in表示传入,out表示输出

2. 变量带取值范围,后面接分号

3. 在判断语句前最好先用count(*)函数判断是否存在该条操做记录

4. 用select 。。。into。。。给变量赋值

5. 在代码中抛异经常使用 raise+异常名

[if !supportLists]3. [endif]如何使用Oracle的游标?(2017-11-25-wzz)

参考博客:https://www.cnblogs.com/sc-xx/archive/2011/12/03/2275084.html

 

(1)、Oracle中的游标分为显示游标和隐式游标

(2)、显示游标是用cursor...is命令定义的游标,它能够对查询语句(select)返回的多条记录进行处理;

(3)、隐式游标是在执行插入 (insert)、删除(delete)、修改(update) 和返回单条记录的查询(select)语句时由PL/SQL自动定义的。

(4)、显式游标的操做:打开游标、操做游标、关闭游标;PL/SQL隐式地打开SQL游标,并在它内部处理SQL语句,而后关闭它。

[if !supportLists]4. [endif]Oracle中字符串用什么链接?(2017-11-25-wzz)

Oracle中使用 || 这个符号链接字符串 如 ‘abc’ || ‘d’ 的结果是abcd。

[if !supportLists]5. [endif]Oracle中是如何进行分页查询的?(2017-11-25-wzz)

Oracle中使用rownum来进行分页, 这个是效率最好的分页方法,hibernate也是使用rownum来进行Oralce分页的 

select * from 

  ( select rownum r,a from tabName where rownum <= 20 ) 

where r > 10  

 

[if !supportLists]6. [endif]存储过程和存储函数的特色和区别?(2017-11-25-wzz)

特色:

[if !supportLists](1)[endif]、通常来讲,存储过程实现的功能要复杂一点,而函数的实现的功能针对性比较强。

[if !supportLists](2)[endif]、对于存储过程来讲能够返回参数,而函数只能返回值或者表对象。

[if !supportLists](3)[endif]、存储过程通常是做为一个独立的部分来执行,而函数能够做为查询语句的一个部分来调用,因为函数能够返回一个表对象,所以它能够在查询语句中位于FROM关键字的后面。

区别:

(1)、函数必须有返回值,而过程没有.

(2)、函数能够单独执行.而过程必须经过execute执行.

(3)、函数能够嵌入到SQL语句中执行.而过程不行.

      其实咱们能够将比较复杂的查询写成函数.而后到存储过程当中去调用这些函数.

[if !supportLists]7. [endif]存储过程与SQL的对比?(2017-11-21-gxb)

优点:

一、提升性能 SQL语句在建立过程时进行分析和编译。 存储过程是预编译的,在首次运行一个存储过程时,查询优化器对其进行分析、优化,并给出最终被存在系统表中的存储计划,这样,在执行过程时即可节省此开销。二、下降网络开销 存储过程调用时只需用提供存储过程名和必要的参数信息,从而可下降网络的流量。三、便于进行代码移植 数据库专业人员能够随时对存储过程进行修改,但对应用程序源代码却毫无影响,从而极大的提升了程序的可移植性。四、更强的安全性 1)系统管理员能够对执行的某一个存储过程进行权限限制,避免非受权用户对数据的访问 2)在经过网络调用过程时,只有对执行过程的调用是可见的。 所以,恶意用户没法看到表和数据库对象名称、嵌入本身的 Transact-SQL 语句或搜索关键数据。 3)使用过程参数有助于避免 SQL 注入攻击。 由于参数输入被视做文字值而非可执行代码,因此,攻击者将命令插入过程内的 Transact-SQL 语句并损害安全性将更为困难。 4)能够对过程进行加密,这有助于对源代码进行模糊处理。 

劣势:

一、存储过程须要专门的数据库开发人员进行维护,但实际状况是,每每由程序开发员人员兼职

二、设计逻辑变动,修改存储过程没有SQL灵活