【问题标题】:Android Library thread safeAndroid 库线程安全
【发布时间】:2019-02-21 07:24:26
【问题描述】:

我有一个处理串行端口的小型 android 库,它具有打开、读取、写入和关闭等基本功能。

我做了一个应用程序,使用这个库在串口上写入并读取响应,在这个应用程序中有一个线程定期打开串口询问状态获取响应并关闭串口。

我想保护串行通信,如果主线程打开通信,只检查状态的辅助线程不能打开它并等待主线程完成。

 class SerialChannel extends Channel
    {
        private SerialPortUtility serialPortUtility;
        private static final String SERIAL_FILE     = "/dev/ttyMT2";
        private static final String CONTROL_FILE    = "/sys/devices/platform/file";
        private static final String UNKNOWN_COMMAND = "UNKNOWN COMMAND";
        private FileOutputStream fileOutputStream;
        private FileInputStream fileInputStream;

        @Override
        public void open() throws CommunicationException
        {

            try
            {
                if (isSerialOpened() != SerialStatus.Open)
                {
                    toggleSerial(SerialStatus.Open.getStatus());
                    Thread.sleep(100);
                }

                serialPortUtility   = getSerialPortUtility();
                fileInputStream     = (FileInputStream) serialPortUtility.getInputStream();
                fileOutputStream    = (FileOutputStream) serialPortUtility.getOutputStream();
                currentProcess      = Optional.of(Thread.currentThread().getId());

                Thread.sleep(500);
            }
            catch (IOException | InterruptedException e)
            {
                throw new CommunicationException(e.getMessage());
            }
        }

        @Override
        public void close() throws CommunicationException
        {


            if (serialPortUtility == null)
            {
                throw new CommunicationException("SerialPort is null");
            }

            try
            {
                toggleSerial(SerialStatus.Close.getStatus());
                fileOutputStream.close();
                fileInputStream.close();
                serialPortUtility.close();

                fileInputStream     = null;
                fileOutputStream    = null;
                serialPortUtility   = null;
            }
            catch (IOException e)
            {
                throw new CommunicationException(e.getMessage());
            }
        }

        @Override
        public void send(byte[] buffer, int timeout, int length) throws CommunicationException
        {
            if (fileOutputStream == null)
            {
                throw new CommunicationException("Problem while sending data!");
            }

            try
            {
                fileOutputStream.write(buffer);
                fileOutputStream.flush();
            }
            catch (IOException e)
            {
                throw new CommunicationException(e.getMessage());
            }
        }

        @Override
        public byte[] receive(int length, int timeout) throws CommunicationException
        {
            StringBuilder stringBuilder = new StringBuilder();
            byte[] buffer               = new byte[length];
            int ret;
            int totalSize               = 0;

            if (fileInputStream == null)
            {
                throw new CommunicationException("FileInputStream is null!");
            }

            try
            {

                long millisStart = Calendar.getInstance().getTimeInMillis();
                boolean timeoutReached;

                while (true)
                {
                    timeoutReached = (Calendar.getInstance().getTimeInMillis() - millisStart > timeout * 1000);

                    if (fileInputStream.available() <= 0 && timeoutReached)
                    {
                        expectingResult = false;
                        throw new CommunicationException("Error");
                    }
                    else if (fileInputStream.available() > 0)
                    {
                        break;
                    }
                }

                millisStart = Calendar.getInstance().getTimeInMillis();

                while (totalSize != length && (ret = fileInputStream.read(buffer)) != -1)
                {
                    String received = new String(buffer);

                    stringBuilder.append(received);

                    if(buffer.length == 15 && received.equals(UNKNOWN_COMMAND))
                    {
                        break;
                    }

                    totalSize += ret;
                }

                expectingResult = false;

            } 
            catch (IOException e)
            {
                throw new CommunicationException(e.getMessage());
            }

            return stringBuilder.toString().getBytes();
        }

        private SerialPortUtility getSerialPortUtility() throws IOException
        {
            if (serialPortUtility == null)
            {
                File file = new File(SERIAL_FILE);
                int baudRate = 115200;

                return new SerialPortUtility(file, baudRate, 0);
            }

            return serialPortUtility;
        }

        private void toggleSerial(String data) throws IOException
        {
            FileOutputStream fos = new FileOutputStream(new File(CONTROL_FILE));
            fos.write(data.getBytes());
            fos.flush();
            fos.close();
        }

        private SerialStatus isSerialOpened() throws IOException
        {
            byte[] buffer       = new byte[1];
            FileInputStream fis = new FileInputStream(new File(CONTROL_FILE));
            int result          = fis.read(buffer);
            fis.close();

            if (result > -1 && buffer[0] == 1)
            {
                return SerialStatus.Open;
            }

            return SerialStatus.Close;
        }


}

这个类扩展了自定义类 Channel,它实现了一个带有方法 open、close、read、send 的接口,还实现了 AutoCloseable。

现在,如果我使 open 方法同步,任何进入这里的线程都会锁定,但会锁定直到它退出 open 方法,当线程移动到另一个方法时,让我们说 read 并留在那里直到它得到响应,检查器线程会来并进入 open 方法。使用 AutoCloseable,close 方法将执行并关闭串口通信。如果我同步一个对象,对象不同步的时候还是有窗口的。

如何告诉checker线程通信已经打开,让他等到主线程结束。

Checker 看起来像这样,它在一个计时器内:

try(Channel ch = CommunicationFactory.getInstance().selectChannel(CommunicationType.SERIAL))
{
     ch.open();
     //do stuff
}
catch (CommunicationException ex)
{
     ex.printStackTrace();
}

“主”线程看起来与 AysncTask 中的相同。

如果需要其他信息,请告诉我!

提前感谢您的努力和时间!

【问题讨论】:

    标签: java android multithreading


    【解决方案1】:

    如何告诉checker线程通信已经开启,让他等到主线程结束。

    我不完全理解您的代码,但线程和锁定的关键是确保所有线程都在 same 对象实例上调用 synchronized 代码。

    如果我同步一个对象,对象不同步的时候还是有窗口的。

    如果您使用对象的相同实例,则不会。在SerialChannel synchronized 中创建每个public 方法将确保一次只能有1 个线程使用该对象。

    我怀疑你真正的问题不是关于保护SerialChannel 对象,而是更多关于线程之间的竞争条件。它们需要对方法进行多次调用,并且它们可以相互阻塞或以不适当的方式交错。

    您可以通过一些更改来解决此问题。您可以使send(...)receive(...) 方法自动打开。线程只会调用send()receive(),如果fileInputStreamfileOutputStreamnull,它们又会在内部调用open()。该线程将位于 synchronized 内部,因此不会被另一个线程中断。

    要考虑的另一种完全不同的模型是让一个线程从串行端口读取,另一个线程写入该任务专用于该任务——它们将被内置到SerialChannel 对象中。他们将使用读取BlockingQueue 和写入BlockingQueue 与外部线程共享数据。然后在您的应用程序中尽早打开串行端口,从而启动 IO 线程,外部线程从不担心 IO。他们只是来自队列中的put()take()。我通常在读取和写入控制台时(例如)这样做。

    希望这里有所帮助。

    【讨论】:

    • 感谢 Gary 的回复,您指出的很好,真正的问题是“线程之间的竞争条件”。我会考虑您的建议。
    猜你喜欢
    • 2014-11-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-09
    • 2010-09-15
    • 1970-01-01
    • 2021-09-28
    相关资源
    最近更新 更多