Android UI线程和子线程使用synchronized,UI线程阻塞但无法获取锁


使用AudioRecord录音时,需要在UI线程的StopRecord方法、数据读取线程的while代码块之间加上锁,我使用了synchronized(byte[])。 但是按下停止录音按钮之后,StopRecord被阻塞了,while代码块却仍然在循环执行。

下面是抽象出来的简单代码,可以直接运行,同样出现了UI线程阻塞但无法获取锁的结果:


 public class MainActivity extends ActionBarActivity implements View.OnClickListener{

    final byte[] lock = new byte[0];
    boolean isRecording = false;
    RecorderThread recorderThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button buttonStart = (Button)findViewById(R.id.button_start);
        Button buttonStop = (Button)findViewById(R.id.button_stop);

        buttonStart.setOnClickListener(this);
        buttonStop.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.button_start:
                if(!isRecording){
                    // Initialize AudioRecord and startRecording
                    isRecording = true;
                    recorderThread = new RecorderThread();
                    recorderThread.start();
                }
                break;

            case R.id.button_stop:
                if(isRecording){
                    synchronized (lock){
                        // Stop AudioRecord and release
                        isRecording = false;
                    }
                }
                break;
        }
    }

    private class RecorderThread extends Thread{
        @Override
        public void run() {
            while (isRecording){
                synchronized (lock){
                    try{
                        // AudioRecord.read()
                        Thread.sleep(200);
                        Log.d("TAG","read a sample data");
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        }

    }
}


 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <Button
        android:id="@+id/button_start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Start"/>
    <Button
        android:id="@+id/button_stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Stop"/>
</LinearLayout>

Android java synchronized 线程

Reborn伪 9 years, 10 months ago

第一,阻塞UI线程永远都不是一个好的选择。
第二,完全看不出你的程序需要同步的点。完全可以写成这样:


 public class MainActivity extends ActionBarActivity implements View.OnClickListener{

    final byte[] lock = new byte[0];
    volatile boolean isRecording = false;//关于volatile关键字看下面的链接
    RecorderThread recorderThread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button buttonStart = (Button)findViewById(R.id.button_start);
        Button buttonStop = (Button)findViewById(R.id.button_stop);

        buttonStart.setOnClickListener(this);
        buttonStop.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.button_start:
                if(!isRecording){
                    // Initialize AudioRecord and startRecording
                    isRecording = true;
                    recorderThread = new RecorderThread();
                    recorderThread.start();
                }
                break;

            case R.id.button_stop:
                if(isRecording){
                    // Stop AudioRecord and release
                    isRecording = false;
                }
                break;
        }
    }

    private class RecorderThread extends Thread{
        @Override
        public void run() {
            while (isRecording){
                try{
                    // AudioRecord.read()
                    Thread.sleep(200);
                    Log.d("TAG","read a sample data");
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }   
   } 
  }

第三, Android中有个MediaRecorder类,你只需要start和stop,android会自动把它放到后台去运行,不用自己写线程啊。

PS:java volatile关键字: http://tutorials.jenkov.com/java-concurrency/volatile.html

追加:
如果题主一定要同步的话,可以这样写(本着不阻塞UI线程的原则):


 public class MainActivity extends ActionBarActivity implements View.OnClickListener{

    RecorderHelper recorderHelper = new RecorderHelper();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button buttonStart = (Button)findViewById(R.id.button_start);
        Button buttonStop = (Button)findViewById(R.id.button_stop);

        buttonStart.setOnClickListener(this);
        buttonStop.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.button_start:
                new Thread(new Runnable(){
                    public void run(){
                        recorderHelper.startRecord();
                    }
                }).start();
                break;

            case R.id.button_stop:
                new Thread(new Runnable(){
                    public void run(){
                        recorderHelper.stopRecord();
                    }
                }).start();
                break;
        }
    }

    private class RecorderHelper {
       final byte[] lock = new byte[0];
       volatile boolean isRecording = false;
       AudioRecorder recorder;

       public RecorderHelper(){
           //没有过AudioRecorder,这里就是初始化
           recorder = new AudioRecorder();
       }

       public void startRecord(){
           isRecording = true;
           while(isRecording){
               synchronized(lock){
                   if(recorder == null){
                       return;
                   }
                   //do something
               }
           }
       }

       public void stopRecord(){
           isRecording = false;
           synchronized(lock){
               //release
               recorder = null;
           }
       }
    } 
 }

最后再说一下volatile,因为线程有的时候会cache一份变量,一个线程中更新变量以后,另一个线程可能还在用自己cache的那一份,列如上面代码中的isRecording。为了让变量的改变及时通知到每个线程,就用volatile关键字,保证每个线程都从内存中读,不会有自己的缓存。

bigger answered 9 years, 10 months ago

Your Answer