JVM
内存管理
垃圾回收和内存分配策略
性能监控和故障处理
调优
类文件结构
虚拟机类加载机制
字节码执行引擎
编译器优化
运行期优化
Java内存模型与线程
线程管理和锁优化
JAVA
Basic
Array
0、定义一个Java数组
String[] aArray = new String[5];
String[] bArray = {"a","b","c", "d", "e"};
String[] cArray = new String[]{"a","b","c","d","e"};
第一种是定义了一个数组,并且指定了数组的长度,我们这里称它为动态定义。
第二种和第三种在分配内存空间的同时还初始化了值。
1、打印Java数组中的元素
int[] intArray = { 1, 2, 3, 4, 5 };
String intArrayString = Arrays.toString(intArray);
// print directly will print reference value
System.out.println(intArray);
// [I@7150bd4d
System.out.println(intArrayString);
// [1, 2, 3, 4, 5]
这里的重点是说明了Java中数组的引用和值得区别,第三行直接打印intArray,输出的是乱码,因为intArray仅仅是一个地址引用。第4行输出的则是真正的数组值,因为它经过了Arrays.toString()的转化。对Java初学者来说,引用和值仍需重视。
2、从Array中创建ArrayList
String[] stringArray = { "a", "b", "c", "d", "e" };
ArrayList<String> arrayList = new ArrayList<String>(Arrays.asList(stringArray));
System.out.println(arrayList);
// [a, b, c, d, e]
为什么要将Array转换成ArrayList呢?可能是因为ArrayList是动态链表,我们可以更方便地对ArrayList进行增删改,我们并不需要循环Array将每一个元素加入到ArrayList中,用以上的代码即可简单实现转换。
3、检查数组中是否包含某一个值
String[] stringArray = { "a", "b", "c", "d", "e" };
boolean b = Arrays.asList(stringArray).contains("a");
System.out.println(b);
// true
先使用Arrays.asList()将Array转换成List<String>,这样就可以用动态链表的contains函数来判断元素是否包含在链表中。
4、连接两个数组
int[] intArray = { 1, 2, 3, 4, 5 };
int[] intArray2 = { 6, 7, 8, 9, 10 };
// Apache Commons Lang library
int[] combinedIntArray = ArrayUtils.addAll(intArray, intArray2);
ArrayUtils是Apache提供的数组处理类库,其addAll方法可以很方便地将两个数组连接成一个数组。
5、声明一个数组内链
method(new String[]{"a", "b", "c", "d", "e"});
6、将数组中的元素以字符串的形式输出
// containing the provided list of elements
// Apache common lang
String j = StringUtils.join(new String[] { "a", "b", "c" }, ", ");
System.out.println(j);
// a, b, c
同样利用StringUtils中的join方法,可以将数组中的元素以一个字符串的形式输出。
7、将Array转化成Set集合
Set<String> set = new HashSet<String>(Arrays.asList(stringArray));
System.out.println(set);
//[d, e, b, c, a]
在Java中使用Set,可以方便地将需要的类型以集合类型保存在一个变量中,主要应用在显示列表。同样可以先将Array转换成List,然后再将List转换成Set。
8、数组翻转
int[] intArray = { 1, 2, 3, 4, 5 };
ArrayUtils.reverse(intArray);
System.out.println(Arrays.toString(intArray));
//[5, 4, 3, 2, 1]
依然用到了万能的ArrayUtils。
9、从数组中移除一个元素
int[] intArray = { 1, 2, 3, 4, 5 };
int[] removed = ArrayUtils.removeElement(intArray, 3);//create a new array
System.out.println(Arrays.toString(removed));
再补充一个:将一个int值转化成byte数组
byte[] bytes = ByteBuffer.allocate(4).putInt(8).array();
for (byte t : bytes) {
System.out.format("0x%x ", t);
}
Thread
- 线程之间的通信方式
- 同步synchronized
- wait和notify(阻塞和异步通知)
- Condition
- BlockingQueue
实现多线程
继承Thread类,并且重写Run方法
Thread thread = new Thread() { @Override // 重写run方法 public void run() { while (true) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("1:" + Thread.currentThread().getName()); // 一般都采用这种方式 System.out.println("2:" + this.getName()); // this 表示当前宿主对象 不建议这么使用 } } };
实现Runnable接口类,并实现Run方法
Thread thread2 = new Thread(new Runnable() { @Override // 实现run方法 public void run() { while (true) { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("1:" + Thread.currentThread().getName()); } } });
- 其他
- 通过Thread的start方法启动线程。
- 在多线程中获得当前的方法:Thread.currentThread()
- 疑问
- run方法的声明上是否可以抛出InterruptedException异常?
不能,不兼容。 - 两种方式的差异? 在面向对象的编程方式里,正常的类只能继承一个父类,可以实现多个接口,通过接口的编程方式,可以提高正常类的扩展性。
- run方法的声明上是否可以抛出InterruptedException异常?
- 扩展
- 多线程扩展:Java自带定时器,还有一些第三方的定时器框架
- Java中的定时器
Timer和TimerTask的使用(实现定时执行某段业务逻辑)
//(TimerTask task, long delay, long period)
//task表示需要执行任务
//delay表示延迟,多少时间后执行
//period表示循环执行的时间间隔
new Timer().schedule(new TimerTask() {
@Override
public void run() {
System.out.println("bombing");
}
}, 10L,1000L);
线程的互斥
使用synchronized代码块及其原理?
synchronized关键字主要是为了实现线程之间的互斥性,保证多个线程执行时候,只有一个线程能够执行,其余线程暂时阻塞,当线程执行完成之后,其余线程也按照这种模式依次执行,保证线程执行的有序性。synchronized一般作用于同一个对象,如果synchronized作用的不是同一对象,是达不到多个线程之间的安全控制,还是会出现错误的结果。
线程安全问题是指在多线程执行的环境下,多个线程对同一方法(逻辑)进行操作的时候,对公共变量的读取可能造成误读,导致最后计算结果不是理想结果。
多线程下,默认的命令集是无序的,比如A线程执行到一半,由于没有加控制,B线程抢到CPU使用权执行同样方法,在方法里存在对公共变量的操作,可能读到初始化的值或者操作后的值,导致得到结果跟预期的结果不一致。Q:分析静态方法所使用的同步监视器对象是什么?
A: 字节码对象 XXX.class
互斥的含义就是多个线程同时访问的时候,只有一个线程能够执行方法或者代码块,执行完成之后其他的线程才能够执行。
互斥必须作用于同一对象。(要想多个方法执行的时候也互斥,只有作用于同一对象的时候)常用的互斥对象:XXX.class
synchronized可以作用于代码块,方法要用到共同数据(包括同步锁)的若干方法应该归在同一类身上
Code
public void output(String name) {
int len = name.length();
synchronized (Output.class) { // 需要使用到字节码对象
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
}
System.out.println();
}
public synchronized void output2(String name) {
int len = name.length();
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
public synchronized static void output3(String name){
int len = name.length();
for (int i = 0; i < len; i++) {
System.out.print(name.charAt(i));
}
System.out.println();
}
线程间的通信
wait与notify
- 问题
让线程执行有序进行,线程A执行一段时间,通过wait让当前线程处于阻塞的状态,在调用notify通知其他的线程开始运行。线程B也同样操作,整个链路实现顺序执行。 线程A和线程B之间需要一个条件表达式或者布尔变量来控制。
示例:子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程,又循环100,如此循环50次,请写出程序
Code
class Business {
private boolean bShouldSub = true;
/**
* 子线程执行10次
*
* @throws InterruptedException
*/
public synchronized void sub() {
while (!bShouldSub) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "::" + i);
}
bShouldSub = false;
this.notify();
}
/**
* 主线程执行100次
*
* @throws InterruptedException
*/
public synchronized void main() {
while (bShouldSub) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "::" + i);
}
bShouldSub = true;
this.notify();
}
}
Main入口:
public static void main(String[] args) {
final Business business = new Business();
//子线程循环10次
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=50;i++){
business.sub();
}
}
}).start();
for(int i=1;i<=50;i++){
business.main();
}
}
Lock
- Q:JAVA中的锁?
A:锁是为了实现多线程并发执行的互斥性,保证某一个段代码或者业务逻辑,在并发的情况下只有一个线程在执行,其他线程等待,当线程执行完成,其他线程也是如此依次执行,保证并发下的有序性。锁类似于synchronized代码块(方法)一样,在逻辑块执行时上锁,执行完成后解锁。 - Q:JAVA中synchronized和Lock有什么区别?
A:synchronized是JAVA中的关键字,常作用于方法,或者代码块。通过synchronized包裹的代码,在多线程执行的情况下,线程与线程之间是互斥,每次只有一个线程执行逻辑,其余线程等待。只有当前线程处理完,释放锁后,其他的线程才能依次执行。Lock是JAVA1.5提供的并发锁接口类,功能与synchronized相似,相对于synchronized,Lock比较灵活,上锁和解锁都由我们手动控制,更加灵活。
Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。锁是上在代表对象的资源的类的内部方法中,而不是线程代码中。 锁的操作类
class Queue3 { private Object data = null; // 共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。 private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); public void get() { rwl.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + " be ready to read data!"); Thread.sleep((long) (Math.random() * 1000)); System.out.println(Thread.currentThread().getName() + " have read data :" + data); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { rwl.readLock().unlock(); } } public void put(Object data) { rwl.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + " be ready to read data!"); Thread.sleep((long) (Math.random() * 1000)); this.data = data; System.out.println(Thread.currentThread().getName() + " have write data :" + data); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { rwl.writeLock().unlock(); } } }
读写锁:分为读锁和写锁 ,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,这是有JVM自已控制的,你只要上好响应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!简单示例:多线程的缓存类!
缓存类
private Map<String, Object> cache = new HashMap<String, Object>(); private ReadWriteLock rwl = new ReentrantReadWriteLock(); public Object getData(String key) { rwl.readLock().lock(); // 读锁 Object value = null; try { // 怕后续逻辑出现错误,导致死锁,直接在逻辑代码的外围,try-catch finally中关闭锁 value = cache.get(key);// 获取值 if (value == null) { // 没有读取到 rwl.readLock().unlock(); // 解开读锁 rwl.writeLock().lock(); // 开启写锁 try { if (value == null) { // 这个判断是为了多个线程写锁等待时候,如不加以判断,导致多次查询,性能浪费 value = "aaa"; // 从数据库中数据指定Key的数据 queryDB() } } finally { rwl.writeLock().unlock(); // 关闭读锁 } rwl.readLock().lock(); // 开启读锁 } } finally { rwl.readLock().unlock(); } return value; }
锁的开启和关闭必须是一一对应,不然会造成程序死锁,一般把锁的开启放在逻辑处理的头部。逻辑处理代码放置try-catch中,finally块指定关闭锁的处理。
线程通信中,wait和notify必须结合synchronized一起使用,不然会出现线程状态不正常的异常。
Synchronized中通过wait和notify来实现通信,在Lock接口中也存在通信的接口(Condition)。通过调用await方法实现阻塞,signal方法唤醒一个等待线程。
阻塞队列
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition(); // 队列是否装满
final Condition notEmpty = lock.newCondition(); // 队列是否为空
final Object[] items = new Object[100]; // 初始化一个长度为100的数组
int putptr, // 表示当前插入数组下标
takeptr, // 表示当前取出数组下标
count;// 表示当前数组总个数
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length) // 当前长度等于数组长度
notFull.await(); // put阻塞
items[putptr] = x; // 放入数据
if (++putptr == items.length)
putptr = 0; // 插入数自增,并且个数相同时,塞的个数清空
count++; // 总个数自增
notEmpty.signal(); // 唤醒拿数据的线程
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0) // 当前队列个数为0
notEmpty.wait(); // 取数据线程等待
Object x = items[takeptr]; // 永远是顺序拿
if (++takeptr == items.length)
takeptr = 0; // 获取数自增,并且个数相同时,获取的个数清空
--count; // 拿完总数递减
notFull.signal(); // 唤醒塞数据的线程
return x;
} finally {
lock.unlock();
}
}
Condition
Condition的功能类似在传统线程技术中的Object.wait和Object.notify功能。在等待Condition时,允许发生“虚假唤醒”。这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为Condition应该总是在一个循环中被等待,并测试整被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。
一个锁内部可以有多个Condition,既有多路等待和通知,可以参看jdk1.5提供的Lock与Condition实现的可阻塞队列的应用案例,从中除了要体味算法,还要体味面向对象的封装。在传统的线程机制中一个监视器对象上只能有一路等待和通知,要实现多路等待和通知,必须嵌套使用多个同步监视器对象。(如果只用一个Condition,两个放的都在等,一旦一个放的进去了,那么它通知可能会导致另一个放接着往下走。)
线程间共享
线程范围内共享变量的概念与作用
- Q:什么是线程范围内共享变量?
A:在多线程应用场景的时候,保证每一个线程拿到的数据是独立的,自已线程内的处理不会影响到别的线程中的数据。(在线程内共享,在线程外独立)。每一个线程都自已独立的数据副本,相互之间不会干扰。
同一线程设置的值只能被相同的线程获取。 - Q:线程范围内共享变量的应用场景?
A:数据库操作的时候,每一个线程拿到的数据库连接,数据都是独立的。 比如100米跑步的时候,每个选手都是一个线程,跑道距离,起点,终点,使用时间等等数据都是独立的,每个选手的数据都是不一样,而且不能相互干扰。 - Q: 线程范围共享变量的实现?
A: 通过静态Map的方式,key为线程,Value对应的是共享变量 ThreadLocal - Q:ThreadLocal主要的作用?
A:主要用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一线程中运行时要共享一份数据,而且在另外线程中运行时又共享另外一份数据。 - Q:ThreadLocal的内部结构?常用的方法?
ThreadLocal内部类似于一个集合的结构,通过set(T,t)设置指定类型的数据, 通过get方法获得共享的数据,clear方法清除设置的数据。每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值。在线程结束时可以调用ThreadLocal.clear方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量。 - Q:ThreadLocal的应用场景?
A:订单处理包含一系列操作:减少库存量、增加一条流水台账、修改总账,这几个操作要在同一个事务中完成,通常在即同一个线程中进行处理,如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块类中。
银行转账包含一系列操作:把转出账户的余额减少,把转入账户的余额增加,这两个操作要在同一事务中完成,他们必须使用相同的数据库连接对象,转入和转出操作的代码分别是两个不同的账户对象的方法。
例如:struts2的ActionContext,同一段代码被不同的线程调用运行时,该代码操作的数据是每个线程各自的状态和数据,对于不同的线程来说,getContext方法拿到的对象都是不相同的,对于同一线程来说,不管调用getContext方法多少次和在那个模块中getContext方法,拿到的都是同一个。 Q:ThreadLocal代码上的使用规范?
A:常见情况下,默认使用ThreadLocal都会在线程里面,而且每个共享的对象都创建一个ThreadLocal对象不便于维护,所以可以将线程内的共享变量都存放在一个数据Model里面,通过单例以及内部定义ThreadLocal对象便于维护和管理。class ThreadScope { private static ThreadScope threadScope = null; // = new ThreadScope(); private static ThreadLocal<ThreadScope> local = new ThreadLocal<ThreadScope>(); // 懒汉和恶汉模式 public static /* synchronized */ ThreadScope getThreadInstance() { threadScope = local.get(); if (threadScope == null) { threadScope = new ThreadScope(); local.set(threadScope); } return threadScope; } }
- Q:使用ThreadLocal的注意点?
A:实现对ThreadLocal变量的封装,让外界不要直接操作ThreadLocal对象
对基本类型的数据的封装,这种应用相对很少见
对对象类型的数据的封装,比较常见,即让某个类针对不同线程分别创建一个独立的实例对象
多个线程访问共享对象和数据的方式
如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,卖票系统可以这么做。
如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,有如下两种方式来实现这些Runnable对象之间的数据共享:
- 将共享数据封装成另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。
- 将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。
- 上面两种方法的组合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上完成,对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的Runnable对象作为外部类中的成员变量或者局部内部类
- 总之,要同步互斥的几段代码最好分别放在几个独立的方法中,这些方法在放在同一勒种,这样比较容易实现它们之间的同步互斥和通信。
原子性
Q:什么是原子性?
A:原子性表示多个线程操作的时候,数据是隔离的,Q:java线程中的原子类型?
A:java.util.concurrent.atomic包理解atomic包的含义:
AtomicInteger类的boolean compareAndSet(expectedValue,updateValue);
AtomicIntegerArray类的int addAndGet(int i,int delta);
线程池
- Q: 什么是线程池?
假如Tomcat容器没有通过线程池的方式管理,每一个HTTP请求都创建一个独立的线程来协助处理响应,一百个请求创建一个线程,一百万个请求就创建一百万个线程,线程被初始化,处理逻辑响应请求,线程被销毁,那CPU就在不断的创建线程,销毁线程,造成大量的资源浪费,所以我们可以通过线程池的方法,初始化指定的线程数据,放在一个线程容器中管理,当一个HTTP请求过来(HTTP请求就类似于一个任务),线程池里的管理线程去判断那些线程是处于空闲的状态,从中抽取一条线程来执行任务,处理完任务线程恢复空闲状态,继续等待下一个任务,这样可以有限的提高任务的效率,避免资源的浪费。
线程中存在一个管理子线程,负责线程的初始化,状态检查,任务分配。 - Q:JAVA存在可用的线程池?
A:在JDK1.5的版本里,已经存在模拟线程池的类,通过线程池来动态管理线程。线程池的工具叫Executors,提供四种方式的线程池:
第一种是指定大小的线程(newFixedThreadPool)
第二种是缓存线程池(newCachedThreadPool),大小不是固定的,根据任务数创建线程数
第三种是单一线程池(newSingleThread)
第四种方式是定时器方式的线程池(newScheduledThreadPool),指定大小 - Q:关闭线程池?
A:Executors中的存在关闭线程的方法,分为两种,shutdown()和shutdownNow()。shutdown表示当线程池中所有的任务都执行完之后关闭连接池,shutdownNow表示马上关闭所有的连接池,不管任务是否执行完成。
在线程池的编程模式下,任务是提交给整个线程池,而不是直接交给某个线程,线程池在拿到任务后,它就在内部找到有无空闲的线程,再把任务交给内部某个空闲的线程,这就是封装。记住,任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
new Thread的弊端如下:
a. 每次new Thread新建对象性能差。
b. 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
c. 缺乏更多功能,如定时执行、定期执行、线程中断。
相比new Thread,Java提供的四种线程池的好处在于:
a. 重用存在的线程,减少对象创建、消亡的开销,性能佳。
b. 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
c. 提供定时执行、定期执行、单线程、并发数控制等功能。
Java通过Executors提供四种线程池,分别为:
- newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
为什么要用线程池:
1.减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
比较重要的几个类:
ExecutorService 真正的线程池接口。
ScheduledExecutorService 能和Timer/TimerTask类似,解决那些需要任务重复执行的问题。
ThreadPoolExecutor ExecutorService的默认实现。
ScheduledThreadPoolExecutor 继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在Executors类里面提供了一些静态工厂,生成一些常用的线程池。
newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。- newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。 - newScheduledThreadPool
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
用线程池启动定时器
- 调用ScheduleExceutorService的schedule方法,返回的ScheduleFuture对象可用取消任务。
- 支持间隔重复任务的定时方式,不直接支持绝对定时方式,需要转换成相对时间方式。
// 1.初始化一个线程池 (初始化一个池子) // ExecutorService threadPool = Executors.newFixedThreadPool(3);//指定初始化线程个数 // ExecutorService threadPool = Executors.newCachedThreadPool(); ExecutorService threadPool = Executors.newSingleThreadExecutor(); // newFixedThreadPool 创建指定数量的线程池(固定个数) 无序的 // newCachedThreadPool 动态创建缓存线程池(个数不一定) 无序的 // newSingleThreadExecutor 单一线程池(一个) 有序的 // 2.指定线程池执行的任务 (告诉池子里面的线程,执行什么任务) for (int i = 1; i <= 10; i++) { final int taskId = i; threadPool.execute(new Runnable() { @Override public void run() { for (int j = 1; j <= 10; j++) { System.out.println(taskId + ":::" + Thread.currentThread().getName() + ":::" + j); } } }); } // 3.关闭线程池 threadPool.shutdown(); // 任务执行完毕后关闭线程池 // threadPool.shutdownNow(); //立马关闭线程池 // 如何实现线程死掉后重新启动? // 线程执行完毕或者中途被kill // 掉,重新调用start方法启动线程,会出现线程状态异常的错误。并不能重新再启动线程。只有重新创建一个线程来执行未完成的任务。 // 在JDK1.5的concurrent并发包中,存在一个单例的线程池可以来模拟满足上面的需求 /* * Thread thread = new Thread(){ * * @Override * public void run() { * // TODO Auto-generated method stub * System.out.println("AAA"); * } * }; * thread.start(); */
// 用线程池启动定时器 // 1.初始化一个任务调度的线程池 ScheduledFuture<?> schedule = Executors.newScheduledThreadPool(3).schedule(new Runnable() { public void run() { System.out.println(Thread.currentThread().getName() + "---bombing!!!"); } }, 10, TimeUnit.SECONDS); // 2.通过schedule方法执行调用的任务, 第一个参数表示时间 // 指定间隔重复任务的定时方法 // Executors.newScheduledThreadPool(3).scheduleAtFixedRate(command, // initialDelay, period, unit); // Callable & Future
Q:Callable和Future有什么作用?
A:Callable与Future主要作用于线程池的处理,在线程池submit提交的时候添加Callable方法(指定的任务),线程执行完成任务之后返回一个future结果。通过这个结果我们可用知道线程执行的结果。(结果是通过泛型自定义的,任意扩展)
- Future取得的结果类型和Callable返回的结果类型必须一致,这是通过泛型来实现的。
- Callable要采用ExecutorService的submit方法提交,返回的future对象可用取消任务。通过指定超时时间
- CompletionService用于提交一组Callable任务,其take方法返回已完成的一个Callable任务对应的Future对象
使用场景:类似于偷菜等等,也比如我同时中了几块地的麦子,然后等待收割。收割时,则是那块先成熟了,则先去收割那块麦子。
线程池示例
// 1.创建一个固定大小的线程池 ExecutorService threadPool = Executors.newFixedThreadPool(3); // 2.线程池提交指定的Callable对象() Future<String> future = threadPool.submit(new Callable<String>() { @Override public String call() throws Exception { Thread.sleep(2000); return "suecces"; } }); // 3.调用Future的get方法返回处理结果 System.out.println("等待结果"); System.out.println("得到结果::" + future.get()); // future.get() 默认一直等待处理结果 // future.get(timeout, unit) 指定超时时间,如果指定时间没有获取到结果,抛出异常TimeOutException // 多个线程多个Callable任务,返回多个future执行结果 ExecutorService threadPool2 = Executors.newFixedThreadPool(10); CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(threadPool2); for (int i = 1; i <= 10; i++) { final int taskId = i; completionService.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { Thread.sleep(new Random().nextInt(5000)); return taskId; } }); } for (int i = 0; i < 10; i++) { System.out.println(completionService.take().get()); }
线程同步工具
Semaphore实现信号灯
Semaphore可以维护当前访问自身的线程个数,并提供了同步机制,使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。
- Semaphore实现的功能类似于厕所有5个坑,假如有十个人要上厕所,那么同时能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中的任何一个人让开后,其中在等待的另外5个人中又有一个人可以占用了
- 另外等待的5个人可以是随机获取优先机会,也可以是按照先来后到的顺序,这取决于构造Semaphore对象时传入的参数选项
- 单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获取了“锁”,再由另外一个线程释放“锁”,者可应用于死锁恢复的一些场合。
信号灯Demo
// 1.创建线程池 ExecutorService threadPool = Executors.newCachedThreadPool(); // 2.创建信号灯 final Semaphore sp = new Semaphore(3); // 3.创建10个任务 for (int i = 0; i < 10; i++) { // 创建任务 Runnable runnable = new Runnable() { @Override public void run() { try { sp.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( "线程" + Thread.currentThread().getName() + "进入,当前已有" + (3 - sp.availablePermits()) + "个并发"); try { Thread.sleep((long) (Math.random() * 10000)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程" + Thread.currentThread().getName() + "即将离开"); sp.release(); System.out.println( "线程" + Thread.currentThread().getName() + "进入,当前已有" + (3 - sp.availablePermits()) + "个并发"); } }; threadPool.execute(runnable); }
CyclicBarrier
表示大家彼此等待,大家集合后才开始出发,分散活动后又在指定地点集合碰面,这就好比整个公司的人员利用周末时间集体郊游一样,先各自从家出发到公司集合,在同时出发到公园游玩,在指定地点集合后在同事开始就餐。....
CyclicBarrier(int parties) parties表示集合的个数
调用await方法集合线程
代码示例
ExecutorService threadPool = Executors.newCachedThreadPool();
final CyclicBarrier cb = new CyclicBarrier(3);
for (int i = 0; i < 3; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep((long) (Math.random() * 10000));
System.out.println(
"线程" + Thread.currentThread().getName() + "即将到达集合地点1,当前已有" + (cb.getNumberWaiting() + 1)
+ "个已经到达," + (cb.getNumberWaiting() == 2 ? "都到齐了,继续走啊" : "正在等待"));
cb.await();
Thread.sleep((long) (Math.random() * 10000));
System.out.println(
"线程" + Thread.currentThread().getName() + "即将到达集合地点2,当前已有" + (cb.getNumberWaiting() + 1)
+ "个已经到达," + (cb.getNumberWaiting() == 2 ? "都到齐了,继续走啊" : "正在等待"));
cb.await();
Thread.sleep((long) (Math.random() * 10000));
System.out.println(
"线程" + Thread.currentThread().getName() + "即将到达集合地点3,当前已有" + (cb.getNumberWaiting() + 1)
+ "个已经到达," + (cb.getNumberWaiting() == 2 ? "都到齐了,继续走啊" : "正在等待"));
cb.await();
} catch (Exception e) {
e.printStackTrace();
}
}
};
threadPool.execute(runnable);
}
CountDownLatch
犹如倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当计数到达0时,则所有等待者或单个等待者开始执行。这直接通过代码来说明CountDownLatch的作用。
可以实现一个人(也可以十多个人)等待其他所有人都来通知他,可以实现一个人通知多个人的鲜果,类似裁判一声口令,运动员同时开始奔跑,或者所有运动员都跑到终点后裁判才可以公布结果,用这个功能做百米赛跑的游戏程序不错哦!
程序示例
ExecutorService threadPool = Executors.newCachedThreadPool();
final CountDownLatch cdOrder = new CountDownLatch(1);
final CountDownLatch cdAnswer = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
System.out.println("线程" + Thread.currentThread().getName() + "正在准备接受命令");
cdOrder.await(); // 线程阻塞
System.out.println("线程" + Thread.currentThread().getName() + "已接受命令");
Thread.sleep((long) (Math.random() * 10000));
System.out.println("线程" + Thread.currentThread().getName() + "回应命令处理结果");
cdAnswer.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
};
threadPool.execute(runnable);
}
try {
Thread.sleep((long) (Math.random() * 10000));
System.out.println("线程" + Thread.currentThread().getName() + "即将发布命令");
cdOrder.countDown();
System.out.println("线程" + Thread.currentThread().getName() + "已发送命令,正在等待执行结果");
cdAnswer.await();
System.out.println("线程" + Thread.currentThread().getName() + "已收到所有响应结果");
} catch (Exception e) {
e.printStackTrace();
}
threadPool.shutdown();
场景示例
CountDownLatch类是一个同步计数器,构造时传入int参数,该参数就是计数器的初始值,每调用一次countDown()方法,
计数器减1,计数器大于0 时,await()方法会阻塞程序继续执行
CountDownLatch如其所写,是一个倒计数的锁存器,当计数减至0时触发特定的事件。利用这种特性,
可以让主线程等待子线程的结束。下面以一个模拟运动员比赛的例子加以说明。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchDemo {
private static final int PLAYER_AMOUNT = 5;
public CountDownLatchDemo() {
// TODO Auto-generated constructor stub
}
public static void main(String[] args) {
// TODO Auto-generated method stub
//对于每位运动员,CountDownLatch减1后即结束比赛
CountDownLatch begin = new CountDownLatch(1);
//对于整个比赛,所有运动员结束后才算结束
CountDownLatch end = new CountDownLatch(PLAYER_AMOUNT);
Player[] plays = new Player[PLAYER_AMOUNT];
for(int i=0;i<PLAYER_AMOUNT;i++)
plays[i] = new Player(i+1,begin,end);
//设置特定的线程池,大小为5
ExecutorService exe = Executors.newFixedThreadPool(PLAYER_AMOUNT);
for(Player p:plays)
exe.execute(p); //分配线程
System.out.println("Race begins!");
begin.countDown();
try{
end.await(); //等待end状态变为0,即为比赛结束
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}finally{
System.out.println("Race ends!");
}
exe.shutdown();
}
}
接下来是Player类
import java.util.concurrent.CountDownLatch;
public class Player implements Runnable {
private int id;
private CountDownLatch begin;
private CountDownLatch end;
public Player(int i, CountDownLatch begin, CountDownLatch end) {
// TODO Auto-generated constructor stub
super();
this.id = i;
this.begin = begin;
this.end = end;
}
@Override
public void run() {
// TODO Auto-generated method stub
try{
begin.await(); //等待begin的状态为0
Thread.sleep((long)(Math.random()*100)); //随机分配时间,即运动员完成时间
System.out.println("Play"+id+" arrived.");
}catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}finally{
end.countDown(); //使end状态减1,最终减至0
}
}
}
Exchanger
用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人一直等待第二个人拿着数据到来时,才能彼此交换数据。
代码示例
ExecutorService threadPool = Executors.newCachedThreadPool();
final Exchanger<String> exchanger = new Exchanger<String>();
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
String data1 = "zxx";
System.out.println("线程" + Thread.currentThread().getName() + "正在把数据" + data1 + "换出去");
Thread.sleep((long) (Math.random() * 10000));
String data2 = exchanger.exchange(data1);
System.out.println("线程" + Thread.currentThread().getName() + "换回的数据为" + data2);
} catch (Exception e) {
e.printStackTrace();
}
}
});
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
String data1 = "lhm";
System.out.println("线程" + Thread.currentThread().getName() + "正在把数据" + data1 + "换出去");
Thread.sleep((long) (Math.random() * 10000));
String data2 = exchanger.exchange(data1);
System.out.println("线程" + Thread.currentThread().getName() + "换回的数据为" + data2);
} catch (Exception e) {
e.printStackTrace();
}
}
});
可阻塞的队列
队列包含固定长度的队列和不固定长度的队列。
- Q:什么是可阻塞队列,阻塞队列的作用和实际应用,阻塞队列的实现原理? A:可阻塞队列是多线程安全的集合类,在并发情况下,保证所有线程存取数据都是有序的,可阻塞表示获取或者移除数据的时候,当前集合的数据已满或者为空的时候,调用put或者take方法,操作会阻塞等待,直到有新的数据插入或者移出。
阻塞队列的作用:保证数据的读取有序性,安全性,当集合的数据满了,其他需要插入的线程阻塞等待,直到集合有空闲位置,线程才插入数据到集合。当集合为空,移除线程也会阻塞等待。
阻塞队列的应用场景:任务池
实现原理:参考Jdk中的Condition类,内部实现一个简单的阻塞队列 - ArrayBlockingQueue
看BlockingQueue类的帮助文档,其中有各个方法的区别对比的表格
只有put的方法和take方法才能具有阻塞功能。
两个具有1个空间的队列来实现同步通知的功能
static class Business {
BlockingQueue<Integer> queue1 = new ArrayBlockingQueue<>(1);
BlockingQueue<Integer> queue2 = new ArrayBlockingQueue<>(1);
{
try {
queue2.put(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 子线程执行10次
*
* @throws InterruptedException
*/
public void sub() { // 此处不能使用synchronized,会导致线程死锁
try {
queue1.put(1);
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + "::" + i);
}
queue2.take();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 主线程执行100次
*
* @throws InterruptedException
*/
public void main() {
try {
queue2.put(1);
for (int i = 1; i <= 100; i++) {
System.out.println(Thread.currentThread().getName() + "::" + i);
}
queue1.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
同步集合
- Q:HashMap为什么不是线程安全的? A: 在多线程并发环境下, 多个线程去读,一个线程去修改集合中的数据,可能导致读取时的数据不一致,容易造成死循环。详细参考:Java HashMap的死循环
Java Collections集合工具类中提供对HashMap包装的并发安全集合,其内部实现机制就是在HashMap上层做了代理,当调用类似于HashMap的增删改的方法,在方法的外部都回家上synchronized同步块,保证多线程下某一个操作只有一个线程在操作,这样从而保证线程的安全性。如:Map<Object, Object> map = Collections.synchronizedMap(null);示例代码: while(hasnext()){ next(){cursor++} } hasnext(){ if(curssr == count){ return false; } return true; } remove(){ count --; count =3; }
- Q:HashMap为什么不是线程安全的? A: 在多线程并发环境下, 多个线程去读,一个线程去修改集合中的数据,可能导致读取时的数据不一致,容易造成死循环。详细参考:Java HashMap的死循环
Q:HashSet和HashMap的区别和关系?
A:HashSet内部也是采用HashMap的结构,只是HashSet只是用到HashMap中的Key那一部分。
Java的并发包中也提供并发安全的集合类:ConcurrentHashMap
资料
Collections
List
ArrayList
LinkedList
Set
HashSet
Map
HashMap
ConcurrentHashMap
常用操作
遍历Map的四种方法
public static void main(String[] args) {
Map<String, String> map = new HashMap<String, String>();
map.put("1", "value1");
map.put("2", "value2");
map.put("3", "value3");
//第一种:普遍使用,二次取值
System.out.println("通过Map.keySet遍历key和value:");
for (String key : map.keySet()) {
System.out.println("key= "+ key + " and value= " + map.get(key));
}
//第二种
System.out.println("通过Map.entrySet使用iterator遍历key和value:");
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
//第三种:推荐,尤其是容量大时
System.out.println("通过Map.entrySet遍历key和value");
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key= " + entry.getKey() + " and value= " + entry.getValue());
}
//第四种
System.out.println("通过Map.values()遍历所有的value,但不能遍历key");
for (String v : map.values()) {
System.out.println("value= " + v);
}
}
List遍历
对List的遍历有三种方式
List<A> list = new ArrayList<A>();
list.add(new A());
list.add(new A());
...
第一种:
for(Iterator<A> it = list.iterator(); it.hasNext(); ) {
....
}
这种方式在循环执行过程中会进行数据锁定, 性能稍差, 同时,如果你想在寻欢过程中去掉某个元素,只能调用it.remove方法, 不能使用list.remove方法, 否则一定出并发访问的错误.
第二种:
for(A a : list) {
.....
}
内部调用第一种, 换汤不换药, 这种循环方式还有其他限制, 不建议使用它
第三种:
for(int i=0; i<list.size(); i++) {
A a = list.get(i);
...
}
内部不锁定, 效率最高, 但是当写多线程时要考虑并发操作的问题!
List去重
因为用到list,要去除重复数据,尝试了几种方法。记录于此。。。
测试数据:
List<string> li1 = new List<string> { "8", "8", "9", "9" ,"0","9"};
List<string> li2 = new List<string> { "张三", "张三", "李四", "张三", "王五", "李四" };
List<string> li3 = new List<string> { "A", "A", "C", "A", "C", "D" };
List<string> li4 = new List<string> { "12", "18", "19", "19", "10", "19" };
方法一:
HashSet<string> hs = new HashSet<string>(li1); //此时已经去掉重复的数据保存在hashset中
方法二:
for (int i = 0; i < li2.Count; i++) //外循环是循环的次数
{
for (int j = li2.Count - 1 ; j > i; j--) //内循环是 外循环一次比较的次数
{
if (li2[i] == li2[j])
{
li2.RemoveAt(j);
}
}
}
方法三:
//把相同的用null代替。
for (int i = 0; i < li3.Count; i++)
{
for (int j = 0; j < li3.Count; j++)
{
if (i == j) continue;
if (li3[i] == li3[j])
{
li3[j] = "null";
}
}
}
方法四:
//这方法跟上面的一样,只是变了逻辑
for (int i = 0; i < li4.Count - 1; i++)
{
for (int j = 0; j < li4.Count ; j++)
{
if (i != j)
{
if (li4[i] == li4[j])
{
li4[j] = "null";
}
}
}
}
最后输出看结果
Console.WriteLine("li1去除重复后的值为");
hs.ToList().ForEach(item => Console.WriteLine(item));
Console.WriteLine("li2去除重复后的值为");
li2.ForEach(item => Console.WriteLine(item));
Console.WriteLine("li3去除重复后的值为");
li3.ForEach(item => Console.WriteLine(item));
Console.WriteLine("li4去除重复后的值为");
li4.ForEach(item => Console.WriteLine(item));
null我没去掉。用的时候去掉即可。
当然。还有许多办法。比如linq Distinct 等等都可以,看看网上的这个例子:去掉modelList中title重复的内容,不区分大小写
class Program
{
static void Main(string[] args)
{
List<Model> modelList = new List<Model>()
{ new Model() { ID = 1, Title = "abcde" },
new Model() { ID = 2, Title = "ABCDE" },
new Model(){ ID = 3, Title = "AbcdE" },
new Model() { ID = 4, Title = "A" },
new Model() { ID = 5, Title = "a" }
};
Console.Read();
}
}
public class Model
{
public int ID { get; set; }
public string Title { get; set; }
}
解决方案一:这里比较的前提是对象的哈希代码相等。否则不会比较,因为哈希代码不相等。两个对象显然不相等
//定义一个类继承IEqualityComparer接口
public class ModelComparer : IEqualityComparer<Model>
{
public bool Equals(Model x, Model y)
{
return x.Title.ToUpper() == y.Title.ToUpper();
}
public int GetHashCode(Model obj)
{
return obj.Title.ToUpper().GetHashCode();
}
}
调用:
modelList = modelList.Distinct(new ModelComparer()).ToList();
解决方案二:
var title = modelList.GroupBy(m => m.Title.ToLower().Trim()).Select(m => new { ID = m.FirstOrDefault().ID });
modelList = modelList.Where(m => title.Select(mo => mo.ID).Contains(m.ID)).ToList();
foreach (var item in modelList)
{
Console.WriteLine(item.ID + "\t" + item.Title);
}
当然。如果你仅仅比较两个值是否相等。
List<string> li1 = new List<string> { "8", "8", "9", "8", "0", "9" };
li1 = li1.Distinct().ToList();
IO
File
读写操作
RandomAccessFile
NIO
Basic
Java NIO由以下几个核心部分组成
- Channels
- Buffers
- Selectors
虽然Java NIO 中除此之外还有很多类和组件,但在我看来,Channel,Buffer 和 Selector 构成了核心的API。其它组件,如Pipe和FileLock,只不过是与三个核心组件共同使用的工具类。Channel和Buffer
基本上,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。
Channel和Buffer有好几种类型。下面是JAVA NIO中的一些主要Channel的实现:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
正如你所看到的,这些通道涵盖了UDP 和 TCP 网络IO,以及文件IO。
以下是Java NIO里关键的Buffer实现:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
这些Buffer覆盖了你能通过IO发送的基本数据类型:byte, short, int, long, float, double 和 char。
Java NIO 还有个 MappedByteBuffer,用于表示内存映射文件。Selector
Selector允许单线s程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。
这是在一个单线程中使用一个Selector处理3个Channel的图示:
要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。Channel
Java NIO的通道类似流,但又有些不同:
- 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
- 通道可以异步地读写。
- 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。
Channel的实现:
- FileChannel
从文件中读写数据。 - DatagramChannel
能通过UDP读写网络中的数据。 - SocketChannel
能通过TCP读写网络中的数据。 - ServerSocketChannel
可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。
示例
下面是一个使用FileChannel读取数据到Buffer中的示例:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
System.out.println("Read " + bytesRead);
buf.flip();
while (buf.hasRemaining()) {
System.out.print((char) buf.get());
}
buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();
注意 buf.flip() 的调用,首先读取数据到Buffer,然后反转Buffer,接着再从Buffer中读取数据。
Buffer
Java NIO中的Buffer用于和NIO通道进行交互。如你所知,数据是从通道读入缓冲区,从缓冲区写入到通道中的。
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。
Buffer的基本用法
使用Buffer读写数据一般遵循以下四个步骤:
- 写入数据到Buffer
- 调用flip()方法
- 从Buffer中读取数据
- 用clear()方法或者compact()方法
当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。在读模式下,可以读取之前写入到buffer的所有数据。
一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()或compact()方法。clear()方法会清空整个缓冲区。compact()方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
下面是一个使用Buffer的例子:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
//create buffer with capacity of 48 bytes
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf); //read into buffer.
while (bytesRead != -1) {
buf.flip(); //make buffer ready for read
while(buf.hasRemaining()){
System.out.print((char) buf.get()); // read 1 byte at a time
}
buf.clear(); //make buffer ready for writing
bytesRead = inChannel.read(buf);
}
aFile.close();
资料
Network
Lang
Reflect
Date/Calendar
Math
Security
SQL
Text
Util
特性
J2EE
JDBC(JavaDatabase Connectivity)
JDBC是以统一方式访问数据库的API.
它提供了独立于平台的数据库访问,也就是说,有了JDBC API,我们就不必为访问Oracle数据库专门写一个程序,为访问Sybase数据库又专门写一个程序等等,只需要用JDBC API写一个程序就够了,它可以向相应数据库发送SQL调用.JDBC是Java应用程序与各种不同数据库之间进行对话的方法的机制.简单地说,它做了三件事:与数据库建立连接--发送操作数据库的语句--处理结果.
JNDI(JavaName and Directory Interface)
JNDI是一组在Java应用中访问命名和目录服务的API. (命名服务将名称和对象联系起来,我们即可用名称访问对象.JNDI允许把名称同Java对象或资源关联起来,建立逻辑关联,而不必知道对象或资源的物理ID.)JNDI为开发人员提供了查找和访问各种命名和目录服务的通用,统一的接口
利用JNDI的命名与服务功能可满足企业级API对命名与服务的访问,诸如EJB,JMS,JDBC 2.0以及IIOP上的RMI通过JNDI来使用CORBA的命名服务.
JNDI和JDBC类似,都是构建在抽象层上.因为:
它提供了标准的独立于命名系统的API,这些API构建在命名系统之上.这一层有助于将应用与实际数据源分离,因此不管是访问的LDAP,RMI还是DNS.也就是说,JNDI独立于目录服务的具体实现,只要有目录的服务提供接口或驱动,就可以使用目录.
EJB(Enterprise JavaBean)
J2EE将业务逻辑从客户端软件中抽取出来,封装在一个组件中。这个组件运行在一个独立的服务器上,客户端软件通过网络调用组件提供的服务以实现业务逻辑,而客户端软件的功能只是负责发送调用请求和显示处理结果。
在J2EE中,这个运行在一个独立的服务器上,并封装了业务逻辑的组件就是EJB组件。其实就是把原来放到客户端实现的代码放到服务器端,并依靠RMI进行通信。
RMI(Remote MethodInvoke)
是一组用户开发分布式应用程序的API.
这一协议调用远程对象上的方法使用了序列化的方式在客户端和服务器之间传递数据,使得原先的程序在同一操作系统的方法调用,变成了不同操作系统之间程序的方法调用,即RMI机制实现了程序组件在不同操作系统之间的通信.它是一种被EJB使用的更底层的协议.
RMI/JNI: RMI可利用标准Java本机方法接口与现有的和原有的系统相连接
RMI/JDBC: RMI利用标准JDBC包与现有的关系数据库连接
这就实现了与非Java语言的现有服务器进行通信.
JavaIDL/CORBA(Common Object Request BrokerArchitecture)
Java接口定义语言/公用对象请求代理程序体系结构
在JavaIDL的支持下,开发人员可以将Java和CORBA集成在一起。他们可以创建Java对象并使之可在CORBA ORB中展开,或者他们还可以创建Java类并作为和其它ORB一起展开的CORBA对象的客户。后一种方法提供了另外一种途径,通过它Java可以被用于将新的应用和旧的系统相集成。
CORBA是面向对象标准的第一步,有了这个标准,软件的实现与工作环境对用户和开发者不再重要,可以把精力更多地放在本地系统的实现与优化上。
JSP(Java Server Pages)
JSP页面=HTML+Java,其根本是一个简化的Servlet设计.
服务器在页面被客户端请求后,对这些Java代码进行处理,然后将执行结果连同原HTML代码生成的新HTML页面返回给客户端浏览器.
Java Servlet
Servlet是一种小型的Java程序,扩展了Web服务器的功能,作为一种服务器的应用,当被请求时开始执行.Servlet提供的功能大多和JSP类似,不过,JSP通常是大多数的HTML代码中嵌入少量的Java代码,而Servlet全部由Java写成并生成HTML.
XML
XML是一个用来定义其它标记语言的语言,可用作数据共享。XML的发展和Java是相互独立的。不过,它和Java具有的相同目标就是跨平台。通过将Java与XML结合,我们可以得到一个完全与平台无关的解决方案。
JMS(JavaMessage Service)
它是一种与厂商无关的API,用来访问消息收发系统消息.它类似于JDBC.JDBC是可以用来访问不同关系数据库的API,而JMS则提供同样与厂商无关的访问消息收发服务的方法,这样就可以通过消息收发服务实现从一个JMS客户机向另一个JMS客户机发送消息,所需要的是厂商支持JMS.换句话说,JMS是Java平台上有关面向消息中间件的技术规范.
JTA(JavaTransaction API)
定义了一种标准API,应用程序由此可以访问各种事务监控.它允许应用程序执行分布式事务处理--在两个或多个网络计算机资源上访问并且更新数据.JTA和JTS为J2EE 平台提供了分布式事务服务.
JTA事务比JDBC事务更强大,一个JTA事务可以有多个参与者,而一个JDBC事务则被限定在一个单一的数据库连接.
JTS(JavaTransaction Service)
JTS是CORBA OTS事务监控器的一个基本实现。JTS指定了一个事务管理器的实现(Transaction Manager),这个管理器在一个高级别上支持JTA规范,并且在一个低级别上实现了OMGOTS规范的Java映射。一个JTS事务管理器为应用服务器、资源管理器、standalone应用和通信资源管理器提供事务服务。
JavaMail
用于访问邮件服务器的API,提供了一套邮件服务器的抽象类.
JAF(JavaBeansActivation Framework)
JAF是一个专用的数据处理框架,它用于封装数据,并为应用程序提供访问和操作数据的接口.也就是说,JAF让Java程序知道怎么对一个数据源进行查看,编辑,打印等. JavaMail利用JAF来处理MIME编码的邮件附件.
题目锦集
Java
- 基础篇
- 基本功
- 面向对象的特征
- final, finally, finalize 的区别
- int 和 Integer 有什么区别
- 重载和重写的区别
- 抽象类和接口有什么区别
- 说说反射的用途及实现
- 说说自定义注解的场景及实现
- HTTP 请求的 GET 与 POST 方式的区别
- session 与 cookie 区别
- session 分布式处理
- JDBC 流程
- MVC 设计思想
- equals 与 == 的区别
- 集合
- List 和 Set 区别
- List 和 Map 区别
- Arraylist 与 LinkedList 区别
- ArrayList 与 Vector 区别
- HashMap 和 Hashtable 的区别
- HashSet 和 HashMap 区别
- HashMap 和 ConcurrentHashMap 的区别
- HashMap 的工作原理及代码实现
- ConcurrentHashMap 的工作原理及代码实现
- 线程
- 创建线程的方式及实现
- sleep() 、join()、yield()有什么区别
- 说说 CountDownLatch 原理
- 说说 CyclicBarrier 原理
- 说说 Semaphore 原理
- 说说 Exchanger 原理
- 说说 CountDownLatch 与 CyclicBarrier 区别
- ThreadLocal 原理分析
- 讲讲线程池的实现原理
- 线程池的几种方式
- 线程的生命周期
- 锁机制
- 说说线程安全问题
- volatile 实现原理
- synchronize 实现原理
- synchronized 与 lock 的区别
- CAS 乐观锁
- ABA 问题
- 乐观锁的业务场景及实现方式
- 基本功
- 核心篇
- 数据存储
- MySQL 索引使用的注意事项
- 说说反模式设计
- 说说分库与分表设计
- 分库与分表带来的分布式困境与应对之策
- 说说 SQL 优化之道
- MySQL 遇到的死锁问题
- 存储引擎的 InnoDB 与 MyISAM
- 数据库索引的原理
- 为什么要用 B-tree
- 聚集索引与非聚集索引的区别
- limit 20000 加载很慢怎么解决
- 选择合适的分布式主键方案
- 选择合适的数据存储方案
- ObjectId 规则
- 聊聊 MongoDB 使用场景
- 倒排索引
- 聊聊 ElasticSearch 使用场景
- 缓存使用
- Redis 有哪些类型
- Redis 内部结构
- 聊聊 Redis 使用场景
- Redis 持久化机制
- Redis 如何实现持久化
- Redis 集群方案与实现
- Redis 为什么是单线程的
- 缓存奔溃
- 缓存降级
- 使用缓存的合理性问题
- 消息队列
- 消息队列的使用场景
- 消息的重发补偿解决思路
- 消息的幂等性解决思路
- 消息的堆积解决思路
- 自己如何实现消息队列
- 如何保证消息的有序性
- 数据存储
- 框架篇
- Spring
- BeanFactory 和 ApplicationContext 有什么区别
- Spring Bean 的生命周期
- Spring IOC 如何实现
- 说说 Spring AOP
- Spring AOP 实现原理
- 动态代理(cglib 与 JDK)
- Spring 事务实现方式
- Spring 事务底层原理
- 如何自定义注解实现功能
- Spring MVC 运行流程
- Spring MVC 启动流程
- Spring 的单例实现原理
- Spring 框架中用到了哪些设计模式
- Spring 其他产品(Srping Boot、Spring Cloud、Spring Secuirity、Spring Data、Spring AMQP 等)
- Netty
- 为什么选择 Netty
- 说说业务中,Netty 的使用场景
- 原生的 NIO 在 JDK 1.7 版本存在 epoll bug
- 什么是TCP 粘包/拆包
- TCP粘包/拆包的解决办法
- Netty 线程模型
- 说说 Netty 的零拷贝
- Netty 内部执行流程
- Netty 重连实现
- Spring
- 微服务篇
- 微服务
- 前后端分离是如何做的
- 微服务哪些框架
- 你怎么理解 RPC 框架
- 说说 RPC 的实现原理
- 说说 Dubbo 的实现原理
- 你怎么理解 RESTful
- 说说如何设计一个良好的 API
- 如何理解 RESTful API 的幂等性
- 如何保证接口的幂等性
- 说说 CAP 定理、 BASE 理论
- 怎么考虑数据一致性问题
- 说说最终一致性的实现方案
- 你怎么看待微服务
- 微服务与 SOA 的区别
- 如何拆分服务
- 微服务如何进行数据库管理
- 如何应对微服务的链式调用异常
- 对于快速追踪与定位问题
- 微服务的安全
- 分布式
- 谈谈业务中使用分布式的场景
- Session 分布式方案
- 分布式锁的场景
- 分布是锁的实现方案
- 分布式事务
- 集群与负载均衡的算法与实现
- 说说分库与分表设计
- 分库与分表带来的分布式困境与应对之策
- 安全&性能
- 安全问题
- 安全要素与 STRIDE 威胁
- 防范常见的 Web 攻击
- 服务端通信安全攻防
- HTTPS 原理剖析
- HTTPS 降级攻击
- 授权与认证
- 基于角色的访问控制
- 基于数据的访问控制
- 性能优化
- 性能指标有哪些
- 如何发现性能瓶颈
- 性能调优的常见手段
- 说说你在项目中如何进行性能调优
- 微服务
- 工程篇
- 需求分析
- 你如何对需求原型进行理解和拆分
- 说说你对功能性需求的理解
- 说说你对非功能性需求的理解
- 你针对产品提出哪些交互和改进意见
- 你如何理解用户痛点
- 设计能力
- 说说你在项目中使用过的 UML 图
- 你如何考虑组件化
- 你如何考虑服务化
- 你如何进行领域建模
- 你如何划分领域边界
- 说说你项目中的领域建模
- 说说概要设计
- 设计模式
- 你项目中有使用哪些设计模式
- 说说常用开源框架中设计模式使用分析
- 说说你对设计原则的理解
- 23种设计模式的设计理念
- 设计模式之间的异同,例如策略模式与状态模式的区别
- 设计模式之间的结合,例如策略模式+简单工厂模式的实践
- 设计模式的性能,例如单例模式哪种性能更好。
- 业务工程
- 你系统中的前后端分离是如何做的
- 说说你的开发流程
- 你和团队是如何沟通的
- 你如何进行代码评审
- 说说你对技术与业务的理解
- 说说你在项目中经常遇到的 Exception
- 说说你在项目中遇到感觉最难Bug,怎么解决的
- 说说你在项目中遇到印象最深困难,怎么解决的
- 你觉得你们项目还有哪些不足的地方
- 你是否遇到过 CPU 100% ,如何排查与解决
- 你是否遇到过 内存 OOM ,如何排查与解决
- 说说你对敏捷开发的实践
- 说说你对开发运维的实践
- 介绍下工作中的一个对自己最有价值的项目,以及在这个过程中的角色
- 软实力
- 说说你的亮点
- 说说你最近在看什么书
- 说说你觉得最有意义的技术书籍
- 工作之余做什么事情
- 说说个人发展方向方面的思考
- 说说你认为的服务端开发工程师应该具备哪些能力
- 说说你认为的架构师是什么样的,架构师主要做什么
- 说说你所理解的技术专家
- 需求分析