Java:终结器防卫者,顺便看一下 C# 如何做的。
背景
多数情况我们不需要重写 finalize 方法,只有当我们需要持有未托管资源的时候才需要,而此时重写 finalize 方法,只是作为一个“安全网”,不能作为常规的资源释放模式,必须提供显式的释放方法,如:close。
如果某个类型重写了 finalize 方法,但是这个类型是可以继承的,这就要求所有的子类如果也重写了 finalize,就必须要调用父类的 finalize 方法,我们有三种策略:
- 按照约定。
- 终结器防卫者。
- 模板方法模式。
本文就介绍第 2 种模式,此模式是昨天看《Effective Java 第二版》时学习的,本文后面会介绍 C# 是如何做的。
Java版:终结器防卫者
测试代码
注意看注释,我就不多说了。
1 public class Program { 2 3 public static void main(String[] args) throws InterruptedException { 4 { 5 new CustomResourceOwner().doSomeThing(); 6 } 7 8 System.gc(); 9 10 System.out.println("程序结束!"); 11 } 12 } 13 14 class ResourceOwnerBase { 15 // 可以将父类中 finalize 的代码放到守卫者里,一定会被调用的。 16 @SuppressWarnings("unused") 17 private final Object finalizeGuarder = new Object() { 18 @Override 19 public void finalize() { 20 System.out.println("在资源守卫者中销毁父类!"); 21 } 22 }; 23 24 // 子类可能故意不调用父类! 25 @Override 26 public void finalize() { 27 System.out.println("销毁父类!"); 28 } 29 } 30 31 final class CustomResourceOwner extends ResourceOwnerBase { 32 @Override 33 public void finalize() { 34 System.out.println("销毁子类!"); 35 36 // 故意不调用父类! 37 // super.finalize(); 38 } 39 40 public void doSomeThing() { 41 System.out.println("随便做点工作!"); 42 } 43 }
输出结果
1 随便做点工作! 2 程序结束! 3 在资源守卫者中销毁父类! 4 销毁子类!
说明
因为终结器防卫者只被资源拥有者持有,当资源拥有者变为垃圾的时候,终结器防卫者也会变为垃圾。
C#版:“终结器防卫者”
测试代码
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.IO; 7 8 namespace DisposeStudy 9 { 10 class Program 11 { 12 static void Main() 13 { 14 { 15 var res = new CustomResourceOwner(IntPtr.Zero); 16 res.DoSomeThing(); 17 } 18 } 19 } 20 21 class ResourceOwnerBase : IDisposable 22 { 23 private bool _disposed; 24 private readonly FileStream _fileStream; 25 private IntPtr _handle; 26 27 protected ResourceOwnerBase(IntPtr handle) 28 { 29 _handle = handle; 30 _fileStream = File.OpenRead(@"E:\Coding\HappyStudy\DisposeStudy\DisposeStudy\Program.cs"); 31 } 32 33 protected bool Disposed 34 { 35 get { return _disposed; } 36 } 37 38 public void Dispose() 39 { 40 Dispose(true); 41 42 GC.SuppressFinalize(this); 43 } 44 45 protected virtual void Dispose(bool disposing) 46 { 47 if (Disposed) 48 { 49 if (disposing) 50 { 51 _fileStream.Dispose(); 52 } 53 54 CloseHandle(_handle); 55 _handle = IntPtr.Zero; 56 57 _disposed = true; 58 } 59 } 60 61 ~ResourceOwnerBase() 62 { 63 Console.WriteLine("父类析构方法!"); 64 Dispose(false); 65 } 66 67 [System.Runtime.InteropServices.DllImport("Kernel32")] 68 private extern static Boolean CloseHandle(IntPtr handle); 69 } 70 71 sealed class CustomResourceOwner : ResourceOwnerBase 72 { 73 public CustomResourceOwner(IntPtr handle) 74 : base(handle) 75 { 76 } 77 78 public void DoSomeThing() 79 { 80 if (Disposed) 81 { 82 throw new ObjectDisposedException("资源已经消耗了,不能执行此操作!"); 83 } 84 85 Console.WriteLine("随便做点工作!"); 86 } 87 88 ~CustomResourceOwner() 89 { 90 Console.WriteLine("子类析构方法!"); 91 } 92 } 93 }
输出结果
说明
让我们看看编译器帮我们做了什么工作:
看完大家就明白了,C#在编译器层面保证了子类的终结器一定会调用父类的终结器。
备注
同时学习 C# 和 Java 是一件挺快乐的事情。