本文相关的好文推荐:
- Java线程的6种状态及切换(透彻讲解)
- 自定义线程池
- NIO的三大组件
- 深入浅出NIO之Selector实现原理
- 写时复制,写时拷贝,写时分裂,Copy on write
- Copy On Write机制了解一下
线程状态图 :
图来自Java线程的6种状态及切换(透彻讲解)
初始、就绪、运行、等待、阻塞(synchronized)、终止。
Thread.sleep(millis)一定时间后自动恢复执行,不释放对象锁。
Object.wait()直接释放锁,一直等待,知道被notify()/notifyAll()唤醒。
Thread1.join()表示运行的线程阻塞自己,等待Thread1执行完,再执行下面语句。
yield()让出cpu,不释放对象锁,进入就绪态,随时可能再次运行。
只有运行态和就绪态之间涉及到CPU资源的切换。(即yield())
Thread开启的3种方式
Thread、Runnable、Callable
- Thread
最简单的一种,直接继承Thread,重写run方法。主类中new ThreadName().start();则进去就绪态。 -
Runnable
- 实现Runnable接口,重写run。主类中new RunnableName().start();
- 实现Runnable接口,重写run。主类中new Thread(new Runnable()).start();
- Thread类还有其他重载方法,如传入ThreadGoup,Runnable初始执行接口等等,以上两个最常用。
Thread类也是实现了Runnable接口,实际上都是调用的Runnable接口的run()方法。
-
Callable
实现Callable接口,重写call()方法。创建FutureTask对象new FutureTask<>(CallableName);再new Thread(FutureTak).start();即可。FutureTask间接实现了Runnable和Future,所以实际调用的还是Thread(Runnable)构造方法。Callable接口只有一个方法V call();
Future接口包含cancel、isCancelled、isDone、get方法,用户获取返回值。(Futuretask对Future接口中的方法进行了重写)
Executor管理线程————线程池
- newFixedThreadPool固定大小的线程池
- newCachedThreadPool可扩展的
- newScheduledThreadPool可调度的,运行一次,重复运行等
- newSingleThreadPool单线程池
- newWorkStealingPool窃取别的线程池的线程
使用:
ExecutorService executor = Executors.newFixedThreadPool(3);
Future future = excutor.submit(Callable);//加入线程池
future.get();//获取返回值
好文推荐:自定义线程池
后台线程deamon
定义:程序运行时在后台提供通用服务的线程,这种线程并不属于不可或缺的部分。后台线程运行的前提是必须有至少一个非后台线程运行。
Thread.setDeamon(ture);必须在start()前设置。
java.util.concurrent包
-
CountDownLatch
同步多个任务,可以设置一个初值,任何在这个对象上调用wait()都将被阻塞,知道值变成0,每个任务执行完调用countDown都使值减1。初始值不能重置,只能一直减下去。CyclicBarrier可以重置。
- CyclicBarrier
用于并发任务等待(类似join),如:idm多线程下载文件,最后进行文件整理。可以重置初始值。 - DelayedQueue
用于延时队列(延时取出元素),队头的延迟最长。 - PriorityBlockingQueue
优先级阻塞队列 - SemaPhore
信号量,允许n个任务同时访问。
安全失败的原理
就是CopyOnWriteArrayList方法。
java.util.concurrent包下面的所有的类都是安全失败的
快速失败的异常:ConcurrentModificationException
Copy On Write(写时拷贝)更准确地来说是一种思想。在文件没有被修改前,两个线程同时指向的同一文件;当发生修改时,就会立即产生一个副本,而另一个线程的文件保持不变。
Redis的两种持久化策略RDB、AOF都有使用Copy On Write这种思想。
相关文章推荐:
写时复制,写时拷贝,写时分裂,Copy on write
Copy On Write机制了解一下
乐观锁: version、CAS(compareAndSwap)(Atomic包、ReentrankLock就是具体实现)
悲观锁: synchroinized
java.nio.*
基于缓冲的流。
主要组成:Channel,Buffer,Selector
clear,flip,compact区别:
ByteBuffer中有pos、lim、cap来记录缓冲使用情况。
- clear: position=0,limit=capacity,【清空缓冲】
- flip: limit=postion;position=0;【读指针移动缓冲头】
- compact: 未读完的数据,压缩到缓冲头。【压缩未读完数据】
整体流程:获取通道,分配缓冲,clear,read一次,{flip,处理读到的数据,compact,read一次}
大括号{}中表示循环执行。
读的模板代码如下,基本大同小异。
void fileChannel(){
try {
FileInputStream fis = new FileInputStream("content.txt");
FileChannel channel = fis.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);//分配缓冲
//position置为0,limit置为capacity
byteBuffer.clear();
int read = channel.read(byteBuffer);
System.out.println(byteBuffer);
while (read!=-1){
//limit置成position最后的位值,再指针position调成0
//从头读。(position、capacity、limit)
byteBuffer.flip();
while(byteBuffer.hasRemaining()){
System.out.print((char)byteBuffer.get());
}
//把未读完的数据压缩至0-position,
//下一次从position到limit读
byteBuffer.compact();
read=channel.read(byteBuffer);
}
channel.close();
fis.close();
}catch (IOException e){
e.printStackTrace();
}
}
fileChannel还有tryLock(),lock()和release()方法。tryLock为非阻塞式,得不到锁就立马返回
IO多路复用——selector【NIO的重点】
基于非阻塞模式,一个选择器管理多个Channel。
阻塞模式BIO,在accept()
这里会阻塞,知道收到客户端的请求,看看代码:
void BIOServerSocket(){
java.net.ServerSocket serverSocket =null;
InputStream is=null;
try {
serverSocket=new java.net.ServerSocket(8888);
byte[] revBuf= new byte[1024];
while (true){
Socket socket = serverSocket.accept();//阻塞
SocketAddress remoteSocketAddress = socket.getRemoteSocketAddress();
System.out.println("连接的IP为:"+remoteSocketAddress);
is = socket.getInputStream();
while(is.read(revBuf)!=-1){
System.out.print(new String(revBuf));
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
NIO的代码:
void selector(){
Selector selector= null;
ServerSocketChannel ssc =null;
try{
selector = Selector.open();
ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress("127.0.0.1",8888));
ssc.configureBlocking(false);
ssc.register(selector,SelectionKey.OP_ACCEPT); //channel注册到selector中
while(true){
Iterator iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey key = iterator.next(); //channel通信类型 connect/accept/read/write
iterator.remove();
if(key.isAcceptable())handleAccept(key);
if (key.isReadable())handleRead(key);
if (key.isWritable()&&key.isValid())handleWrite(key);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
整体流程:
- open一个selector
- open一个severSocketChannel,
- bind ip,
- configureBlocking(false) 通道设置成非阻塞
- ssr.register(selector,SelectionKey)注册到selector中
- 后面就是不断获取遍历器,根据key进行相应处理。
SeletionKey主要是存放着channel、seletor,取消,可读/可写,添加附属信息,获取信息等操作;以及四个操作关键字常量 读、写、连接、收。
自己能力和精力有限,推荐再阅读完下面NIO及IO多路复用相关优秀博客:
NIO的三大组件
深入浅出NIO之Selector实现原理
IO多路复用select、poll、epoll
同步、异步、阻塞、非阻塞