La tienda Java reflejó el método estáticamente en la clase: ¿seguro?

CorePress2024-01-25  11

¿Algo como lo siguiente es 'seguro' en Java y por qué?

public final class Utility {

    private Utility() {}

    private static Method sFooMethod = null;

    public static void callFoo(SomeThing thing) {
        try {
            if(sFooMethod == null)
                sFooMethod = SomeThing.class.getMethod("foo");
            sFooMethod.invoke(thing);
        } catch(Exception e) {}  // Just for simplicity here
    }

}

Mi razonamiento sería que incluso si otro subproceso escribe en sFooMethod en segundo plano y el subproceso actual lo ve repentinamente en algún lugar durante la ejecución de callFoo(), todavía resultaría en la misma vieja invocación reflexiva de thing.foo() ?

Pregunta adicional: ¿En qué se diferencia el siguiente enfoque (positivo/negativo) del anterior? ¿Sería preferible?

public final class Utility {

    private Utility() {}

    private static final Method sFooMethod;
    static {
        try {
            sFooMethod = SomeThing.class.getMethod("foo");
        } catch(Exception e) {}
    }

    public static void callFoo(SomeThing thing) {
        try {
            if(sFooMethod != null)
                sFooMethod.invoke(thing);
        } catch(Exception e) {}
    }

}

Actualización de fondo del comentario: Estoy escribiendo una aplicación para Android y necesito llamar a un método que era privado hasta la API 29, cuando se hizo público sin modificarse. En una versión alfa (todavía no puedo usarla) de la biblioteca central de AndroidX, Google proporciona un método HandlerCompat que usa la reflexión para llamar al privado.método si no es público. Así que copié el método de Google en mi propia clase HandlerCompatP por ahora, pero noté que si lo llamo 1000 veces, la búsqueda reflexiva ocurrirá 1000 veces (no pude ver ningún almacenamiento en caché). Eso me hizo pensar si existe una buena manera de realizar la reflexión solo una vez y solo si es necesario.

"No uses el reflejo" No hay una respuesta aquí, ya que en este caso es necesaria, y el propio Google tenía la intención de que esto sucediera en su biblioteca de compatibilidad. Mi pregunta tampoco es si usar la reflexión es seguro y/o una buena práctica, soy muy consciente de que no es bueno en general, sino si, dado que estoy usando la reflexión, qué método sería seguro/mejor.

¿por qué? ¿Qué estás intentando lograr? Me suena como un problema XY...

- Andrew Tobilko

28/03/2021 a las 11:50

¿Estás intentando asignar sólo la referencia del método foo()? ¿O esperas que otros hilos almacenen referencias a otros métodos en la clase SomeThng?

- el hutt

28/03/2021 a las 12:03

2

El segundo enfoque es mejor, pero no se coma la excepción: envuelva la excepción en algún Error/RuntimeException y vuelva a lanzarla. No estoy seguro de cómo funciona Android, pero el JIT de OpenJDK puede plegar constantemente campos finales estáticos.

- Johannes Kuhn

28/03/2021 a las 13:39

1

El primero está roto, ya que en ausencia de una construcción segura para subprocesos, sFooMethod == null podría ver una escritura realizada por un subproceso diferente.d, evaluar a falso, seguido del siguiente sFooMethod.invoke(thing); no ver la escritura y generar una NullPointerException. Esto no dice nada sobre el estado interno de Method. Debes seguir el consejo de Johannes Kuhn y utilizar el segundo pero sin tragar excepciones. Esto no se compila de todos modos si no se asegura de que el campo final se asigne siempre y como máximo una vez. Y tenga en cuenta que no se debe utilizar el setAccessible mutable.

- Holger

29/03/2021 a las 14:57

2

@pallgeuer estas son dos lecturas independientes sinseguridad del hilo. Cada uno de ellos puede percibir una escritura concurrente o no. Entonces sí, la primera lectura puede percibir la escritura mientras que la segunda no. No se trata de "olvidar", ya que las lecturas no recuerdan lecturas anteriores. Consulte shipilev.net/blog/2014/safe-public-construction/… el ejemplo de UnsafeDCLFactory y el punto 2 debajo.

- Holger

30 de marzo de 2021 a las 8:34



------------------------------------

La clave para evitar errores de coherencia de la memoria es comprender la relación de lo que sucede antes. Esta relación es simplemente una garantía de que la memoria escribe por una persona específica.La declaración c son visibles para otra declaración específica. La especificación del lenguaje Java establece lo siguiente: 17.4.5. Sucede antes del pedido

Se pueden ordenar dos acciones mediante una relación de suceso anterior. Si uno acción sucede antes que otra, entonces la primera es visible y ordenado antes del segundo.

Si tenemos dos acciones x e y, escribimos hb(x, y) para indicar que x sucede antes de y.

Si x e y son acciones del mismo hilo y x viene antes que y en orden del programa, luego hb(x, y).

Como, en su caso, escribir y luego leer desde el campo estático ocurre en el mismo paso. Entonces se establece la relación "sucede antes". Por lo tanto, la operación de lectura siempre verá los efectos de la operación de escritura. Además, todos los hilos escribirán los mismos datos. En el peor de los casos, todos los hilos elegiblesEscribo en la variable al mismo tiempo. La variable tendrá referencia al objeto que se asignó en último lugar y el resto de los objetos desreferenciados se recolectarán como basura. No habrá muchos subprocesos en su aplicación que ingresen al mismo método a la vez, lo que afectará significativamente el rendimiento debido a la gran cantidad de creación de objetos. Pero si desea configurar la variable solo una vez, entonces es mejor el segundo enfoque. Como los bloques estáticos son seguros para subprocesos.

2

2

“escribir y luego leer desde el campo estáticoestán sucediendo en la misma banda de rodadura”, solo si sFooMethod == null se evalúa como verdadero.

- Holger

30 de marzo de 2021 a las 9:03

Parece que @Holger tiene razón. Es posible que sFooMethod esté configurado en un objeto parcialmente construido en el subproceso A mientras se ejecuta getMethod(). Luego, el subproceso A continúa construyendo sFooMethod lo más rápido que puede, pero mientras tanto el subproceso B puede ver que sFooMethod no es nulo e intentar invocarlo antes de que el objeto esté completamente construido, o antes de que el subproceso B vea las escrituras del campo constructivo realizadas. por el hilo A. Parece ser un problema similar a por qué los ingenuosEl bloqueo doble verificado no funciona: en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java

Pallgeuer

30 de marzo de 2021 a las 13:29



------------------------------------

¿Algo como lo siguiente es 'seguro' en Java y por qué?

No, no recomendaría usar reflejos, a menos que sea necesario. La mayoría de las veces los desarrolladores diseñan sus clases de tal manera que nunca sea necesario acceder a un campo o método oculto. Lo más probable es que exista una mejor manera de acceder al contenido oculto. Especialmente los campos y métodos ocultos podrían cambiar su nombre cuando se actualiza la biblioteca que los contiene. Entonces tu código podría dejar de funcionar repentinamentey no sabría por qué, ya que el compilador no generaría ningún error. También es más rápido acceder directamente a un método o campo que a través de reflexiones, porque las reflexiones primero necesitan buscarlo y el acceso directo no.

Así que no uses reflejos si no es necesario

2

Esto no responde a mi pregunta. No estoy preguntando si la reflexión es fundamentalmente una buena idea. Suponiendo que estoy usando la reflexión y no quiero realizar la búsqueda reflexiva cada vez, ¿cuál de mis dos propuestas es segura/mejor?

Pallgeuer

28/03/2021 a las 12:16

Sería mejor (como lo hizo en el segundo ejemplo) almacenar el método. De esta forma llamarlo será mucho más rápido

-IntoVoid

28/03/2021 a las 17:10



------------------------------------

No estoy seguro de cuál es tu objetivo; probablemente haya una manera mejor de hacer lo que estás intentando hacer.

El segundo enfoque, con un inicializador estático, es preferible porque su primera implementaciónEl ion tiene una condición de carrera.

1

He actualizado la pregunta con mi objetivo. ¿Puede explicarnos de qué manera la condición de carrera podría ser un problema potencial?

Pallgeuer

28/03/2021 a las 12:17

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare