一、stop和interrupt
stop
方法会立即打断线程,此方法已经被废弃,因为立即打断线程无法判断程序执行的位置,无法做一下补救操作。
推荐使用interrupt
,见如下代码:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private Thread mThread;
private TextView mTv;
private int value;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTv = findViewById(R.id.tv);
run();
}
public void run(){
mThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10_0000; i++) {
//收到通知需要打断线程,则打断线程,Thread.interrupt()不会打断线程,只是起通知作用
//Thread.interrupted()也是判断线程是否被打断,但它与isInterrupted()的区别是它会重置线程打断的标记位为false
//建议放在耗时操作之前
if(mThread.isInterrupted()){
//做一些线程被打断的补救操作
runOnUiThread(new Runnable() {
@Override
public void run() {
mTv.setText("线程被打断"+(value++));
}
});
//需要加return,因为这部分会多次被回调
return;
}else{
System.out.println("线程操作:当前的值:"+i);
}
}
}
});
//开启线程
mThread.start();
//等待300毫秒后结束线程
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//结束线程
//mThread.stop();
//通知打断线程,只是起通知的作用
//打断线程的操作是在主动获取线程状态是否有被打断的时候,如果为true就打断线程,让程序员有机会补救
mThread.interrupt();
}
}
主逻辑见代码注释。
InterruptedException
这个异常就是线程被通知Interrupted
了而抛出来的,因为线程被Interrupted
,某些操作执行不下去了,比如sleep
则抛出了这个异常。
二、wait、join、yield、notify和notifyAll
看下面一个死锁的例子:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private TextView mTv;
private String mName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTv = findViewById(R.id.tv);
run();
}
public void run(){
Thread readThread = new Thread(() -> {
//模拟后台线程耗时操作
SystemClock.sleep(1000);
getValueNotNull();
});
readThread.start();
Thread writeThread = new Thread(() -> {
//模拟后台线程耗时操作
SystemClock.sleep(2000);
setValue();
});
writeThread.start();
}
//获取值
public synchronized void getValueNotNull(){
while(mName==null){
Log.d(TAG,"mName为null,会一直循环,走不到下面,除非mName不为null");
}
//不为null,主线程更新UI
runOnUiThread(() -> mTv.setText(mName));
}
//设置值
public synchronized void setValue(){
mName = "ZhangSan";
}
}
run
方法中有个读的线程和写的线程(赋值),其中读的线程延时1秒,写的线程延时2秒。getValueNotNull
方法和setValue
方法都是synchronized
的,它们使用同一把锁。
程序的运行情况是先调用getValueNotNull
方法,进入循环,这时候锁一直被getValueNotNull
方法(循环)占用,等2秒后因为锁被占用,无法调用setValue
方法,所以mName
无法被赋值永远为null,getValueNotNull
方法的循环因为mName
永远为null也就永远不能被break,永远无法释放锁。
解决办法就是使用wait
和notify
,见如下改进代码:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private TextView mTv;
private String mName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTv = findViewById(R.id.tv);
run();
}
public void run(){
Thread readThread = new Thread(() -> {
//模拟后台线程耗时操作
SystemClock.sleep(1000);
getValueNotNull();
});
readThread.start();
Thread writeThread = new Thread(() -> {
//模拟后台线程耗时操作
SystemClock.sleep(2000);
setValue();
});
writeThread.start();
}
//获取值
public synchronized void getValueNotNull(){
while(mName==null){
Log.d(TAG,"mName为null,会一直循环,走不到下面....");
try {
//线程等待
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//不为null,主线程更新UI
runOnUiThread(() -> mTv.setText(mName));
}
//设置值
public synchronized void setValue(){
mName = "ZhangSan";
//被赋值后通知线程
notifyAll();
}
}
进入getValueNotNull()
方法,当检测到mName==null
时,调用wait
方法使线程释放锁等待,2秒后setValue()
方法拿到锁赋值成功调用notifyAll
方法唤醒等待的线程使程序继续往下执行。
为什么不使用notify
而使用notifyAll
,因为notify
只会随机唤醒一个线程,可能唤醒的不是你期待唤醒的线程,所以使用notifyAll
唤醒所有的线程更保险。
如果明确的知道哪个线程先执行完,哪个线程后执行完,则使用join
可以优化上面的代码:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private TextView mTv;
private String mName;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTv = findViewById(R.id.tv);
run();
}
public void run() {
Thread writeThread = new Thread(() -> {
//模拟后台线程耗时操作
SystemClock.sleep(2000);
setValue();
});
writeThread.start();
Thread readThread = new Thread(() -> {
//模拟后台线程耗时操作
SystemClock.sleep(1000);
//已知writeThread后执行完,readThread先执行完
//让readThread阻塞,直到writeThread执行完
try {
writeThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
getValueNotNull();
});
readThread.start();
}
//获取值
public void getValueNotNull() {
//不为null,主线程更新UI
runOnUiThread(() -> mTv.setText(mName));
}
//设置值
public void setValue() {
mName = "ZhangSan";
}
}
总结:
sleep
:
1、使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会执行。但是时间到了之后线程会进入就绪队列,重新去竞争cpu资源。
2.sleep()会释放cpu资源,但是不会释放同步锁(类锁和对象锁)。
yield
:
使当前正在执行的线程向另一个线程交出运行权。注意这是一个静态方法。
该方法与sleep
类似,只是不能由用户指定暂停多长时间,并且yield
方法只能让同优先级的线程有执行的机会。 1、yield
执行后线程直接进入就绪状态。 2、yield
会释放cpu资源,但是不会释放同步锁(类锁和对象锁)。
join
:
执行后线程进入阻塞状态,例如在线程B中调用线程A的join
,那线程B会进入到阻塞队列,直到join
结束或中断线程B才开始进入阻塞队列。 可以实现一个线程的顺序执行。
wait、notify和notifyAll
:
1、wait
方法用于协调多个线程对共享数据的存取,所以必须在Synchronized
语句块内使用
2、wait
方法使当前线程暂停执行并释放会cpu资源,以及同步锁(类锁和对象锁)
3、调用wait
后必须调用notify
或notifyAll
后线程才会从等待池进入到锁池,当我们的线程竞争得到同步锁后就会重新进入绪状态等待cpu资源分配。
当调用notify
方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志;如果锁标志等待池中没有线程,则notify
不起作用。
notifyAll
则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。
三、Condition
Condition
是 JDK 1.5 中提供的用来替代 wait
和 notify
的线程通讯方法,替代的原因是:
1、使用 notify
在极端环境下会造成线程“假死”;
2、Condition
性能更高。
使用Condition
改写上面wait
的代码:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private TextView mTv;
private String mName;
private Condition mCondition;
private ReentrantLock mReentrantLock;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTv = findViewById(R.id.tv);
initThread();
run();
}
private void initThread() {
mReentrantLock = new ReentrantLock();
mCondition = mReentrantLock.newCondition();
}
public void run() {
Thread writeThread = new Thread(() -> {
//模拟后台线程耗时操作
SystemClock.sleep(2000);
setValue();
});
writeThread.start();
Thread readThread = new Thread(() -> {
//模拟后台线程耗时操作
SystemClock.sleep(1000);
getValueNotNull();
});
readThread.start();
}
//获取值
public void getValueNotNull() {
//加锁
mReentrantLock.lock();
try {
if (mName == null) {
//为null让线程等待
try {
mCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//主线程更新UI
runOnUiThread(() -> mTv.setText(mName));
} finally {
//解锁
mReentrantLock.unlock();
}
}
//设置值
public void setValue() {
//加锁
mReentrantLock.lock();
try {
mName = "ZhangSan";
//唤醒等待的线程
mCondition.signalAll();
} finally {
//解锁
mReentrantLock.unlock();
}
}
}
Condition
需要使用 Lock
锁来创建,Condition
提供了 3 个重要的方法:
await
:对应wait
方法;signal
:对应notify
方法;signalAll
:notifyAll
方法。
Condition
的使用和 wait/notify
类似,也是先获得锁然后在锁中进行等待和唤醒操作。
本篇内容参考了以下文章,表示感谢:
并发编程——线程中sleep(),yield(),join(),wait(),notify(),notifyAll()区别
--个人学习笔记