线程基础知识

来源:互联网 发布:淘宝客采集器 编辑:程序博客网 时间:2024/06/16 16:10

前言:复习了一遍线程,查漏补缺,但又经常记不住,只好写下来,时不时回来看看,可能会有错误。。。。我能力有限。。。阅读大约需要一刻多

目录:
1、线程的创建
2、对象及变量的并发访问
3、线程间通信
4、Lock
5、线程池
6、Fork/Join框架
7、定时器Timer

java线程基础

线程有六种状态:New(新建)、Runnable(可运行)、Blocked(被阻塞)、Waiting(等待)、Timed waiting(计时等待)、Terminated(被终止),有两种创建方法,继承Thread或者实现Runnable接口,由于Java是单继承,所以继承Thread的话,在设计上有局限性,不支持多继承,Thread.java类也是实现了Runnable接口,这意味着Thread(Rnnable r)不仅可以传一个Runnable接口的对象,还可以传Thread类的对象。

Thread(Runnable target):构造一个新线程,用于调用给定目标的run()方法
新建:继承Thread:
MyThread thread=new MyThread();
实现Runnable:

MyRunnable runnable=new MyRunnable();        Thread thread=new Thread(runnable);

从类Thread继承或者实现Runnable接口的创建的线程,方法run是没有返回值的,带返回值的线程通过Callable来定义,并通过接口Future来获得线程的返回值。
接口Callable的定义如下:

public interface Callable<V> {    V call() throws Exception;}

使用Callable创建的线程须实现call方法,call方法的返回值是由V指定的。
接口Future允许在某个时间获得线程运行的结果,它保存了使用Callable接口定义的线程异步运行的结果。FutureTask包装器同时实现了Runnable和Callable接口,可以把Callable对象进行封装并转换为Future对象,构造方法如下:

FutureTask(Callable<V> task)    FutureTask(Runnable runnable,V result)

通过V get()获得线程的返回值,cancel()尝试取消任务的运行,使用如下:

Callable<V> caller=xxxx;    FutureTask<V> task=new FutureTask<V> (caller);    Thread t=new Thread(task);    t.start();    V result=task.get();
currentThread():返回代表当前线程的Thread对象isAlive():判断当前的线程是否处于活动状态,即已经启动且尚未终止sleep(long mills):休眠给定的毫秒数,sleep方法可能抛出InterruptedException异常voiod start():启动这个线程,将引发调用run方法。这个方法将立即返回,并且新线程将并发执行void run():调用关联Runnable的run()方法void interrupt():向线程发送中断请求,线程的中断状态设为true,仅仅实在当前线程中打了个停止的标志,不是真的停止了线程static boolean interrupted():测试当前线程是否被中断,这是一个静态方法会有副作用,第二次调用将当前线程的中断状态重置为false,即清楚状态标志位

boolean isInterrupted():测试线程是否被终止,不改变线程的中断状态,不清除状态标志
终止正在运行的线程有三种方法:
(1)使用退出标志,使线程正常退出,也就是当run方法执行完后退出;
(2)使用stop方法,已过时;
(3)使用interrupt方法中断进程。

异常法:
沉睡停止法:如果在sleep状态下停止某一线程,会进入catch语句,并且清除状态值,使之变为false
return法:将方法interrupt和return连用实现停止线程的目的

//void joid():等待终止指定的线程void join():等待指定的线程死亡或经过指定的毫秒数getState():得到这一线程的状态:new/Runnable/Waiting/Timed_Waiting/Terminatedvoid stop():停止该线程,此方法已过时void suspend():暂停这一线程的执行,已过时void resume():恢复线程,仅在调用suspend()之后调用,已过时

每一个线程都有一个默认的继承父线程的优先级,MIN_PRIORITY(1)与MAX_PRIORITY(10)之间,NORM_PRIORITY优先级为5,优先级高只是被调度的机会大,不是一定比低优先级的先调度

void setPriority()//设置线程的优先级,优先级必须在MIN_PRIORITY(1)与MAX_PRIORITY(10)之间,一般使用NORM_PRIORITY优先级为5static int MIN_PRIORITY//线程的最小优先级为1static int NORM_PRIORITY//线程的默认优先级5static int MAX_PRIORITY//线程的最高优先级10

satic void yield():导致当前执行线程处于让步状态,放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。如果有其他的可运行线程具有至少与此线程同样高的优先级,那么这些线程接下来会被调度,ATTENTION!这是一个静态方法

void setDaemon(boolean isDaemon):标识该线程为守护线程或用户线程,这一方法须在线程启动之前调用,当进程中不存在非守护线程时,守护线程将自动销毁,例如垃圾回收线程,任何一个守护线程都是JVM中所有非守护线程的保姆。

对象及变量的并发访问

如果多个线程共同访问1个对象中的实例变量,有可能出现非线程安全问题。使用带synchronized关键字的方法,哪个线程就持有该方法所属对象的锁lock,其他线程只能等待。只有共享资源的读写访问才需要同步化,如果多个线程访问多个对象,则JVM会创建多个锁。
如果线程A先持有object对象的Lock锁,B线程可以以异步的方式调用object对象的非synchronized类型的方法;如果线程A先持有,B线程这时候调用object对象中的synchronized类型的方法时则需要等待,也就是同步。

通过在run方法前加synchronized关键字,使多个线程在执行run方法时,以排队的方式进行处理。当一个线程调用run前,先判断run方法有没有上锁,如果上锁则等待其他线程执行完再调用。synchronized可以在任意对象及方法上加锁,这段区域称为互斥区或临界区。

synchronized:自动提供一个锁及相关的“条件”,同步不具有继承性,synchronized方法是对当前对象加锁,而synchronized代码块是对某一个对象加锁
volatile关键字:使变量在多个线程中可见,强制从公共堆栈中取得变量的值,而不是从线程私有堆栈中,但不支持原子性!

synchronized与volatile比较:
修饰范围 多线程是否阻塞 数据可见性和原子性
synchronized 方法以及代码块 会出现 都有
volatile 变量 不会 可见但不原子

将静态方法声明为synchronized也是合法的,调用该方法获得相关的类对象的内部锁,没有其他线程可以调用同一个类的这个或任何其他的同步静态方法,内部锁和条件存在一些局限:
(1)不能中断一个正在试图获取锁的线程
(2)试图获得锁时不能设定超时
(3)每个锁仅有单一的条件,可能是不够的
(4)最好既不使用Lock/Condition也不使用synchronized关键字,可以考虑使用阻塞队列或者并行流
(5)如果synchronized适合就用,减少编写的代码数量
(6)如果特别需要Lock/Condition结构提供的独有特性才使用

同步块的内置监视器使用:

public class T{    synchronized (this){        //使用默认的当前对象this    }    private Object object;    public void test(){        synchronized (object){            //使用当前类的属性        }    }    public void test(Object object){        synchronized (object){            //使用本地变量或方法的形式化参数        }    }    public Object object;    public class Test{        private T t;        public void test(){            synchronized (t.object){                //使用其他类的属性            }        }    }    synchronized (A){        synchronized (B){            //嵌套的同步块        }    }    public static void test(){        synchronized (T.class){            //静态类的对象        }    }}

线程间通信

等待/通知机制(wait/notify):
void wait():导致线程进入等待状态直到它被通知,Object类方法,将当前线程置入“预执行队列”,并在wait所在代码行停止执行,直到接到通知或被中断为止,该方法只能在一个同步方法中调用,调用前须取得对象的对象级别的锁,若没有就抛异常,执行后,当前线程释放锁,然后从运行状态退出,进入等待队列直到被唤醒

void wait(long mills)void wait(long mills,inr nanos)

void notify():如果多个线程等待则随机选择一个在该对象上调用wait()方法的线程,解除其阻塞状态,该方法只能在一个同步方法或同步块中调用,也要在同步方法或块中调用。执行完后当前线程不会马上释放对象锁,wait状态的线程也不能马上获取该对象锁,要等到退出synchronized代码块后,当前线程才会释放锁

void notifyAll():解除那些在该对象调用wait方法的线程的阻塞状态,使所有正在等待队列中等待同一共享资源的“全部”线程从等待状态退出进入可运行状态。该方法只能在同步方法或者同步块内部调用,如果当前线程不是对象锁的持有者,该方法抛出IllegalMonitorStateException异常

生产者/消费者模型:

void join():等待指定的线程死亡或经过指定的毫秒数后,具有使线程排队运行的作用,但与synchronized不同,join在内部使用wait方法进行等待,synchronized使用的是对象监视器原理
join(long)的功能是在内部使用wait(long)方法进行等待,会释放锁,而sleep(long)不释放锁

类ThreadLocal的使用,可以使每个线程绑定自己私有变量

Lock

可重入锁是一种无阻塞的同步机制,class ReentrantLock extends Object implements Lock,Serializable,这是一种互斥锁,增加了如获取锁时公平性设置、测试锁trylock、测试锁是否正在被持有等功能,锁分为公平锁和非公平锁,公平锁表示线程获取锁的顺序按照线程加锁的顺序来分配,即先来先分;非公平锁是一种获取锁的抢占机制,随机获得锁
。Condition对象与ReentrantLock结合可以实现等待/通知,“选择性通知”。
构造方法如下:

ReentrantLock();//fair默认为falseReentrantLock(boolean fair)

使用可重入锁在进行同步控制时,需要明确地定义可重入的锁对象,并在该对象上进行加锁和解锁操作,通常放进try{}catch(){}finally{}语句块中,用ReentrantLock类保护代码块,确保任何时刻都只有一线程进入临界区,一旦一个线程封锁了锁对象,其他任何线程都无法通过lock()语句,把解锁操作括在finally子句,不要因为异常的抛出而跳出临界区,如果在结束之前抛出了异常,finally子句将释放锁,但会使对象可能处于一种受损状态。

Lock lock = new ReentrantLock();lock.lock();try{    //}catch (Exception e){   //}finally {    lock.unlock();}

ReentrantReadWriteLock读写锁维护了一对相互关联的读锁和写锁,在没有线程持有写锁的情况下,读锁可以有多个线程同时持有,写锁只能由一个线程持有,具有 锁的获取属性、可重入性、锁降级、获取时中断、条件操作、监视状态等属性。

ReentrantReadWriteLock reentrantReadWriteLock=new ReentrantReadWriteLock();Lock readLock=reentrantReadWriteLock.readLock();Lock writeLock=reentrantReadWriteLock.writeLock();readLock.lock();try{    //}catch (Exception e){    //}finally {    readLock.unlock();}writeLock.lock();try{    //}catch (Exception e){    //}finally {    writeLock.lock();}

synchronized同步锁是一种互斥锁,形式简单易于使用,但依赖于不直观的对象后的内置监视器;
可重入锁是一种互斥锁,在同步锁的基础上拓展了很多功能,如非阻塞枷锁操作、尝试获取锁时可以中断、测试锁等;
读写锁除了可重入锁的特性外,把锁分为读锁和写锁,多个线程可以同时获取读锁,还可以锁降级把写锁降为读锁,提供了更高程度的并发

void lock()//获取这个锁,如果锁同时被另一个线程拥有则发生阻塞void unlock()//释放这个锁int getHold()//查询当前线程保持此锁定的个数,即调用lock()的次数int getQueueLengh()//返回正在等待获取此锁定的线程的估计数boolean hasQueuedThread(Thread thread)//查询指定线程是否正在等待获取此锁定boolean hasQueuedThreads()//查询是否有线程正在等待获取此锁定boolean hasWaiters(Condition c)//查询是否有线程正在等待与此锁定有关的condition条件boolean isFair()//判断是不是公平锁boolean isHeldByCurrentThread()//查询当前线程是否保持此锁定boolean isLocked()//此锁定是否由任意线程保持void lockInterruptibly()//如果当前线程未被中断,则获取锁定,否则出现异常boolean tryLock()//仅在调用时锁定未被另一个线程保持的情况下,才获取锁定ReentrantLock()//构建一个可以被用来保护临界区的可重入锁ReentrantLock(boolean fair)//创建一个带有公平策略的锁,一个公平锁偏爱等待时间最长的线程,但是这公平的保证将大大降低性能,所以,默认锁没有被强制为公平的Condition newCondition()//返回一个与该锁相关的条件对象void await()//将该线程放到条件的等待集中void signalAll()//接触该条件的等待集中的所有线程的阻塞状态void signal()//从该条件的等待集中随机选取一个线程,解除其阻塞状态

void notifyAll():解除那些在该对象调用wait方法的线程的阻塞状态。该方法只能在同步方法或者同步块内部调用,如果当前线程不是对象锁的持有者,该方法抛出IllegalMonitorStateException异常
void notify():随机选择一个在该对象上调用wait()方法的线程,解除其阻塞状态,该方法只能在一个同步方法或同步块中调用
void wait():导致线程进入等待状态直到它被通知,该方法只能在一个同步方法中调用

void wait(long mills)void wait(long mills,inr nanos)

线程池

线程池包含了若干个准备运行的空闲线程,县城在程序运行的开始创建,可以把创建的Runnable对象交给线程池中的线程来运行,运行后,如果没有其他任务,线程转入休眠状态,等有任务再被唤醒,直到所有任务都执行结束再关闭线程池。线程池机制分离了任务的创建和执行,使用线程池执行器,仅需要实现Runnable对象并交其给执行器,执行器会使用线程池的线程执行,起到了维护和管理线程的作用。接口Executor的对象可以接受提交到线程池的Runnable任务,其中的execute方法异步地执行给定的Runnable对象
Executor executor=new Executor() {    @Override    public void execute(Runnable command) {        //    }};

或者

public class T implements Executor{    @Override    public void execute(Runnable command) {     //    }}

接口ExecutorService从父接口Executor继承,提供了关闭线程池的方法shutdown。

类ThreadPoolExecutor可以用来创建一个线程池,构造方法有四个,有七个参数,

public ThreadPoolExecutor(int corePoolSize,//线程池的线程数                          int maximumPoolSize,//允许的最大线程数                          long keepAliveTime,//线程数超过处理核数时多余的空闲线程等待新任务的最大等待时间                          TimeUnit unit,//上一个参数的等待时间                          BlockingQueue<Runnable> workQueue,//任务队列,调用执行方法提交的Runnable任务队列                          ThreadFactory threadFactory,//创建新线程时所用的工厂                          RejectedExecutionHandler handler//超出线程队列容量时执行的处理程序) {    if (corePoolSize < 0 ||        maximumPoolSize <= 0 ||        maximumPoolSize < corePoolSize ||        keepAliveTime < 0)        throw new IllegalArgumentException();    if (workQueue == null || threadFactory == null || handler == null)        throw new NullPointerException();    this.corePoolSize = corePoolSize;    this.maximumPoolSize = maximumPoolSize;    this.workQueue = workQueue;    this.keepAliveTime = unit.toNanos(keepAliveTime);    this.threadFactory = threadFactory;    this.handler = handler;}

类Executors提供了线程池创建的工厂方法,静态的可以直接调用:
newFixedThreadPool:创建固定大小的线程
newSingleThread:创建只有一个线程的线程池
newCachedThreadPool:创建一个线程池,需要时创建新线程,会重复利用旧线程
newSingleThreadScheduledExecutor:创建一个线程的线程池,可以在指定时间间隔或周期性地执行
newScheduledThreadPool:创建一个线程池,可以在指定的时间间隔或周期性执行

ExecutorService executorService=Executors.newSingleThreadExecutor(); ThreadPoolExecutor threadPoolExecutor= (ThreadPoolExecutor) Executors.newCachedThreadPool();
                Fork/Join框架

主要由Fork和Join两个操作构成,Fork操作主要用于对任务和数据的划分,Join操作主要用于对各部分结果的合并。感觉类似于Hadoop中的Map/Reduce思想,都是先把任务划分,然后对结果进行合并。Fork/Join框架实现了任务定义和任务处理功能的分离,实现了ExecutorService接口,会自动地将任务分配给线程池的线程,并负责线程管理等。

工作窃取算法:框架采用双端队列,即支持先进先出FIFO和后进先出LIFO,进入队列就push,出队列就pop,窃取任务就take,当某个工作线程执行完自己的任务后,它“很热心”,想帮助别人,但有不好意思从队列的开头拿出任务执行,就从队列的尾端拿出任务执行,主要由ForkJoinPool和ForkJoinTask类完成。

类ForkJoinPool是执行的入口,实现了ExecutorService类,负责管理线程,与其他ExecutorService的区别就是实现了工作窃取算法

@sun.misc.Contendedpublic class ForkJoinPool extends AbstractExecutorService

创建如下:

ForkJoinPool forkJoinPool=new ForkJoinPool();ForkJoinPool forkJoinPool1=new ForkJoinPool(10);

有两种使用方法,一是通过invoke、execute、submit在外部对Fork/Join操作调用,二是在框架内调用fork或者invoke等

类ForkJoinTask是执行的任务的基类,实现了Future接口,提供了一系列机制来实现操作,有两个子类,

RecursiveAction recursiveAction=new RecursiveAction() {    @Override    protected void compute() {    //无返回值    }};
RecursiveTask recursiveTask=new RecursiveTask() {    @Override    protected Object compute() {        return null;//有返回值    }};

定时器Timer

Timer类主要负责计划任务的功能,也就是在指定时间开始执行某一任务,Timer类设置计划任务,但封装任务类却是TimeTask类,是一个抽象类,因此执行计划任务的代码要放入TimerTask的子类,以队列的方式一个一个顺序执行,所以执行时间有可能与预期的不同

schedule(TimerTask t,Date time):在指定的日期执行一次任务
schedule(TimerTask t,Date firsttime,long period):在指定的日期之后按指定的间隔周期,无限循环地执行某一任务
TimerTask的cancel():将自身从任务队列中进行清除
Timer的cancel():将任务队列的全部任务进行清空,但有时不一定会停止计划任务,任务仍正常执行
schedule(TimerTask t,long delyTime):以执行方法时的当前时间为参考时间,在此时间基础上延迟指定的毫秒数后执行一次TimerTask任务

阻塞队列

不要调用Thread类和Runnable对象的run()方法,直接调用run()只会执行同一个线程中的任务,而不会启动新线程,应该调用Thread.start()方法,这将创建一个执行run的新线程
被阻塞就无法检查中断状态,中断一个线程不过是引起它的注意,被中断的线程可以决定如何响应中断,如果在中断状态被置位时调用sleep方法,不会休眠,相反,它会消除这一状态并抛出异常
没有可以强制线程终止的方法。然而,interrupt方法可以用来请求终止线程,调用方法,线程的中断状态将被置位,每个线程都有一个boolean标志,调用静态的Thread.currentThread方法获得当前线程,然后调用isInterrupted方法

原创粉丝点击