多线程

线程和进程

线程是CPU调度的最小单元,进程是系统资源管理的最小单元,线程之间的切换开销更小,一个进程包括多个线程。

线程的状态

线程有六种状态:
New:线程新创建,没有调用start方法,在线程运行前做一些基础工作。
Runnable:调用了start方法,此时线程进入就绪状态,可能在运行,也可能不在运行。
Blocked:线程被锁阻塞。
Waiting:线程不活动,占用最小的资源,直到调度器再次唤醒。
Timed Waiting:超时等待状态,到一定时间会自动返回。
Terminated:终止状态,两种可能,程序执行完毕或者遇到异常退出。

线程的创建

线程的创建有三种种方法:
(1)继承Thread类,重写run方法。
(2)实现Runnable接口,实现run方法。
(3)实现callable接口,实现call方法,不同于Runnable,Callable会返回一个异步计算结果Future,并且能抛出异常,Future通过get方法获得结果的时候就会阻塞,知道call返回结果。

中断

当使用intecept的时候,将会把中断标识位置为true,线程会不断的检测这个中断标识位,并抛出异常,然后将中断标识位重新置为false,最好的方法是将这个异常直接抛出,交给中断调用者去处理。

安全的中止线程的方式

第一种方式,是使用thread的intercept中断,第二种是使用thread.cancel,通过boolean值来设置终止。

public class StopThread {
    public static void main(String[] args) throws InterruptedException {
        MoonRunner runnable = new MoonRunner();
        Thread thread = new Thread(runnable,"MoonThread");
        thread.start();
        TimeUnit.MILLISECONDS.sleep(10);
        thread.interrupt();
    }

    private static class MoonRunner implements Runnable{
        private long i;
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()){
                i++;
                System.out.println("i="+i);
            }
            System.out.println("stop");
        }
    }
}
    public static void main(String[] args) throws InterruptedException {
        MoonRunner runnable = new MoonRunner();
        Thread thread = new Thread(runnable,"MoonThread");
        thread.start();
        TimeUnit.MILLISECONDS.sleep(10);
        runnable.cancel();
    }

    private static class MoonRunner implements Runnable{
        private long i;
        private volatile boolean on = true;
        @Override
        public void run() {
            while (on){
                i++;
                System.out.println("i="+i);
            }
            System.out.println("stop");
        }
        public void cancel(){
            on = false;
        }
    }
同步

同步是多个线程竞争一个资源的时候,为了保证资源的原子性,每个线程在对资源进行修改的时候,就对资源加锁,不允许其他线程修改。

ReentranLock

可重入锁,表示一个线程可以重复获得一个锁。可重入锁的实现方式如下:

 Lock mLock = new ReentrantLock();
        mLock.lock();
        try{
            //TODO
        }finally {
            mLock.unlock();
        }

这样就能保护try代码块中的内容,finally是有必要的,保证退出时即使释放锁。

条件对象

一个锁可以有多个条件对象,当条件对象使用await方法的时候,会阻塞当前线程,并释放锁,等待其他线程使用signal或者signalAll唤醒条件所在的线程。 比如图中的转账功能,当转账方的余额不够转账,那么就会一直等待,但是有可能转账方收到别人的转账,余额又够了。为了处理这种情况,加入condition。

public class Alipay {
    private double[] accounts;
    private Lock alipaylock;
    public Alipay(int n,double money){
        accounts = new double[n];
        alipaylock = new ReentrantLock();
        for (int i = 0 ; i< accounts.length ; i ++){
            accounts[i] = money;
        }
    }
    public void  transfer (int from,int to,int amount){
        alipaylock.lock();
        try{
            while (accounts[from]  < amount){
                //wait
            }
        }finally {
            alipaylock.unlock();
        }
    }
}

修改如下,1处声明一个Condition,2处获得锁的条件对象,3处当余额不足,阻塞线程,并释放锁,其他线程得以继续实现转账操作,转账完成后,通过signalAll唤醒阻塞的线程,继续进行转账操作。

public class Alipay {
    private double[] accounts;
    private Lock alipaylock; //1
    private Condition condition;
    public Alipay(int n,double money){
        accounts = new double[n];
        alipaylock = new ReentrantLock();
        condition=alipaylock.newCondition();//2
        for (int i = 0 ; i< accounts.length ; i ++){
            accounts[i] = money;
        }
    }
    public void  transfer (int from,int to,int amount){
        alipaylock.lock();
        try{
            while (accounts[from]  < amount){
                condition.await();//3
            }
            //转账
            condition.signalAll();//4
        }finally {
            alipaylock.unlock();
        }
    }
}
同步方法

使用synchronized的tongue方法可以代替你以上的写法,不需要显示的调用锁,用Object的,同步方法使用一个内部对象锁,内部对象锁只有一个相关条件,比如await相当于condition.await。转账功能就能修改如下,简洁很多。

    public synchronized void  transfer (int from,int to,int amount) throws InterruptedException {
            while (accounts[from]  < amount){
                wait();
            }
            //转账
            notifyAll();
    }
同步代码块

如图,相当于获得obj的锁

    synchronized(obj){
        
    }

修改转账功能如下:

public class Alipay {
    private double[] accounts;
    private Object lock  = new Object();
    public Alipay(int n,double money){
        accounts = new double[n];
        for (int i = 0 ; i< accounts.length ; i ++){
            accounts[i] = money;
        }
    }
    public synchronized void  transfer (int from,int to,int amount) throws InterruptedException {
        synchronized (lock){
            //转账
        }
    }
}

定义了一个Object的lock,就是为了获得Object内部的锁,同步代码块是非常脆弱的,不建议使用。

volatile

java中的内存:线程之间的共享变量存储在主存中,每个线程都有自己的本地内存,存储共享变量的副本。线程间的通信先要修改本地内存中的变量,再同步到主存的共享变量上。
原子性:操作时不能中断的,要么不执行,要么就执行完毕。比如X=3是原子性操作,而y=x不是原子性操作,包含两个部分,读取x的值,将值赋值给y。x++包含3个操作,读取x的值,将x的值+1,将值写入工作内存中去。atomic包中包含了很多原子操作来保证原子性。
可见性:一个线程中的变量的修改对其他线程是可见的,所做的修改会立刻同步到主存上去。
有序性,java中允许编译器对指令进行重排序,而volatile和synchronized和lock能够保证有序性。
volatile包含两个意思,一个是对其他线程是可见的,一个是禁止指令的重排序。但是volatile不保证原子性。使用volatile必须满足两个条件,一个条件是写操作不能依赖于当前值,比如自增,自减,另一个条件就是变量不能包含在另一个变量的不变式中。volatile的使用场景主要有两个,一个是状态标志,一个是双重检查。

阻塞队列

阻塞队列适合生产者,消费者的模式,生产者往队列里添加元素,消费者从队列中拿走缘故。阻塞场景:队列中没有元素的时候,消费者端的所有线程就会被挂起,知道队列中出现数据。当队列满的时候,生产者端的所有线程就会被挂起,直到队列中有空的位置。

ArrayBlockingQueue

由数组实现的有界阻塞队列,一般不保证公平的访问。

LinkedBlockingQueue

由链表实现的,没有界限,表头和表尾是可以并行进行,提高了队列的性能。

PriorityBlockingQueue

支持优先级的无界队列。

DelayQueuw

支持延时获取的无界阻塞队列

SynchronousQueue

不存储元素的阻塞队列,插入元素必须等元素移出。

LinkedTransferQueue

主要是提供了各种策略,当消费者端有需求时,先给消费者端再进入队列。

LinkedBlockingDeque

双向的阻塞队列。

阻塞队列的实现原理

put往队列里放元素的时候获得锁,然后等待notFull信号,然后放入元素。take的时候获得锁,然后等待notEmpty信号,然后拿走元素,并notify唤醒其他线程。

线程池

线程的创建和销毁都是需要消耗资源的,因此使用线程池来对线程进行管理,当不需要线程的时候释放线程给线程池即可。线程池的核心是ThreadPoolExecutor。

ThreadPoolExecutor

构造方法传递的参数有:
corePoolSize:核心线程数,当前线程数小于核心线程数的时候就会创建,否则不会创建。
maximumPoolSize:如果任务队列满了,并且线程数小于小于maximumPoolSize的时候线程池任然会创建线程。
keepAliveTime:非核心线程数的超时时间。
TimeUtil:KeepAliveTime的时间单位。
workQueue:如果当前的线程数大于corePoolSize,那么将任务添加到这个队列中去。
ThreadFactory:线程工厂。
RejectExecutionHandler:设置饱和策略。
线程池的处理流程:提交任务后,先判断线程数是否到达了核心线程数,如果没有,那么创建新的线程。然后判断任务队列是否满了,如果没满,那么将任务添加到任务队列中去,如果满了,那么判断当前线程是否到了最大线程数,如果没到最大线程数,那么创建非核心线程,如果到达了最大线程数,那么执行饱和策略。

线程池的种类
FixedThreadPool

是固定数量的线程池,没有非核心线程。

CachedThreadPool

CachedThreadPool是没有核心线程的,全是非核心线程的线程池。它是不存储元素的,每个插入操作都需要等待其他线程的移出。

SingleThreadExecutor

是使用单个线程的线程池

ScheduledThreadPoolExcutor

它执行的任务是ScheduledFutureTask,如果没到达核心线程数,那么就会新建线程去DelayedWorkQueue中找任务并执行,如果超出核心线程数,那么就会直接添加到DelayedWorkQueue中。DelayedWorkQueue会对任务进行排序,先要执行的任务会放在前面。

AsyncTask原理

Android提供了AsyncTask,使得异步任务实现起来变得简单。AsyncTask是抽象的泛型类,有3个泛型参数,分别是Param,Progress,Result,Param是参数类型,Progress表示进程的类型,Result是返回结果的类型。主要有4个方法:
(1) onPreExecute():在主线程执行,在执行任务之前做一些准备工作。
(2) doInBackgroud:在线程池中执行,用来执行耗时的操作,可以调用publishProgress来更新任务进度。
(3)onProgressUpdate:在主线程中执行,当调用publishProgress时,此方法将会把进度更新到UI界面上。
(4)onPostExecutor:在主线程中执行,做一些收尾工作,比如更新UI和数据。

AsyncTask的实现
首先在构造方法中,持有一个WorkRunnable实现了Callable借口,实现了call方法,在call方法中调用了doInBackground来处理任务并得到结果。最终调用postResult将结果投递出去。还持有了一个FutureTask,它是一个可管理的异步任务,可以交给线程池执行,这里,WorkRunnable作为参数传给了FutureTask。AsyncTask执行时调用了execute方法,execute方法又调用了executeOnExecutor方法,首先调用了onPreExecute,WorkRunnable和参数封装到FutureTask中后,使用SerialEXcutor来执行,SerialEXcutor中Task是串行的,然后将Task交给ThreadPoolExecutor执行,最终执行了FutureTask的run方法,里面最终执行了WorkRunnable的call方法,通过postResult将结果投递出去,通过一个handler发消息,最终通过onPostExecute方法,得到异步结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值