一. 线程管理之Thread基础

不忘初心 砥砺前行, Tomorrow Is Another Day !html

相关文章

本文概要:java

  1. 进程与线程
  2. 线程的生命周期
  3. 线程易混淆的函数
  4. 锁机制
  5. 线程同步的四种方式
  6. 对 volatile 关键字的理解

一. 进程和线程

1.1 基本概念

  • 进程

概念:进程是程序的实体,是受操做系统管理的基本运行单元.编程

  • 线程

概念:操做系统中最小调度单元,一个进程能够拥有多个线程.bash

1.2 线程的生命周期

  • new : 新建状态,当new Thread实例化时.
  • Runnable : 可运行状态,当调用start方法时.
  • Running : 运行状态,线程被cpu执行,调用了run方法时.
  • Blocked 阻塞状态,当调用join()、sleep()、wait()时.分三种阻塞状况
    • wait : 等待状态,当调用wait方法时,此时须要调用它的notify方法去唤醒它,才会重回可运行状态.
    • timeWait : 超时等待状态,当调用t.join(long)、Thread.sleep(long),obj.wait(long)时,超过指定时间,都会自动返回可运行状态.
    • lock: 同步状态,获取对象的同步锁,若该同步锁被别的线程占用时.
  • Dead : 销毁状态,线程执行完毕或者发生异常时.

1.3 线程易混淆的函数

  • Thread.sleep()/sleep(long millis),当前线程进阻塞状态,不会释放锁
  • t.join()/join(long millis),在当前线程里调用其它线程的join方法,当前线程进阻,不放锁.
    • (至关于其余线程插队进来,须要等待其余线程执行完毕才可往下执行),
  • obj.wait()/wait(long timeout),当前线程进阻,放锁.须要依靠notify()/notifyAll()唤醒或者等待时间到自动唤醒
  • obj.notify()/obj.nofiyAll唤醒在此对象监视器上阻塞的任意某个线程/全部线程.
  • Thread.yield(),当前线程不进阻,不放锁.而是重置为可运行状态.
  • interrupt(),中断线程.

二. 线程的建立

线程的建立有3种方式.多线程

2.1 继承Thread,重写run方法.

这种方式的本质也是实现Runnable接口.当咱们调用start方法时,并不会当即执行线程里面代码,而只是将线程状态变为可执行状态,具体的执行时机由操做系统决定.并发

public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("直接继承Thread,重写run方法");
    }

    public static void main(String[] args) {
        new MyThread().start();
    }
}
复制代码

2.2 实现Runnable接口,重写run方法.

public class MyRunnable implements Runnable {

    @Override
    public void run() {
        System.out.println("实现Runnable接口,重写run方法");
    }

    public static void main(String[] args) {
        new Thread(new MyRunnable()).start();
    }
}
复制代码

2.3 实现Callable接口,重写Call方法.

相比Runnable的三大功能.异步

  • 能够抛出异常
  • 提供返回值
  • 经过Future异步任务统计,能够对目标线程Call方法监视执行状况,获取执行完毕时的返回值结果.

关于ExecutorService与Future相关知识,将在线程池一篇文中详细讲解.这里只须要知道Callable通常是和ExecutorService配合来使用的.ide

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("子线程正在干活");
        Thread.sleep(3000);
        return "实现Callable接口,重写Call方法";
    }

    public static void main(String[] args) throws Exception {
        MyCallable myCallable = new MyCallable();
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        Future<String> future = executorService.submit(myCallable);
        executorService.shutdown();

        Thread.sleep(1000);//模拟正在干活
        System.out.println("主线程正在干活");
        //阻塞当前线程,等待返回结果.
        System.out.println("等待返回结果:" + future.get());
        System.out.println("主线程全部的活都干完了");

    }
}

//调用输出
子线程正在干活
主线程正在干活
等待返回结果:实现Callable接口,重写Call方法
主线程全部的活都干完了
复制代码

三. 线程的同步

线程同步的目的就是为了防止当多个线程对同一个数据对象进行存储时,形成数据不一致的问题.函数

同步问题示例post

public class SyncThread extends Thread {
    private static final String TAG = "SyncThread";
    private Pay pay;

    public SyncThread(String name, Pay pay) {
        super(name);
        this.pay = pay;

    }
    
    @Override
    public void run() {
        while (isRunning) {
            pay.count();
        }
    }
}

Pay.java
    /**
     * 未同步时
     */
    public void count() {
        if (count > 0) {
            System.out.println(Thread.currentThread().getName() + ":>" + count--);
        } else {
            isRunning = false;
        }
    }
复制代码
//未使用同步时
D: 线程3:>912
D: 线程1:>911
D: 线程2:>911 //此时已经出现数据不同
D: 线程1:>909
D: 线程3:>910
D: 线程2:>908
D: 线程1:>907
D: 线程3:>906
D: 线程2:>905
D: 线程1:>904
D: 线程2:>903
D: 线程3:>903
D: 线程1:>902
D: 线程2:>901
D: 线程3:>900

//使用同步时
D: 线程1:>1000
D: 线程2:>999
D: 线程2:>998
D: 线程1:>997
D: 线程2:>996
D: 线程1:>995
D: 线程2:>994
D: 线程1:>993
D: 线程2:>992
D: 线程1:>991
D: 线程2:>990
D: 线程1:>989
D: 线程2:>988
D: 线程1:>987
D: 线程2:>986
D: 线程1:>985
D: 线程2:>984
D: 线程1:>983
复制代码

3.1 锁机制

为了解决异步的问题,JAVA提供了锁的机制,synchronized 关键字.能够很方便的实现线程的同步.

先理解如何进行手动加锁,这样更容易理解自动加锁的机制.

3.1.1 认识重入锁与条件对象
  1. 重入锁:对资源进行手动枷锁.
  • ReentrantLock() : 建立一个ReentrantLock实例
  • lock() : 得到锁
  • unlock() : 释放锁
  1. 条件对象: 使线程知足某一条件后才能执行,不知足则进阻,放锁.用于管理已得到锁可是暂时没做用的线程.
  • lock.newCondition : 得到一个条件对象.
  • condition.await() :进阻,放锁.至关于obj.wait方法.
  • condition.signal/signalAll : 唤醒在此条件上阻塞的任意某个线程/全部线程.至关于obj.notify/notifyAll

伪代码

Lock lock = new ReentrantLock();
Condition condition = lock.newCondition;
lock.lock();
        try {
            if(count == 0){
                //进阻,放锁
                condition.await();
            }
            //唤醒所以条件,而阻塞的全部线程
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally{
           lock.unlock(); 
        }

复制代码
3.1.2 synchronized 关键字

资源互斥,线程数据同步,提供了自动加锁.同步本质见java内存模型.

  • 对象锁: 每个对象有一个内部锁,而且只有一个内部条件.
    • 对应synchronized关键字,若是是多个线程访问同个对象的sychronized块,才是同步的,可是访问不一样对象的话就是不一样步的。
  • 类锁: 是一个全局锁
    • 对应static sychronized关键字,不管是多线程访问单个对象仍是多个对象的sychronized块,都是同步的

在实际开发中大多数状况使用同步方法与同步代码块,实现同步,除非一些须要高度控制锁的则使用重入锁和条件对象.

3.2 实现同步的方式

线程同步的四种方式.

1. 同步方法

private synchronized void countSyncMethod() {
        if (count > 0) {
            Log.d(TAG, Thread.currentThread().getName() + ":>" + count--);
        } else {
            isRunning = false;
        }
    }
复制代码

2. 同步代码块

private void countSyncCode() {
        synchronized (this) {
            if (count > 0) {
                Log.d(TAG, Thread.currentThread().getName() + ":>" + count--);
            } else {
                isRunning = false;
            }
        }
    }
复制代码

3. 使用重入锁

private void countSyncLock() {
        mLock.lock();
        try {
            if (count > 0) {
                Log.d(TAG, Thread.currentThread().getName() + ":>" + count--);
            } else {
                isRunning = false;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }inally{
           mLock.unlock(); 
        }

    }
复制代码

4. 使用特殊域变量(volatile)

3.3 volatile 关键字

先了解java内存模型与“三性”知识.

3.3.1 Java内存模型

java内存模型定义了线程和主内存之间的抽象关系.

  • 全部线程的共享变量在主内存中.
  • 每一个线程都有一个本地内存.

java内存模型基础图

线程在对变量进行存与取时,通常先改变工做内存的变量值,再在某个时机刷新到主存中去.这样就会致使多线程并发时另外一个线程从主存中获取到的不必定是最新的值.

3.3.2 Java并发编程的原子性、可见性、有序性
  • 原子性:对于基本数据类型只是简单的读取和赋值(将数字赋值给某个变量),仅有一个操做就是原子性操做,操做是不可中断的.
  • 可见性:一个线程的修改对另一个线程是当即可见的.
    • 即volatile修饰的变量,若是在一个线程修改值,则会当即更新到主存中去,那么另外一个线程会获取到最新的值.
  • 有序性:编译时和运行时会对指令进行从新排序,会影响多线程并发执行的正确性.

这样当一个共享变量被volatile修饰时.

  1. 保证可见性,即一个线程对变量值进行了修改,另外一个线程能够当即获取到最新修改后的值.
  2. 保证有序性,即禁止指令重排序.
  3. 不保证原子性.
    • 因此不适用于修饰如自增自减等一些依赖于自身或者其余变量值的变量时.
private int x;
private int y;

//依赖自身
x++;
//依赖其余变量
if(x > y){
    x = y;
}
复制代码

四. 线程的中断

  • 关键字: interrupted
    中断目标线程,并非指当即中止线程,而仅仅只是将线程的标识为true,通常由目标线程本身去检测并决定是否终止线程。

示例代码

@Override
    public  void run() {
        //中断目标线程,并非指当即中止线程,而仅仅只是将线程的标识为true,通常由目标线程本身去检测并决定是否终止线程.
        for (int j = 0; j <100000000 ; j++) {
            if (Thread.currentThread().isInterrupted()){
                System.out.println("Interrupted!已经中断中止输出.开始收尾工做1");
                return;
            }
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                //处于阻塞状态的线程,也会立马被中断,因此就会抛出此异常.
                System.out.println("Interrupted!已经中断中止输出.开始收尾工做2");
                return;
            }
            System.out.println("还没中断继续输出j:" + j);
        }
    }

复制代码

关于Thread基础相关就介绍到这里了.接着下一篇介绍多线程编程中的线程池.Demo源码在最后一篇一块儿给出.

因为本人技术有限,若有错误的地方,麻烦你们给我提出来,本人不胜感激,你们一块儿学习进步.

参考连接:

相关文章
相关标签/搜索