Android库线程安全

时间:2019-02-21 07:24:27

标签: java android multithreading

我有一个处理串口的小型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,该类实现了具有打开,关闭,读取,发送和实现AutoCloseable方法的接口。

现在,如果我使open方法同步,则​​进入此处的任何线程都将被锁定,但是将一直锁定,直到退出open方法为止,并且当该线程移至另一个方法时,假设读取并停留在那里直到获得响应为止,检查线程将进入并进入open方法。使用AutoCloseable,close方法将执行并关闭串行端口通信。如果我同步对象,则当对象不同步时还会有一个窗口。

我如何告诉检查线程已打开通信,并让他等到主线程完成。

检查器看起来像这样,它在计时器内:

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

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

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

在此先感谢您的努力和时间!

1 个答案:

答案 0 :(得分:1)

  

我如何告诉检查线程已打开通信,并让他等到主线程完成。

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

  

如果我同步对象,则当对象不同步时还会有一个窗口。

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

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

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

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

希望这里有帮助。

相关问题