极力推荐文章:欢迎收藏
Android 干货分享

阅读五分钟,每日十点,和您一起终身学习,这里是程序员Android

1.什么是线程

线程就是进程中运行的多个子任务,是操作系统调用的最小单元

2.线程的状态

1.New:

新建状态,new出来,还没有调用start

2.Runnable:

可运行状态,调用start进入可运行状态,可能运行也可能没有运行,取决于操作系统的调度

3.Blocked:

阻塞状态,被锁阻塞,暂时不活动,阻塞状态是线程阻塞在进入

4.synchronized:

关键字修饰的方法或代码块(获取锁)时的状态。

5.Waiting:

等待状态,不活动,不运行任何代码,等待线程调度器调度,waitsleep用来暂停当前线程的执行,以毫秒为单位,任何其它线程都可以中断当前线程的睡眠,这种情况下将抛出InterruptedException异常

6.Timed Waiting:

超时等待,在指定时间自行返回

7.Terminated:

终止状态,包括正常终止和异常终止

3.线程的创建

线程创建的常用方法
  • 1.继承Thread重写run方法
  • 2.实现Runnable重写run方法
  • 3.实现Callable重写call方法
实现Callable重写call方法

实现Callable和实现Runnable类似,但是功能更强大,具体表现在

  • a.可以在任务结束后提供一个返回值,Runnable不行
  • b.call方法可以抛出异常,Runnablerun方法不行
  • 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,那么在这六种状态下调用线程中断的代码会怎样呢,NewTerminated状态下,线程不会理会线程中断的请求,既不会设置标记位,在RunnableBlocked状态下调用interrupt会将标志位设置位true,在WaitingTimed 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,包括在catchfinally语句中。
  • 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

原文地址:https://juejin.im/post/5d6d0f2fe51d4561fd6cb53d