文档章节

你的java代码可中断吗?在连接中

writeademo
 writeademo
发布于 2017/05/31 11:44
字数 1722
阅读 16
收藏 0

原文:www.securecoding.cert.org,THI04-J. Ensure thatthreads performing blocking operations can be terminated。

来自开涛的博客:http://mp.weixin.qq.com/s/RPX9GygSuCx0f84BsZOIxw

 

在网络或文件I/O上操作阻塞的线程和任务,必须给调用者提供一种明确终止执行的机制,防止出现拒绝服务(DoS)漏洞。

 

违规代码示例(Blocking I/O, Volatile Flag)

该违规代码示例使用了 THI05-J. Do not use Thread.stop() to terminate threads中的建议,使用一个volatile变量done判断是否安全的终止了线程。 不过当线程被网络I/O上的readLine() 操作阻塞时,在网络I/O完成之前,它是不能响应心设置的done变量的。因此,线程终止操作可以被无限期的延迟,直到readLine()网络I/O完成。

 

public class SocketReader implements Runnable{

       private final Socket socket;

       private final BufferedReader in;

       private volatile boolean done=false;

       private final Object lock=new Object();

       public SocketReader(String host,int port) throws IOException{

              this.socket=new Socket(host,port);

              this.in=new BufferedReader(new InputStreamReader(this.socket.getInputStream()));

             

       }

       @Override

       public void run() {

              try{

                     synchronized(lock){

                            readData();

                     }

              }catch(IOException in){

                     //forward to handler

              }

       }

      

       public void readData() throws IOException{

              String string;

              while(!done&& (string=in.readLine())!=null){

                     //Blocks until end of stream(null)

              }

             

       }

       public void shutDown(){

              done=true;

       }

       public static void main(String[] args) throws IOException, InterruptedException {

              SocketReader reader=new SocketReader("somehost",25);

              Thread thread=new Thread(reader);

              thread.start();

              Thread.sleep(1000);

              reader.shutDown();

             

       }

 

}

 

 

违规代码示例(Blocking I/O, Interruptible)

该违规代码示例和上面的例子类似,但使用线程中断来终止线程。java.net.Socket上的网络I/O操作是阻塞的,线程中断引起的线程终止操作也可以被无限期的延迟。

 

public class SocketReader1 implements Runnable {

       // other methods...

 

       private final Socket socket;

       private final BufferedReader in;

       private final Object lock = new Object();

 

       public SocketReader1(String host, int port) throws IOException {

              this.socket = new Socket(host, port);

              this.in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));

 

       }

 

       public void readData() throws IOException {

              String string;

              while (!Thread.interrupted() && (string = in.readLine()) != null) {

                     // Blocks until end of stream(null)

 

              }

       }

 

       @Override

       public void run() {

              try {

                     synchronized (lock) {

                            readData();

                     }

              } catch (IOException in) {

                     // forward to handler

              }

       }

 

       public static void main(String[] args) throws IOException, InterruptedException {

              SocketReader reader = new SocketReader("somehost", 25);

              Thread thread = new Thread(reader);

              thread.start();

              thread.sleep(1000);

              thread.interrupt();

       }

 

}

 

 

合规解决方案(Close Socket Connection)

合规解决方案是通过在shutdown方法关闭socket来终止阻塞的网络I/O操作。当socket关闭后,readLine()方法会抛出SocketException异常,从而继续线程中的其他处理。注意,想要即保持连接存活,同时干净地立即停止线程,这是不可能的。

 

public class SocketReaderRight implements Runnable{

       private final Socket socket;

       private final BufferedReader in;

       private volatile boolean done=false;

       private final Object lock=new Object();

       public SocketReaderRight(String host,int port) throws IOException{

              this.socket=new Socket(host,port);

              this.in=new BufferedReader(new InputStreamReader(this.socket.getInputStream()));

             

       }

       @Override

       public void run() {

              try{

                     synchronized(lock){

                            readData();

                     }

              }catch(IOException in){

                     //forward to handler

              }

       }

      

       public void readData() throws IOException{

              String string;

              try {

                     while((string=in.readLine())!=null){

                            //Blocks until end of stream(null)

                     }

              } catch (Exception e) {

                     shutDown();

              }

             

             

       }

       public void shutDown() throws IOException{

              socket.close();

       }

       public static void main(String[] args) throws IOException, InterruptedException {

              SocketReader reader=new SocketReader("somehost",25);

              Thread thread=new Thread(reader);

              thread.start();

              Thread.sleep(1000);

              reader.shutDown();

             

       }

 

}

当从main()方法调用了shutdown()方法后,readData()中的finally块又调用了一次shutdown(),再次关闭socket。这种情况不用担心,当socket已经被关闭,再次关闭不作任何操作。

 

当执行异步I/O时,可以调用java.nio.channels.Selector的close()或者wakeup()来立即中断java.nio.channels.Selector上的阻塞操作。(译者注:可以参考笔者的下一篇)

 

当出现阻塞状态之后必须执行额外的操作,可以使用一个boolean值标识等待终止。当需要用这种标志补全代码时,shutdown()方法应设置标志为false从而让线程退出while循环。

 

合规解决方案(InterruptibleChannel)

该合规解决方案没使用Socket连接而是使用一个可中断channeljava.nio.channels.SocketChannel。如果线程执行的网络I/O在读取数据时使用Thread.interrupt() 方法中断,线程会收到一个ClosedByInterruptException异常,且channel立即关闭,线程状态状态也会被设置。

 

public class SocketReaderRight1 implements Runnable{

       private final SocketChannel sc;

       private final String host;

       private final int port;

 

       private final Object lock=new Object();

       public SocketReaderRight1(String host,int port) throws IOException{

              this.host=host;

              this.port=port;

              this.sc=SocketChannel.open(new InetSocketAddress(host,port)); 

             

       }

       @Override

       public void run() {

              ByteBuffer buf=ByteBuffer.allocate(1024);

             

              try{

                     synchronized(lock){

                            while(!Thread.interrupted()){

                                   sc.read(buf);

                                   //...

                            }

                     }

              }catch(IOException ie){

                     //forward to handler

              }

       }

      

 

       public static void main(String[] args) throws IOException, InterruptedException {

              SocketReader reader=new SocketReader("somehost",25);

              Thread thread=new Thread(reader);

              thread.start();

              Thread.sleep(1000);

              thread.interrupt();

             

       }

}

此技术中断当前线程。然而,它停止线程只是通过轮询 Thread.interrupted() 方法的线程状态状态并在线程中断状态时终止线程实现的。使用SocketChannel  时要确保while循环中的条件在收到中断时立即进行测试,即使读取通常是一个阻塞操作。同样地,调用 java.nio.channels.Selector 所在的阻塞线程上的 interrupt() 方法也导致线程被唤醒。

 

Database Connection中查询的中断

违规代码示例(Database Connection)

该不合规示例展示了一个线程安全的DBConnector类,其每个线程创建一个JDBC连接。每一个连接属于一个线程,不会与其他线程共享。这是一个常见的用例,因为JDBC连接有意设计为单线程。

 

 

public class DataBaseConnection implements Runnable {

       private final String query;

 

       DataBaseConnection(String query) {

              this.query = query;

       }

 

       @Override

       public void run() {

              Connection connection;

              try {

                     connection = DriverManager.getConnection("jdbc:driver:name", "username", "password");

                     Statement stmt = connection.createStatement();

                     ResultSet rs = stmt.executeQuery(query);

              } catch (Exception e) {

                     // TODO: handle exception

              }

 

       }

 

       public static void main(String[] args) throws InterruptedException {

              DataBaseConnection connector = new DataBaseConnection("suitable query");

              Thread thread = new Thread(connector);

              thread.start();

              thread.sleep(5000);

              thread.interrupt();

 

       }

 

}

数据库连接像socket,没有内在的可中断性。因此,该设计无法实现当相应的线程阻塞在一个慢查询上时,客户端通过关闭资源来尝试取消任务, 如一个join操作。

 

合规解决方案(Statement.cancel())

该合规解决方案使用ThreadLocal 包装连接,线程调用initialValue()方法获取一个唯一的连接实例。这种方法可以提供一个cancelStatement(),以便其他线程或客户端可以在需要时中断慢查询。 cancelStatement() 方法调用 Statement.cancel()方法实现。

 

public class DBConnectorRight implements Runnable{

      

        final String query;

       private volatile Statement stmt;

       DBConnectorRight(String query){

              this.query=query;

       }

       private static final ThreadLocal<Connection> connectionHolder=

                     new ThreadLocal<Connection>(){

              Connection connection=null;

              @Override

              public Connection initialValue(){

                     try {

                    connection=DriverManager.getConnection("jdbc:driver:name","username","password");

                     } catch (SQLException e) {

                            // Forward to handler

                     }

                     return connection;

              }

             

       };

       public Connection getConnection(){

              return connectionHolder.get();

             

       }

 

       public boolean cancelStatement(){

              Statement tmpStmt=stmt;

              if(tmpStmt!=null){

                     try {

                            tmpStmt.cancel();

                            return true;

                     } catch (Exception e) {

                            // Forword to handler

                     }

              }

              return false;

       }

       @Override

       public void run() {

              try {

                     if(getConnection()!=null){

                            stmt=getConnection().createStatement();

                     }

                     if(stmt==null||(stmt.getConnection()!=getConnection())){

                            throw new IllegalStateException();

                     }

                     ResultSet rs=stmt.executeQuery(query);

              } catch (Exception e) {

                     // forward to handler

              }

             

             

       }

       public static void main(String[] args) throws InterruptedException {

              DBConnectorRight connector=new DBConnectorRight("suitable query");

              Thread thread=new Thread(connector);

              thread.start();

              thread.sleep(4000);

              connector.cancelStatement();

       }

 

}

Statement.cancel() 方法取消查询需要数据库和驱动两者都支持(译者注:比如mysql通过发送KILL QUERY 命令进行取消)。如果数据库或驱动不支持取消,则取消查询是不可能的。

 

根据Java API,Statement接口文档:

默认情况下,每个Statement同一时刻尽可以打开一个ResultSet对象。因此,如果一个ResultSet对象上的读取与另一个上的读取是交替的,那么每个ResultSet对象必须由不同的Statement对象生成。

 

该合规解决方案可以确保只有一个与Statement关联的ResultSet属于一个实例,且只有一个线程能访问到查询结果。

 

本文转载自:http://mp.weixin.qq.com/s/RPX9GygSuCx0f84BsZOIxw

共有 人打赏支持
writeademo
粉丝 23
博文 516
码字总数 188548
作品 0
东城
多线程编程读书笔记之线程中断的本质

Java试图提供过抢占式限制中断,但问题多多,例如已被废弃的Thread.stop、Thread.suspend和 Thread.resume等。另一方面,出于Java应用代码的健壮性的考虑,降低了编程门槛,减少不清楚底层机...

刘学炜
2012/07/03
0
0
读书笔记之《Java并发编程的艺术》-并发编程基础

读书笔记部分内容来源书出版书,版权归本书作者,如有错误,请指正。 欢迎star、fork,读书笔记系列会同步更新 git https://github.com/xuminwlt/j360-jdk module j360-jdk-thread/me.j360....

Hi徐敏
2015/11/11
0
8
java远程调试与JVM调优工具

Java远程调试方法: 1、被调试程序当作调试服务器(本地主动连接远程服务器,需要用如下命令让远程服务器jvm开启调试模式)。 -Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8765 ...

chape
2013/12/24
0
0
Spring DataSource

何为DataSource DataSource 接口是 JDBC 2.0 API 中的新增内容,它提供了连接到数据源的另一种方法。 作为 工具的替代项, 对象是获取连接的首选方法。 实现 DataSource 接口的对象通常在基于...

冰雷卡尔
2012/06/04
0
1
Java并发编程笔记之基础总结(二)

一.线程中断 Java 中线程中断是一种线程间协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是需要被中断的线程根据中断状态自行处理。   1.void interrupt() 方法:中断线...

狂小白
07/17
0
0

没有更多内容

加载失败,请刷新页面

加载更多

20180920 rzsz传输文件、用户和用户组相关配置文件与管理

利用rz、sz实现Linux与Windows互传文件 [root@centos01 ~]# yum install -y lrzsz # 安装工具sz test.txt # 弹出对话框,传递到选择的路径下rz # 回车后,会从对话框中选择对应的文件传递...

野雪球
今天
2
0
OSChina 周四乱弹 —— 毒蛇当辣条

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @ 达尔文:分享花澤香菜/前野智昭/小野大輔/井上喜久子的单曲《ミッション! 健?康?第?イチ》 《ミッション! 健?康?第?イチ》- 花澤香菜/前野智...

小小编辑
今天
9
3
java -jar运行内存设置

java -Xms64m #JVM启动时的初始堆大小 -Xmx128m #最大堆大小 -Xmn64m #年轻代的大小,其余的空间是老年代 -XX:MaxMetaspaceSize=128m # -XX:CompressedClassSpaceSize=6...

李玉长
今天
4
0
Spring | 手把手教你SSM最优雅的整合方式

HEY 本节主要内容为:基于Spring从0到1搭建一个web工程,适合初学者,Java初级开发者。欢迎与我交流。 MODULE 新建一个Maven工程。 不论你是什么工具,选这个就可以了,然后next,直至finis...

冯文议
今天
2
0
RxJS的另外四种实现方式(四)——性能最高的库(续)

接上一篇RxJS的另外四种实现方式(三)——性能最高的库 上一篇文章我展示了这个最高性能库的实现方法。下面我介绍一下这个性能提升的秘密。 首先,为了弄清楚Most库究竟为何如此快,我必须借...

一个灰
今天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部