我正在使用Android开发环境。
我在后台运行线程,并且有一个复杂的for循环结构。对于" for循环的每次运行"我想听一些UI输入,收集结果并继续" for循环"。要抽象问题使其更简短,请查看以下代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
mLeftText = (TextView) findViewById(R.id.leftText);
mVerboseText = (TextView) findViewById(R.id.verbose);
LoopTask taskA = new LoopTask();
Thread a = new Thread(taskA);
a.start();
}
class LoopTask implements Runnable {
@Override
public void run() {
synchronized (mSync) {
mLeftText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
doSomething();
mSync.notify();
}
});
for (int i = 0; i < 10; i++) {
try {
mSync.wait();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
}
我知道我一定做错了,因为它让我异常:&#34; java.lang.IllegalMonitorStateException:对象在notify()&#34; 之前没有被线程锁定
我正在尝试这个复杂的结构,因为我的循环结构非常复杂,我无法使用某些数据结构来记录循环的进度并继续触发onClickEvent。
请告诉我如何修复它,以及是否欢迎任何替代解决方案。
由于
答案 0 :(得分:1)
您的代码的问题是方法 onClickListener()由外部线程执行,该线程与您创建的线程(实际上持有mSync锁定的线程)不同。因此,当它到达 notify()时,它会抱怨没有锁定。您可以使用用0初始化的信号量完成相同的操作。您的代码看起来像这样:
class LoopTask implements Runnable {
private Semaphore s = new Semaphore(0);
@Override
public void run() {
mLeftText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
doSomething();
s.release();
}
});
for (int i = 0; i < 10; i++) {
try {
s.acquire();
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
信号量和通知/等待之间存在差异:当您调用 notify()并且 wait()上没有阻塞线程时,该调用是丢失,它不会累积,所以下一次调用 wait()不会阻止。使用信号量,如果您调用 release()一次,则下一次对 acquire()的调用将不会阻止。
我希望这会有所帮助。
答案 1 :(得分:0)
首先你必须记住这两件事:
你不能让你的UI线程等待,
您无法从UI线程外部操纵UI
对于那些认为:
的人你自相矛盾......&#34;不要让UI-Thread等待&#34;和 然后你将同步块编码到UI-Thread中?它应该是 好的,如果这两个是唯一访问它,但它是可能的 危险 - &gt;应用没响应
我没有让我的UI等待,仔细查看我的代码虽然注意线程调用wait
它释放锁(mSync)。而且在我看来,APR
没有机会,你可以在其他答案下面阅读我的评论。
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction()
.add(R.id.container, new PlaceholderFragment()).commit();
}
}
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
public class MonitorObject{
}
final MonitorObject mSync = new MonitorObject();
public PlaceholderFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container,
false);
TextView txt = (TextView) rootView.findViewById(R.id.textview1);
txt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
synchronized (mSync) {
mSync.notify();
}
});
LoopTask taskA = new LoopTask();
Thread a = new Thread(taskA);
a.start();
return rootView;
}
class LoopTask implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
synchronized (mSync) {
try {
mSync.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
final int runNumber = i;
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(getActivity(), "inside the thread1 " + runNumber , Toast.LENGTH_SHORT).show();
}
});
}
}
}
}
}
和xml文件:
activity_main.xml中:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.thread_notify_wait.MainActivity"
tools:ignore="MergeRootFrame" />
fragment_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="16dip"
android:paddingLeft="16dip"
android:paddingRight="16dip"
android:paddingTop="16dip"
tools:context="com.example.thread_notify_wait.MainActivity$PlaceholderFragment" >
<TextView
android:id="@+id/textview1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="mmlooloo: Hello TypingPanda" />
</RelativeLayout>
答案 2 :(得分:-1)
对于UI上的任何更改,最佳解决方案是信号量和runOnUiThread!
一个重要的补充:如果你想使用同步 - 请注意同步调用可能会阻塞。即使后台线程只调用synchronized (s) { s.wait(); }
在获取锁之后,但在使用wait()再次释放之前,线程可能会因各种原因暂停/暂停。在这种情况下,UI-Thread将阻塞,并且您的UI将挂起,直到再次激活后台线程并获得释放锁定的执行时间。由于Android上的Dalvik-VM将Java-Threads实现为本机linux-threads,因此它们具有不同的优先级并争夺相同的系统资源。由于在重载系统上可能有许多线程的优先级高于后台线程(并发GC,ui-threads,系统线程),后台线程可能会非常慢,因为它获得的资源非常少,而UI-线程将获得高优先级,并经过调整以便经常运行。在最糟糕的情况下,UI-Thread将挂起等待后台线程释放锁定超过5秒并触发“应用程序未响应”
所以解决这个问题的方法是:在一个单独的线程上调用synchronized(s){s.notify()}
或使用一个信号量,它将在Semaphore.release()上进行两步非阻塞同步本地处理这些问题
private Semaphore s = new Semaphore(0);
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
final Thread a = new Thread() {
@Override
public void run() {
loopTask();
}
}.start();
TextView txt = (TextView) rootView.findViewById(R.id.textview1);
txt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// s.release() will not Block the UI-Thread
// even if Background-Task is suspended
// Because of Semaphore two-step implementation
s.release();
}
});
return rootView;
}
public void loopTask() {
for (int i = 0; i < 10; i++) {
try {
s.acquire();
final int runNumber = i;
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
// Any UI Action happens here!!!
Toast.makeText(getActivity(), "Running Loop " + runNumber , Toast.LENGTH_SHORT).show();
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}