Lý do tại sao không được phép đồng bộ hóa trong các phương thức giao diện Java 8 là gì?


210

Trong Java 8, tôi có thể dễ dàng viết:

interface Interface1 {
    default void method1() {
        synchronized (this) {
            // Something
        }
    }

    static void method2() {
        synchronized (Interface1.class) {
            // Something
        }
    }
}

Tôi sẽ nhận được ngữ nghĩa đồng bộ hóa đầy đủ mà tôi có thể sử dụng trong các lớp. Tuy nhiên, tôi không thể sử dụng công cụ synchronizedsửa đổi trên các khai báo phương thức:

interface Interface2 {
    default synchronized void method1() {
        //  ^^^^^^^^^^^^ Modifier 'synchronized' not allowed here
    }

    static synchronized void method2() {
        // ^^^^^^^^^^^^ Modifier 'synchronized' not allowed here
    }
}

Bây giờ, người ta có thể lập luận rằng hai giao diện hoạt động theo cùng một cách ngoại trừ việc Interface2thiết lập một hợp đồng trên method1()và trên method2(), mạnh hơn một chút so với những gì Interface1. Tất nhiên, chúng tôi cũng có thể lập luận rằng defaultviệc triển khai không nên đưa ra bất kỳ giả định nào về trạng thái triển khai cụ thể hoặc từ khóa như vậy đơn giản sẽ không làm giảm sức nặng của nó.

Câu hỏi:

Lý do tại sao nhóm chuyên gia JSR-335 quyết định không hỗ trợ synchronizedcác phương thức giao diện là gì?


1
Đồng bộ hóa là một hành vi triển khai và nó thay đổi kết quả mã byte cuối cùng được thực hiện bởi trình biên dịch để nó có thể được sử dụng bên cạnh một mã. Nó không có ý nghĩa trong khai báo phương pháp. Sẽ khó hiểu những gì có trình biên dịch tạo ra nếu được đồng bộ hóa trên lớp trừu tượng.
Martin Strejc

@MartinStrejc: Đó có thể là một lời giải thích cho việc bỏ qua default synchronized, nhưng không nhất thiết phải như vậy static synchronized, mặc dù tôi sẽ chấp nhận rằng cái sau có thể đã bị bỏ qua vì lý do thống nhất.
Lukas Eder

1
Tôi không chắc chắn nếu câu hỏi này thêm bất kỳ giá trị nào vì công cụ synchronizedsửa đổi có thể được ghi đè trong các lớp con, do đó sẽ chỉ có vấn đề nếu có một cái gì đó là phương thức mặc định cuối cùng. (Câu hỏi khác của bạn)
skiwi

@skiwi: Đối số ghi đè là không đủ. Các lớp con có thể ghi đè các phương thức được khai báo synchronizedtrong các siêu lớp, loại bỏ hiệu quả đồng bộ hóa. Tuy nhiên, tôi sẽ không ngạc nhiên rằng việc không hỗ trợ synchronizedvà không hỗ trợ finalcó liên quan, tuy nhiên, có thể do nhiều kế thừa (ví dụ: thừa kế void x() synchronized void x() , v.v.). Nhưng đó là suy đoán. Tôi tò mò về một lý do có thẩm quyền, nếu có.
Lukas Eder

2
>> "Các lớp con có thể ghi đè các phương thức được khai báo đồng bộ hóa trong các siêu lớp, loại bỏ hiệu quả đồng bộ hóa" ... chỉ khi chúng không gọi supermà yêu cầu thực hiện lại đầy đủ và có thể truy cập vào các thành viên tư nhân. Btw, có một lý do những phương thức đó được gọi là "người bảo vệ" - chúng có mặt để cho phép dễ dàng thêm các phương thức mới.
bestsss

Câu trả lời:


260

Mặc dù lúc đầu, có vẻ như rõ ràng là người ta muốn hỗ trợ công cụ synchronizedsửa đổi trên các phương thức mặc định, nhưng hóa ra việc làm như vậy sẽ nguy hiểm và do đó bị cấm.

Các phương thức được đồng bộ hóa là một tốc ký cho một phương thức hoạt động như thể toàn bộ cơ thể được đặt trong một synchronizedkhối có đối tượng khóa là máy thu. Có vẻ hợp lý khi mở rộng ngữ nghĩa này sang các phương thức mặc định; Rốt cuộc, chúng cũng là các phương thức ví dụ với một máy thu. (Lưu ý rằng synchronizedphương pháp này là hoàn toàn tối ưu hóa cú pháp; họ không cần thiết, chúng tôi chỉ nhỏ gọn hơn tương ứng synchronizedkhối Có một lập luận hợp lý phải được thực hiện rằng đây là một cú pháp tối ưu hóa sớm ở nơi đầu tiên, và đó là phương pháp đồng bộ. gây ra nhiều vấn đề hơn họ giải quyết, nhưng con tàu đó đã đi thuyền từ lâu.)

Vậy, tại sao chúng nguy hiểm? Đồng bộ hóa là về khóa. Khóa là về việc phối hợp truy cập chia sẻ đến trạng thái có thể thay đổi. Mỗi đối tượng nên có chính sách đồng bộ hóa để xác định khóa nào bảo vệ biến trạng thái nào. (Xem Java đồng thời trong thực tiễn , phần 2.4.)

Nhiều đối tượng sử dụng làm chính sách đồng bộ hóa của họ, Mẫu màn hình Java (JCiP 4.1), trong đó trạng thái của một đối tượng được bảo vệ bởi khóa nội tại của nó. Không có gì kỳ diệu hay đặc biệt về mẫu này, nhưng nó thuận tiện và việc sử dụng synchronizedtừ khóa trên các phương thức mặc nhiên giả định mẫu này.

Đó là lớp sở hữu trạng thái được xác định chính sách đồng bộ hóa của đối tượng đó. Nhưng các giao diện không sở hữu trạng thái của các đối tượng mà chúng được trộn lẫn vào nhau. Vì vậy, sử dụng một phương thức được đồng bộ hóa trong một giao diện giả định một chính sách đồng bộ hóa cụ thể, nhưng một giao thức mà bạn không có cơ sở hợp lý để giả định, vì vậy có thể đó là trường hợp việc sử dụng đồng bộ hóa không cung cấp thêm sự an toàn của luồng nào (bạn có thể đồng bộ hóa trên khóa sai). Điều này sẽ mang lại cho bạn cảm giác sai lầm về sự tự tin rằng bạn đã làm gì đó về an toàn luồng và không có thông báo lỗi nào cho bạn biết rằng bạn đang giả sử chính sách đồng bộ hóa sai.

Nó đã đủ khó để duy trì chính sách đồng bộ hóa cho một tệp nguồn duy nhất; thậm chí còn khó hơn để đảm bảo rằng một lớp con tuân thủ chính xác chính sách đồng bộ hóa được xác định bởi siêu lớp của nó. Cố gắng làm như vậy giữa các lớp được ghép lỏng lẻo như vậy (một giao diện và có thể nhiều lớp thực hiện nó) sẽ gần như không thể và rất dễ bị lỗi.

Với tất cả những lập luận chống lại, đối số sẽ là gì? Có vẻ như họ chủ yếu làm cho các giao diện hoạt động giống như các đặc điểm. Mặc dù đây là một mong muốn dễ hiểu, trung tâm thiết kế cho các phương thức mặc định là tiến hóa giao diện, không phải là "Đặc điểm--". Trường hợp cả hai có thể đạt được một cách nhất quán, chúng tôi cố gắng làm như vậy, nhưng khi một trong những xung đột với nhau, chúng tôi phải chọn theo hướng có lợi cho mục tiêu thiết kế chính.


26
Cũng lưu ý rằng trong JDK 1.1, công cụ synchronizedsửa đổi phương thức xuất hiện trong đầu ra javadoc, khiến mọi người hiểu lầm rằng đó là một phần của đặc tả. Điều này đã được sửa trong JDK 1.2. Ngay cả khi nó xuất hiện trên một phương thức công khai, công cụ synchronizedsửa đổi là một phần của việc thực hiện, không phải là hợp đồng. (Lý do và cách xử lý tương tự đã xảy ra đối với người nativesửa đổi.)
Stuart Marks

15
Một lỗi phổ biến trong các chương trình Java ban đầu là rắc đủ synchronizedvà xâu các thành phần an toàn xung quanh và bạn đã có một chương trình gần như an toàn luồng. Vấn đề là điều này thường hoạt động tốt nhưng nó đã bị hỏng theo những cách đáng ngạc nhiên và dễ vỡ. Tôi đồng ý rằng việc hiểu cách khóa của bạn hoạt động là chìa khóa cho các ứng dụng mạnh mẽ.
Peter Lawrey

10
@BrianGoetz Lý do rất tốt. Nhưng tại sao được synchronized(this) {...}phép trong một defaultphương pháp? (Như được hiển thị trong câu hỏi của Lukas.) Không cho phép phương thức mặc định sở hữu trạng thái của lớp thực hiện sao? Chúng ta cũng không muốn ngăn chặn điều đó sao? Chúng ta sẽ cần một quy tắc FindBugs để tìm các trường hợp mà các nhà phát triển không hiểu biết làm điều đó chứ?
Geoffrey De Smet

17
@Geoffrey: Không, không có lý do gì để hạn chế điều này (mặc dù nó luôn được sử dụng cẩn thận.) Khối đồng bộ yêu cầu tác giả chọn rõ ràng một đối tượng khóa; điều này cho phép họ tham gia vào chính sách đồng bộ hóa của một số đối tượng khác, nếu họ biết chính sách đó là gì. Phần nguy hiểm là giả định rằng đồng bộ hóa trên 'cái này' (đó là những gì phương thức đồng bộ hóa thực hiện) thực sự có ý nghĩa; điều này cần phải là một quyết định rõ ràng hơn. Điều đó nói rằng, tôi hy vọng các khối đồng bộ hóa trong các phương thức giao diện là khá hiếm.
Brian Goetz

6
@GeoffreyDeSmet: Vì lý do tương tự bạn có thể làm, ví dụ synchronized(vector). Nếu bạn muốn an toàn, bạn không bao giờ nên sử dụng một đối tượng công khai (chẳng hạn như thischính nó) để khóa.
Yogu

0
public class ParentSync {

public synchronized void parentStart() {
    System.out.println("I am " + this.getClass() + " . parentStarting. now:" + nowStr());
    try {
        Thread.sleep(30000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("I am " + this.getClass() + " . parentFinished. now" + nowStr());
}

private String nowStr() {
    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
}
}


public class SonSync1 extends ParentSync {
public void sonStart() {
    System.out.println("I am " + this.getClass() + ". sonStarting,calling parent now ... ");
    super.parentStart();
    System.out.println("I am " + this.getClass() + ". sonFinished");
}
}



public class SonSync2 extends ParentSync {

public void sonStart() {
    System.out.println("I am " + this.getClass() + ". sonStarting,calling parent now ... ");
    super.parentStart();
    System.out.println("I am " + this.getClass() + ". sonFinished");
}
}



public class SyncTest {
public static void main(String[] args) throws Exception {

    new Thread(() -> {
        new SonSync1().sonStart();
    }).start();

    new Thread(() -> {
        new SonSync2().sonStart();
    }).start();

    System.in.read();
}
}

kết quả:

I am class com.common.interface18_design.whynotsync_onmethod.SonSync1. sonStarting,calling parent now ... 
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2. sonStarting,calling parent now ... 
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2 . parentStarting. now:2019-04-18 09:50:08
I am class com.common.interface18_design.whynotsync_onmethod.SonSync1 . parentStarting. now:2019-04-18 09:50:08
I am class com.common.interface18_design.whynotsync_onmethod.SonSync1 . parentFinished. now2019-04-18 09:50:38
I am class com.common.interface18_design.whynotsync_onmethod.SonSync1. sonFinished
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2 . parentFinished. now2019-04-18 09:50:38
I am class com.common.interface18_design.whynotsync_onmethod.SonSync2. sonFinished

(xin lỗi vì đã sử dụng lớp cha làm ví dụ)

từ kết quả, chúng ta có thể biết rằng khóa lớp cha được sở hữu bởi mọi lớp con, đối tượng SonSync1 và SonSync2 có khóa đối tượng khác nhau. mỗi khóa là độc lập. Vì vậy, trong trường hợp này, tôi nghĩ rằng nó không nguy hiểm khi sử dụng đồng bộ hóa trong lớp cha hoặc giao diện chung. bất cứ ai có thể giải thích thêm về điều này?

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.