你的java代码可中断吗?(2)
你的java代码可中断吗?(2)
writeademo 发表于7个月前
你的java代码可中断吗?(2)
  • 发表于 7个月前
  • 阅读 14
  • 收藏 0
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

 

 

 

1.确保提交到线程池的任务可中断
 

原文:www.securecoding.cert.org,TPS02-J. Ensure thattasks submitted to a thread pool are interruptible。

  

为了能完全关闭线程池或者取消线程池中的个别任务,程序应提交支持使用Thread.interrupt()中断的任务到线程池。程序不应提交不支持中断的任务到线程池,除非是无阻塞且短时间运行的任务。

 

根据java.util.concurrent.ExecutorService.shutdownNow() Java API的描述:

尝试终止所有正在执行的任务,停止所有等待处理的任务,并且返回等待执行的任务列表…

 

除了尽最大努力尝试终止正在执行的任务之外,没有任何其他的保证。例如,典型实现是通过Thread.interrupt()进行取消,因此任何不能中断的任务可能永远不会被终止。

 

违规代码示例(Shutting Down Thread Pools)

如下是违规代码示例,提交SocketReader任务到PoolService声明的线程池中:

 

 

因为任务不支持Thread.interrupt()进行中断,且因为shutdown()方法必须等到所有正执行的任务执行完成,所以shutdownNow()方法可能不能立即完全关闭线程池。

 

同样,某些不用Thread.interrupted() 而使用其他的机制来决定何时关闭的任务,其是无法响应shutdown()和shutdownNow()。例如,通过检查一个volatile标志位来决定是否可安全关闭的任务,对这些方法是无法响应的。THI05-J. Do not use Thread.stop() to terminate threads提供了使用标志位终止线程的更多信息。

 

合规解决方案(Submit InterruptibleTasks)

如下合规解决方案定义了SocketReader的一个可中断的版本,实例化并提交到线程池:

 

 

2.持有锁时不要执行阻塞操作

原文:www.securecoding.cert.org,LCK09-J.Do not perform operations that canblock while holding a lock

 

持有锁时执行耗时或阻塞操作会严重降低系统性能,且可能导致饥饿。此外,死锁会导致相互依赖的线程无限阻塞。阻塞操作包括网络、文件、控制台I/O(如Console.readLine())和对象序列化,无限延迟线程执行也算是一种阻塞操作(如通过Thread.sleep())。因此,在持有锁时,程序不应该执行阻塞操作。

 

当Java虚拟机(JVM)与不可靠网络、文件I/O交互时,可能引起巨大的性能损耗。在这种情况下,应避免持有锁时执行网络上的文件I/O操作。在等待输出流锁或I/O完成事件的文件操作(如日志)会阻塞,可以在一个单独的线程中执行来加速任务处理。记录请求日志可以添加到一个队列,与直接文件I/O相比,队列put()操作会增加一点小的开销。

 

违规代码示例(Deferring a Thread)

如下违规代码示例定义了一个接受timeout参数的工具类:

 

方法是synchronized,当线程休眠后其他线程不能使用synchronized的方法。当前对象的监视器并未被释放,这是因为Thread.sleep() 方法没有同步语义。

 

合规解决方案(Intrinsic Lock)                  

如下合规解决方案定义了doSomething()方法,其带有一个timeout参数而不是time参数。使用Object.wait()代替Thread.sleep(),Object.wait()允许设置一个通知的超时周期,超时后可以唤醒线程。

 

进入到等待状态后当前对象的监视器会立即释放。当超时后,线程会在重新获取到当前对象的监视器后恢复继续执行。

 

根据Object类Java API的描述:

wait方法,它将当前线程放入到该对象的等待集,且只有该对象能释放锁;当线程等待时,当前线程上的任何synchronized的其他对象将保持锁定。

 

此方法应仅由该对象监视器拥有者线程调用。

 

程序必须确保持有其他对象锁的线程在进入等待状态之前适时释放这些锁。等待和通知的一些额外帮助可见 THI03-J. Always invoke wait() and await() methods insidea loop 和 THI02-J. Notify all waiting threads rather than a single thread。

 

违规代码示例(Network I/O)

如下违规代码示例定义了sendPage()方法,其从服务端向客户端发送一个Page对象。该方法是synchronized,在多线程请求并发访问时来来保护 pageBuff数组。

public class SendPage {
 private final int MAX_PAGE_SIZE = 10;
 Page[] pageBuff = new Page[MAX_PAGE_SIZE];

 public class Page {
  int code;
  String message;
  Object page;
  String pageName;

  public String getName() {
   // TODO Auto-generated method stub
   return pageName;
  }
 }

 public synchronized boolean sendPage(Socket socket, String pageName) throws IOException {
  ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
  Page targetPage = null;
  for (Page page : pageBuff) {
   if (page.getName().compareTo(pageName) == 0) {
    targetPage = page;
   }
  }
  if (targetPage == null) {
   return false;
  }
  out.writeObject(targetPage);
  out.flush();
  out.close();
  return true;

 }
}

 

在synchronized的sendPage()方法中调用writeObject()会造成延迟和死锁,像在高延迟网络或当网络连接有损(丢包)情况时。

合规解决方案

如下合规解决方案将整个过程拆分为如下步骤:

1.    在数据结构上执行的操作需要同步;

2.    创建对象的副本用于发送;

3.    在单独的无同步方法中执行网络调用。

 

public class SendPageCanStop {
 private final int MAX_PAGE_SIZE = 10;
 Page[] pageBuff = new Page[MAX_PAGE_SIZE];

 public class Page {
  int code;
  String message;
  Object page;
  String pageName;

  public String getName() {
   // TODO Auto-generated method stub
   return pageName;
  }
 }

 public boolean sendPage(Socket socket, String pageName) throws IOException {
  Page targetPage = getPage(pageName);
  if (targetPage == null) {
   return false;
  }
  return deliverPage(socket, targetPage);
 }

 private synchronized Page getPage(String pageName) {
  Page targetPage = null;
  for (Page p : pageBuff) {
   if (p.getName().compareTo(pageName) == 0) {
    targetPage = p;
   }

  }
  return targetPage;
 }

 private boolean deliverPage(Socket socket, Page page) {
  ObjectOutputStream out = null;
  boolean result = true;
  try {
   out = new ObjectOutputStream(socket.getOutputStream());
   out.writeObject(page);
   out.flush();
   out.close();

  } catch (Exception e) {
   result = false;
  } finally {
   try {
    if (out != null) {
     out.close();
    }
   } catch (Exception e2) {
    result = false;
   }

  }
  return result;
 }

在该合规解决方案中,无同步的sendPage()方法调用同步的getPage()方法从pageBuff 数组获取请求的Page。在Page取出后,sendPage()调用无同步的deliverPage()方法将Page交付给客户端。

 

 

 

共有 人打赏支持
粉丝 18
博文 376
码字总数 157301
×
writeademo
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: