开发者

Reflection across threads

开发者 https://www.devze.com 2023-04-06 09:47 出处:网络
I am working with a setup where one thread sets up many threads (services), runs them all together to simulate the running of the system, then joins them all at the end and handles termination, etc.My

I am working with a setup where one thread sets up many threads (services), runs them all together to simulate the running of the system, then joins them all at the end and handles termination, etc. My test is run as one of the services and communicates with the others via JMS. For one of my tests, I need access to a private variable contained in another thread. I cannot change the code that is running in the other thread to, say, add an accessor method or to have it send the variable via JMS. I also do not have a way to pass a reference to the service that I want to have access to into my test service due to the way the framework sets things up.

I know the name of the thread that I contains the class I need access to, and I can get a reference to the thread by enumerating the threads that are running, but I don't know how to grab anything out of the thread once I've got it.

Is there some way for me to use reflection or other techniques to get a reference to a class in another thread?

EDIT: Here is an example of the situation I am in:

import java.lang.reflect.Field;

public class Runner
{
    /**
     * Pretend this is my test class.
     */
    public static void main( String[] args )
    {
        // this is how my test starts up the system and runs the test
        runTest( TestService.class );
    }

    /**
     * Instantiate the test service and start up all of the threads in the
     * system. Doesn't return until test has completed.
     * 
     * @param testServiceClass
     *            the class that will run the test
     */
    static void runTest( Class<? extends Service> testServiceClass )
    {
        try
        {
            // setup the services
            Service testService =
                    testServiceClass.getConstructor( new Class<?>[] { String.class } )
                            .newInstance( "test service" );

            FixedService fixedService = new FixedService( "fixed service" );

            // start the services
            testService.start();
            fixedService.start();

            // wait for testService to signal that it is done
            System.out.println( "Started threads" );
            while ( !testService.isDone() )
            {
                try
                {
                    Thread.sleep( 1000 );
                }
                catch ( InterruptedException e )
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
            // stop the fixed service
            fixedService.stop();
            System.out.println( "TestService done, fixed service told to shutdown" );
        }
        catch ( Exception e )
        {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * I cannot modify this class. Handling of thread start is similar to real
     * system.
     */
    abstract static class Service implements Runnable
    {
        protected boolean isDone = false;
        protected bo开发者_StackOverflow社区olean stop = false;
        private Thread thisServiceThread;

        public Service( String name )
        {
            thisServiceThread = new Thread( this, name );
        }

        public boolean isDone()
        {
            return isDone;
        }

        public void start()
        {
            thisServiceThread.start();
        }

        public void stop()
        {
            this.stop = true;
        }
    }

    /**
     * I can modify this class. This is the class that actually runs my test.
     */
    static class TestService extends Service
    {
        public TestService( String name )
        {
            super( name );
        }

        @Override
        public void run()
        {
            System.out.println( "TestService: started" );

            // TODO: How can I access FixedService.getMe from where without
            // modifying FixedService?
            try
            {
                Field field = FixedService.class.getDeclaredField( "getMe" );
                field.setAccessible( true );
                System.out.println( field.get( null ) );
            }
            catch ( SecurityException e )
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            catch ( NoSuchFieldException e )
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            catch ( IllegalArgumentException e )
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            catch ( IllegalAccessException e )
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            System.out.println( "TestService: done" );
            isDone = true;
        }

    }

    /**
     * I cannot modify this class. This is part of the system being tested.
     */
    static class FixedService extends Service
    {
        private boolean getMe = false;

        public FixedService( String name )
        {
            super( name );
        }

        @Override
        public void run()
        {
            System.out.println( "FixedService: started" );

            // don't stop until signaled to do so
            while ( !stop )
            {
                try
                {
                    Thread.sleep( 1000 );
                }
                catch ( InterruptedException e )
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }

            System.out.println( "FixedService: gotMe? " + getMe );

            System.out.println( "FixedService: done" );
            isDone = true;
        }
    }
}


As Hemal Pandya stated, you will need the service object, not just the class, if you want to actually read or manipulate the field.

Assuming the Object you need is the Runnable set on the thread, it's possible, with some very dirty reflection hacks. You have to use the private member access hack to get the target field from the thread, then use it again to access the field you need on the runnable itself.

Here is some sample code. Note that I didn't really consider thread synchronization issues here (though I'm not sure it's even possible to properly synchronize access of this sort)

import java.lang.reflect.Field;

public class SSCCE {
    static class T extends Thread {
        private int i;

        public T(int i) {
            this.i = i;
        }

        @Override
        public void run() {
            while(true) {
                System.out.println("T: " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // ignore
                }
            }
        }
    }

    static class R implements Runnable {
        private int i;

        public R(int i) {
            this.i = i;
        }

        @Override
        public void run() {
            while(true) {
                System.out.println("R: " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // ignore
                }
            }
        }
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        Thread t1 = new T(1);
        Thread t2 = new Thread(new R(2));

        t1.start();
        t2.start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            // ignore
        }

        setI(t1,3);
        setI(t2,4);
    }

    static void setI(Thread t, int newVal) {
        // Secret sauce here...
        try {
            Field fTarget = Thread.class.getDeclaredField("target");
            fTarget.setAccessible(true);
            Runnable r = (Runnable) fTarget.get(t);

            // This handles the case that the service overrides the run() method
            // in the thread instead of setting the target runnable
            if (r == null) r = t;

            Field fI = r.getClass().getDeclaredField("i");
            fI.setAccessible(true);
            fI.setInt(r, newVal);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}


It is not the Thread but rather the Object that you have to get the variable out of. Try to come up with a small code sample of the controlling and the controlled threads and it will become clearer.

Accessing private members is the easy part.

EDIT: Use a Map from Thread.getId() to Runnable. When you want inspect a thread, identify the runnable, and use reflection. You may not get the latest value due to synchronization issues.

0

精彩评论

暂无评论...
验证码 换一张
取 消