极力推荐文章:欢迎收藏
Android 干货分享
阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android
1.什么是线程
线程就是进程中运行的多个子任务,是操作系统调用的最小单元
2.线程的状态
1.New:
新建状态,new
出来,还没有调用start
2.Runnable:
可运行状态,调用start
进入可运行状态,可能运行也可能没有运行,取决于操作系统的调度
3.Blocked:
阻塞状态,被锁阻塞,暂时不活动,阻塞状态是线程阻塞在进入
4.synchronized:
关键字修饰的方法或代码块(获取锁)时的状态。
5.Waiting:
等待状态,不活动,不运行任何代码,等待线程调度器调度,wait
、sleep
用来暂停当前线程的执行,以毫秒为单位,任何其它线程都可以中断当前线程的睡眠,这种情况下将抛出InterruptedException
异常
6.Timed Waiting:
超时等待,在指定时间自行返回
7.Terminated:
终止状态,包括正常终止和异常终止
3.线程的创建
线程创建的常用方法
- 1.继承Thread重写run方法
- 2.实现Runnable重写run方法
- 3.实现Callable重写call方法
实现Callable重写call方法
实现Callable
和实现Runnable类似,但是功能更强大,具体表现在
- a.可以在任务结束后提供一个返回值,
Runnable
不行 - b.
call
方法可以抛出异常,Runnable
的run
方法不行 - c.可以通过运行
Callable
得到的Fulture
对象监听目标线程调用call
方法的结果,得到返回值,(fulture.get()
,调用后会阻塞,直到获取到返回值)
4.线程中断
一般情况下,线程不执行完任务不会退出,但是在有些场景下,我们需要手动控制线程中断结束任务,Java
中有提供线程中断机制相关的Api
,每个线程都一个状态位用于标识当前线程对象是否是中断状态
public boolean isInterrupted() //判断中断标识位是否是true,不会改变标识位
public void interrupt() //将中断标识位设置为true
public static boolean interrupted() //判断当前线程是否被中断,并且该方法调用结束的时候会清空中断标识位
复制代码
需要注意的是interrupt()
方法并不会真的中断线程,它只是将中断标识位设置为true
,具体是否要中断由程序来判断,如下,只要线程中断标识位为false
,也就是没有中断就一直执行线程方法
new Thread(new Runnable(){
while(!Thread.currentThread().isInterrupted()){
//执行线程方法
}
}).start();
复制代码
前边我们提到了线程的六种状态,New 、Runnable、 Blocked、 Waiting、 Timed Waiting、 Terminated
,那么在这六种状态下调用线程中断的代码会怎样呢,New
和Terminated
状态下,线程不会理会线程中断的请求,既不会设置标记位,在Runnable
和Blocked
状态下调用interrupt
会将标志位设置位true
,在Waiting
和Timed Waiting
状态下会发生InterruptedException
异常,针对这个异常我们如何处理?
- 1.在
catch
语句中通过interrupt
设置中断状态,因为发生中断异常时,中断标志位会被复位,我们需要重新将中断标志位设置为true
,这样外界可以通过这个状态判断是否需要中断线程
try{
....
}catch(InterruptedException e){
Thread.currentThread().interrupt();
}
复制代码
- 2.更好的做法是,不捕获异常,直接抛出给调用者处理,这样更灵活
5.Thread为什么不能用stop方法停止线程
从SUN
的官方文档可以得知,调用Thread.stop()
方法是不安全的,这是因为当调用Thread.stop()
方法时,会发生下面两件事:
- 1.即刻抛出
ThreadDeath
异常,在线程的run()
方法内,任何一点都有可能抛出ThreadDeath Error
,包括在catch
或finally
语句中。 - 2.释放该线程所持有的所有的锁。调用
thread.stop()
后导致了该线程所持有的所有锁的突然释放,那么被保护数据就有可能呈现不一致性,其他线程在使用这些被破坏的数据时,有可能导致一些很奇怪的应用程序错误。
6.同步方法和同步代码块
为何要使用同步?
java
允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查), 将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。
- 1.同步方法
即有synchronized
关键字修饰的方法, 由于java
的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
代码如:
public synchronized void save(){
}
复制代码
注:synchronized
关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类
- 2.同步代码块
即有synchronized
关键字修饰的语句块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
代码如:
synchronized(object){
}
复制代码
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。
通常没有必要同步整个方法,使用synchronized
代码块同步关键代码即可。
/**
* 线程同步的运用
*
* @author XIEHEJUN
*
*/
public class SynchronizedThread {
class Bank {
private int account = 100;
public int getAccount() {
return account;
}
/**
* 用同步方法实现
*
* @param money
*/
public synchronized void save(int money) {
account += money;
}
/**
* 用同步代码块实现
*
* @param money
*/
public void save1(int money) {
synchronized (this) {
account += money;
}
}
}
class NewThread implements Runnable {
private Bank bank;
public NewThread(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
// bank.save1(10);
bank.save(10);
System.out.println(i + "账户余额为:" + bank.getAccount());
}
}
}
/**
* 建立线程,调用内部类
*/
public void useThread() {
Bank bank = new Bank();
NewThread new_thread = new NewThread(bank);
System.out.println("线程1");
Thread thread1 = new Thread(new_thread);
thread1.start();
System.out.println("线程2");
Thread thread2 = new Thread(new_thread);
thread2.start();
}
public static void main(String[] args) {
SynchronizedThread st = new SynchronizedThread();
st.useThread();
}
}
复制代码
-
3.使用特殊域变量(volatile)实现线程同步
a.volatile关键字为域变量的访问提供了一种免锁机制,
b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,
c.因此每次使用该域就要重新计算,而不是使用寄存器中的值
d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
例如:
在上面的例子当中,只需在account前面加上volatile修饰,即可实现线程同步。
//只给出要修改的代码,其余代码与上同
class Bank {
//需要同步的变量加上volatile
private volatile int account = 100;
public int getAccount() {
return account;
}
//这里不再需要synchronized
public void save(int money) {
account += money;
}
}
复制代码
注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。用final
域,有锁保护的域和volatile
域可以避免非同步的问题。
- 4.使用重入锁实现线程同步
在JavaSE5.0
中新增了一个java.util.concurrent
包来支持同步。
ReentrantLock
类是可重入、互斥、实现了Lock
接口的锁, 它与使用synchronized
方法和快具有相同的基本行为和语义,并且扩展了其能力
ReenreantLock
类的常用方法有:
ReentrantLock() :
创建一个ReentrantLock实例
lock() :
获得锁
unlock() :
释放锁
注:ReentrantLock()
还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用
例如:
在上面例子的基础上,改写后的代码为:
//只给出要修改的代码,其余代码与上同
class Bank {
private int account = 100;
//需要声明这个锁
private Lock lock = new ReentrantLock();
public int getAccount() {
return account;
}
//这里不再需要synchronized
public void save(int money) {
lock.lock();
try{
account += money;
}finally{
lock.unlock();
}
}
}
复制代码
注:关于Lock
对象和synchronized
关键字的选择:
-
a.最好两个都不用,使用一种
java.util.concurrent
包提供的机制,
能够帮助用户处理所有与锁相关的代码。 -
b.如果
synchronized
关键字能满足用户的需求,就用synchronized
,因为它能简化代码 -
c.如果需要更高级的功能,就用
ReentrantLock
类,此时要注意及时释放锁,否则会出现死锁,通常在finally
代码释放锁 -
5.使用局部变量实现线程同步
如果使用ThreadLocal
管理变量,则每一个使用该变量的线程都获得该变量的副本, 副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
ThreadLocal 类的常用方法
ThreadLocal() :
创建一个线程本地变量
get() :
返回此线程局部变量的当前线程副本中的值
initialValue() :
返回此线程局部变量的当前线程的”初始值”
set(T value) :
将此线程局部变量的当前线程副本中的值设置为value
例如:
在上面例子基础上,修改后的代码为:
//只改Bank类,其余代码与上同
public class Bank{
//使用ThreadLocal类管理共享变量account
private static ThreadLocal
GankRobot转载声明
原文出处:掘金Androidd
原文作者:公众号_程序员Android