java 工程创建

This commit is contained in:
法然
2022-07-08 20:27:10 +08:00
parent acfd5dd7a2
commit 992e1ce3b2
169 changed files with 367 additions and 0 deletions

View File

@@ -1,2 +0,0 @@
\|
具体应该参考相应的jdk进行了解相应的方法。这里只说明类的主要作用和自己的相关理解以及部分常用的方法。

View File

@@ -1,370 +0,0 @@
**引**
如果对什么是线程、什么是进程仍存有疑惑请先Google之因为这两个概念不在本文的范围之内。
用多线程只有一个目的那就是更好的利用cpu的资源因为所有的多线程代码都可以用单线程来实现。说这个话其实只有一半对因为反应“多角色”的程序代码最起码每个角色要给他一个线程吧否则连实际场景都无法模拟当然也没法说能用单线程来实现比如最常见的“生产者消费者模型”。
很多人都对其中的一些概念不够明确,如同步、并发等等,让我们先建立一个数据字典,以免产生误会。
- 多线程:指的是这个程序(一个进程)运行时产生了不止一个线程
- 并行与并发:
- 并行多个cpu实例或者多台机器同时执行一段处理逻辑是真正的同时。
- 并发通过cpu调度算法让用户看上去同时执行实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源那么针对这个公用的资源往往产生瓶颈我们会用TPS或者QPS来反应这个系统的处理能力。
![a468b2694253.png](media/7932f8d9c5acc2d3a1896b4c3233612e.png)
并发与并行
- 线程安全经常用来描绘一段代码。指在并发的情况之下该代码经过多线程使用线程的调度顺序不影响任何结果。这个时候使用多线程我们只需要关注系统的内存cpu是不是够用即可。反过来线程不安全就意味着线程的调度顺序会影响最终结果如不加事务的转账代码
void transferMoney(User from, User to, float amount){
to.setMoney(to.getBalance() + amount); from.setMoney(from.getBalance() -
amount); }
- 同步Java中的同步指的是通过人为的控制和调度保证共享资源的多线程访问成为线程安全来保证结果的准确。如上面的代码简单加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。
好了,让我们开始吧。我准备分成几部分来总结涉及到多线程的内容:
1. 扎好马步:线程的状态
2. 内功心法:每个对象都有的方法(机制)
3. 太祖长拳:基本线程类
4. 九阴真经:高级多线程控制类
**扎好马步:线程的状态**
先来两张图:
![5b75b44e972c.png](media/c5008c69466298cf4ed608e4f6b569f0.png)
线程状态
![7101e6588094.png](media/995e20b243fa0e000d51589ebb780e75.png)
线程状态转换
各种状态一目了然,值得一提的是"blocked"这个状态:
线程在Running的过程中可能会遇到阻塞(Blocked)情况
1. 调用join()和sleep()方法sleep()时间结束或被打断join()中断,IO完成都会回到Runnable状态等待JVM的调度。
2. 调用wait(),使该线程处于等待池(wait blocked
pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool
)释放同步锁使线程回到可运行状态Runnable
3. 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool
),同步锁被释放进入可运行状态(Runnable)。
此外在runnable状态的线程是处于被调度的线程此时的调度顺序是不一定的。Thread类中的yield方法可以让一个running状态的线程转入runnable。
**内功心法:每个对象都有的方法(机制)**
synchronized, wait, notify 是任何对象都具有的同步工具。让我们先来了解他们
![0771d68cb2ba.png](media/51d56de3dfb3391d64c34cc5121e3bd0.png)
monitor
他们是应用于同步问题的人工线程调度工具。讲其本质首先就要明确monitor的概念Java中的每个对象都有一个监视器来监测并发代码的重入。在非多线程编码时该监视器不发挥作用反之如果在synchronized
范围内,监视器发挥作用。
wait/notify必须存在于synchronized块中。并且这三个关键字针对的是同一个监视器某对象的监视器。这意味着wait之后其他线程可以进入同步块执行。
当某代码并不持有监视器的使用权时如图中5的状态即脱离同步块去wait或notify会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify因为不同对象的监视器不同同样会抛出此异常。
再讲用法:
- synchronized单独使用
- 代码块如下在多线程环境下synchronized块中的方法获取了lock实例的monitor如果实例相同那么只有一个线程能执行该块内容
![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif)
复制代码
public class Thread1 implements Runnable { Object lock; public void run() {
synchronized(lock){ ..do something } } }
![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif)
复制代码
- 直接用于方法:
相当于上面代码中用lock来锁定的效果实际获取的是Thread1类的monitor。更进一步如果修饰的是static方法则锁定该类所有实例。
public class Thread1 implements Runnable { public synchronized void run() { ..do
something } }
- synchronized, wait, notify结合:典型场景生产者消费者问题
![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif)
复制代码
/\*\* \* 生产者生产出来的产品交给店员 \*/ public synchronized void produce() {
if(this.product \>= MAX_PRODUCT) { try { wait();
System.out.println("产品已满,请稍候再生产"); } catch(InterruptedException e) {
e.printStackTrace(); } return; } this.product++;
System.out.println("生产者生产第" + this.product + "个产品."); notifyAll();
//通知等待区的消费者可以取出产品了 } /\*\* \* 消费者从店员取产品 \*/ public
synchronized void consume() { if(this.product \<= MIN_PRODUCT) { try { wait();
System.out.println("缺货,稍候再取"); } catch (InterruptedException e) {
e.printStackTrace(); } return; } System.out.println("消费者取走了第" +
this.product + "个产品."); this.product--; notifyAll();
//通知等待去的生产者可以生产产品了}
![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif)
复制代码
**volatile**
多线程的内存模型main memory主存、working
memory线程栈在处理数据时线程会把值从主存load到本地栈完成操作后再save回去(volatile关键词的作用每次针对该变量的操作都激发一次load
and save)。
![6cfda7042c67.png](media/54db549dedb71c1ac0e24d21fa81faf4.png)
volatile
针对多线程使用的变量如果不是volatile或者final修饰的很有可能产生不可预知的结果另一个线程修改了这个值但是之后在某线程看到的是修改之前的值。其实道理上讲同一实例的同一属性本身只有一个副本。但是多线程是会缓存值的本质上volatile就是不去缓存直接取值。在线程安全的情况下加volatile会牺牲性能。
**太祖长拳:基本线程类**
基本线程类指的是Thread类Runnable接口Callable接口
Thread 类实现了Runnable接口启动一个线程的方法
MyThread my = new MyThread(); my.start();
**Thread类相关方法**
![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif)
复制代码
//当前线程可转让cpu控制权让别的就绪状态线程运行切换public static
Thread.yield() //暂停一段时间public static Thread.sleep()
//在一个线程中调用other.join(),将等待other执行完后才继续本线程。 public join()
//后两个函数皆可以被打断public interrupte()
![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif)
复制代码
**关于中断**它并不像stop方法那样会中断一个正在运行的线程。线程会不时地检测中断标识位以判断线程是否应该被中断中断标识值是否为true。终端只会影响到wait状态、sleep状态和join状态。被打断的线程会抛出InterruptedException。
Thread.interrupted()检查当前线程是否发生中断返回boolean
synchronized在获锁的过程中是不能被中断的。
中断是一个状态interrupt()方法只是将这个状态置为true而已。所以说正常运行的程序不去检测状态就不会终止而wait等阻塞方法会去检查并抛出异常。如果在正常运行的程序中添加while(!Thread.interrupted())
,则同样可以在中断后离开代码体
**Thread类最佳实践**
写的时候最好要设置线程名称 Thread.name并设置线程组
ThreadGroup目的是方便管理。在出现问题的时候打印线程栈 (jstack -pid)
一眼就可以看出是哪个线程出的问题,这个线程是干什么的。
**如何获取线程中的异常**
![5b7f8df6e8d3.png](media/429f749d3870e8c9a198746da0d0ca6e.png)
不能用try,catch来获取线程中的异常
**Runnable**
与Thread类似
**Callable**
future模式并发模式的一种可以有两种形式即无阻塞和阻塞分别是isDone和get。其中Future对象用来存放该线程的返回值以及状态
ExecutorService e = Executors.newFixedThreadPool(3);
//submit方法有多重参数版本及支持callable也能够支持runnable接口类型.Future
future = e.submit(new myCallable()); future.isDone() //return true,false 无阻塞
future.get() // return 返回值,阻塞直到该线程运行结束
**九阴真经:高级多线程控制类**
以上都属于内功心法接下来是实际项目中常用到的工具了Java1.5提供了一个非常高效实用的多线程包:*java.util.concurrent*,
提供了大量高级工具,可以帮助开发者编写高效、易维护、结构清晰的Java多线程程序。
**1.ThreadLocal类**
用处保存线程的独立变量。对一个线程类继承自Thread)
当使用ThreadLocal维护变量时ThreadLocal为每个使用该变量的线程提供独立的变量副本所以每一个线程都可以独立地改变自己的副本而不会影响其它线程所对应的副本。常用于用户登录控制如记录session信息。
实现每个Thread都持有一个TreadLocalMap类型的变量该类是一个轻量级的Map功能与map一样区别是桶里放的是entry而不是entry的链表。功能还是一个map。以本身为key以目标为value。
主要方法是get()和set(T a)set之后在map里维护一个threadLocal -\>
aget时将a返回。ThreadLocal是一个特殊的容器。
**2.原子类AtomicInteger、AtomicBoolean……**
如果使用atomic wrapper
class如atomicInteger或者使用自己保证原子的操作则等同于synchronized
//返回值为booleanAtomicInteger.compareAndSet(int expect,int update)
该方法可用于实现乐观锁考虑文中最初提到的如下场景a给b付款10元a扣了10元b要加10元。此时c给b2元但是b的加十元代码约为
![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif)
复制代码
if(b.value.compareAndSet(old, value)){ return ; }else{ //try again // if that
fails, rollback and log}
![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif)
复制代码
**AtomicReference**
对于AtomicReference 来讲也许对象会出现属性丢失的情况即oldObject ==
current但是oldObject.getPropertyA != current.getPropertyA。
这时候AtomicStampedReference就派上用场了。这也是一个很常用的思路即加上版本号
**3.Lock类**
lock: 在java.util.concurrent包内。共有三个实现
ReentrantLock ReentrantReadWriteLock.ReadLock ReentrantReadWriteLock.WriteLock
主要目的是和synchronized一样
两者都是为了解决同步问题,处理资源争端而产生的技术。功能类似但有一些区别。
区别如下:
![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif)
复制代码
lock更灵活可以自由定义多把锁的枷锁解锁顺序synchronized要按照先加的后解顺序
提供多种加锁方案lock 阻塞式, trylock 无阻塞式, lockInterruptily 可打断式,
还有trylock的带超时时间版本。 本质上和监视器锁即synchronized是一样的
能力越大,责任越大,必须控制好加锁和解锁,否则会导致灾难。 和Condition类的结合。
性能更高,对比如下图:
![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif)
复制代码
![93b31bfed934.png](media/4c9b2a5287c91f386c9db0ba1920904b.png)
synchronized和Lock性能对比
**ReentrantLock**
可重入的意义在于持有锁的线程可以继续持有,并且要释放对等的次数后才真正释放该锁。
使用方法是:
1.先new一个实例
static ReentrantLock r=new ReentrantLock();
2.加锁
r.lock()或r.lockInterruptibly();
此处也是个不同后者可被打断。当a线程lock后b线程阻塞此时如果是lockInterruptibly那么在调用b.interrupt()之后b线程退出阻塞并放弃对资源的争抢进入catch块。如果使用后者必须throw
interruptable exception 或catch
3.释放锁
r.unlock()
必须做何为必须做呢要放在finally里面。以防止异常跳出了正常流程导致灾难。这里补充一个小知识点finally是可以信任的经过测试哪怕是发生了OutofMemoryErrorfinally块中的语句执行也能够得到保证。
**ReentrantReadWriteLock**
可重入读写锁(读写锁的一个实现)
ReentrantReadWriteLock lock = new ReentrantReadWriteLock() ReadLock r =
lock.readLock(); WriteLock w = lock.writeLock();
两者都有lock,unlock方法。写写写读互斥读读不互斥。可以实现并发读的高效线程安全代码
**4.容器类**
这里就讨论比较常用的两个:
BlockingQueue ConcurrentHashMap
**BlockingQueue**
阻塞队列。该类是java.util.concurrent包下的重要类通过对Queue的学习可以得知这个queue是单向队列可以在队列头添加元素和在队尾删除或取出元素。类似于一个管
特别适用于先进先出策略的一些应用场景。普通的queue接口主要实现有PriorityQueue优先队列有兴趣可以研究
BlockingQueue在队列的基础上添加了多线程协作的功能
![7b06b8d86db8.png](media/6cb91c2845273c5693a564bab023d4e3.png)
BlockingQueue
除了传统的queue功能表格左边的两列之外还提供了阻塞接口put和take带超时功能的阻塞接口offer和poll。put会在队列满的时候阻塞直到有空间时被唤醒take在队
列空的时候阻塞,直到有东西拿的时候才被唤醒。用于生产者-消费者模型尤其好用,堪称神器。
常见的阻塞队列有:
ArrayListBlockingQueue LinkedListBlockingQueue DelayQueue SynchronousQueue
**ConcurrentHashMap**
高效的线程安全哈希map。请对比hashTable , concurrentHashMap, HashMap
**5.管理类**
管理类的概念比较泛,用于管理线程,本身不是多线程的,但提供了一些机制来利用上述的工具做一些封装。
了解到的值得一提的管理类ThreadPoolExecutor和 JMX框架下的系统级管理类
ThreadMXBean
**ThreadPoolExecutor**
如果不了解这个类应该了解前面提到的ExecutorService开一个自己的线程池非常方便
![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif)
复制代码
ExecutorService e = Executors.newCachedThreadPool(); ExecutorService e =
Executors.newSingleThreadExecutor(); ExecutorService e =
Executors.newFixedThreadPool(3); //
第一种是可变大小线程池,按照任务数来分配线程, //
第二种是单线程池相当于FixedThreadPool(1) // 第三种是固定大小线程池。 //
然后运行e.execute(new MyRunnableImpl());
![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif)
复制代码
该类内部是通过ThreadPoolExecutor实现的掌握该类有助于理解线程池的管理本质上他们都是ThreadPoolExecutor类的各种实现版本。请参见javadoc
![8a70ba646843.png](media/ec7fd9666c2bd70bfc26c6c2a1f7538a.png)
ThreadPoolExecutor参数解释
翻译一下:
![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif)
复制代码
corePoolSize:池内线程初始值与最小值,就算是空闲状态,也会保持该数量线程。
maximumPoolSize:线程最大值,线程的增长始终不会超过该值。
keepAliveTime当池内线程数高于corePoolSize时经过多少时间多余的空闲线程才会被回收。回收前处于wait状态
unit 时间单位可以使用TimeUnit的实例如TimeUnit.MILLISECONDS
workQueue:待入任务Runnable的等待场所该参数主要影响调度策略如公平与否是否产生饿死(starving)
threadFactory:线程工厂类有默认实现如果有自定义的需要则需要自己实现ThreadFactory接口并作为参数传入。

View File

@@ -1 +0,0 @@
以功能为单位进行记录。主要说明实现某个具体功能时用到的相关类。以及类的主要用法。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,56 +0,0 @@
# DAO模式的原理说明
DAO(Data Access
Object)是一个数据访问接口,数据访问:顾名思义就是与数据库打交道,夹在业务逻辑与数据库资源中间。
在核心J2EE模式中DAO的定义是为了建立一个健壮的J2EE应用应该将所有对数据源的访问操作抽象封装在一个公共API中。用程序设计的语言来说就是建立一个接口接口中定义了此应用程序中将会用到的所有事务方法。在这个应用程序中当需要和数据源进行交互的时候则使用这个接口并且编写一个单独的类来实现这个接口在逻辑上对应这个特定的数据存储。
DAO层本质上就是MVC中的Model部分的具体实现。相当于整个工程的javabean实现了对数据库的增删查改。
![dao.jpg](media/896ddc649ace2c4d5b318d11c887ece9.jpeg)
# DAO模式的具体实现
\-VO类提供javabean表示数据库中的一条记录主要有getter和setter方法
\-DAO类提供了DAOmodel的接口供控制器调用
\-DAOimplement实现了DAO类提供的外部接口主要是通过jdbc链接数据库实现了数据库的增删查改操作。
\-DAOFactory通过工厂类获取一个DAO的实例化对象。
在当前的工程中分别对应一下包:
.domain
.dao
.dao.impl
.service.impl
这四个类分别代daobean,dao接口,dao的jdbc实现,dao的工厂类在这里实现工程需要的所有的数据库的操作提供给其他部分调用。
最根本的事通过jdbcUtils提供了访问数据库的链接
逻辑结构非常清晰明显
# 最后说明dao的工作原理
![dao2.jpg](media/0df06acb323561f7014fed6f60125206.jpeg)
关于到的层级在此说明
数据库数据存储
\|-jdbc提供数据库的链接
\|-daoBean数据保存
\|-daoImpl的具体实现
\|-dao提供数据库访问的接口
\|-daoFactory实例化数据库的接口提供具体的数据操作
控制器数据操作需求

View File

@@ -1,9 +0,0 @@
java本身就是一个依赖各种库来构建工程的语言实际上需要自己写的东西并不多就是调用各种已经编辑好的库来实现各自的功能。
大部分库都是由jre/jdk内部自带的java库提供的。包括各种不同的版本导入的时候必须要注意可能是1.61.71.8等。
在做javaEE开发过程中部分依赖的网络编程的库org.apache.\*是由Tomcat服务器插件提供的
在实现软件测试的过程中junit4相关的库是由第三方插件提供也就是说在构建工程的时候下载了eclemma插件提供了org.junit.\*库和junit.\*库。并且提供了net.mooctest.plugin.\*库实现与mooctest的网站的交互。
所以在构建工程过程中必须得考虑相关库的导入。可能是来自于jdk/jre的官方标准库也可能是来自于其他特定功能的库tomcat也许是来自相关插件的库如adt插件、eclemma插件、junit4插件等。

View File

@@ -1,15 +0,0 @@
服务器端:
java Servlet
资源配置文件
与前端结合:
jspjava脚本直接在前端页面jsp嵌入前端脚本实现动态化。
jspjsp标签使用jsp标签来实现基础的控制
jspEL表达式使用简化的表达式实现动态数据获取
jsptag标签包括标准标签库和自定义标签库的使用。

View File

@@ -1,11 +0,0 @@
jsp代码编写属于后端因为前端开发看不懂JSP代码他们追求的是网页效果。而JSP其实就是JAVA代码来拼接前端页面而已本身也是Servlet因此JAVA
WEB工程师也要学习一些HTMLJSCSS等实际开法中前端工程师写好网页Java
web开发人员负责填写JSP脚本也可以反过来后端定义好标签库让前端以标签的方式来写代码。但近年来大部分项目都是用Ajax来调用后端接口了做到前后分离直接写JSP的少了。
作者:清浅池塘
链接https://www.zhihu.com/question/52695070/answer/228804504
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

View File

@@ -1,11 +0,0 @@
## JSP的内置对象
request对象
通过from表单能够发送GET和POST方法的Request。
request.getParameter("name"):得到键值对的值
request.setCharacterEncoding(utf-8);解决编码问题
getProtocol

View File

@@ -1,120 +0,0 @@
# -*- coding: utf-8 -*-
import queue
import re
import urllib2
import time
import urllib2
headers=("User-Agent","Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36")
opener=urllib2.build_opener()
opener.addheaders=[headers]
#global the opener
urllib2.install_opener(opener)
listurl=[]
#use the procy server
def use_proxy(proxy_addr,url):
try:
import urllib2
data = urllib2.urlopen(url).read().decode('utf-8')
return data
except urllib2.URLError as e:
if hasattr(e,"code"):
print(e.code)
if hasattr(e,"reason"):
print(e.reason)
time.sleep(10)
except Exception as e:
print("exception:"+str(e))
time.sleep(1)
# get the picture
def craw(url,number):
html1=urllib2.urlopen(url).read().decode('utf-8')
html1=html1
pat1='<tr><td class="contentstyle54611">(.*?)</body></html>'
result1=re.compile(pat1,re.S).findall(html1)
result1=result1[0]
pat2='<IMG border="0" src="(.*?).jpg"'
imagelist=re.compile(pat2,re.S).findall(result1)
x=1;
for imageurl in imagelist:
imagename="E:/hello/"+str(x)+str(number)+".jpg"
imageurl = imageurl.replace('../../', "")
imageurl = "http://zdhxy.nwpu.edu.cn/" + imageurl+'.jpg'
try:
urllib2.urlretrieve(imageurl,filename=imagename)
except urllib2.URLError as e:
if hasattr(e, "code"):
print(e.code)
if hasattr(e, "reason"):
print(e.reason)
x+=1;
#get the content through the link
def getcontent(listurl,proxy):
i=0
#set local files's html code
html1='''<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://
www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>computer</title>
</head>
<body>'''
fh=open("E:/hello/2.html","wb")
fh.write(html1.encode("utf-8"))
fh.close()
fh=open("E:/hello/2.html","ab")
for i in range(0,len(listurl)):
for j in range(0,len(listurl[i])):
try:
url=listurl[i][j]
url=url.replace("\"","")
url = url.replace('../', "")
url="http://jsj.nwpu.edu.cn/"+url
data2=use_proxy(proxy,url)
craw(url,j)
titlepat='<title>(.*?)</title>'
contentpat='<tr><td class="contentstyle54611">(.*?)</body></html>'
title=re.compile(titlepat).findall(data2)
content=re.compile(contentpat,re.S).findall(data2)
thistitle=" "
thiscontent=" "
if(title!=[]):
thistitle=title[0]
if(content!=[]):
thiscontent=content[0]
dataall="<p>title is :"+thistitle+"</p><p>content is :"+thiscontent+"</p><br>"
fh.write(dataall.encode("utf-8"))
print(""+str(i)+"第个网站"+str(j)+"次处理")
except urllib2.URLError as e:
if hasattr(e, "code"):
print(e.code)
if hasattr(e, "reason"):
print(e.reason)
time.sleep(10)
except Exception as e:
print("exception:" + str(e))
time.sleep(1)
fh.close()
html2='''</body>
</html>
'''
fh=open("E:/hello/2.html","ab")
fh.write(html2.encode("utf-8"))
fh.close()
proxy="123.145.128.139:8118"
pagestart=1
pageend=2
url="http://jsj.nwpu.edu.cn/index/xyxw.htm"
data1=use_proxy(proxy,url)
listurlpat='<a class="c54630" href="(.*?)"'
listurl.append(re.compile(listurlpat,re.S).findall(data1))
getcontent(listurl,proxy)
#print(title);
#listurl=getlisturl(key,pagestart,pageend,proxy)
#getcontent(listurl,proxy)

View File

@@ -1,157 +0,0 @@
JDBC操作数据库的基本步骤
1加载注册数据库驱动到JVM
2建立获取数据库连接。
3创建获取数据库操作对象。
4定义操作的SQL语句。
5执行数据库操作。
6获取并操作结果集。
7关闭对象回收数据库资源关闭结果集--\>关闭数据库操作对象--\>关闭连接)。
**[java]** [view plain](http://blog.csdn.net/hpu_a/article/details/51354867)
[copy](http://blog.csdn.net/hpu_a/article/details/51354867)
1. **package** com.yangshengjie.jdbc;
2. **import** java.sql.Connection;
3. **import** java.sql.DriverManager;
4. **import** java.sql.ResultSet;
5. **import** java.sql.SQLException;
6. **import** java.sql.Statement;
7.
8. **public** **class** JDBCTest {
9. /\*\*
10. \* 使用JDBC连接并操作mysql数据库
11. \*/
12. **public** **static** **void** main(String[] args) {
13. // 数据库驱动类名的字符串
14. String driver = "com.mysql.jdbc.Driver";
15. // 数据库连接串
16. String url = "jdbc:mysql://127.0.0.1:3306/jdbctest";
17. // 用户名
18. String username = "root";
19. // 密码
20. String password = "mysqladmin";
21. Connection conn = **null**;
22. Statement stmt = **null**;
23. ResultSet rs = **null**;
24. **try** {
25. // 1、加载数据库驱动
成功加载后会将Driver类的实例注册到DriverManager类中
26. Class.forName(driver );
27. // 2、获取数据库连接
28. conn = DriverManager.getConnection(url, username, password);
29. // 3、获取数据库操作对象
30. stmt = conn.createStatement();
31. // 4、定义操作的SQL语句
32. String sql = "select \* from user where id = 100";
33. // 5、执行数据库操作
34. rs = stmt.executeQuery(sql);
35. // 6、获取并操作结果集
36. **while** (rs.next()) {
37. System.out.println(rs.getInt("id"));
38. System.out.println(rs.getString("name"));
39. }
40. } **catch** (Exception e) {
41. e.printStackTrace();
42. } **finally** {
43. // 7、关闭对象回收数据库资源
44. **if** (rs != **null**) { //关闭结果集对象
45. **try** {
46. rs.close();
47. } **catch** (SQLException e) {
48. e.printStackTrace();
49. }
50. }
51. **if** (stmt != **null**) { // 关闭数据库操作对象
52. **try** {
53. stmt.close();
54. } **catch** (SQLException e) {
55. e.printStackTrace();
56. }
57. }
58. **if** (conn != **null**) { // 关闭数据库连接对象
59. **try** {
60. **if** (!conn.isClosed()) {
61. conn.close();
62. }
63. } **catch** (SQLException e) {
64. e.printStackTrace();
65. }
66. }
67. }
68. }
69. }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -1 +0,0 @@
不能用过度的MVC框架的知识来理解简单的jsp页面怎么说现在写的jsp的级别就是将这些内容组合起来一个页面与另一个页面杂糅式的交互。起始php与python这种直接接触框架的学习反而非常不好。有时间多做东西救火啊提高效率。才能有更多的理解。

View File

@@ -1,55 +0,0 @@
# 序列化
对序列化的理解
JAVA序列化是指把JAVA对象转换为字节序列的过程。JAVA反序列化是吧字节恢复为JAVA对象。
应用:当两个进程进行远程通信时,可以相互发送各种类型的数据。包括文本、图片、音频、视频,这些数据都是以二进制序列的形式在网络上传送。
两个JAVA进程通信的时候也可以传送对象将JAVA序列化为字节序列装载为字节流然后进行传送。.接收端将字节流反序列化为JAVA对象。
作用:实现了数据的持久化,通过序列化,能够将数据永久的保存到硬盘上。利用序列化可以实现数据流的输入输出和远程通信。
实现方法:
java.io.ObjectOuptutStream:对象输出流writeObject()
java.io.ObjectInputStream:对象输入流readObject()
对象能被序列化的要求
implement了Serializable接口通过默认的方法进行序列化
implement了Serializable接口并且实现了相应的方法然后通过自己的方式进行序列化和反序列化
序列化的步骤:
步骤一:创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流:
ObjectOutputStream out = new ObjectOutputStream(new
fileOutputStream(“D:\\\\objectfile.obj”));
步骤二通过对象输出流的writeObject()方法写对象:
out.writeObject(“Hello”);
out.writeObject(new Date());
反序列化的步骤:
步骤一:创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:
ObjectInputStream in = new ObjectInputStream(new
fileInputStream(“D:\\\\objectfile.obj”));
步骤二通过对象输出流的readObject()方法读取对象:
String obj1 = (String)in.readObject();
Date obj2 = (Date)in.readObject();
# transient关键字说明
可以理解为瞬态、不可序列化。
简单的说truansient 变量就是在对象序列化的过程中没有被序列化变量。

View File

@@ -1,27 +0,0 @@
![968033-20160622111445922-1688525852.png](media/033dc67bcd465edb89b52cc8d9bdd5c1.png)
使用appium的原理及过程说明:
运行测试脚本的电脑我们称为Client。
打开Appium就开启了Appium Server默认监听4723端口。
Appium
Server接收到Client命令测试脚本翻译成测试机器可以理解的语言然后发送给测试机器运行。
测试机器运行结束后再把测试结果返回给Appium Server之后Appium
Server再把测试结果返回给Client。
![appium.jpg](media/4f52e7856884d1457ab7d1867caab247.jpeg)
1.appium是c/s模式的
2.appium是基于webdriver协议添加对移动设备自动化api扩展而成的所以具有和webdriver一样的特性比如多语言支持
3.webdriver是基于http协议的第一连接会建立一个session会话并通过post发送一个json告知服务端相关测试信息
4.对于android来说4.2以后是基于uiautomator框架实现查找注入事件的4.2以前则是instrumentation框架的并封装成一个叫Selendroid这玩意提供服务
5.客户端只需要发送http请求实现通讯意味着客户端就是多语言支持的
6.appium服务端是node.js写的所以你安装的时候无论哪个平台都是先装node

View File

@@ -1,96 +0,0 @@
WEB-INF web.xml
文件用来配置整个工程。
\<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4"\>
\<servlet\>
\<servlet-name\>FirstServlet\</servlet-name\>
\<servlet-class\>lesson.j2ee.ex1.FirstServlet\</servlet-class\>
\</servlet\>
\<servlet-mapping\>
\<servlet-name\>FirstServlet\</servlet-name\>
\<url-pattern\>/Serv1\</url-pattern\>
\</servlet-mapping\>
\</web-app\>
也许讲过但是感觉从来没有学过怎么开发知识知道Javaservlet的一些基础理论知识真的需要学习一下这方面的东西了。如何真正的使用Java开发一个工程。
Servlet通过回调函数管理自己的生命周期
init()-\>初始化
sevice()-\>提供servlet服务。
\-doGet()用于相应GET方法的函数HTTPSevlet类中实现
\-doPost()用于响应POST方法的函数HTTPSevlet类中实现
\-doHead()用于响应HEAD方法的函数HTTPSevlet类中实现。
destroy()-\>销毁
getServletConfig()用来获取Servlet配置信息。
getSeviceInfo()用来获取其他信息。
关于Servlet的继承关系必须知道每一级继承都实现了什么玩意。
Servlet \<-- GenericServlet \<-- HttpServlet \<--- MyServlet
关于servlet中常用到的两个对象
ServletConfig对象
每个servlet都有一个ServletConfig对象用于向servlet传递部署时的信息用于访问ServletContext其参数在DD中配置
ServletContext对象
每个web应用都有一个ServletContext用于访问web应用的参数在DD中配置用于获取服务器信息存放其它公用信息
请求与响应
ServletRequest \<--- HTTPServletRequest \<--- realRequest
SerbletResponse \<---- HTTPSerbletResponse \<---realResponse
java
与你写的python还是有很多不同的面向对象思想比较深刻。所以每次设计到一个内容首先了解有哪些类-对象,
每个类-对象由哪些属性 每个类-对象有那些方法;
因为每个类的功能和作用比较固定,所以属性和方法一般是与此功能相关的,笔记哦啊容易记忆。
已经涉及到的对象
HttpServlet -对象,每次请求会实例化。
Request -对象
Response -对象
ServletConfig - 对象
SerletContext - 对象
还是第一次真正的用到cookie那么思考一下如何使用cookie。
用户携带cookie值如果有的话请求一个网页。
服务器端读取cookie值如果存在cookie值则添加到页面当中。
当前用的这个东西存在一定的弊端。直接通过cookie获取当前用户的身份。

View File

@@ -1,27 +0,0 @@
感觉配置环境的过程中,对这些东西一知半解。
1\. 首先安装eclipsejdk/jreadt插件完成eclipse开发Android环境的基础设备。
2.
安装了node.js并配置了相关的环境使得系统能够编译并运行node.js的代码。然而跟我们的工程有什么联系一点都不知道。
经过了解得知安装node的原因是因为node的服务器端是由node.js写的也就是说node并不是用来进行编译客户端的代码的而是用来运行appium的服务器端程序的。
3.
安装Appium软件这玩意不是系统插件是一个调试的监视工具吧。具体怎么运作也不知道。
经过了解得知这个东西封装了对各种移动设备框架的调试方法通过服务器端连接客户端和移动设备。客户端通过url的形式访问服务器端完成对移动设备的调试。
4\. APPium调试过程依赖的jar包包括测试类库
Selenum类库(selenium.jar)
Appium类库client.jar)不知道是干啥用的
5\. 关联了相关的文件apk appt appt.exe)不知道是干啥用的
6\. 启动了Android模拟器或者连接了真机。
7\. 启动Appium工具并通过junit的方式对软件进行调试。
总之今天最大的收获可能就是对eclipse自动构建java工程结构有了更多的了解。

View File

@@ -1,30 +0,0 @@
下面来介绍将小数值舍入为整数的几个方法Math.ceil()、Math.floor()和Math.round()。
这三个方法分别遵循下列舍入规则:
Math.ceil()执行向上舍入,即它总是将数值向上舍入为最接近的整数;
Math.floor()执行向下舍入,即它总是将数值向下舍入为最接近的整数;
Math.round()执行标准舍入,即它总是将数值四舍五入为最接近的整数(这也是我们在数学课上学到的舍入规则)。
下面来看几个例子:
Math.ceil(25.9) //26
Math.ceil(25.5) //26
Math.ceil(25.1) //26
Math.ceil(25.0) //25
Math.round(25.9) //26
Math.round(25.5) //26
Math.round(25.1) //25
Math.floor(25.9) //25
Math.floor(25.5) //25
Math.floor(25.1) //25

View File

@@ -1,622 +0,0 @@
# Java IO
<!-- GFM-TOC -->
* [Java IO](#java-io)
* [一、概览](#一概览)
* [二、磁盘操作](#二磁盘操作)
* [三、字节操作](#三字节操作)
* [实现文件复制](#实现文件复制)
* [装饰者模式](#装饰者模式)
* [四、字符操作](#四字符操作)
* [编码与解码](#编码与解码)
* [String 的编码方式](#string-的编码方式)
* [Reader 与 Writer](#reader-与-writer)
* [实现逐行输出文本文件的内容](#实现逐行输出文本文件的内容)
* [五、对象操作](#五对象操作)
* [序列化](#序列化)
* [Serializable](#serializable)
* [transient](#transient)
* [六、网络操作](#六网络操作)
* [InetAddress](#inetaddress)
* [URL](#url)
* [Sockets](#sockets)
* [Datagram](#datagram)
* [七、NIO](#七nio)
* [流与块](#流与块)
* [通道与缓冲区](#通道与缓冲区)
* [缓冲区状态变量](#缓冲区状态变量)
* [文件 NIO 实例](#文件-nio-实例)
* [选择器](#选择器)
* [套接字 NIO 实例](#套接字-nio-实例)
* [内存映射文件](#内存映射文件)
* [对比](#对比)
* [八、参考资料](#八参考资料)
<!-- GFM-TOC -->
## 一、概览
Java 的 I/O 大概可以分成以下几类:
- 磁盘操作File
- 字节操作InputStream 和 OutputStream
- 字符操作Reader 和 Writer
- 对象操作Serializable
- 网络操作Socket
- 新的输入/输出NIO
## 二、磁盘操作
File 类可以用于表示文件和目录的信息,但是它不表示文件的内容。
递归地列出一个目录下所有文件:
```java
public static void listAllFiles(File dir) {
if (dir == null || !dir.exists()) {
return;
}
if (dir.isFile()) {
System.out.println(dir.getName());
return;
}
for (File file : dir.listFiles()) {
listAllFiles(file);
}
}
```
从 Java7 开始,可以使用 Paths 和 Files 代替 File。
## 三、字节操作
### 实现文件复制
```java
public static void copyFile(String src, String dist) throws IOException {
FileInputStream in = new FileInputStream(src);
FileOutputStream out = new FileOutputStream(dist);
byte[] buffer = new byte[20 * 1024];
int cnt;
// read() 最多读取 buffer.length 个字节
// 返回的是实际读取的个数
// 返回 -1 的时候表示读到 eof即文件尾
while ((cnt = in.read(buffer, 0, buffer.length)) != -1) {
out.write(buffer, 0, cnt);
}
in.close();
out.close();
}
```
### 装饰者模式
Java I/O 使用了装饰者模式来实现。以 InputStream 为例,
- InputStream 是抽象组件;
- FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作;
- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能。例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/9709694b-db05-4cce-8d2f-1c8b09f4d921.png" width="650px"> </div><br>
实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。
```java
FileInputStream fileInputStream = new FileInputStream(filePath);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
```
DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。
## 四、字符操作
### 编码与解码
编码就是把字符转换为字节,而解码是把字节重新组合成字符。
如果编码和解码过程使用不同的编码方式那么就出现了乱码。
- GBK 编码中,中文字符占 2 个字节,英文字符占 1 个字节;
- UTF-8 编码中,中文字符占 3 个字节,英文字符占 1 个字节;
- UTF-16be 编码中,中文字符和英文字符都占 2 个字节。
UTF-16be 中的 be 指的是 Big Endian也就是大端。相应地也有 UTF-16lele 指的是 Little Endian也就是小端。
Java 的内存编码使用双字节编码 UTF-16be这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位也就是两个字节Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。
### String 的编码方式
String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。
```java
String str1 = "中文";
byte[] bytes = str1.getBytes("UTF-8");
String str2 = new String(bytes, "UTF-8");
System.out.println(str2);
```
在调用无参数 getBytes() 方法时,默认的编码方式不是 UTF-16be。双字节编码的好处是可以使用一个 char 存储中文和英文,而将 String 转为 bytes[] 字节数组就不再需要这个好处因此也就不再需要双字节编码。getBytes() 的默认编码方式与平台有关,一般为 UTF-8。
```java
byte[] bytes = str1.getBytes();
```
### Reader 与 Writer
不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
- InputStreamReader 实现从字节流解码成字符流;
- OutputStreamWriter 实现字符流编码成为字节流。
### 实现逐行输出文本文件的内容
```java
public static void readFileContent(String filePath) throws IOException {
FileReader fileReader = new FileReader(filePath);
BufferedReader bufferedReader = new BufferedReader(fileReader);
String line;
while ((line = bufferedReader.readLine()) != null) {
System.out.println(line);
}
// 装饰者模式使得 BufferedReader 组合了一个 Reader 对象
// 在调用 BufferedReader 的 close() 方法时会去调用 Reader 的 close() 方法
// 因此只要一个 close() 调用即可
bufferedReader.close();
}
```
## 五、对象操作
### 序列化
序列化就是将一个对象转换成字节序列,方便存储和传输。
- 序列化ObjectOutputStream.writeObject()
- 反序列化ObjectInputStream.readObject()
不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。
### Serializable
序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。
```java
public static void main(String[] args) throws IOException, ClassNotFoundException {
A a1 = new A(123, "abc");
String objectFile = "file/a1";
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(objectFile));
objectOutputStream.writeObject(a1);
objectOutputStream.close();
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(objectFile));
A a2 = (A) objectInputStream.readObject();
objectInputStream.close();
System.out.println(a2);
}
private static class A implements Serializable {
private int x;
private String y;
A(int x, String y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "x = " + x + " " + "y = " + y;
}
}
```
### transient
transient 关键字可以使一些属性不会被序列化。
ArrayList 中存储数据的数组 elementData 是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。
```java
private transient Object[] elementData;
```
## 六、网络操作
Java 中的网络支持:
- InetAddress用于表示网络上的硬件资源即 IP 地址;
- URL统一资源定位符
- Sockets使用 TCP 协议实现网络通信;
- Datagram使用 UDP 协议实现网络通信。
### InetAddress
没有公有的构造函数,只能通过静态方法来创建实例。
```java
InetAddress.getByName(String host);
InetAddress.getByAddress(byte[] address);
```
### URL
可以直接从 URL 中读取字节流数据。
```java
public static void main(String[] args) throws IOException {
URL url = new URL("http://www.baidu.com");
/* 字节流 */
InputStream is = url.openStream();
/* 字符流 */
InputStreamReader isr = new InputStreamReader(is, "utf-8");
/* 提供缓存功能 */
BufferedReader br = new BufferedReader(isr);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
}
```
### Sockets
- ServerSocket服务器端类
- Socket客户端类
- 服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1e6affc4-18e5-4596-96ef-fb84c63bf88a.png" width="550px"> </div><br>
### Datagram
- DatagramSocket通信类
- DatagramPacket数据包类
## 七、NIO
新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。
### 流与块
I/O 与 NIO 最重要的区别是数据打包和传输的方式I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
面向流的 I/O 一次处理一个字节数据:一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责复杂处理机制的一部分。不利的一面是,面向流的 I/O 通常相当慢。
面向块的 I/O 一次处理一个数据块,按块处理数据比按流处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。
I/O 包和 NIO 已经很好地集成了java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。
### 通道与缓冲区
#### 1. 通道
通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。
通道与流的不同之处在于,流只能在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。
通道包括以下类型:
- FileChannel从文件中读写数据
- DatagramChannel通过 UDP 读写网络中数据;
- SocketChannel通过 TCP 读写网络中数据;
- ServerSocketChannel可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。
#### 2. 缓冲区
发送给一个通道的所有数据都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要先读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。
缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
缓冲区包括以下类型:
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
### 缓冲区状态变量
- capacity最大容量
- position当前已经读写的字节数
- limit还可以读写的字节数。
状态变量的改变过程举例:
① 新建一个大小为 8 个字节的缓冲区,此时 position 为 0而 limit = capacity = 8。capacity 变量不会改变,下面的讨论会忽略它。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1bea398f-17a7-4f67-a90b-9e2d243eaa9a.png"/> </div><br>
② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 为 5limit 保持不变。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/80804f52-8815-4096-b506-48eef3eed5c6.png"/> </div><br>
③ 在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position并将 position 设置为 0。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/952e06bd-5a65-4cab-82e4-dd1536462f38.png"/> </div><br>
④ 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b5bdcbe2-b958-4aef-9151-6ad963cb28b4.png"/> </div><br>
⑤ 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/67bf5487-c45d-49b6-b9c0-a058d8c68902.png"/> </div><br>
### 文件 NIO 实例
以下展示了使用 NIO 快速复制文件的实例:
```java
public static void fastCopy(String src, String dist) throws IOException {
/* 获得源文件的输入字节流 */
FileInputStream fin = new FileInputStream(src);
/* 获取输入字节流的文件通道 */
FileChannel fcin = fin.getChannel();
/* 获取目标文件的输出字节流 */
FileOutputStream fout = new FileOutputStream(dist);
/* 获取输出字节流的文件通道 */
FileChannel fcout = fout.getChannel();
/* 为缓冲区分配 1024 个字节 */
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
/* 从输入通道中读取数据到缓冲区中 */
int r = fcin.read(buffer);
/* read() 返回 -1 表示 EOF */
if (r == -1) {
break;
}
/* 切换读写 */
buffer.flip();
/* 把缓冲区的内容写入输出文件中 */
fcout.write(buffer);
/* 清空缓冲区 */
buffer.clear();
}
}
```
### 选择器
NIO 常常被叫做非阻塞 IO主要是因为 NIO 在网络通信中的非阻塞特性被广泛使用。
NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。
通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel找到 IO 事件已经到达的 Channel 执行。
因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件,对于 IO 密集型的应用具有很好地性能。
应该注意的是,只有套接字 Channel 才能配置为非阻塞,而 FileChannel 不能,为 FileChannel 配置非阻塞也没有意义。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/093f9e57-429c-413a-83ee-c689ba596cef.png" width="350px"> </div><br>
#### 1. 创建选择器
```java
Selector selector = Selector.open();
```
#### 2. 将通道注册到选择器上
```java
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
```
通道必须配置为非阻塞模式,否则使用选择器就没有任何意义了,因为如果通道在某个事件上被阻塞,那么服务器就不能响应其它事件,必须等待这个事件处理完毕才能去处理其它事件,显然这和选择器的作用背道而驰。
在将通道注册到选择器上时,还需要指定要注册的具体事件,主要有以下几类:
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
它们在 SelectionKey 的定义如下:
```java
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
```
可以看出每个事件可以被当成一个位域,从而组成事件集整数。例如:
```java
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
```
#### 3. 监听事件
```java
int num = selector.select();
```
使用 select() 来监听到达的事件,它会一直阻塞直到有至少一个事件到达。
#### 4. 获取到达的事件
```java
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// ...
} else if (key.isReadable()) {
// ...
}
keyIterator.remove();
}
```
#### 5. 事件循环
因为一次 select() 调用不能处理完所有的事件,并且服务器端有可能需要一直监听事件,因此服务器端处理事件的代码一般会放在一个死循环内。
```java
while (true) {
int num = selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// ...
} else if (key.isReadable()) {
// ...
}
keyIterator.remove();
}
}
```
### 套接字 NIO 实例
```java
public class NIOServer {
public static void main(String[] args) throws IOException {
Selector selector = Selector.open();
ServerSocketChannel ssChannel = ServerSocketChannel.open();
ssChannel.configureBlocking(false);
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
ServerSocket serverSocket = ssChannel.socket();
InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
serverSocket.bind(address);
while (true) {
selector.select();
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel();
// 服务器会为每个新连接创建一个 SocketChannel
SocketChannel sChannel = ssChannel1.accept();
sChannel.configureBlocking(false);
// 这个新连接主要用于从客户端读取数据
sChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel sChannel = (SocketChannel) key.channel();
System.out.println(readDataFromSocketChannel(sChannel));
sChannel.close();
}
keyIterator.remove();
}
}
}
private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(1024);
StringBuilder data = new StringBuilder();
while (true) {
buffer.clear();
int n = sChannel.read(buffer);
if (n == -1) {
break;
}
buffer.flip();
int limit = buffer.limit();
char[] dst = new char[limit];
for (int i = 0; i < limit; i++) {
dst[i] = (char) buffer.get(i);
}
data.append(dst);
buffer.clear();
}
return data.toString();
}
}
```
```java
public class NIOClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1", 8888);
OutputStream out = socket.getOutputStream();
String s = "hello world";
out.write(s.getBytes());
out.close();
}
}
```
### 内存映射文件
内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。
向内存映射文件写入可能是危险的,只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。
下面代码行将文件的前 1024 个字节映射到内存中map() 方法返回一个 MappedByteBuffer它是 ByteBuffer 的子类。因此,可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行映射。
```java
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
```
### 对比
NIO 与普通 I/O 的区别主要有以下两点:
- NIO 是非阻塞的;
- NIO 面向块I/O 面向流。
## 八、参考资料
- Eckel B, 埃克尔, 昊鹏, 等. Java 编程思想 [M]. 机械工业出版社, 2002.
- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html)
- [Java NIO Tutorial](http://tutorials.jenkov.com/java-nio/index.html)
- [Java NIO 浅析](https://tech.meituan.com/nio.html)
- [IBM: 深入分析 Java I/O 的工作机制](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html)
- [IBM: 深入分析 Java 中的中文编码问题](https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/index.html)
- [IBM: Java 序列化的高级认识](https://www.ibm.com/developerworks/cn/java/j-lo-serial/index.html)
- [NIO 与传统 IO 的区别](http://blog.csdn.net/shimiso/article/details/24990499)
- [Decorator Design Pattern](http://stg-tud.github.io/sedc/Lecture/ws13-14/5.3-Decorator.html#mode=document)
- [Socket Multicast](http://labojava.blogspot.com/2012/12/socket-multicast.html)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,755 +0,0 @@
# Java 虚拟机
<!-- GFM-TOC -->
* [Java 虚拟机](#java-虚拟机)
* [一、运行时数据区域](#一运行时数据区域)
* [程序计数器](#程序计数器)
* [Java 虚拟机栈](#java-虚拟机栈)
* [本地方法栈](#本地方法栈)
* [](#堆)
* [方法区](#方法区)
* [运行时常量池](#运行时常量池)
* [直接内存](#直接内存)
* [二、垃圾收集](#二垃圾收集)
* [判断一个对象是否可被回收](#判断一个对象是否可被回收)
* [引用类型](#引用类型)
* [垃圾收集算法](#垃圾收集算法)
* [垃圾收集器](#垃圾收集器)
* [三、内存分配与回收策略](#三内存分配与回收策略)
* [Minor GC 和 Full GC](#minor-gc-和-full-gc)
* [内存分配策略](#内存分配策略)
* [Full GC 的触发条件](#full-gc-的触发条件)
* [四、类加载机制](#四类加载机制)
* [类的生命周期](#类的生命周期)
* [类加载过程](#类加载过程)
* [类初始化时机](#类初始化时机)
* [类与类加载器](#类与类加载器)
* [类加载器分类](#类加载器分类)
* [双亲委派模型](#双亲委派模型)
* [自定义类加载器实现](#自定义类加载器实现)
* [参考资料](#参考资料)
<!-- GFM-TOC -->
本文大部分内容参考 **周志明《深入理解 Java 虚拟机》** ,想要深入学习的话请看原书。
## 一、运行时数据区域
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/5778d113-8e13-4c53-b5bf-801e58080b97.png" width="400px"> </div><br>
### 程序计数器
记录正在执行的虚拟机字节码指令的地址(如果正在执行的是本地方法则为空)。
### Java 虚拟机栈
每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/8442519f-0b4d-48f4-8229-56f984363c69.png" width="400px"> </div><br>
可以通过 -Xss 这个虚拟机参数来指定每个线程的 Java 虚拟机栈内存大小,在 JDK 1.4 中默认为 256K而在 JDK 1.5+ 默认为 1M
```java
java -Xss2M HackTheJava
```
该区域可能抛出以下异常:
- 当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常;
- 栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。
### 本地方法栈
本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
本地方法一般是用其它语言C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/66a6899d-c6b0-4a47-8569-9d08f0baf86c.png" width="300px"> </div><br>
### 堆
所有对象都在这里分配内存,是垃圾收集的主要区域("GC 堆")。
现代的垃圾收集器基本都是采用分代收集算法,其主要的思想是针对不同类型的对象采取不同的垃圾回收算法。可以将堆分成两块:
- 新生代Young Generation
- 老年代Old Generation
堆不需要连续内存,并且可以动态增加其内存,增加失败会抛出 OutOfMemoryError 异常。
可以通过 -Xms 和 -Xmx 这两个虚拟机参数来指定一个程序的堆内存大小,第一个参数设置初始值,第二个参数设置最大值。
```java
java -Xms1M -Xmx2M HackTheJava
```
### 方法区
用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。
对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。
HotSpot 虚拟机把它当成永久代来进行垃圾回收。但很难确定永久代的大小,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。
方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。在 JDK 1.8 之后,原来永久代的数据被分到了堆和元空间中。元空间存储类的元信息,静态变量和常量池等放入堆中。
### 运行时常量池
运行时常量池是方法区的一部分。
Class 文件中的常量池(编译器生成的字面量和符号引用)会在类加载后被放入这个区域。
除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()。
### 直接内存
在 JDK 1.4 中新引入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在堆内存和堆外内存来回拷贝数据。
## 二、垃圾收集
垃圾收集主要是针对堆和方法区进行。程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后就会消失,因此不需要对这三个区域进行垃圾回收。
### 判断一个对象是否可被回收
#### 1. 引用计数算法
为对象添加一个引用计数器,当对象增加一个引用时计数器加 1引用失效时计数器减 1。引用计数为 0 的对象可被回收。
在两个对象出现循环引用的情况下,此时引用计数器永远不为 0导致无法对它们进行回收。正是因为循环引用的存在因此 Java 虚拟机不使用引用计数算法。
```java
public class Test {
public Object instance = null;
public static void main(String[] args) {
Test a = new Test();
Test b = new Test();
a.instance = b;
b.instance = a;
a = null;
b = null;
doSomething();
}
}
```
在上述代码中a 与 b 引用的对象实例互相持有了对象的引用,因此当我们把对 a 对象与 b 对象的引用去除之后,由于两个对象还存在互相之间的引用,导致两个 Test 对象无法被回收。
#### 2. 可达性分析算法
以 GC Roots 为起始点进行搜索,可达的对象都是存活的,不可达的对象可被回收。
Java 虚拟机使用该算法来判断对象是否可被回收GC Roots 一般包含以下内容:
- 虚拟机栈中局部变量表中引用的对象
- 本地方法栈中 JNI 中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中的常量引用的对象
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/83d909d2-3858-4fe1-8ff4-16471db0b180.png" width="350px"> </div><br>
#### 3. 方法区的回收
因为方法区主要存放永久代对象,而永久代对象的回收率比新生代低很多,所以在方法区上进行回收性价比不高。
主要是对常量池的回收和对类的卸载。
为了避免内存溢出,在大量使用反射和动态代理的场景都需要虚拟机具备类卸载功能。
类的卸载条件很多,需要满足以下三个条件,并且满足了条件也不一定会被卸载:
- 该类所有的实例都已经被回收,此时堆中不存在该类的任何实例。
- 加载该类的 ClassLoader 已经被回收。
- 该类对应的 Class 对象没有在任何地方被引用,也就无法在任何地方通过反射访问该类方法。
#### 4. finalize()
类似 C++ 的析构函数,用于关闭外部资源。但是 try-finally 等方式可以做得更好,并且该方法运行代价很高,不确定性大,无法保证各个对象的调用顺序,因此最好不要使用。
当一个对象可被回收时,如果需要执行该对象的 finalize() 方法,那么就有可能在该方法中让对象重新被引用,从而实现自救。自救只能进行一次,如果回收的对象之前调用了 finalize() 方法自救,后面回收时不会再调用该方法。
### 引用类型
无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否可达,判定对象是否可被回收都与引用有关。
Java 提供了四种强度不同的引用类型。
#### 1. 强引用
被强引用关联的对象不会被回收。
使用 new 一个新对象的方式来创建强引用。
```java
Object obj = new Object();
```
#### 2. 软引用
被软引用关联的对象只有在内存不够的情况下才会被回收。
使用 SoftReference 类来创建软引用。
```java
Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null; // 使对象只被软引用关联
```
#### 3. 弱引用
被弱引用关联的对象一定会被回收,也就是说它只能存活到下一次垃圾回收发生之前。
使用 WeakReference 类来创建弱引用。
```java
Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
```
#### 4. 虚引用
又称为幽灵引用或者幻影引用,一个对象是否有虚引用的存在,不会对其生存时间造成影响,也无法通过虚引用得到一个对象。
为一个对象设置虚引用的唯一目的是能在这个对象被回收时收到一个系统通知。
使用 PhantomReference 来创建虚引用。
```java
Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj, null);
obj = null;
```
### 垃圾收集算法
#### 1. 标记 - 清除
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/005b481b-502b-4e3f-985d-d043c2b330aa.png" width="400px"> </div><br>
在标记阶段,程序会检查每个对象是否为活动对象,如果是活动对象,则程序会在对象头部打上标记。
在清除阶段,会进行对象回收并取消标志位,另外,还会判断回收后的分块与前一个空闲分块是否连续,若连续,会合并这两个分块。回收对象就是把对象作为分块,连接到被称为 “空闲链表” 的单向链表,之后进行分配时只需要遍历这个空闲链表,就可以找到分块。
在分配时,程序会搜索空闲链表寻找空间大于等于新对象大小 size 的块 block。如果它找到的块等于 size会直接返回这个分块如果找到的块大于 size会将块分割成大小为 size 与 (block - size) 的两部分,返回大小为 size 的分块,并把大小为 (block - size) 的块返回给空闲链表。
不足:
- 标记和清除过程效率都不高;
- 会产生大量不连续的内存碎片,导致无法给大对象分配内存。
#### 2. 标记 - 整理
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ccd773a5-ad38-4022-895c-7ac318f31437.png" width="400px"> </div><br>
让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
优点:
- 不会产生内存碎片
不足:
- 需要移动大量对象,处理效率比较低。
#### 3. 复制
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/b2b77b9e-958c-4016-8ae5-9c6edd83871e.png" width="400px"> </div><br>
将内存划分为大小相等的两块,每次只使用其中一块,当这一块内存用完了就将还存活的对象复制到另一块上面,然后再把使用过的内存空间进行一次清理。
主要不足是只使用了内存的一半。
现在的商业虚拟机都采用这种收集算法回收新生代,但是并不是划分为大小相等的两块,而是一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 和其中一块 Survivor。在回收时将 Eden 和 Survivor 中还存活着的对象全部复制到另一块 Survivor 上,最后清理 Eden 和使用过的那一块 Survivor。
HotSpot 虚拟机的 Eden 和 Survivor 大小比例默认为 8:1保证了内存的利用率达到 90%。如果每次回收有多于 10% 的对象存活,那么一块 Survivor 就不够用了,此时需要依赖于老年代进行空间分配担保,也就是借用老年代的空间存储放不下的对象。
#### 4. 分代收集
现在的商业虚拟机采用分代收集算法,它根据对象存活周期将内存划分为几块,不同块采用适当的收集算法。
一般将堆分为新生代和老年代。
- 新生代使用:复制算法
- 老年代使用:标记 - 清除 或者 标记 - 整理 算法
### 垃圾收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/c625baa0-dde6-449e-93df-c3a67f2f430f.jpg" width=""/> </div><br>
以上是 HotSpot 虚拟机中的 7 个垃圾收集器,连线表示垃圾收集器可以配合使用。
- 单线程与多线程:单线程指的是垃圾收集器只使用一个线程,而多线程使用多个线程;
- 串行与并行:串行指的是垃圾收集器与用户程序交替执行,这意味着在执行垃圾收集的时候需要停顿用户程序;并行指的是垃圾收集器和用户程序同时执行。除了 CMS 和 G1 之外,其它垃圾收集器都是以串行的方式执行。
#### 1. Serial 收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/22fda4ae-4dd5-489d-ab10-9ebfdad22ae0.jpg" width=""/> </div><br>
Serial 翻译为串行,也就是说它以串行的方式执行。
它是单线程的收集器,只会使用一个线程进行垃圾收集工作。
它的优点是简单高效,在单个 CPU 环境下,由于没有线程交互的开销,因此拥有最高的单线程收集效率。
它是 Client 场景下的默认新生代收集器,因为在该场景下内存一般来说不会很大。它收集一两百兆垃圾的停顿时间可以控制在一百多毫秒以内,只要不是太频繁,这点停顿时间是可以接受的。
#### 2. ParNew 收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/81538cd5-1bcf-4e31-86e5-e198df1e013b.jpg" width=""/> </div><br>
它是 Serial 收集器的多线程版本。
它是 Server 场景下默认的新生代收集器,除了性能原因外,主要是因为除了 Serial 收集器,只有它能与 CMS 收集器配合使用。
#### 3. Parallel Scavenge 收集器
与 ParNew 一样是多线程收集器。
其它收集器目标是尽可能缩短垃圾收集时用户线程的停顿时间,而它的目标是达到一个可控制的吞吐量,因此它被称为“吞吐量优先”收集器。这里的吞吐量指 CPU 用于运行用户程序的时间占总时间的比值。
停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能提升用户体验。而高吞吐量则可以高效率地利用 CPU 时间,尽快完成程序的运算任务,适合在后台运算而不需要太多交互的任务。
缩短停顿时间是以牺牲吞吐量和新生代空间来换取的:新生代空间变小,垃圾回收变得频繁,导致吞吐量下降。
可以通过一个开关参数打开 GC 自适应的调节策略GC Ergonomics就不需要手工指定新生代的大小-Xmn、Eden 和 Survivor 区的比例、晋升老年代对象年龄等细节参数了。虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。
#### 4. Serial Old 收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/08f32fd3-f736-4a67-81ca-295b2a7972f2.jpg" width=""/> </div><br>
是 Serial 收集器的老年代版本,也是给 Client 场景下的虚拟机使用。如果用在 Server 场景下,它有两大用途:
- 在 JDK 1.5 以及之前版本Parallel Old 诞生以前)中与 Parallel Scavenge 收集器搭配使用。
- 作为 CMS 收集器的后备预案,在并发收集发生 Concurrent Mode Failure 时使用。
#### 5. Parallel Old 收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/278fe431-af88-4a95-a895-9c3b80117de3.jpg" width=""/> </div><br>
是 Parallel Scavenge 收集器的老年代版本。
在注重吞吐量以及 CPU 资源敏感的场合,都可以优先考虑 Parallel Scavenge 加 Parallel Old 收集器。
#### 6. CMS 收集器
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/62e77997-6957-4b68-8d12-bfd609bb2c68.jpg" width=""/> </div><br>
CMSConcurrent Mark SweepMark Sweep 指的是标记 - 清除算法。
分为以下四个流程:
- 初始标记:仅仅只是标记一下 GC Roots 能直接关联到的对象,速度很快,需要停顿。
- 并发标记:进行 GC Roots Tracing 的过程,它在整个回收过程中耗时最长,不需要停顿。
- 重新标记:为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,需要停顿。
- 并发清除:不需要停顿。
在整个过程中耗时最长的并发标记和并发清除过程中,收集器线程都可以与用户线程一起工作,不需要进行停顿。
具有以下缺点:
- 吞吐量低:低停顿时间是以牺牲吞吐量为代价的,导致 CPU 利用率不够高。
- 无法处理浮动垃圾,可能出现 Concurrent Mode Failure。浮动垃圾是指并发清除阶段由于用户线程继续运行而产生的垃圾这部分垃圾只能到下一次 GC 时才能进行回收。由于浮动垃圾的存在,因此需要预留出一部分内存,意味着 CMS 收集不能像其它收集器那样等待老年代快满的时候再回收。如果预留的内存不够存放浮动垃圾,就会出现 Concurrent Mode Failure这时虚拟机将临时启用 Serial Old 来替代 CMS。
- 标记 - 清除算法导致的空间碎片,往往出现老年代空间剩余,但无法找到足够大连续空间来分配当前对象,不得不提前触发一次 Full GC。
#### 7. G1 收集器
G1Garbage-First它是一款面向服务端应用的垃圾收集器在多 CPU 和大内存的场景下有很好的性能。HotSpot 开发团队赋予它的使命是未来可以替换掉 CMS 收集器。
堆被分为新生代和老年代,其它收集器进行收集的范围都是整个新生代或者老年代,而 G1 可以直接对新生代和老年代一起回收。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/4cf711a8-7ab2-4152-b85c-d5c226733807.png" width="600"/> </div><br>
G1 把堆划分成多个大小相等的独立区域Region新生代和老年代不再物理隔离。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/9bbddeeb-e939-41f0-8e8e-2b1a0aa7e0a7.png" width="600"/> </div><br>
通过引入 Region 的概念,从而将原来的一整块内存空间划分成多个的小空间,使得每个小空间可以单独进行垃圾回收。这种划分方法带来了很大的灵活性,使得可预测的停顿时间模型成为可能。通过记录每个 Region 垃圾回收时间以及回收所获得的空间(这两个值是通过过去回收的经验获得),并维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的 Region。
每个 Region 都有一个 Remembered Set用来记录该 Region 对象的引用对象所在的 Region。通过使用 Remembered Set在做可达性分析的时候就可以避免全堆扫描。
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/f99ee771-c56f-47fb-9148-c0036695b5fe.jpg" width=""/> </div><br>
如果不计算维护 Remembered Set 的操作G1 收集器的运作大致可划分为以下几个步骤:
- 初始标记
- 并发标记
- 最终标记:为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程的 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中。这阶段需要停顿线程,但是可并行执行。
- 筛选回收:首先对各个 Region 中的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划。此阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region时间是用户可控制的而且停顿用户线程将大幅度提高收集效率。
具备如下特点:
- 空间整合:整体来看是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上来看是基于“复制”算法实现的,这意味着运行期间不会产生内存空间碎片。
- 可预测的停顿:能让使用者明确指定在一个长度为 M 毫秒的时间片段内,消耗在 GC 上的时间不得超过 N 毫秒。
## 三、内存分配与回收策略
### Minor GC 和 Full GC
- Minor GC回收新生代因为新生代对象存活时间很短因此 Minor GC 会频繁执行,执行的速度一般也会比较快。
- Full GC回收老年代和新生代老年代对象其存活时间长因此 Full GC 很少执行,执行速度会比 Minor GC 慢很多。
### 内存分配策略
#### 1. 对象优先在 Eden 分配
大多数情况下,对象在新生代 Eden 上分配,当 Eden 空间不够时,发起 Minor GC。
#### 2. 大对象直接进入老年代
大对象是指需要连续内存空间的对象,最典型的大对象是那种很长的字符串以及数组。
经常出现大对象会提前触发垃圾收集以获取足够的连续空间分配给大对象。
-XX:PretenureSizeThreshold大于此值的对象直接在老年代分配避免在 Eden 和 Survivor 之间的大量内存复制。
#### 3. 长期存活的对象进入老年代
为对象定义年龄计数器,对象在 Eden 出生并经过 Minor GC 依然存活,将移动到 Survivor 中,年龄就增加 1 岁,增加到一定年龄则移动到老年代中。
-XX:MaxTenuringThreshold 用来定义年龄的阈值。
#### 4. 动态对象年龄判定
虚拟机并不是永远要求对象的年龄必须达到 MaxTenuringThreshold 才能晋升老年代,如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 空间的一半,则年龄大于或等于该年龄的对象可以直接进入老年代,无需等到 MaxTenuringThreshold 中要求的年龄。
#### 5. 空间分配担保
在发生 Minor GC 之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC 可以确认是安全的。
如果不成立的话虚拟机会查看 HandlePromotionFailure 的值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC如果小于或者 HandlePromotionFailure 的值不允许冒险,那么就要进行一次 Full GC。
### Full GC 的触发条件
对于 Minor GC其触发条件非常简单当 Eden 空间满时,就将触发一次 Minor GC。而 Full GC 则相对复杂,有以下条件:
#### 1. 调用 System.gc()
只是建议虚拟机执行 Full GC但是虚拟机不一定真正去执行。不建议使用这种方式而是让虚拟机管理内存。
#### 2. 老年代空间不足
老年代空间不足的常见场景为前文所讲的大对象直接进入老年代、长期存活的对象进入老年代等。
为了避免以上原因引起的 Full GC应当尽量不要创建过大的对象以及数组。除此之外可以通过 -Xmn 虚拟机参数调大新生代的大小,让对象尽量在新生代被回收掉,不进入老年代。还可以通过 -XX:MaxTenuringThreshold 调大对象进入老年代的年龄,让对象在新生代多存活一段时间。
#### 3. 空间分配担保失败
使用复制算法的 Minor GC 需要老年代的内存空间作担保,如果担保失败会执行一次 Full GC。具体内容请参考上面的第 5 小节。
#### 4. JDK 1.7 及以前的永久代空间不足
在 JDK 1.7 及以前HotSpot 虚拟机中的方法区是用永久代实现的,永久代中存放的为一些 Class 的信息、常量、静态变量等数据。
当系统中要加载的类、反射的类和调用的方法较多时,永久代可能会被占满,在未配置为采用 CMS GC 的情况下也会执行 Full GC。如果经过 Full GC 仍然回收不了,那么虚拟机会抛出 java.lang.OutOfMemoryError。
为避免以上原因引起的 Full GC可采用的方法为增大永久代空间或转为使用 CMS GC。
#### 5. Concurrent Mode Failure
执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure 错误,并触发 Full GC。
## 四、类加载机制
类是在运行期间第一次使用时动态加载的,而不是一次性加载所有类。因为如果一次性加载,那么会占用很多的内存。
### 类的生命周期
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/335fe19c-4a76-45ab-9320-88c90d6a0d7e.png" width="600px"> </div><br>
包括以下 7 个阶段:
- **加载Loading**
- **验证Verification**
- **准备Preparation**
- **解析Resolution**
- **初始化Initialization**
- 使用Using
- 卸载Unloading
### 类加载过程
包含了加载、验证、准备、解析和初始化这 5 个阶段。
#### 1. 加载
加载是类加载的一个阶段,注意不要混淆。
加载过程完成以下三件事:
- 通过类的完全限定名称获取定义该类的二进制字节流。
- 将该字节流表示的静态存储结构转换为方法区的运行时存储结构。
- 在内存中生成一个代表该类的 Class 对象,作为方法区中该类各种数据的访问入口。
其中二进制字节流可以从以下方式中获取:
- 从 ZIP 包读取,成为 JAR、EAR、WAR 格式的基础。
- 从网络中获取,最典型的应用是 Applet。
- 运行时计算生成,例如动态代理技术,在 java.lang.reflect.Proxy 使用 ProxyGenerator.generateProxyClass 的代理类的二进制字节流。
- 由其他文件生成,例如由 JSP 文件生成对应的 Class 类。
#### 2. 验证
确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
#### 3. 准备
类变量是被 static 修饰的变量,准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。
实例变量不会在这阶段分配内存,它会在对象实例化时随着对象一起被分配在堆中。应该注意到,实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只进行一次,实例化可以进行多次。
初始值一般为 0 值,例如下面的类变量 value 被初始化为 0 而不是 123。
```java
public static int value = 123;
```
如果类变量是常量,那么它将初始化为表达式所定义的值而不是 0。例如下面的常量 value 被初始化为 123 而不是 0。
```java
public static final int value = 123;
```
#### 4. 解析
将常量池的符号引用替换为直接引用的过程。
其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。
#### 5. 初始化
<div data="modify -->"></div>
初始化阶段才真正开始执行类中定义的 Java 程序代码。初始化阶段是虚拟机执行类构造器 &lt;clinit\>() 方法的过程。在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。
&lt;clinit\>() 是由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。特别注意的是,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问。例如以下代码:
```java
public class Test {
static {
i = 0; // 给变量赋值可以正常编译通过
System.out.print(i); // 这句编译器会提示“非法向前引用”
}
static int i = 1;
}
```
由于父类的 &lt;clinit\>() 方法先执行,也就意味着父类中定义的静态语句块的执行要优先于子类。例如以下代码:
```java
static class Parent {
public static int A = 1;
static {
A = 2;
}
}
static class Sub extends Parent {
public static int B = A;
}
public static void main(String[] args) {
System.out.println(Sub.B); // 2
}
```
接口中不可以使用静态语句块,但仍然有类变量初始化的赋值操作,因此接口与类一样都会生成 &lt;clinit\>() 方法。但接口与类不同的是,执行接口的 &lt;clinit\>() 方法不需要先执行父接口的 &lt;clinit\>() 方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的 &lt;clinit\>() 方法。
虚拟机会保证一个类的 &lt;clinit\>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 &lt;clinit\>() 方法,其它线程都会阻塞等待,直到活动线程执行 &lt;clinit\>() 方法完毕。如果在一个类的 &lt;clinit\>() 方法中有耗时的操作,就可能造成多个线程阻塞,在实际过程中此种阻塞很隐蔽。
### 类初始化时机
#### 1. 主动引用
虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随之发生):
- 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译期把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。
- 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。
- 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类;
- 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化;
#### 2. 被动引用
以上 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的常见例子包括:
- 通过子类引用父类的静态字段,不会导致子类初始化。
```java
System.out.println(SubClass.value); // value 字段在 SuperClass 中定义
```
- 通过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,数组类是一个由虚拟机自动生成的、直接继承自 Object 的子类,其中包含了数组的属性和方法。
```java
SuperClass[] sca = new SuperClass[10];
```
- 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
```java
System.out.println(ConstClass.HELLOWORLD);
```
### 类与类加载器
两个类相等,需要类本身相等,并且使用同一个类加载器进行加载。这是因为每一个类加载器都拥有一个独立的类名称空间。
这里的相等,包括类的 Class 对象的 equals() 方法、isAssignableFrom() 方法、isInstance() 方法的返回结果为 true也包括使用 instanceof 关键字做对象所属关系判定结果为 true。
### 类加载器分类
从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器:
- 启动类加载器Bootstrap ClassLoader使用 C++ 实现,是虚拟机自身的一部分;
- 所有其它类的加载器,使用 Java 实现,独立于虚拟机,继承自抽象类 java.lang.ClassLoader。
从 Java 开发人员的角度看,类加载器可以划分得更细致一些:
- 启动类加载器Bootstrap ClassLoader此类加载器负责将存放在 &lt;JRE_HOME\>\lib 目录中的,或者被 -Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。
- 扩展类加载器Extension ClassLoader这个类加载器是由 ExtClassLoadersun.misc.Launcher$ExtClassLoader实现的。它负责将 &lt;JAVA_HOME\>/lib/ext 或者被 java.ext.dir 系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。
- 应用程序类加载器Application ClassLoader这个类加载器是由 AppClassLoadersun.misc.Launcher$AppClassLoader实现的。由于这个类加载器是 ClassLoader 中的 getSystemClassLoader() 方法的返回值因此一般称为系统类加载器。它负责加载用户类路径ClassPath上所指定的类库开发者可以直接使用这个类加载器如果应用程序中没有自定义过自己的类加载器一般情况下这个就是程序中默认的类加载器。
### 双亲委派模型
应用程序是由三种类加载器互相配合从而实现类加载,除此之外还可以加入自己定义的类加载器。
下图展示了类加载器之间的层次关系称为双亲委派模型Parents Delegation Model。该模型要求除了顶层的启动类加载器外其它的类加载器都要有自己的父类加载器。这里的父子关系一般通过组合关系Composition来实现而不是继承关系Inheritance
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/0dd2d40a-5b2b-4d45-b176-e75a4cd4bdbf.png" width="500px"> </div><br>
#### 1. 工作过程
一个类加载器首先将类加载请求转发到父类加载器,只有当父类加载器无法完成时才尝试自己加载。
#### 2. 好处
使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。
例如 java.lang.Object 存放在 rt.jar 中,如果编写另外一个 java.lang.Object 并放到 ClassPath 中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,这是因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。rt.jar 中的 Object 优先级更高,那么程序中所有的 Object 都是这个 Object。
#### 3. 实现
以下是抽象类 java.lang.ClassLoader 的代码片段,其中的 loadClass() 方法运行过程如下:先检查类是否已经加载过,如果没有则让父类加载器去加载。当父类加载器加载失败时抛出 ClassNotFoundException此时尝试自己去加载。
```java
public abstract class ClassLoader {
// The parent class loader for delegation
private final ClassLoader parent;
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
}
```
### 自定义类加载器实现
以下代码中的 FileSystemClassLoader 是自定义类加载器,继承自 java.lang.ClassLoader用于加载文件系统上的类。它首先根据类的全名在文件系统上查找类的字节代码文件.class 文件),然后读取该文件内容,最后通过 defineClass() 方法来把这些字节代码转换成 java.lang.Class 类的实例。
java.lang.ClassLoader 的 loadClass() 实现了双亲委派模型的逻辑,自定义类加载器一般不去重写它,但是需要重写 findClass() 方法。
```java
public class FileSystemClassLoader extends ClassLoader {
private String rootDir;
public FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] getClassData(String className) {
String path = classNameToPath(className);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead;
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private String classNameToPath(String className) {
return rootDir + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
}
```
## 参考资料
- 周志明. 深入理解 Java 虚拟机 [M]. 机械工业出版社, 2011.
- [Chapter 2. The Structure of the Java Virtual Machine](https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.4)
- [Jvm memory](https://www.slideshare.net/benewu/jvm-memory)
[Getting Started with the G1 Garbage Collector](http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/G1GettingStarted/index.html)
- [JNI Part1: Java Native Interface Introduction and “Hello World” application](http://electrofriends.com/articles/jni/jni-part1-java-native-interface/)
- [Memory Architecture Of JVM(Runtime Data Areas)](https://hackthejava.wordpress.com/2015/01/09/memory-architecture-by-jvmruntime-data-areas/)
- [JVM Run-Time Data Areas](https://www.programcreek.com/2013/04/jvm-run-time-data-areas/)
- [Android on x86: Java Native Interface and the Android Native Development Kit](http://www.drdobbs.com/architecture-and-design/android-on-x86-java-native-interface-and/240166271)
- [深入理解 JVM(2)——GC 算法与内存分配策略](https://crowhawk.github.io/2017/08/10/jvm_2/)
- [深入理解 JVM(3)——7 种垃圾收集器](https://crowhawk.github.io/2017/08/15/jvm_3/)
- [JVM Internals](http://blog.jamesdbloom.com/JVMInternals.html)
- [深入探讨 Java 类加载器](https://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html#code6)
- [Guide to WeakHashMap in Java](http://www.baeldung.com/java-weakhashmap)
- [Tomcat example source code file (ConcurrentCache.java)](https://alvinalexander.com/java/jwarehouse/apache-tomcat-6.0.16/java/org/apache/el/util/ConcurrentCache.java.shtml)

View File

@@ -1,117 +0,0 @@
## 新的事件监听机制
-----
* 之前是定义一个组件对象(事件源),队组建对象绑定一个监听器对象,在监听器对象中有监听同一事件不同动作的函数,函数内部对事件进行处理。从监听器的角度对事件和动作进行处理。
* 现在是直接将静态事件绑定到事件源上通过process函数运行运行事件发生时的处理方式。而且这些静态事件是已经存在于awtEvent类中的。
```
package EventHandle;
import java.awt.*;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
/**
* @author 宙斯
* 准确的说这并不是先前的事件监听机制吧。它不存在指明的监听器。
* 应该更像一种组件自带的事件匹配功能。
* 具体步骤:
* 打开某一类型的事件匹配功能。
* 使用process函数运行某个事件
* 当事件发生时通过事件的ID匹配具体的动作。
* 匹配后设置相应的处理方式。
*/
public class baseEvent extends Frame{
private int x = 0,y = 0,flag = 0;
private Image img;
private int dx = 0,dy = 0;
public static void main(String[] args) {
baseEvent bE = new baseEvent();
}
public baseEvent(){
super();
setSize(500,500);
this.setVisible(true);
Toolkit tk = Toolkit.getDefaultToolkit();
img = tk.getImage("first.png");
//相当于添加了对Component事件的监听器似乎监听器类在底层已经直接绑定。
enableEvents(AWTEvent.COMPONENT_EVENT_MASK);
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
repaint();
}
//直接调用底层的事件处理函数
public void processComponentEvent(ComponentEvent e){
if(e.getID() == ComponentEvent.COMPONENT_MOVED){
System.out.println(e.getSource());
System.out.println(e.getID());
}
}
public void processWindowEvent(WindowEvent e){
if(e.getID() == WindowEvent.WINDOW_CLOSING){
System.exit(0);
}
}
/*
public void processMouseEvent(MouseEvent e){
if(e.getID() == MouseEvent.MOUSE_PRESSED){
System.out.println("pressed");
if((e.getX()>= x)&&(e.getX()<=x+100)&&(e.getY()>=y)&&(e.getY()<=y+100)){
flag = 1;
System.out.println(e.getX()+"..."+e.getY());
}
}
if((e.getID()==MouseEvent.MOUSE_RELEASED)&&(flag == 1)){
x = e.getX();
y = e.getY();
repaint();
flag = 0;
}
}
*/
public void processMouseEvent(MouseEvent e){
if(e.getID() == MouseEvent.MOUSE_PRESSED){
System.out.println("pressed");
if((e.getX()>= x)&&(e.getX()<=x+100)&&(e.getY()>=y)&&(e.getY()<=y+100)){
dx = e.getX()-x;
dy = e.getY()-y;
flag = 1;
System.out.println(e.getX()+"..."+e.getY());
}
}
if(e.getID()== MouseEvent.MOUSE_MOVED&&flag ==1){
x = e.getX()-dx;
y = e.getY()-dy;
System.out.println(e.getX()+"..."+e.getY());
repaint();
}
if((e.getID()==MouseEvent.MOUSE_RELEASED)&&(flag == 1)){
flag = 0;
}
}
public void paint(Graphics g){
g.drawImage(img, x, y,100,100, this);
}
}
```

View File

@@ -1,84 +0,0 @@
# 画图的标准步骤
------
package painting;
import java.awt.Frame;
import java.awt.Graphics;
/**
* 画图过程的整体框架.
* 继承了Frame类能够使用相关的框体数据。
* 实现了Rnnable接口本类能够作为一个线程被执行。
* @author 宙斯
*
*/
public class framePaint extends Frame implements Runnable{
public static void main(String [] args){
framePaint workStart = new framePaint();
}
/**
* 无参构造函数.
* 设置了窗体的基本属性。
* 创建了与本类相关的线程并且执行。
* 也就是说,这个类的对象在创建的时候,就已经执行了相关的画图代码,并且产生了一个看见的图形对象。
* 而且这个绘制的过程,会作为一个普通的线程被执行。
*/
public framePaint(){
super("framePaint");
setSize(350,350);
setVisible(true);
new Thread(this).start();
}
/**
* 实现多线程的方法.
* run方法时Runnable线程的主要方法当线程开始是执行这个方法所以画图方法在这个线程方法中被调用
* 这个方法相当于画图过程的总方法。
* @see java.lang.Runnable#run()
*/
public void run(){
repaint();
}
/**
* 实现画图的过程.
* 主要的画图方法在调用画图对象的repaint方法时这个方法被自动加载。
* @see java.awt.Window#paint(java.awt.Graphics)
*/
public void paint(Graphics g){
}
}
# Graphics类中的主要方法
-----
> 能绘制的图形:
> String 字符串
> Line 直线
> Rect 长方形
> Oval 椭圆形
> Arc 弧线
> Polygon 多边形
>
* draw系列
绘制线条图形
* fill系列
绘制平面图形,用前景色填充内容物
* clearRect()
清除指定区域内的图形
* clipRect()
截取制定区域内的图形
* copyArea()
赋值制定区域内的图形到指定区域
* get/setColor/Font
设置颜色字体等
* setClip()
截取制定形状内的图形

View File

@@ -1,5 +0,0 @@
# 底层事件处理
-----
* 在java.awt学习的时候将监听器绑定到事件源上当指定事件发生时监听器会自动匹配到相应的动作执行相应的处理方式。但在底层事件处理过程中不许要监听器能够直接获取当前发生的底层事件然后进行匹配处理。

View File

@@ -1,117 +0,0 @@
## 新的事件监听机制
-----
* 之前是定义一个组件对象(事件源),队组建对象绑定一个监听器对象,在监听器对象中有监听同一事件不同动作的函数,函数内部对事件进行处理。从监听器的角度对事件和动作进行处理。
* 现在是直接将静态事件绑定到事件源上通过process函数运行运行事件发生时的处理方式。而且这些静态事件是已经存在于awtEvent类中的。
```
package EventHandle;
import java.awt.*;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
/**
* @author 宙斯
* 准确的说这并不是先前的事件监听机制吧。它不存在指明的监听器。
* 应该更像一种组件自带的事件匹配功能。
* 具体步骤:
* 打开某一类型的事件匹配功能。
* 使用process函数运行某个事件
* 当事件发生时通过事件的ID匹配具体的动作。
* 匹配后设置相应的处理方式。
*/
public class baseEvent extends Frame{
private int x = 0,y = 0,flag = 0;
private Image img;
private int dx = 0,dy = 0;
public static void main(String[] args) {
baseEvent bE = new baseEvent();
}
public baseEvent(){
super();
setSize(500,500);
this.setVisible(true);
Toolkit tk = Toolkit.getDefaultToolkit();
img = tk.getImage("first.png");
//相当于添加了对Component事件的监听器似乎监听器类在底层已经直接绑定。
enableEvents(AWTEvent.COMPONENT_EVENT_MASK);
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
repaint();
}
//直接调用底层的事件处理函数
public void processComponentEvent(ComponentEvent e){
if(e.getID() == ComponentEvent.COMPONENT_MOVED){
System.out.println(e.getSource());
System.out.println(e.getID());
}
}
public void processWindowEvent(WindowEvent e){
if(e.getID() == WindowEvent.WINDOW_CLOSING){
System.exit(0);
}
}
/*
public void processMouseEvent(MouseEvent e){
if(e.getID() == MouseEvent.MOUSE_PRESSED){
System.out.println("pressed");
if((e.getX()>= x)&&(e.getX()<=x+100)&&(e.getY()>=y)&&(e.getY()<=y+100)){
flag = 1;
System.out.println(e.getX()+"..."+e.getY());
}
}
if((e.getID()==MouseEvent.MOUSE_RELEASED)&&(flag == 1)){
x = e.getX();
y = e.getY();
repaint();
flag = 0;
}
}
*/
public void processMouseEvent(MouseEvent e){
if(e.getID() == MouseEvent.MOUSE_PRESSED){
System.out.println("pressed");
if((e.getX()>= x)&&(e.getX()<=x+100)&&(e.getY()>=y)&&(e.getY()<=y+100)){
dx = e.getX()-x;
dy = e.getY()-y;
flag = 1;
System.out.println(e.getX()+"..."+e.getY());
}
}
if(e.getID()== MouseEvent.MOUSE_MOVED&&flag ==1){
x = e.getX()-dx;
y = e.getY()-dy;
System.out.println(e.getX()+"..."+e.getY());
repaint();
}
if((e.getID()==MouseEvent.MOUSE_RELEASED)&&(flag == 1)){
flag = 0;
}
}
public void paint(Graphics g){
g.drawImage(img, x, y,100,100, this);
}
}
```

View File

@@ -1,14 +0,0 @@
# 多线程共享受限资源
----
## &gt;多线程共享受限资源存在的问题
----
* **这个描述???**
当你坐在桌子旁边时,手里有有一把叉子,准备插起盘子里最后一块十五,当叉子碰到十五的时候,它忽然消失了。
* **解决方法**
给这个资源加锁,每个线程访问这个资源时上锁,访问结束后开锁。

View File

@@ -1,14 +0,0 @@
# 多线程共享受限资源
----
## &gt;多线程共享受限资源存在的问题
----
* **这个描述???**
当你坐在桌子旁边时,手里有有一把叉子,准备插起盘子里最后一块十五,当叉子碰到十五的时候,它忽然消失了。
* **解决方法**
给这个资源加锁,每个线程访问这个资源时上锁,访问结束后开锁。

View File

@@ -1,48 +0,0 @@
# 多线程理论补充
------
## &gt;让步机制
-----
* **yield**
thread.yield()线程执行这个函数会主动放弃执行的优先级但只是暗示别的线程能够抢到更多的资源没有确定的机制保证cpu资源一定被其他线程使用。
## &gt;休眠机制
---
* **sleep**
thread.sleep(30)线程停止执行30ms可能会跑出异常此线程调用interrupt方法中断了线程的执行。
## &gt;优先权机制
----
* **setPriority()**
thread.setPriority()通过thread类中的常量对优先级进行设定。thread.getPriority()能够获取当前线程的优先级。
## &gt;后台进程
-----
* **后台线程守护线程thread.setDaemon()**
程序运行时在后台提供的一种通用的线程当所有的非后台线程结束时后台线程守护线程自动终止。必须在线程启动之前调用方法setDaemon()将线程设置成后台线程。isDeamon判断是否为后台线程。
## &gt;加入线程
----
* **t.join(a)**
t线程此时将被挂起直到a线程结束才会被执行。当a线程结束时t.isAlive()返回为假,线程恢复。
## &gt;编码变体
-----
* **implements Runnable**
通过实现接口Runnable来达到作为线程类的方法。必须实现run方法。
* **建立有响应的用户界面**
让运算作为一个独立的线程在后台独立运行。

View File

@@ -1,40 +0,0 @@
# 多线程的应用
------
## 多线程停止线程
----
* stop方法已经过时不能使用只能当run方法结束时才能终止线程。开启多线程程运行时代码通常是循环结构只要控制住线程通常可以让run方法结束。
* 应当设计可以修改的无限循环标志。跳出无限循环,则会终止线程。
* 当线程在循环内进入等待状态时及时线程的循环条件不满足必须终止线程但是无法执行到判断语句进行线程的终止此时必须使用interrupt()函数来达到要求。
* interrupt将处于冻结状态的线程强制转换到运行状态。此时wait()就会跑出我们处理已久的中断异常。
* 当没有指定的方式让冻结的线程回复到运行状态时这是需要对冻结进行清除。Thread提供了interrupt方法。
## 多线程 守护线程
-----
* 守护线程就是后台线程,也是一种依赖线程
* 特点:
当前台线程结束后,后台线程会自动终止,作为依赖线程,守护线程,不用强调结束。
* 方法:
setDemon()设置某个线程为守护线程
## 多线程join方法
----
* t1.join() t1抢夺cpu执行权主线程处于冻结状态t1优先执行。相当于顺序执行两个线程。主线程碰到谁的join就会等谁执行。
* 当A线程执行到了B线程的Join方法时A线程就会等待等B线程都执行完成A才会执行。join才会临时加入线程执行。当B线程进入wait时A线程也能继续执行。
* toString 方法能够显示线程的名称,线程的优先级,线程当前的分组(线程组谁调用就是谁的线程组的)
* 所有线程包括主线程默认是5。数越大优先级越高。MAX_PRIORITY,MIN_PRIORITY,
* yield()方法,当线程读到这里是,会释放执行权。这样会使所有的线程都有平均执行的效果。
## 什么时候会使用到多线程
----
* 当程序独立运算相互之间不相关的时候,可以用多线程封装一下,提高程序执行的速度

View File

@@ -1,40 +0,0 @@
# 多线程的应用
------
## 多线程停止线程
----
* stop方法已经过时不能使用只能当run方法结束时才能终止线程。开启多线程程运行时代码通常是循环结构只要控制住线程通常可以让run方法结束。
* 应当设计可以修改的无限循环标志。跳出无限循环,则会终止线程。
* 当线程在循环内进入等待状态时及时线程的循环条件不满足必须终止线程但是无法执行到判断语句进行线程的终止此时必须使用interrupt()函数来达到要求。
* interrupt将处于冻结状态的线程强制转换到运行状态。此时wait()就会跑出我们处理已久的中断异常。
* 当没有指定的方式让冻结的线程回复到运行状态时这是需要对冻结进行清除。Thread提供了interrupt方法。
## 多线程 守护线程
-----
* 守护线程就是后台线程,也是一种依赖线程
* 特点:
当前台线程结束后,后台线程会自动终止,作为依赖线程,守护线程,不用强调结束。
* 方法:
setDemon()设置某个线程为守护线程
## 多线程join方法
----
* t1.join() t1抢夺cpu执行权主线程处于冻结状态t1优先执行。相当于顺序执行两个线程。主线程碰到谁的join就会等谁执行。
* 当A线程执行到了B线程的Join方法时A线程就会等待等B线程都执行完成A才会执行。join才会临时加入线程执行。当B线程进入wait时A线程也能继续执行。
* toString 方法能够显示线程的名称,线程的优先级,线程当前的分组(线程组谁调用就是谁的线程组的)
* 所有线程包括主线程默认是5。数越大优先级越高。MAX_PRIORITY,MIN_PRIORITY,
* yield()方法,当线程读到这里是,会释放执行权。这样会使所有的线程都有平均执行的效果。
## 什么时候会使用到多线程
----
* 当程序独立运算相互之间不相关的时候,可以用多线程封装一下,提高程序执行的速度

View File

@@ -1,166 +0,0 @@
# 多线程间的通讯
-----
## 多线程通讯的定义:
----
- 多个不同的线程对共同的数据进行不同的操作。
## 多线程通讯间的安全问题
-----
* 安全问题的原因
例如当多个线程对同一个数据进行不同操作时,导致各种操作的先后顺序出现混乱。
* 安全问题的解决方式
对这些线程操作数据的部分进行同步处理,使用相同的锁,将不同的部分锁起。
## 线程间通讯等待唤醒机制
-----
* 可以模仿锁的工作原理(设置标志位,记录当前是够线程占用锁内的程序,实现只能有一个线程执行锁内代码的现象)
* 步骤:
1. 设置标志位flag
2. 当标志位处于输入状态时,执行输入程序,执行完成后修改为输出状态。
3. 当标志位处于输出状态时,执行输出程序,执行完成后修改为输入状态。
## 等待唤醒机制的具体实现
----
* wait()和notify()函数必须在同步代码块或者同步函数当中使用。注意wait()会抛出中断异常。对持有监视器(锁)的线程进行操作。
* wait()和notify()的操作对象是同步中持有当前锁的线程。
* 线程的等待唤醒机制必须制定一把确定的锁。锁是任意的对象任意对象都能成为锁成为锁之后都能调用wait和notify的方法。而且这些方法都定义在Object类中。只有同一个锁上的被等待线程可以被同一个notify唤醒。等待唤醒必须是同一个锁。
## 使用新的工具类实现程序锁定和解锁
----
package painting;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 宙斯
*
*/
public class LockerUsing {
/**
* @param args
*/
public static void main(String[] args) {
Resource2 r = new Resource2();
Produce2 pro = new Produce2(r);
Consumer2 con = new Consumer2(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(pro);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Resource2{
private String name;
private int count =1;
private boolean flag = false;
private Lock lock = new ReentrantLock();//新建了一个锁对象
private Condition condition_con = lock.newCondition();//生成了一个与锁相关的控制对象
private Condition condition_pro = lock.newCondition();
public void set(String name) throws InterruptedException{
lock.lock();
try{
while(flag)
condition_pro.await();
this.name= name+"..."+count++;
System.out.println(Thread.currentThread().getName()+"生产者:"+this.name);
flag = true;
condition_con.signal();
}
finally{
lock.unlock();//异常处理当中释放锁的动过一定要被执行
}
}
public void out() throws InterruptedException{
lock.lock();
try{
while(!flag)
condition_con.await();
System.out.println(Thread.currentThread().getName()+"消费者:"+this.name);
flag = false;
condition_pro.signalAll();
}
finally{
lock.unlock();
}
}
}
class Produce2 implements Runnable{
private Resource2 res;
Produce2(Resource2 res){
this.res = res;
}
public void run()
{
while(true){
try {
res.set("商品");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Consumer2 implements Runnable{
private Resource2 res;
Consumer2(Resource2 res){
this.res = res;
}
public void run()
{
while(true){
try {
res.out();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
* 代码的解释:
1. 单线程 -----多线程-----多线程操作同一个数据源的时候实行同步机制------带有满足条件的同步机制(睡眠唤醒机制)
2. 实现了多线程不同操作相同的程序,这个类具有模板的价值.对资源的不同操作写到资源类中并使用this资源类的锁对各种不同的操作进行上锁
3. 而非写到其他操作类中,这样能够将同步和冲突解决都封装到资源类中便于理解和操作。
4. 当线程数多于两个,比如此程序中有两个在生产两个在消费,那么标准的方式应该是通过循环判断标志位是否合格,
因为当某个线程判断满足后,但在进入之前肯能被其他线程修改标志位。
而且必须使用notifyAll()唤醒所有的线程。
5. 使用Locker及其相关类的好处
一个Locker上可以有很多condition对象执行操作也就是只唤醒对方的condition
jdk1.5实现了多线程的升级解决方案
将同步设置Synchronize替换成Lock操作可见
将Object中waitnotifynotifyAll替换成Condition对象。该对象可以Locker锁进行获取

View File

@@ -1,166 +0,0 @@
# 多线程间的通讯
-----
## 多线程通讯的定义:
----
- 多个不同的线程对共同的数据进行不同的操作。
## 多线程通讯间的安全问题
-----
* 安全问题的原因
例如当多个线程对同一个数据进行不同操作时,导致各种操作的先后顺序出现混乱。
* 安全问题的解决方式
对这些线程操作数据的部分进行同步处理,使用相同的锁,将不同的部分锁起。
## 线程间通讯等待唤醒机制
-----
* 可以模仿锁的工作原理(设置标志位,记录当前是够线程占用锁内的程序,实现只能有一个线程执行锁内代码的现象)
* 步骤:
1. 设置标志位flag
2. 当标志位处于输入状态时,执行输入程序,执行完成后修改为输出状态。
3. 当标志位处于输出状态时,执行输出程序,执行完成后修改为输入状态。
## 等待唤醒机制的具体实现
----
* wait()和notify()函数必须在同步代码块或者同步函数当中使用。注意wait()会抛出中断异常。对持有监视器(锁)的线程进行操作。
* wait()和notify()的操作对象是同步中持有当前锁的线程。
* 线程的等待唤醒机制必须制定一把确定的锁。锁是任意的对象任意对象都能成为锁成为锁之后都能调用wait和notify的方法。而且这些方法都定义在Object类中。只有同一个锁上的被等待线程可以被同一个notify唤醒。等待唤醒必须是同一个锁。
## 使用新的工具类实现程序锁定和解锁
----
package painting;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author 宙斯
*
*/
public class LockerUsing {
/**
* @param args
*/
public static void main(String[] args) {
Resource2 r = new Resource2();
Produce2 pro = new Produce2(r);
Consumer2 con = new Consumer2(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(pro);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Resource2{
private String name;
private int count =1;
private boolean flag = false;
private Lock lock = new ReentrantLock();//新建了一个锁对象
private Condition condition_con = lock.newCondition();//生成了一个与锁相关的控制对象
private Condition condition_pro = lock.newCondition();
public void set(String name) throws InterruptedException{
lock.lock();
try{
while(flag)
condition_pro.await();
this.name= name+"..."+count++;
System.out.println(Thread.currentThread().getName()+"生产者:"+this.name);
flag = true;
condition_con.signal();
}
finally{
lock.unlock();//异常处理当中释放锁的动过一定要被执行
}
}
public void out() throws InterruptedException{
lock.lock();
try{
while(!flag)
condition_con.await();
System.out.println(Thread.currentThread().getName()+"消费者:"+this.name);
flag = false;
condition_pro.signalAll();
}
finally{
lock.unlock();
}
}
}
class Produce2 implements Runnable{
private Resource2 res;
Produce2(Resource2 res){
this.res = res;
}
public void run()
{
while(true){
try {
res.set("商品");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Consumer2 implements Runnable{
private Resource2 res;
Consumer2(Resource2 res){
this.res = res;
}
public void run()
{
while(true){
try {
res.out();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
* 代码的解释:
1. 单线程 -----多线程-----多线程操作同一个数据源的时候实行同步机制------带有满足条件的同步机制(睡眠唤醒机制)
2. 实现了多线程不同操作相同的程序,这个类具有模板的价值.对资源的不同操作写到资源类中并使用this资源类的锁对各种不同的操作进行上锁
3. 而非写到其他操作类中,这样能够将同步和冲突解决都封装到资源类中便于理解和操作。
4. 当线程数多于两个,比如此程序中有两个在生产两个在消费,那么标准的方式应该是通过循环判断标志位是否合格,
因为当某个线程判断满足后,但在进入之前肯能被其他线程修改标志位。
而且必须使用notifyAll()唤醒所有的线程。
5. 使用Locker及其相关类的好处
一个Locker上可以有很多condition对象执行操作也就是只唤醒对方的condition
jdk1.5实现了多线程的升级解决方案
将同步设置Synchronize替换成Lock操作可见
将Object中waitnotifynotifyAll替换成Condition对象。该对象可以Locker锁进行获取

View File

@@ -1,5 +0,0 @@
# 底层事件处理
-----
* 在java.awt学习的时候将监听器绑定到事件源上当指定事件发生时监听器会自动匹配到相应的动作执行相应的处理方式。但在底层事件处理过程中不许要监听器能够直接获取当前发生的底层事件然后进行匹配处理。

View File

@@ -1,48 +0,0 @@
# 多线程理论补充
------
## &gt;让步机制
-----
* **yield**
thread.yield()线程执行这个函数会主动放弃执行的优先级但只是暗示别的线程能够抢到更多的资源没有确定的机制保证cpu资源一定被其他线程使用。
## &gt;休眠机制
---
* **sleep**
thread.sleep(30)线程停止执行30ms可能会跑出异常此线程调用interrupt方法中断了线程的执行。
## &gt;优先权机制
----
* **setPriority()**
thread.setPriority()通过thread类中的常量对优先级进行设定。thread.getPriority()能够获取当前线程的优先级。
## &gt;后台进程
-----
* **后台线程守护线程thread.setDaemon()**
程序运行时在后台提供的一种通用的线程当所有的非后台线程结束时后台线程守护线程自动终止。必须在线程启动之前调用方法setDaemon()将线程设置成后台线程。isDeamon判断是否为后台线程。
## &gt;加入线程
----
* **t.join(a)**
t线程此时将被挂起直到a线程结束才会被执行。当a线程结束时t.isAlive()返回为假,线程恢复。
## &gt;编码变体
-----
* **implements Runnable**
通过实现接口Runnable来达到作为线程类的方法。必须实现run方法。
* **建立有响应的用户界面**
让运算作为一个独立的线程在后台独立运行。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 225 KiB

View File

@@ -1,321 +0,0 @@
# java并发控制
> 参考文献
> * [并发编程](https://www.cnblogs.com/flashsun/p/10776168.html)
> * [java高并发编程](https://blog.csdn.net/cx105200/article/details/80220937)
synchronized 关键字
可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。可能锁对象包括: this 临界资源对象Class 类对象
同步方法
同步方法锁定的是当前对象。当多线程通过同一个对象引用多次调用当前同步方法时, 需同步执行。
public synchronized void test(){
System.out.println("测试一下");
}
1
2
3
同步代码块
同步代码块的同步粒度更加细致,是商业开发中推荐的编程方式。可以定位到具体的同步位置,而不是简单的将方法整体实现同步逻辑。在效率上,相对更高。
锁定临界对象
同步代码块在执行时,是锁定 object 对象。当多个线程调用同一个方法时,锁定对象不变的情况下,需同步执行。
public void test(){
synchronized(o){
System.out.println("测试一下");
}
}
1
2
3
4
5
锁定当前对象
public void test(){
synchronized(this){
System.out.println("测试一下");
}
}
1
2
3
4
5
锁的底层实现
Java 虚拟机中的同步(Synchronization)基于进入和退出管程(Monitor)对象实现。同步方法 并不是由 monitor enter 和 monitor exit 指令来实现同步的,而是由方法调用指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来隐式实现的。
对象内存简图
对象头:存储对象的 hashCode、锁信息或分代年龄或 GC 标志类型指针指向对象的类元数据JVM 通过这个指针确定该对象是哪个类的实例等信息。
实例变量:存放类的属性数据信息,包括父类的属性信息
填充数据:由于虚拟机要求对象起始地址必须是 8 字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐
当在对象上加锁时,数据是记录在对象头中。当执行 synchronized 同步方法或同步代码块时,会在对象头中记录锁标记,锁标记指向的是 monitor 对象(也称为管程或监视器锁) 的起始地址。每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如 monitor 可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。
在 Java 虚拟机(HotSpot)中monitor 是由 ObjectMonitor 实现的。
ObjectMonitor 中有两个队列_WaitSet 和 _EntryList以及_Owner 标记。其中_WaitSet 是用于管理等待队列(wait)线程的_EntryList 是用于管理锁池阻塞线程的_Owner 标记用于记录当前执行线程。线程状态图如下:
这里写图片描述
当多线程并发访问同一个同步代码时首先会进入_EntryList当线程获取锁标记后
monitor 中的_Owner 记录此线程,并在 monitor 中的计数器执行递增计算(+1代表锁定其他线程在_EntryList 中继续阻塞。若执行线程调用 wait 方法,则 monitor 中的计数器执行赋值为 0 计算并将_Owner 标记赋值为 null代表放弃锁执行线程进如_WaitSet 中阻塞。若执行线程调用 notify/notifyAll 方法_WaitSet 中的线程被唤醒进入_EntryList 中阻塞等待获取锁标记。若执行线程的同步代码执行结束同样会释放锁标记monitor 中的_Owner 标记赋值为 null且计数器赋值为 0 计算。
锁的种类
Java 中锁的种类大致分为偏向锁,自旋锁,轻量级锁,重量级锁。
锁的使用方式为:先提供偏向锁,如果不满足的时候,升级为轻量级锁,再不满足,升级为重量级锁。自旋锁是一个过渡的锁状态,不是一种实际的锁类型。
锁只能升级,不能降级。
重量级锁
在锁的底层实现中解释的就是重量级锁。
偏向锁
是一种编译解释锁。如果代码中不可能出现多线程并发争抢同一个锁的时候JVM 编译代码,解释执行的时候,会自动的放弃同步信息。消除 synchronized 的同步代码结果。使用锁标记的形式记录锁状态。在 Monitor 中有变量 ACC_SYNCHRONIZED。当变量值使用的时候 代表偏向锁锁定。可以避免锁的争抢和锁池状态的维护。提高效率。
轻量级锁
过渡锁。当偏向锁不满足,也就是有多线程并发访问,锁定同一个对象的时候,先提升为轻量级锁。也是使用标记 ACC_SYNCHRONIZED 标记记录的。ACC_UNSYNCHRONIZED 标记记录未获取到锁信息的线程。就是只有两个线程争抢锁标记的时候,优先使用轻量级锁。
两个线程也可能出现重量级锁。
自旋锁
是一个过渡锁,是偏向锁和轻量级锁的过渡。
当获取锁的过程中未获取到。为了提高效率JVM 自动执行若干次空循环,再次申请锁,而不是进入阻塞状态的情况。称为自旋锁。自旋锁提高效率就是避免线程状态的变更。
volatile 关键字
变量的线程可见性。在 CPU 计算过程中,会将计算过程需要的数据加载到 CPU 计算缓存中,当 CPU 计算中断时,有可能刷新缓存,重新读取内存中的数据。在线程运行的过程中,如果某变量被其他线程修改,可能造成数据不一致的情况,从而导致结果错误。而 volatile 修饰的变量是线程可见的,当 JVM 解释 volatile 修饰的变量时,会通知 CPU在计算过程中 每次使用变量参与计算时,都会检查内存中的数据是否发生变化,而不是一直使用 CPU 缓存中的数据,可以保证计算结果的正确。
volatile 只是通知底层计算时CPU 检查内存数据,而不是让一个变量在多个线程中同步。
volatile int count = 0;
1
wait&notify
AtomicXxx 类型组
原子类型。
在 concurrent.atomic 包中定义了若干原子类型,这些类型中的每个方法都是保证了原子操作的。多线程并发访问原子类型对象中的方法,不会出现数据错误。在多线程开发中,如果某数据需要多个线程同时操作,且要求计算原子性,可以考虑使用原子类型对象。
AtomicInteger count = new AtomicInteger(0);
void m(){
count.incrementAndGet();
}
1
2
3
4
注意:原子类型中的方法是保证了原子操作,但多个方法之间是没有原子性的。如:
AtomicInteger i = new AtomicInteger(0);
if(i.get() != 5){
i.incrementAndGet();
}
1
2
3
4
在上述代码中get 方法和 incrementAndGet 方法都是原子操作,但复合使用时,无法保证原子性,仍旧可能出现数据错误。
CountDownLatch 门闩
门闩是 concurrent 包中定义的一个类型,是用于多线程通讯的一个辅助类型。
门闩相当于在一个门上加多个锁,当线程调用 await 方法时,会检查门闩数量,如果门
闩数量大于 0线程会阻塞等待。当线程调用 countDown 时,会递减门闩的数量,当门闩数量为 0 时await 阻塞线程可执行。
CountDownLatch latch = new CountDownLatch(5);
void m1(){
try {
latch.await();// 等待门闩开放。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m1() method");
}
void m2(){
for(int i = 0; i < 10; i++){
if(latch.getCount() != 0){
System.out.println("latch count : " + latch.getCount());
latch.countDown(); // 减门闩上的锁。
}
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("m2() method : " + i);
}
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
锁的重入
在 Java 中,同步锁是可以重入的。只有同一线程调用同步方法或执行同步代码块,对同一个对象加锁时才可重入。
当线程持有锁时,会在 monitor 的计数器中执行递增计算若当前线程调用其他同步代码且同步代码的锁对象相同时monitor 中的计数器继续递增。每个同步代码执行结束,
monitor 中的计数器都会递减直至所有同步代码执行结束monitor 中的计数器为 0 时释放锁标记_Owner 标记赋值为 null。
ReentrantLock
重入锁,建议应用的同步方式。相对效率比 synchronized 高。量级较轻。
synchronized 在 JDK1.5 版本开始,尝试优化。到 JDK1.7 版本后,优化效率已经非常好了。在绝对效率上,不比 reentrantLock 差多少。
使用重入锁,必须必须必须手工释放锁标记。一般都是在 finally 代码块中定义释放锁标记的 unlock 方法。
公平锁
这里写图片描述
private static ReentrantLock lock = new ReentrantLock(true);
public void run(){
for(int i = 0; i < 5; i++){
lock.lock();
try{
System.out.println(Thread.currentThread().getName() + " get lock");
}finally{
lock.unlock();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
8ThreadLocal
remove 问题
这里写图片描述
同步容器
解决并发情况下的容器线程安全问题的。给多线程环境准备一个线程安全的容器对象。线程安全的容器对象: Vector, Hashtable。线程安全容器对象都是使用 synchronized
方法实现的。
concurrent 包中的同步容器,大多数是使用系统底层技术实现的线程安全。类似 native。
Java8 中使用 CAS。
Map/Set
ConcurrentHashMap/ConcurrentHashSet
底层哈希实现的同步 Map(Set)。效率高,线程安全。使用系统底层技术实现线程安全。量级较 synchronized 低。key 和 value 不能为 null。
ConcurrentSkipListMap/ConcurrentSkipListSet
底层跳表SkipList实现的同步 Map(Set)。有序,效率比 ConcurrentHashMap 稍低。
这里写图片描述
List
CopyOnWriteArrayList
写时复制集合。写入效率低,读取效率高。每次写入数据,都会创建一个新的底层数组。
Queue
ConcurrentLinkedQueue
基础链表同步队列。
LinkedBlockingQueue
阻塞队列,队列容量不足自动阻塞,队列容量为 0 自动阻塞。
ArrayBlockingQueue
底层数组实现的有界队列。自动阻塞。根据调用 APIadd/put/offer不同有不同特性。
当容量不足的时候,有阻塞能力。
add 方法在容量不足的时候,抛出异常。
put 方法在容量不足的时候,阻塞等待。
offer 方法,
单参数 offer 方法,不阻塞。容量不足的时候,返回 false。当前新增数据操作放弃。三参数 offer 方法offer(value,times,timeunit)),容量不足的时候,阻塞 times 时长(单
位为 timeunit如果在阻塞时长内有容量空闲新增数据返回 true。如果阻塞时长范围
内,无容量空闲,放弃新增数据,返回 false。
DelayQueue
延时队列。根据比较机制,实现自定义处理顺序的队列。常用于定时任务。如:定时关机。
LinkedTransferQueue
转移队列,使用 transfer 方法,实现数据的即时处理。没有消费者,就阻塞。
SynchronusQueue
同步队列,是一个容量为 0 的队列。是一个特殊的 TransferQueue。必须现有消费线程等待才能使用的队列。
add 方法,无阻塞。若没有消费线程阻塞等待数据,则抛出异常。
put 方法,有阻塞。若没有消费线程阻塞等待数据,则阻塞。
ThreadPool&Executor
Executor
线程池顶级接口。
常用方法 - void execute(Runnable)
作用是: 启动线程任务的。
ExecutorService
Executor 接口的子接口。
常见方法 - Future submit(Callable) Future submit(Runnable)
Future
未来结果,代表线程任务执行结束后的结果。
Callable
可执行接口。
接口方法 Object call();相当于 Runnable 接口中的 run 方法。区别为此方法有返回值。不能抛出已检查异常。
和 Runnable 接口的选择 - 需要返回值或需要抛出异常时,使用 Callable其他情况可任意选择。
Executors
工具类型。为 Executor 线程池提供工具方法。类似 ArraysCollections 等工具类型的功用。
FixedThreadPool
容量固定的线程池
queued tasks - 任务队列
completed tasks - 结束任务队列
CachedThreadPool
缓存的线程池。容量不限Integer.MAX_VALUE。自动扩容。默认线程空闲 60 秒,自动销毁。
ScheduledThreadPool
计划任务线程池。可以根据计划自动执行任务的线程池。
SingleThreadExceutor
单一容量的线程池。
ForkJoinPool
分支合并线程池mapduce 类似的设计思想)。适合用于处理复杂任务。初始化线程容量与 CPU 核心数相关。
线程池中运行的内容必须是 ForkJoinTask 的子类型RecursiveTask,RecursiveAction
WorkStealingPool
JDK1.8 新增的线程池。工作窃取线程池。当线程池中有空闲连接时,自动到等待队列中窃取未完成任务,自动执行。
初始化线程容量与 CPU 核心数相关。此线程池中维护的是精灵线程。
ExecutorService.newWorkStealingPool();
ThreadPoolExecutor
线程池底层实现。除 ForkJoinPool 外,其他常用线程池底层都是使用 ThreadPoolExecutor
实现的。
public ThreadPoolExecutor
(int corePoolSize, // 核心容量
int maximumPoolSize, // 最大容量
long keepAliveTime, // 生命周期0 为永久
TimeUnit unit, // 生命周期单位
BlockingQueue workQueue // 任务队列,阻塞队列。
);

View File

@@ -1,125 +0,0 @@
# java并发机制与底层实现原理
## 1 volatile
volatile是轻量级的synchronize,它在多处理器开发中保证了共享变量的“可见性”因为它不会引起线程上下文的切换和调度所以比synchronize的使用和执行成本更底。
为了提高处理速度,处理器不直接和内存进行通信,而是先将系统内存的数据读到内部缓存(L1,L2或其他)后再进行操作,但操作完不知道何时会写到内存。使用volatile变量在操作后JVM会发出lock指令
将当前处理器缓存行的数据写回到系统内存
这个写回内存的操作会使在其他cpu里缓存了该内存地址的数据无效
## 2 synchronize
### 同步基础
synchronize实现同步的基础具体表现为三种形式
* 对于普通同步方法,锁是当前实例对象
* 对于静态同步方法锁是当前类的class对象
* 对于同步方法块锁是Synchronize括号里配置的对象
* 当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。那么锁到底存在那里,锁里会存储什么信息。
### java对象头
synchonize用的锁是存在java对象头里的。如果对象是数组类型则JVM用三个字宽存储对象头如果对象为非数组类型则用二个字宽存储对象头。32位中一字宽等于四字节(32bit)
<table>
<tbody><tr>
<td>长度</td>
<td>内容</td>
<td>说明</td>
</tr>
<tr>
<td>32/64bit</td>
<td>Mark Word</td>
<td>存储对象的hashCode或锁信息等。</td>
</tr>
<tr>
<td>32/64bit</td>
<td>Class Metadata Address</td>
<td>存储到对象类型数据的指针</td>
</tr>
<tr>
<td>32/64bit</td>
<td>Array length</td>
<td>数组的长度(如果当前对象是数组)</td>
</tr>
</tbody></table>
在运行期间Mark Word里存存储的数据会随着锁标志位的变化而变化。会成为下面的一种
![](image/2021-09-06-21-50-41.png)
## 3 锁类型
为了减少获得锁与释放锁所带来的性能消耗,引入“偏向锁”和“轻量级锁'.所以在java中存在四种状态
* 无锁状态
* 偏向锁状态
* 轻量级锁状态
* 自旋锁
* 重量级锁状态
它会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁
### 偏向锁
Hotspot的作者经过以往的研究发现大多数情况下锁不仅不存在多线程竞争而且总是由同一线程多次获得为了让线程获得锁的代价更低而引入了偏向锁。当一个线程访问同步块并获取锁时会在对象头和栈帧中的锁记录里存储锁偏向的线程ID以后该线程在进入和退出同步块时不需要花费CAS操作来加锁和解锁。
流程图中展示偏向锁的获取释放以及升级至轻量锁
![](image/2021-09-06-21-51-16.png)
### 轻量级锁
1. 轻量级锁加锁:
线程在执行同步块之前JVM会先在当前线程的栈桢中创建用于存储锁记录的空间并将对象头中的Mark Word复制到锁记录中官方称为Displaced Mark Word。然后线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针。如果成功当前线程获得锁如果失败表示其他线程竞争锁当前线程便尝试使用自旋来获取锁。
2. 轻量级锁解锁
轻量级解锁时会使用原子的CAS操作来将Displaced Mark Word替换回到对象头如果成功则表示没有竞争发生。如果失败表示当前锁存在竞争锁就会膨胀成重量级锁。下图是两个线程同时争夺锁导致锁膨胀的流程图。
借用网上流程图如下:
![](image/2021-09-06-21-51-37.png)
### 自旋锁
当竟争存在时如果线程可以很快获得锁那么可以不在OS层挂起线程线程切换平均消耗8K个时钟周期让线程多做几个空操作自旋
1. 如果同步块过长,自旋失败,会降低系统性能
2. 如果同步块很短,自旋成功,节省线程挂起切换时间,担升系统性能
## 4 锁对比
<table>
<tbody><tr>
<td>锁</td>
<td align="center">优点</td>
<td align="center">缺点</td>
<td align="center">适用场景</td>
</tr>
<tr>
<td>偏向锁</td>
<td>加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距。</td>
<td>如果线程间存在锁竞争,会带来额外的锁撤销的消耗。</td>
<td>适用于只有一个线程访问同步块场景。&lt;/td</td>
</tr>
<tr>
<td>轻量级锁</td>
<td>竞争的线程不会阻塞,提高了程序的响应速度。</td>
<td>如果始终得不到锁竞争的线程使用自旋会消耗CPU</td>
<td>追求响应时间。同步块执行速度非常快。</td>
</tr>
<tr>
<td>重量级锁</td>
<td>线程竞争不使用自旋不会消耗CPU。</td>
<td>线程阻塞,响应时间缓慢。</td>
<td>追求吞吐量。同步块执行速度较长。</td>
</tr>
</tbody></table>
总结
* 偏向锁轻量级锁自旋锁不是JAVA语言层上的优化方法
* 内置于JVM中的获取锁的优化方法与获取锁的步骤
* 偏向锁可用可先尝试偏向锁
* 轻量级锁可用可先尝试轻量级锁
* 1与2都失败则尝试自旋锁
* 再失败尝试普通锁使用OS互斥量在操作系统层挂起

View File

@@ -1,284 +0,0 @@
# java并发机制
## java多线程
### 继承Thread类
Thread类本质上是实现了Runnable接口的一个实例代表一个线程的实例。启动方法就是通过继承了Thread类的start()实例方法。执行run()方法(重写的)。就可以启动新线程并执行自己定义。例如:
```java
//实现方法的类
public class Demo1 extends Thread {
public void run(){
System.out.println("继承Thread类");
}
}
//执行的方法
public static void main(String[] args) {
Demo1 demo1=new Demo1();
demo1.start();
}
```
### 实现Runnable接口
由于java是单继承的那么在平时开发中就提倡使用接口的方式实现。则需要实现多线程的类通过实现Runnable接口的run方法。通过Thread的start()方法进行启动,例如:
```java
//实现的方法类:
public class Demo2 implements Runnable {
@Override
public void run() {
System.out.println("实现runnable接口");
}
//执行方法:
public static void main(String[] args) {
Demo2 demo2=new Demo2();
Thread thread=new Thread(demo2);
thread.start();
}
```
### 通过内部类的方式实现多线程
直接可以通过Thread类的start()方法进行实现因为Thread类实现了Runnable接口并重写了run方法在run方法中实现自己的逻辑例如
```java
//这里通过了CountDownLatch来进行阻塞来观察两个线程的启动这样更加体现的明显一些
public static CountDownLatch countDownLatch=new CountDownLatch(2);
public static void main(String[] args) {
new Thread(()->{
countDownLatch.countDown();
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T1");
}).start();
new Thread(()->{
countDownLatch.countDown();
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("T2");
}).start();
}
```
### 通过实现Callable接口
通过实现Callable接口的call方法可以通过FutureTask的get()方法来获取call方法中的返回值具体实现如下
```java
//实现类方法:
public class Demo3 implements Callable {
@Override
public Object call() {
return "1";
}
}
//执行方法:
public static void main(String[] args) {
//创建实现类对象
Callable demo3=new Demo3();
FutureTask oneTask = new FutureTask(demo3);
Thread thread=new Thread(oneTask);
thread.start();
Object o = null;
try {
//获取返回值
o = oneTask.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println(o);
}
```
### 通过线程池来实现多线程
线程池可以根据不同的场景来选择不同的线程池来进行实现,这里我仅使用其中之一进行演示,后续会单独写一个线程池相关的单独介绍:
```java
//实现代码如下:
public class Demo5 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for(int i=0;i<5;i++){
int finalI = i;
executorService.execute(()-> {
System.out.println(finalI);
});
}
}
}
```
### 通过Timer定时器来实现多线程
就Timer来讲就是一个调度器,而TimerTask呢只是一个实现了run方法的一个类,而具体的TimerTask需要由你自己来实现,同样根据参数得不同存在多种执行方式,例如其中延迟定时任务这样:
```
//具体代码如下:
public class Demo6 {
public static void main(String[] args) {
Timer timer=new Timer();
timer.schedule(new TimerTask(){
@Override
public void run() {
System.out.println(1);
}
},2000l,1000l);
}
}
```
### 通过stream实现多线程
jdk1.8 API添加了一个新的抽象称为流Stream可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
具体简单代码实现如下:
```
//代码实现:
public class Demo7 {
//为了更形象体现并发通过countDownLatch进行阻塞
static CountDownLatch countDownLatch=new CountDownLatch(6);
public static void main(String[] args) {
List list=new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
list.add(6);
list.parallelStream().forEach(p->{
//将所有请求在打印之前进行阻塞,方便观察
countDownLatch.countDown();
try {
System.out.println("线程执行到这里啦");
Thread.sleep(10000);
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(p);
});
}
}
```
## java异步IO
### java NIO
> 参考文献
> * [java nio一篇博客](https://blog.csdn.net/forezp/article/details/88414741)
> * [java nio并发编程网](http://ifeve.com/overview/)
### java akka
1. 前情提要
面向对象编程理论中对象之间通信依赖的是消息但java里对象之间通信用的是对象方法
2. Actor模型
计算模型计算单位Actor所有的计算都在Actor中执行。Actor中一切都是actor,actor之间完全隔离,不共享任何变量。不共享变量,就不会有并发问题。java本身不支持actor模型,需要引入第三方类库Akka
3. 代码范例
```
//该Actor当收到消息message后
//会打印Hello message
static class HelloActor
extends UntypedActor {
@Override
public void onReceive(Object message) {
System.out.println("Hello " + message);
}
}
public static void main(String[] args) {
//创建Actor系统
ActorSystem system = ActorSystem.create("HelloSystem");
//创建HelloActor
ActorRef helloActor =
system.actorOf(Props.create(HelloActor.class));
//发送消息给HelloActor
helloActor.tell("Actor", ActorRef.noSender());
}
```
actor之间通信完美遵循了消息机制。而不是通过调用对象的方式
4. 消息和对象方法的区别
actor内部有一个邮箱mailbox接受到的消息先放到邮箱如果有积压新消息不会马上得到处理。actor是单线程处理消息。所以不会有并发问题
说白了,就是消费者线程的生产者-消费者模式
5. 区别
对相关的方法调用一般是同步的而actor的消息机制是异步的。
6. Actor规范定义
1. 处理能力,处理接收到的消息
2. 存储能力actor可以存储自己的内部状态
3. 通信能力actor可以和其他actor之间通信
7. actor实现线程安全的累加器
无锁算法,因为只有1个线程在消费不会存在并发问题
```
//累加器
static class CounterActor extends UntypedActor {
private int counter = 0;
@Override
public void onReceive(Object message){
//如果接收到的消息是数字类型,执行累加操作,
//否则打印counter的值
if (message instanceof Number) {
counter += ((Number) message).intValue();
} else {
System.out.println(counter);
}
}
}
public static void main(String[] args) throws InterruptedException {
//创建Actor系统
ActorSystem system = ActorSystem.create("HelloSystem");
//4个线程生产消息
ExecutorService es = Executors.newFixedThreadPool(4);
//创建CounterActor
ActorRef counterActor =
system.actorOf(Props.create(CounterActor.class));
//生产4*100000个消息
for (int i=0; i<4; i++) {
es.execute(()->{
for (int j=0; j<100000; j++) {
counterActor.tell(1, ActorRef.noSender());
}
});
}
//关闭线程池
es.shutdown();
//等待CounterActor处理完所有消息
Thread.sleep(1000);
//打印结果
counterActor.tell("", ActorRef.noSender());
//关闭Actor系统
system.shutdown();
}
```
8. 总结
actor计算模型基本计算单元。消息通信。
9. 应用
sparkfilinkplay

View File

@@ -1,159 +0,0 @@
RMI的定义
RPC (Remote Procedure
Call):远程方法调用,用于一个进程调用另一个进程中的过程,从而提供了过程的分布能力。
RMIRemote Method
Invocation):远程方法调用即在RPC的基础上有向前迈进了一步提供分布式对象间的通讯。允许运行在一个java
虚拟机的对象调用运行在另一个java虚拟机上对象的方法。这两个虚拟机可以是运行在相同计算机上的不同进程中也可以是运行在网络上的不同计算机中。
RMI的作用
RMI的全称宗旨就是尽量简化远程接口对象的调用。
RMI大大增强了java开发分布式应用的能力例如可以将计算方法复杂的程序放在其他的服务器上主服务器只需要去调用而真正的运算是在其他服务器上进行最后将运算结果返回给主服务器这样就减轻了主服务器的负担提高了效率但是也有其他的开销
RMI网络模型
在设计初始阶段,我们真正想要的是这样一种机制,客户端程序员以常规方式进行方法调用,而无需操心将数据发送到网络上或者解析响应之类的问题。所以才有了如下的网络模型:在客户端为远程对象安装一个代理。代理是位于客户端虚拟机中的一个对象,它对于客户端程序来说,就像是要访问的远程对象一样。客户端调用此代理时,只需进行常规的方法调用。而客户端代理则负责使用网络协议与服务器进行联系。
![8-343985609.jpeg](media/b24ac0390788e8547c3f60daee9eaacf.jpeg)
现在的问题在于代理之间是如何进行通信的?通常有三种方法:
1、CORBA通过对象请求代理架构支持任何编程语言编写的对象之间的方法调用。
2、SOAP
3、RMIJAVA的远程方法调用技术支持java的分布式对象之间的方法调用。
其中CORBA与SOAP都是完全独立于言语的可以使用C、C++、JAVA来编写而RMI只适用于JAVA。
RMI的工作原理
**术语介绍**
> 1、存根当客户端要调用远程对象的一个方法时实际上调用的是代理对象上的一个普通方法**我们称此代理对象为存根stub。**存根位于客户端机器上,而非服务器上。
> 2、参数编组存根会将**远程方法所需的参数打包成一组字节**对参数编码的过程就称为参数编组。参数编组的目的是将参数转换成适合在虚拟机之间进行传递的格式在RMI协议中对象是使用序列化机制进行编码的。
编程模型
为了介绍RMI的编程模型我下面会编写一个DEMO。远程对象表示的是一个仓库而客户端程序向仓库询问某个产品的价格。
**接口定义**
远程对象的能力是由在客户端和服务器之间共享的接口所表示的:
![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif)
复制代码
package rmi; import java.rmi.Remote; import java.rmi.RemoteException; public
interface Warehouse extends Remote { double getPrice(String description) throws
RemoteException; }
远程对象的接口必须扩展Remote接口它位于java.rmi包中。接口中所有的方法必须声明抛出RemoteException异常。这是因为远程方法总是存在失败的可能所以java编程语言要求每一次远程方法的调用都必须捕获RemoteException,并且指明当调用不成功时应执行的相应处理操作。
**接口的实现**
![copycode.gif](media/51e409b11aa51c150090697429a953ed.gif)
复制代码
package rmi; import java.rmi.RemoteException; import
java.rmi.server.UnicastRemoteObject; import java.util.HashMap; import
java.util.Map; public class WarehouseImpl extends UnicastRemoteObject implements
Warehouse { private static final long serialVersionUID = 1L; private
Map\<String,Double\> prices; protected WarehouseImpl() throws RemoteException {
prices = new HashMap\<String,Double\>(); prices.put("mate7",3700.00); } public
double getPrice(String description) throws RemoteException { Double price =
prices.get(description); return price == null? 0 : price; }}
你可以看出这个类是远程方法调用的目标因为它扩展自UnicastRemoteObject这个类的构造器使得它的对象可供远程访问。
**RMI注册表:通过JNDI发布RMI服务**
1、要访问服务器上的一个远程对象时客户端必须先得到一个本地的存根对象也就是客户端机器上的代理对象。那么问题来了如何才能得到这个存根呢
2、为此JDK提供了自举注册服务bootstrap registry
service服务器程序应该使用自举注册服务来注册至少一个远程对象。
3、而要注册一个远程对象需要一个RMI URL和一个对实现对象的引用。
4、RMI
的URL以rmi:开头后接域名或IP地址host紧接着是端口号port最后是服务名service
rmi://regserver.mycompany.cmo:99/central_warehouse
如果我们是在本地发布RMI服务那么host就是“localhost”此外RMI默认的端口号是“1099”当然我们也可以自行设置只要不与其他端口重复即可。service实际上是基于同一个host与port下唯一的服务名。
发布RMI服务
1 package rmi; 2 3 import java.net.MalformedURLException; 4 import
java.rmi.AlreadyBoundException; 5 import java.rmi.Naming; 6 import
java.rmi.RemoteException; 7 import java.rmi.registry.LocateRegistry; 8 9 import
javax.naming.NamingException; 10 11 12 public class WarehouseServer 13 { 14
public static void main(String[] args) throws RemoteException, NamingException,
MalformedURLException, AlreadyBoundException 15 { 16
System.out.println("Constructing server implementation"); 17 WarehouseImpl
centralWarehouse = new WarehouseImpl(); 18 19 System.out.println("Binding server
implementation to registry"); 20 LocateRegistry.createRegistry(1099); 21
Naming.bind("rmi://localhost:1099/central_warehoues",centralWarehouse); 22 23
System.out.println("Waiting for invocations from clients ..."); 24 } 25 }
运行结果:
Constructing server implementation Binding server implementation to registry
Waiting for invocations from clients ...
1、第20行只需提供一个port就在JNDI中创建了一个注册表。
2、第21行通过bind方法绑定了RMI地址与RMI服务实现类。
3、执行这个方法后相当于自动发布了RMI服务。接下来要做的事情就是写一个RM客户端调用已发布的RMI服务。
**客户端调用RMI服务**
1 package rmi; 2 3 import java.net.MalformedURLException; 4 import
java.rmi.Naming; 5 import java.rmi.NotBoundException; 6 import
java.rmi.RemoteException; 7 import javax.naming.NamingException; 8 9 public
class WarehouseClient 10 { 11 public static void main(String[] args) throws
NamingException, RemoteException, MalformedURLException, NotBoundException 12 {
13 System.out.println("RMI registry binding:"); 14 String url =
"rmi://localhost:1099/central_warehoues"; 15 Warehouse centralWarehouse =
(Warehouse) Naming.lookup(url); 16 String descr = "mate7"; 17 double price =
centralWarehouse.getPrice(descr); 18 System.out.println(descr + ":" + price); 19
} 20 }
运行结果:
RMI registry binding: mate7:3700.0
补充说明:
> 服务调用只需要知道两个东西1、RMI请求路径2、RMI接口名
> 第15行这里用的是接口名Warehouse,而不是实现类。一定不能RMI接口的实现类否则就是本地调用了。
> 查看运行结果我们知道这次DEMO展示的远程调用成功了。
**下面我们来看下RMI的网络示意图**
![3-765464905.jpeg](media/f2f880588756d88cbf5083a94fbd41ae.jpeg)
1、借助JNDI这个所谓的命名与目录服务我们成功地发布并调用了RMI服务。实际上JNDI就是一个注册表服务端将服务对象放入到注册表中客户端从注册表中获取服务对象。
2、在服务端我们发布了RMI服务并在JNDI中进行了注册此时就在服务端创建了一个Skeleton骨架当客户端第一次成功连接JNDI并获取远程服务对象后立马在本地创建了一个Stub存根
3、远程通信实际是通过Skeleton与Stub来完成的数据是基于TCP/IP协议在“传输层”上发送的。
4、毋庸置疑理论上RMI一定比WebService要快毕竟WebService是基于http协议的而http所携带的数据是通过“应用层”来传输的。传输层较应用层更为底层越底层越快。
RMI的局限性
1、只能实现JAVA系统之间的调用而WebService可以实现跨语言实现系统之间的调用。
2、RMI使用了JAVA默认的序列化方式对于性能要求比较高的系统可能需要其他的序列化方案来解决。
3、RMI服务在运行时难免会存在故障例如如果RMI服务无法连接了就会导致客户端无法响应的现象。

View File

@@ -1,140 +0,0 @@
String与StringBuffer的区别
简单地说就是一个变量和常量的关系。StringBuffer对象的内容可以修改而String对象一旦产生后就不可以被修改重新赋值其实是两个对象。
StringBuffer的内部实现方式和String不同StringBuffer在进行字符串处理时不生成新的对象在内存使用上要优于String类。所以在实际使用时如果经常需要对一个字符串进行修改例如插入、删除等操作使用StringBuffer要更加适合一些。
String:在String类中没有用来改变已有字符串中的某个字符的方法由于不能改变一个java字符串中的某个单独字符所以在JDK文档中称String类的对象是不可改变的。然而不可改变的字符串具有一个很大的优点:编译器可以把字符串设为共享的。
StringBuffer:StringBuffer类属于一种辅助类可预先分配指定长度的内存块建立一个字符串缓冲区。这样使用StringBuffer类的append方法追加字符
比 String使用 + 操作符添加字符 到 一个已经存在的字符串后面有效率得多。因为使用 +
操作符每一次将字符添加到一个字符串中去时字符串对象都需要寻找一个新的内存空间来容纳更大的字符串这无凝是一个非常消耗时间的操作。添加多个字符也就意味着要一次又一次的对字符串重新分配内存。使用StringBuffer类就避免了这个问题。
StringBuffer是线程安全的在多线程程序中也可以很方便的进行使用但是程序的执行效率相对来说就要稍微慢一些。
StringBuffer的常用方法
StringBuffer类中的方法要偏重于对字符串的变化例如追加、插入和删除等这个也是StringBuffer和String类的主要区别。
1、append方法
public StringBuffer append(boolean b)
该方法的作用是追加内容到当前StringBuffer对象的末尾类似于字符串的连接。调用该方法以后StringBuffer对象的内容也发生改变例如
StringBuffer sb = new StringBuffer(“abc”);
sb.append(true);
则对象sb的值将变成”abctrue”。
使用该方法进行字符串的连接将比String更加节约内容例如应用于数据库SQL语句的连接例如
StringBuffer sb = new StringBuffer();
String user = “test”;
String pwd = “123”;
sb.append(“select \* from userInfo where username=“)
.append(user)
.append(“ and pwd=”)
.append(pwd);
这样对象sb的值就是字符串“select \* from userInfo where username=test and
pwd=123”。
2、deleteCharAt方法
public StringBuffer deleteCharAt(int index)
该方法的作用是删除指定位置的字符,然后将剩余的内容形成新的字符串。例如:
StringBuffer sb = new StringBuffer(“Test”);
sb. deleteCharAt(1);
该代码的作用删除字符串对象sb中索引值为1的字符也就是删除第二个字符剩余的内容组成一个新的字符串。所以对象sb的值变为”Tst”。
还存在一个功能类似的delete方法
public StringBuffer delete(int start,int end)
该方法的作用是删除指定区间以内的所有字符包含start不包含end索引值的区间。例如
StringBuffer sb = new StringBuffer(“TestString”);
sb. delete (1,4);
该代码的作用是删除索引值1(包括)到索引值4(不包括)之间的所有字符剩余的字符形成新的字符串。则对象sb的值是”TString”。
3、insert方法
public StringBuffer insert(int offset, String s)
该方法的作用是在StringBuffer对象中插入内容然后形成新的字符串。例如
StringBuffer sb = new StringBuffer(“TestString”);
sb.insert(4,“false”);
该示例代码的作用是在对象sb的索引值4的位置插入字符串false形成新的字符串则执行以后对象sb的值是”TestfalseString”。
4、reverse方法
public StringBuffer reverse()
该方法的作用是将StringBuffer对象中的内容反转然后形成新的字符串。例如
StringBuffer sb = new StringBuffer(“abc”);
sb.reverse();
经过反转以后对象sb中的内容将变为”cba”。
5、setCharAt方法
public void setCharAt(int index, char ch)
该方法的作用是修改对象中索引值为index位置的字符为新的字符ch。例如
StringBuffer sb = new StringBuffer(“abc”);
sb.setCharAt(1,D);
则对象sb的值将变成”aDc”。
6、trimToSize方法
public void trimToSize()
该方法的作用是将StringBuffer对象的中存储空间缩小到和字符串长度一样的长度减少空间的浪费。
7、构造方法
StringBuffer s0=new StringBuffer();分配了长16字节的字符缓冲区
StringBuffer s1=new StringBuffer(512);分配了512字节的字符缓冲区
8、获取字符串的长度: length()
StringBuffer s = new StringBuffer("www");
int i=s.length();
m.返回字符串的一部分值
substring(int start) //返回从start下标开始以后的字符串
substring(int start,int end) //返回从start到 end-1字符串
9.替换字符串
replace(int start,int end,String str)
s.replace(0,1,"qqq");
10.转换为不变字符串:toString()。

View File

@@ -1,189 +0,0 @@
简介
**作用:**
用来传输和存储数据
**定义:**
可扩展标记语言。
**特点:**
自行定义标签,具有自我描述性。
能够跨越各种平台
**具体应用:**
用于描述可用的web服务的WSDL
树结构
**范例**
\<?xml version="1.0"
encoding="UTF-8"?\>\<note\>\<to\>Tove\</to\>\<from\>Jani\</from\>\<heading\>Reminder\</heading\>\<body\>Don't
forget me this weekend!\</body\>\</note\>
说明第一行是XML文档声明定义了版本号和文档的编码方式。
**树结构:**
![clipboard.png](media/ab38369f8043d690b448fe8e8004a755.png)
**元素:**
起始标记、结束标记、内容(可以是字符文本、其他元素、混合物)
**属性:**
属性名=属性值
**语法规则:**
标签闭合,正确嵌套
有一个根元素
可以有树枝也可以由实体引用
XML声明可选放在第一行
XML标签对大小写敏感
属性值必须加引号,应当尽量用子元素代替属性。数据的数据存储为属性,而数据本身应当存储为元素。
id属性可以用来标识XML元素
注释方式\<!-- This is a comment --\>
部分字符使用转义字符
| &lt; | \< | less than |
|---------|----|----------------|
| &gt; | \> | greater than |
| \&amp; | & | ampersand |
| \&apos; | ' | apostrophe |
| &quot; | " | quotation mark |
命名空间
**名字冲突**
同时使用相同的名字
**限定名字**
使用名字空间。在标签之前加上前缀
> \<h:table xmlns:h="http://www.w3.org/TR/html4/"\> \<h:tr\>
> \<h:td\>Apples\</h:td\> \<h:td\>Bananas\</h:td\> \</h:tr\> \</h:table\>
使用默认的名字空间
> \<table xmlns="http://www.w3.org/TR/html4/"\> \<tr\> \<td\>Apples\</td\>
> \<td\>Bananas\</td\> \</tr\> \</table\>
XML定义方式
**XML DTD**
DTD 的目的是定义 XML 文档的结构。它使用一系列合法的元素来定义文档结构:
> \<!DOCTYPE note
> [
> \<!ELEMENT note (to,from,heading,body)\>
> \<!ELEMENT to (\#PCDATA)\>
> \<!ELEMENT from (\#PCDATA)\>
> \<!ELEMENT heading (\#PCDATA)\>
> \<!ELEMENT body (\#PCDATA)\>
> ]\>
**XML Schema**
W3C 支持一种基于 XML 的 DTD 代替者,它名为 XML Schema
> \<xs:element name="note"\>
> \<xs:complexType\>
> \<xs:sequence\>
> \<xs:element name="to" type="xs:string"/\>
> \<xs:element name="from" type="xs:string"/\>
> \<xs:element name="heading" type="xs:string"/\>
> \<xs:element name="body" type="xs:string"/\>
> \</xs:sequence\>
> \</xs:complexType\>
> \</xs:element\>
XML验证方式
对xml语法进行检查
XML显示方式
**源代码**
**CSS格式显示**
> CATALOG { background-color: \#ffffff; width: 100%; } CD { display: block;
> margin-bottom: 30pt; margin-left: 0; } TITLE { color: \#FF0000; font-size:
> 20pt; } ARTIST { color: \#0000FF; font-size: 20pt; }
> COUNTRY,PRICE,YEAR,COMPANY { display: block; color: \#000000; margin-left:
> 20pt; }
**XSLT格式显示XML**
将XML转化为对应的HTML语言
> \<?xml version="1.0" encoding="ISO-8859-1"?\>
> \<!-- Edited by XMLSpy® --\>
> \<html xsl:version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
> xmlns="http://www.w3.org/1999/xhtml"\>
> \<body style="font-family:Arial;font-size:12pt;background-color:\#EEEEEE"\>
> \<xsl:for-each select="breakfast_menu/food"\>
> \<div style="background-color:teal;color:white;padding:4px"\>
> \<span style="font-weight:bold"\>\<xsl:value-of select="name"/\>\</span\>
> \- \<xsl:value-of select="price"/\>
> \</div\>
> \<div style="margin-left:20px;margin-bottom:1em;font-size:10pt"\>
> \<p\>\<xsl:value-of select="description"/\>.
> \<span style="font-style:italic"\>
> \<xsl:value-of select="calories"/\> (calories per serving)
> \</span\>.\</p\>
> \</div\>
> \</xsl:for-each\>
> \</body\>
> \</html\>

View File

@@ -1,71 +0,0 @@
1确定是数据源和数据目的输入还是输出
源:输入流 InputStream Reader
目的:输出流 OutputStream Writer
2明确操作的数据对象是否是纯文本
是:字符流ReaderWriter
否:字节流InputStreamOutputStream
3明确具体的设备。
是硬盘文件File++
读取FileInputStream,, FileReader,
写入FileOutputStreamFileWriter
是内存用数组
byte[]ByteArrayInputStream, ByteArrayOutputStream
是char[]CharArrayReader, CharArrayWriter
是StringStringBufferInputStream(已过时因为其只能用于String的每个字符都是8位的字符串),
StringReader, StringWriter
是网络用Socket流
是键盘用System.in是一个InputStream对象读取用System.out是一个OutoutStream对象打印
3是否需要转换流
就使用转换流从Stream转化为ReaderWriterInputStreamReaderOutputStreamWriter
4是否需要缓冲提高效率
是就加上BufferedBufferedInputStream, BufferedOuputStream, BuffereaReader,
BufferedWriter
5是否需要格式化输出
例:将一个文本文件中数据存储到另一个文件中。
1数据源和数据目的读取流InputStream Reader 输出OutputStream Writer
2是否纯文本这时就可以选择Reader Writer。
3设备是硬盘文件。Reader体系中可以操作文件的对象是 FileReader FileWriter。
FileReader fr = new FileReader("a.txt");
FileWriter fw = new FileWriter("b.txt");
4是否需要提高效率加Buffer
BufferedReader bfr = new BufferedReader(new FileReader("a.txt"); );
BufferedWriter bfw = new BufferedWriter(new FileWriter("b.txt"); );
PrintStream
PrintStream在OutputStream基础之上提供了增强的功能即可以方便地输出各种类型的数据而不仅限于byte型的格式化表示形式。PrintStream的方法从不抛出IOEceptin
PrintWriter
PrintWriter提供了PrintStream的所有打印方法其方法也从不抛出IOException。
与PrintStream的区别作为处理流使用时PrintStream只能封装OutputStream类型的字节流而PrintWriter既可以封装OutputStream类型的字节流还能够封装Writer类型的字符输出流并增强其功能。

View File

@@ -1,15 +0,0 @@
**值得参考的网址**
**http://tutorials.jenkov.com/java-io/stringreader.html**
![clipboard.png](media/999fffbe3d46bc883b98ea7b5769a7da.png)
应该使用以下格式
- 作用分析
- 功能方法
- 代码示例
- 注意事项

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Some files were not shown because too many files have changed in this diff Show More