Bạn phải phân biệt giữa việc thực thi thường xuyên của cùng một trang web gọi , đối với lambda không trạng thái hoặc lambda có trạng thái và việc sử dụng thường xuyên một phương thức tham chiếu đến cùng một phương thức (bởi các trang web gọi khác nhau).
Hãy xem các ví dụ sau:
Runnable r1=null;
for(int i=0; i<2; i++) {
Runnable r2=System::gc;
if(r1==null) r1=r2;
else System.out.println(r1==r2? "shared": "unshared");
}
Ở đây, cùng một trang web gọi được thực thi hai lần, tạo ra một lambda không trạng thái và việc triển khai hiện tại sẽ được in "shared"
.
Runnable r1=null;
for(int i=0; i<2; i++) {
Runnable r2=Runtime.getRuntime()::gc;
if(r1==null) r1=r2;
else {
System.out.println(r1==r2? "shared": "unshared");
System.out.println(
r1.getClass()==r2.getClass()? "shared class": "unshared class");
}
}
Trong ví dụ thứ hai này, cùng một trang web gọi được thực thi hai lần, tạo ra một lambda chứa tham chiếu đến một Runtime
thể hiện và việc triển khai hiện tại sẽ in ra "unshared"
nhưng "shared class"
.
Runnable r1=System::gc, r2=System::gc;
System.out.println(r1==r2? "shared": "unshared");
System.out.println(
r1.getClass()==r2.getClass()? "shared class": "unshared class");
Ngược lại, trong ví dụ cuối cùng là hai trang web cuộc gọi khác nhau tạo ra một tham chiếu phương thức tương đương nhưng khi 1.8.0_05
nó sẽ in "unshared"
và "unshared class"
.
Đối với mỗi tham chiếu phương thức hoặc biểu thức lambda, trình biên dịch sẽ phát ra một invokedynamic
lệnh tham chiếu đến phương thức bootstrap do JRE cung cấp trong lớp LambdaMetafactory
và các đối số tĩnh cần thiết để tạo ra lớp triển khai lambda mong muốn. Nó được để lại cho JRE thực tế mà meta factory tạo ra nhưng nó là một hành vi được chỉ định của invokedynamic
lệnh để ghi nhớ và sử dụng lại CallSite
thể hiện được tạo trong lần gọi đầu tiên.
JRE hiện tại tạo ra một ConstantCallSite
chứa một MethodHandle
đối tượng không đổi cho các lambdas không trạng thái (và không có lý do gì có thể tưởng tượng để làm điều đó theo cách khác). Và các tham chiếu phương thức đến static
phương thức luôn không trạng thái. Vì vậy, đối với lambdas không trạng thái và các trang web gọi đơn lẻ, câu trả lời phải là: không lưu vào bộ nhớ cache, JVM sẽ làm được và nếu không, nó phải có những lý do chính đáng mà bạn không nên phản đối.
Đối với this::func
lambda có tham số và là lambda có tham chiếu đến this
cá thể, mọi thứ hơi khác một chút. JRE được phép lưu vào bộ nhớ cache của chúng nhưng điều này có nghĩa là duy trì một số loại Map
giữa các giá trị tham số thực tế và lambda kết quả có thể tốn kém hơn so với việc chỉ tạo lại cá thể lambda có cấu trúc đơn giản đó. JRE hiện tại không lưu vào bộ nhớ cache các cá thể lambda có trạng thái.
Nhưng điều này không có nghĩa là lớp lambda được tạo ra mọi lúc. Nó chỉ có nghĩa là call-site được giải quyết sẽ hoạt động giống như một cấu trúc đối tượng bình thường khởi tạo lớp lambda đã được tạo trong lần gọi đầu tiên.
Những điều tương tự áp dụng cho các tham chiếu phương thức đến cùng một phương pháp đích được tạo bởi các trang web gọi khác nhau. JRE được phép chia sẻ một cá thể lambda duy nhất giữa chúng nhưng trong phiên bản hiện tại thì không, hầu hết có thể là do không rõ liệu bảo trì bộ nhớ cache có thành công hay không. Ở đây, ngay cả các lớp được tạo có thể khác nhau.
Vì vậy, bộ nhớ đệm như trong ví dụ của bạn có thể giúp chương trình của bạn làm những việc khác so với khi không có. Nhưng không nhất thiết phải hiệu quả hơn. Đối tượng được lưu trong bộ nhớ cache không phải lúc nào cũng hiệu quả hơn đối tượng tạm thời. Trừ khi bạn thực sự đo lường tác động hiệu suất do tạo lambda gây ra, bạn không nên thêm bất kỳ bộ nhớ đệm nào.
Tôi nghĩ, chỉ có một số trường hợp đặc biệt mà bộ nhớ đệm có thể hữu ích:
- chúng ta đang nói về rất nhiều trang web cuộc gọi khác nhau đề cập đến cùng một phương pháp
- lambda được tạo trong khởi tạo phương thức khởi tạo / lớp vì sau này trang sử dụng sẽ
- được gọi bởi nhiều chủ đề đồng thời
- bị hiệu suất thấp hơn của lời kêu gọi đầu tiên