[原创]java:Stream、Socket等源码分析
一、对于java启动之后的线程的说明
java在启动后会有几个特殊线程:
1、main线程,主线程
2、JVM线程,虚拟机的线程
3、GC垃圾回收线程,是个守护线程
4、EDT&Toolkit
5、在启动图形界面时会自动创建两个线程,用于接收事件之前阻塞界面
AWT-Shutdown与AWT-EventQueue-0,所以在触发按钮事件时,所有的操作都是在AWT-EventQueue-0线程中进行的,而不是在主线程中。
在AWT.setVisible之后这两个线程会开辟出来,setVisible之后的代码还是在当前线程中运行的。
二、InputStream的read操作。
int read(),如果因为已经到达流末尾而没有可用的字节,则返回值 -1
。在输入数据可用、检测到流末尾或者抛出异常前,此方法一直阻塞。
要使用int作为返回值也是因为返回值-1的缘故,这样就不会和byte中的-1冲突了。
那么什么时候才算流末尾呢?
不同子类read操作不同,流末尾的判别也不同。
ByteArrayInputStream:
public synchronized int read() {
return (pos < count) ? (buf[pos++] & 0xff) : -1;
}
因为此类是使用已有缓冲区创建的,所以在读到缓冲区结尾时即返回-1,流末尾即超过缓冲区,不存在线程阻塞。
FileInputStream:
public native int read() throws IOException;
使用其他语言实现,很大可能是使用C语言来实现的,C语言读到文件末尾会返回EOF标记,处理为-1返回给调用方。
BufferedInputStream:
因为仅仅是加了缓冲区的InputStream,所以read操作还是调用的其他InputStream类的read。
PipedInputStream:稍微复杂点
使用了两个线程,所以PipedInputStream与PipedOutputStream一定要使用两个线程来创建,否则容易阻塞当前线程。
在in<0时死循环,在连接未关闭时,只有在接收到数据时才会把in在其他线程中置为0,此时可以跳出此循环,可以继续往下执行。连接关闭时会返回-1。
public synchronized int read() throws IOException {
if (!connected) {
throw new IOException("Pipe not connected");
} else if (closedByReader) {
throw new IOException("Pipe closed");
} else if (writeSide != null && !writeSide.isAlive()
&& !closedByWriter && (in < 0)) {
throw new IOException("Write end dead");
} readSide = Thread.currentThread();
int trials = 2;
while (in < 0) {
if (closedByWriter) {
/* closed by writer, return EOF */
return -1;
}
if ((writeSide != null) && (!writeSide.isAlive()) && (--trials < 0)) {
throw new IOException("Pipe broken");
}
/* might be a writer waiting */
notifyAll();
try {
wait(1000);
} catch (InterruptedException ex) {
throw new java.io.InterruptedIOException();
}
}
int ret = buffer[out++] & 0xFF;
if (out >= buffer.length) {
out = 0;
}
if (in == out) {
/* now empty */
in = -1;
} return ret;
}
三、Reader的read操作
Reader类中有一个lock对象,表示当前对象,在Reader的所有操作用都会有一句:synchronized (lock),即锁定当前对象,所以在阻塞时,也不允许其他线程对该对象的其他任何操作,包括关闭等。
protected Reader() {
this.lock = this;
}
BufferedReader:
public int read(char cbuf[], int off, int len) throws IOException {
synchronized (lock) {
ensureOpen();
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
} int n = read1(cbuf, off, len);
if (n <= 0) return n;
while ((n < len) && in.ready()) {
int n1 = read1(cbuf, off + n, len - n);
if (n1 <= 0) break;
n += n1;
}
return n;
}
}
而在read1中,则是调用了fill()方法,去填充缓冲区:
private void fill() throws IOException {
int dst;
if (markedChar <= UNMARKED) {
/* No mark */
dst = 0;
} else {
/* Marked */
int delta = nextChar - markedChar;
if (delta >= readAheadLimit) {
/* Gone past read-ahead limit: Invalidate mark */
markedChar = INVALIDATED;
readAheadLimit = 0;
dst = 0;
} else {
if (readAheadLimit <= cb.length) {
/* Shuffle in the current buffer */
System.arraycopy(cb, markedChar, cb, 0, delta);
markedChar = 0;
dst = delta;
} else {
/* Reallocate buffer to accommodate read-ahead limit */
char ncb[] = new char[readAheadLimit];
System.arraycopy(cb, markedChar, ncb, 0, delta);
cb = ncb;
markedChar = 0;
dst = delta;
}
nextChar = nChars = delta;
}
} int n;
do {
n = in.read(cb, dst, cb.length - dst);
} while (n == 0);
if (n > 0) {
nChars = dst + n;
nextChar = dst;
}
}
可以看到在n==0时,do会死循环,也就是说,在未从流中读到数据时,此线程会通过死循环阻塞。在流中读到0个字节的数据与读到-1是不同的,-1表示流结尾。
readLine方法中:
String readLine(boolean ignoreLF) throws IOException {
StringBuffer s = null;
int startChar; synchronized (lock) {
ensureOpen();
boolean omitLF = ignoreLF || skipLF; bufferLoop:
for (;;) { if (nextChar >= nChars)
fill();
if (nextChar >= nChars) { /* EOF */
if (s != null && s.length() > 0)
return s.toString();
else
return null;
}
boolean eol = false;
char c = 0;
int i; /* Skip a leftover '\n', if necessary */
if (omitLF && (cb[nextChar] == '\n'))
nextChar++;
skipLF = false;
omitLF = false; charLoop:
for (i = nextChar; i < nChars; i++) {
c = cb[i];
if ((c == '\n') || (c == '\r')) {
eol = true;
break charLoop;
}
} startChar = nextChar;
nextChar = i; if (eol) {
String str;
if (s == null) {
str = new String(cb, startChar, i - startChar);
} else {
s.append(cb, startChar, i - startChar);
str = s.toString();
}
nextChar++;
if (c == '\r') {
skipLF = true;
}
return str;
} if (s == null)
s = new StringBuffer(defaultExpectedLineLength);
s.append(cb, startChar, i - startChar);
}
}
}
同样是调用了fill(),不同的是readLine在未读到/r与/n之前都会一直阻塞。
CharArrayReader:
直接通过现有缓冲区读数据,不存在阻塞等问题。
InputStreamReader:
使用StreamDecoder进行read。
PipedReader:
同样是使用两个线程进行操作,同PipedInputStream。
public synchronized int read(char cbuf[], int off, int len) throws IOException {
if (!connected) {
throw new IOException("Pipe not connected");
} else if (closedByReader) {
throw new IOException("Pipe closed");
} else if (writeSide != null && !writeSide.isAlive()
&& !closedByWriter && (in < 0)) {
throw new IOException("Write end dead");
} if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
} /* possibly wait on the first character */
int c = read();
if (c < 0) {
return -1;
}
cbuf[off] = (char)c;
int rlen = 1;
while ((in >= 0) && (--len > 0)) {
cbuf[off + rlen] = buffer[out++];
rlen++;
if (out >= buffer.length) {
out = 0;
}
if (in == out) {
/* now empty */
in = -1;
}
}
return rlen;
}
StringReader:
使用现有字符串进行操作,所以也不存在阻塞。
public int read(char cbuf[], int off, int len) throws IOException {
synchronized (lock) {
ensureOpen();
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
if (next >= length)
return -1;
int n = Math.min(length - next, len);
str.getChars(next, next + n, cbuf, off);
next += n;
return n;
}
}
因为上边这些都是通过while死循环来进行锁死的,所以在使用到这些的时候建议放在一个单独的线程中,以免影响程序正常运行。
四、DatagramSocket的receive与send
receive方法在接收到数据报前一直阻塞。
public synchronized void receive(DatagramPacket p) throws IOException {
synchronized (p) {
if (!isBound())
bind(new InetSocketAddress(0));
if (connectState == ST_NOT_CONNECTED) {
SecurityManager security = System.getSecurityManager();
……
……
}
if (connectState == ST_CONNECTED_NO_IMPL) {
boolean stop = false;
……
……
}
getImpl().receive(p);
}
}
两个锁,一个锁当前对象,一个锁DatagramPacket。阻塞线程的操作是交给getImpl().receive(p)来进行的。
对于send()方法:
public void send(DatagramPacket p) throws IOException {
InetAddress packetAddress = null;
synchronized (p) {
只锁了DatagramPacket,所以在不使用同一个DatagramPacket来发送和接收数据的情况下,可以使用同一个DatagramSocket对象在不同线程中进行发送和接收操作。
[原创]java:Stream、Socket等源码分析的更多相关文章
- Java并发指南10:Java 读写锁 ReentrantReadWriteLock 源码分析
Java 读写锁 ReentrantReadWriteLock 源码分析 转自:https://www.javadoop.com/post/reentrant-read-write-lock#toc5 ...
- 【死磕 Java 集合】— ConcurrentSkipListMap源码分析
转自:http://cmsblogs.com/?p=4773 [隐藏目录] 前情提要 简介 存储结构 源码分析 主要内部类 构造方法 添加元素 添加元素举例 删除元素 删除元素举例 查找元素 查找元素 ...
- java线程池ThreadPoolExector源码分析
java线程池ThreadPoolExector源码分析 今天研究了下ThreadPoolExector源码,大致上总结了以下几点跟大家分享下: 一.ThreadPoolExector几个主要变量 先 ...
- 死磕 java集合之DelayQueue源码分析
问题 (1)DelayQueue是阻塞队列吗? (2)DelayQueue的实现方式? (3)DelayQueue主要用于什么场景? 简介 DelayQueue是java并发包下的延时阻塞队列,常用于 ...
- 死磕 java集合之PriorityBlockingQueue源码分析
问题 (1)PriorityBlockingQueue的实现方式? (2)PriorityBlockingQueue是否需要扩容? (3)PriorityBlockingQueue是怎么控制并发安全的 ...
- 死磕 java集合之PriorityQueue源码分析
问题 (1)什么是优先级队列? (2)怎么实现一个优先级队列? (3)PriorityQueue是线程安全的吗? (4)PriorityQueue就有序的吗? 简介 优先级队列,是0个或多个元素的集合 ...
- 死磕 java集合之CopyOnWriteArraySet源码分析——内含巧妙设计
问题 (1)CopyOnWriteArraySet是用Map实现的吗? (2)CopyOnWriteArraySet是有序的吗? (3)CopyOnWriteArraySet是并发安全的吗? (4)C ...
- 死磕 java集合之LinkedHashSet源码分析
问题 (1)LinkedHashSet的底层使用什么存储元素? (2)LinkedHashSet与HashSet有什么不同? (3)LinkedHashSet是有序的吗? (4)LinkedHashS ...
- 死磕 java集合之ConcurrentHashMap源码分析(三)
本章接着上两章,链接直达: 死磕 java集合之ConcurrentHashMap源码分析(一) 死磕 java集合之ConcurrentHashMap源码分析(二) 删除元素 删除元素跟添加元素一样 ...
- 死磕 java集合之ArrayDeque源码分析
问题 (1)什么是双端队列? (2)ArrayDeque是怎么实现双端队列的? (3)ArrayDeque是线程安全的吗? (4)ArrayDeque是有界的吗? 简介 双端队列是一种特殊的队列,它的 ...
随机推荐
- css3作3D旋转视频展示
代码如下: <!doctype html> <html lang="en"> <head> <meta charset="UTF ...
- 参数化防SQL注入
private void AddStudent(){ string strName =txtName.Text.Trim(); string strPwd = txtPwd.Text.Trim(); ...
- jinja2的一些用法
1.split用法以及乘法运算 {% set user_list=l.users.split(',') %} <tr> <td>{{ l.name }}</td> ...
- elang和python互通的例子
抄袭自http://www.erlangsir.com/2011/04/14/python-%E5%92%8Cerlang%E4%BA%92%E9%80%9A%E4%BE%8B%E5%AD%90/ t ...
- centos7防火墙 启动和关闭
CentOS 7.0默认使用的是firewall作为防火墙,这里改为iptables防火墙.firewall:systemctl start firewalld.service #启动firewa ...
- Go基本语句
递增递减语句 在GO中,++与--是作为语句而并不是作为表达式 package main import "fmt" func main() { a:= //a=a++ //语句而非 ...
- 蓝桥杯 算法训练 ALGO-50 数组查找及替换
算法训练 数组查找及替换 时间限制:1.0s 内存限制:512.0MB 问题描述 给定某整数数组和某一整数b.要求删除数组中可以被b整除的所有元素,同时将该数组各元素按从小到大排序.如果数组元 ...
- linnx常用命令学习
ll命令就相当于ls -l. [-][rwx][r-x][r--] [-] 代表这个文件名为目录或文件(d为目录-为文件) [rwx]为:拥有人的权限(rwx为可读.可写.可执行) [r-x]为:同群 ...
- oracle查看表空间和物理文件大小
查看各表空间的使用情况 select a.tablespace_name,a.bytes/1024/1024 "Sum MB",(a.bytes-b.bytes)/1024/102 ...
- C#使用protobuf
C# protobuf的使用方法 通过.proto文件导出C#支持的.cs类文件 protocolbuffer(以下简称PB)是google 的一种数据交换的格式,它独立于语言,独立于平台.googl ...