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

原文: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属于一个实例,且只有一个线程能访问到查询结果。

 

转载于:https://my.oschina.net/iioschina/blog/911609

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值