Tại sao BufferedInputStream sao chép một trường vào một biến cục bộ thay vì sử dụng trường trực tiếp


107

Khi tôi đọc mã nguồn từ java.io.BufferedInputStream.getInIfOpen(), tôi bối rối không hiểu tại sao nó lại viết mã như thế này:

/**
 * Check to make sure that underlying input stream has not been
 * nulled out due to close; if not return it;
 */
private InputStream getInIfOpen() throws IOException {
    InputStream input = in;
    if (input == null)
        throw new IOException("Stream closed");
    return input;
}

Tại sao nó lại sử dụng bí danh thay vì sử dụng intrực tiếp biến trường như bên dưới:

/**
 * Check to make sure that underlying input stream has not been
 * nulled out due to close; if not return it;
 */
private InputStream getInIfOpen() throws IOException {
    if (in == null)
        throw new IOException("Stream closed");
    return in;
}

Ai đó có thể đưa ra một lời giải thích hợp lý?


Trong đó Eclipse, bạn không thể tạm dừng trình gỡ lỗi trên một ifcâu lệnh. Có thể là một lý do cho biến bí danh đó. Chỉ muốn ném nó ra ngoài kia. Tôi suy đoán, tất nhiên.
Debosmit Ray

@DebosmitRay: Thực sự không thể tạm dừng trên iftuyên bố?
rkosegi

@rkosegi Trên phiên bản Eclipse của tôi, vấn đề tương tự như vấn đề này . Có thể không phải là một sự xuất hiện rất phổ biến. Và dù sao đi nữa, tôi không có ý nói điều đó (rõ ràng là một trò đùa tồi tệ). :)
Debosmit Ray

Câu trả lời:


119

Nếu bạn nhìn mã này ngoài ngữ cảnh thì không có lời giải thích tốt nào cho "bí danh" đó. Nó chỉ đơn giản là mã thừa hoặc kiểu mã kém.

Nhưng ngữ cảnh là BufferedInputStreammột lớp có thể được phân lớp và nó cần hoạt động trong bối cảnh đa luồng.

Đầu mối là inđược khai báo trong FilterInputStreamprotected volatile. Điều đó có nghĩa là có khả năng một lớp con có thể tiếp cận và gán nullcho in. Với khả năng đó, "bí danh" thực sự ở đó để ngăn chặn tình trạng chủng tộc.

Hãy xem xét mã không có "bí danh"

private InputStream getInIfOpen() throws IOException {
    if (in == null)
        throw new IOException("Stream closed");
    return in;
}
  1. Chuỗi cuộc gọi A getInIfOpen()
  2. Thread A đánh giá in == nullvà thấy rằng inkhông phải null.
  3. Chủ đề B gán nullcho in.
  4. Luồng A thực thi return in. Mà trả về nullalà a volatile.

"Bí danh" ngăn chặn điều này. Bây giờ chỉ inđược đọc một lần bởi luồng A. Nếu luồng B chỉ định nullsau khi luồng A có inthì không thành vấn đề. Luồng A sẽ ném một ngoại lệ hoặc trả về một giá trị không null (được đảm bảo).


11
Điều này cho thấy lý do tại sao protectedcác biến là xấu trong ngữ cảnh đa luồng.
Mick Mnemonic

2
Nó thực sự. Tuy nhiên, AFAIK các lớp này quay trở lại Java 1.0. Đây chỉ là một ví dụ khác về một quyết định thiết kế kém mà không thể sửa vì sợ phá vỡ mã khách hàng.
Stephen C

2
@StephenC Cảm ơn vì lời giải thích chi tiết +1. Vậy điều đó có nghĩa là, chúng ta không nên sử dụng protectedcác biến trong mã của mình nếu nó là đa luồng?
Madhusudana Reddy Sunnapu

3
@MadhusudanaReddySunnapu Bài học tổng thể là trong một môi trường mà nhiều luồng có thể truy cập vào cùng một trạng thái, bạn cần phải kiểm soát quyền truy cập đó bằng cách nào đó. Đó có thể là một biến private chỉ có thể truy cập thông qua setter, nó có thể là một trình bảo vệ cục bộ như thế này, nó có thể là bằng cách làm cho biến ghi một lần theo cách an toàn.
Chris Hayes

3
@sam - 1) Nó không cần phải giải thích tất cả các điều kiện và trạng thái chủng tộc. Mục đích của câu trả lời là chỉ ra lý do tại sao đoạn mã dường như không thể giải thích này lại cần thiết. 2) Làm thế nào như vậy?
Stephen C

20

Điều này là do lớp BufferedInputStreamđược thiết kế để sử dụng đa luồng.

Tại đây, bạn thấy phần khai báo của in , được đặt trong lớp cha FilterInputStream:

protected volatile InputStream in;

Vì nó là như vậy protected, giá trị của nó có thể được thay đổi bởi bất kỳ lớp con nào của FilterInputStream, bao gồm BufferedInputStreamvà các lớp con của nó. Ngoài ra, nó được khai báo volatile, có nghĩa là nếu bất kỳ luồng nào thay đổi giá trị của biến, thay đổi này ngay lập tức sẽ được phản ánh trong tất cả các luồng khác. Sự kết hợp này là không tốt, vì nó có nghĩa là lớp BufferedInputStreamkhông có cách nào để kiểm soát hoặc biết khi nào inđược thay đổi. Do đó, giá trị thậm chí có thể được thay đổi giữa việc kiểm tra null và câu lệnh trả về BufferedInputStream::getInIfOpen, điều này làm cho việc kiểm tra null trở nên vô dụng. Bằng cách đọc giá trị inchỉ một lần để lưu vào bộ nhớ cache trong biến cục bộ input, phương pháp BufferedInputStream::getInIfOpennày an toàn trước những thay đổi từ các luồng khác, vì các biến cục bộ luôn thuộc sở hữu của một luồng duy nhất.

Có một ví dụ trong BufferedInputStream::close đó đặt inthành null:

public void close() throws IOException {
    byte[] buffer;
    while ( (buffer = buf) != null) {
        if (bufUpdater.compareAndSet(this, buffer, null)) {
            InputStream input = in;
            in = null;
            if (input != null)
                input.close();
            return;
        }
        // Else retry in case a new buf was CASed in fill()
    }
}

Nếu BufferedInputStream::closeđược gọi bởi một luồng khác trong khi BufferedInputStream::getInIfOpenđược thực thi, điều này sẽ dẫn đến điều kiện chủng tộc được mô tả ở trên.


Tôi đồng ý, bởi vì như chúng ta đang thấy những thứ như compareAndSet(), CASvv trong các mã và trong các ý kiến. Tôi cũng đã tìm kiếm BufferedInputStreammã và tìm thấy nhiều synchronizedphương pháp. Vì vậy, nó được thiết kế để sử dụng đa luồng, mặc dù tôi chắc chắn chưa bao giờ sử dụng nó theo cách đó. Dù sao, tôi nghĩ câu trả lời của bạn là chính xác!
sparc_spread

Điều này có lẽ có ý nghĩa vì getInIfOpen()chỉ được gọi từ public synchronizedcác phương thức của BufferedInputStream.
Mick Mnemonic

6

Đây là một đoạn mã ngắn như vậy, nhưng về mặt lý thuyết, trong môi trường đa luồng, incó thể thay đổi ngay sau khi so sánh, vì vậy phương thức có thể trả về một thứ gì đó mà nó không kiểm tra (nó có thể trả về null, do đó làm đúng như ý muốn của nó ngăn chặn).


Tôi có đúng không nếu tôi nói rằng tham chiếu in có thể thay đổi giữa thời gian bạn gọi phương thức và trả về giá trị (trong môi trường đa luồng)?
Debosmit Ray,

Vâng, bạn có thể nói như vậy. Cuối cùng thì khả năng sẽ thực sự phụ thuộc vào từng trường hợp cụ thể (tất cả những gì chúng ta biết thực tế là incó thể thay đổi bất cứ lúc nào).
acdcjunior

4

Tôi tin rằng việc nắm bắt biến lớp thành biến incục bộ inputlà để ngăn chặn hành vi không nhất quán nếu inbị thay đổi bởi một luồng khác trong khigetInIfOpen() đang chạy.

Lưu ý rằng chủ sở hữu của inlà lớp cha và không đánh dấu nó làfinal .

Mô hình này được nhân rộng trong các phần khác của lớp và có vẻ là mã hóa phòng thủ hợp lý.

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.