Android 多线程之几个基本问题
Android中的进程和线程
- Android中的一个应用程序一般就对应着一个进程,多进程的情况可以参考Android 多进程通信之几个基本问题
- Android中更常见的是多线程的情况,一个应用程序中一般都有包括UI线程等多个线程。Android中规定网络访问必须在子线程中进行,而操作更新UI则只能在UI线程。
- 常见的网络请求库,如OkHttp、Volly等都为我们封装好了线程池,所以我们在进行网络请求时一般不是很能直观地感受到创建线程以及切换线程的过程。
- 线程是一种很宝贵的资源,要避免频繁创建销毁线程,一般推荐用线程池来管理线程。
线程的状态
线程可能存在6种不同的状态:新创建(New)、可运行(Runnable)、阻塞状态(Blocked)、等待状态(Waiting)、限期等待(Timed Waiting)、终止状态(Terminated)
- 新创建(New):创建后但还未启动的线程(还没有调用start方法)处于这种状态
- 可运行(Runnable):一旦调用了start方法,线程就处于这种状态。需要注意的是此时线程可能正在执行,也可能在等待CPU分配执行的时间
- 阻塞状态(Blocked):表示线程被锁阻塞,等待获取到一个排他锁。在程序等待进入同步区域时,线程将进入这种状态
-
等待状态(Waiting):处于这种状态的线程不会被分配CPU执行时间,它们要等待被其他线程显示地唤醒。调用以下方法会让线程进入这种状态:
- 没有设置Timeout参数的Object.wait()方法
- 没有设置Timeout参数的Thread.join()方法
-
限期等待(Timed Waiting):与等待状态(Waiting)不同的是,处于这种状态的线程不需要等待其它线程唤醒,在一定时间之后会由系统唤醒。调用以下方法会让线程进入这种状态:
- Thread.sleep()方法
- 设置了Timeout参数的Object.wait()方法
- 设置了Timeout参数的Thread.join()方法
-
终止状态(Terminated):表示线程已经执行完毕。导致线程终止有2种情况:
- 线程的run方法执行完毕,正常退出
- 因为一个没有捕获的异常而终止了run方法
创建线程
创建线程一般有如下几种方式:继承Thread类;实现Runnable接口;实现Callable接口
- 继承Thread类,重写run方法
public class TestThread extends Thread { @Override public void run() { System.out.println("Hello World"); } public static void main(String[] args) { Thread mThread = new TestThread(); mThread.start(); } }
- 实现Runnable接口,并实现run方法
public class TestRunnable implements Runnable { @Override public void run() { System.out.println("Hello World"); } public static void main(String[] args) { TestRunnable mTestRunnable = new TestRunnable(); Thread mThread = new Thread(mTestRunnable); mThread.start(); } }
-
实现Callable接口,重写call方法
- Callable可以在任务接受后提供一个返回值而Runnable不行
- Callable的call方法可以抛出异常,Runnable的run方法不行
- 运行Callable可以拿到一个Future对象,表示计算的结果,通过Future的get方法可以拿到异步计算的结果,不过当前线程会阻塞。
public class TestCallable { public static class MyTestCallable implements Callable<String> { @Override public String call() throws Exception { //call方法可以提供返回值,而Runnable不行 return "Hello World"; } } public static void main(String[] args) { MyTestCallable myTestCallable = new MyTestCallable(); //手动创建线程池 ExecutorService executorService = new ThreadPoolExecutor(1,1,0L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(10)); //运行callable可以拿到一个Future对象 Future future = executorService.submit(myTestCallable); try { //等待线程结束,future.get()方法会使当前线程阻塞 System.out.println(future.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
- 以上三种方式就是常见的创建线程的方式。推荐使用实现Runnable接口的方法。
线程中断
- 当一个线程调用interrupt方法时,线程的中断标识为将被设置成true
- 通过Thread.currentThread().isInterrupted()方法可以判断线程是否应该被中断
- 可以通过调用Thread.interrupted()对中断标志位进行复位(设置为false)
- 如果一个线程处于阻塞状态,线程在检查中断标志位时如果发现中断标志位为true,则会在阻塞方法处抛出InterruptedException异常,并且在抛出异常前会将中断标志位复位,即重新设置为false
- 不要在代码底层捕获InterruptedException异常后不做处理
同步的几种方法
同步的方式一般有如下3种:volatile关键字、synchronized关键字、重入锁ReentrantLock
volatile关键字
- volatile关键字实现多线程安全关键在于它的可见性特性,但它需要满足一些条件才能保证线程安全,具体可以查看文章深入理解Java虚拟机(八)之Java内存模型
- 在用volatile关键字来实现多线程安全时需要注意volatile不保证原子性,也就是不能用于一些自增、自减等操作,也不能用于一些不变式中,自增、自减比较好理解,下面看看不变式的情况
public class VolatileTest { private volatile int lower,upper; public int getLower() { return lower; } public void setLower(int value) { if (value > upper) { throw new IllegalArgumentException(); } this.lower = value; } public int getUpper() { return upper; } public void setUpper(int value) { if (value < lower) { throw new IllegalArgumentException(); } this.upper = value; } }
- 上面的例子中,如果初始值是(0,5),线程A调用setLower(4),线程B调用setUpper(3),显然最后结果就会变成(4,3)了
- volatile使用的场景常见的有作为状态标志以及DCL单例模式
synchronized关键字和重入锁ReentrantLock
- synchronized关键字比较常见,可以用于同步方法也可以用于同步代码块,一般推荐用同步方法,同步代码块的安全性不高。
- 重入锁ReentrantLock相比synchronized提供了一些独有的特性:可以绑定多个解锁的条件Condition、可以实现公平锁、可以设置放弃等待获取锁的时间。
public class ReentrantLockTest { private Lock mLock = new ReentrantLock(); //true,表示实现公平锁 <!--private Lock mLock = new ReentrantLock(true);--> private Condition condition; private void thread1() throws InterruptedException{ mLock.lock(); try { condition = mLock.newCondition(); condition.await(); System.out.println("thread1:Hello World"); }finally { mLock.unlock(); } } private void thread2() throws InterruptedException{ mLock.lock(); try { System.out.println("thread2:Hello World"); condition.signalAll(); }finally { mLock.unlock(); } } }
- 一个ReentrantLock有多个相关的Condition,调用Condition的await方法会让当前线程进入该条件的等待集并阻塞,直到另一个线程调用了同一个条件的signalAll方法激活因为这个条件而进入阻塞的所有线程
- 一般线程同步用得比较多的还是synchronized同步方法和一些java.util.concurrent包提供的一些类
如何安全的终止线程
虽然我们一般都是利用线程池来管理线程而不会直接显示地创建线程,但是作为线程相关知识的一部分,我们还是要了解如何安全地终止一个线程。
要安全地终止一个线程,一般有2种方法:中断和标志位
(1)利用中断来终止线程
Thread thread = new Thread(new Runnable() { @Override public void run() { while (!Thread.currentThread().isInterrupted()) { //do something } } }); //当我们调用Thread的interrupt方法时,线程就会退出循环停止了。 thread.interrupt();
(2)通过标志位
private static class MyRunnable implements Runnable { //控制线程的标志位,需要用 volatile关键字 private volatile boolean on = true; @Override public void run() { while (on) { //do something } } public void cancel() { on = false; } } //启动线程 MyRunnable myRunnable = new MyRunnable(); Thread thread = new Thread(myRunnable); //终止线程 myRunnable.cancel();
欢迎关注我的微信公众号,期待与你一起学习,一起交流,一起成长!
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Java Web项目,Android和微信小程序的初始页面配置
Java Web项目 我们在Eclipse里开了Java Web项目之后,Run As Tomcat或者Apache服务器,本地运行,如果直接用http://localhost:8080访问项目,会发现浏览器会打开一个默认的页面。 这个页面是在什么地方配置的呢? 每个Java Web工程的目录下面,都有一个名为WEB-INF的目录,里面包含一个配置文件web.xml。Web项目的默认页面就定义在里面的welcome-file-list节点里。 Android项目 我们知道一个Android项目里可以定义不同的Activity,那么Android应用启动后看到的初始页面对应的Activity,定义在Android项目的AndroidManifest.xml配置文件里,属性为android:name。看下面的例子: package="com.jerry.android4" android:versionCode="1" android:versionName="1.0" > android:minSdkVersion="8" android:targetSdkVersion="19"...
- 下一篇
iOS 文件模板(.xctemplate)ViewController.m
前言 为了使项目整体代码更加简洁,易读,多人开发时不仅能再代码规范上有一套规范,以及最常见的 ViewController 情况允许的情况下, 尽量保持一套发放调用排序, 这样我们在后期维护 Review 起来会非常轻松。并且通过自定义设置 Xcode 创建类文件模板, 可以轻松的每次就自动帮我们生成。 自定义 生成模板方法 来到: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates 即可. 具体就不重复阐述了 参考: iOS自定义代码模板 Xcode iOS ViewController.m File Template 这一套是我在项目中对 ViewController 的 #pragma 定义, 供大家参考。 #pragma mark - 1.View Controller Life Cycle - (void)viewDidLoad { [super viewDidLoad]; // Do any additional se...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS8编译安装MySQL8.0.19
- CentOS6,CentOS7官方镜像安装Oracle11G
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题