线程原理-java

Java线程:概念和原理

  • 一个程序至少有一个进程,一个进程至少有一个线程。
  • 线程的划分尺度小于进程,使得多进程程序的并发性高。
  • 另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
  • 线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
  • 从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

在Java中,每次程序运行至少启动2个线程:一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实际上就是在操作系统中启动了一个进程。

image

Java线程:线程状态的转换

一、线程状态

image
1、新状态:线程对象已经创建,还没有在其上调用start()方法。

2、可运行状态:当线程有资格运行,但调度程序还没有把它选定为运行线程时线程所处的状态。当start()方法调用时,线程首先进入可运行状态。在线程运行之后或者从阻塞、等待或睡眠状态回来后,也返回到可运行状态。

3、运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

4、等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。

5、死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

二、阻止线程执行
  • 1、睡眠:Thread.sleep(longmillis),静态方法强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。sleep()是静态方法,只能控制当前正在运行的线程。
  • 2、线程的优先级和线程让步(都无法保证):setPriority(int newPriority)和Thread.yield(),暂停当前正在执行的线程对象,并执行其他线程。yield让线程返回到可运行状态
  • 3、join()方法:保证当前线程停止执行,直到该线程所加入的线程完成为止。然而,如果它加入的线程没有存活,则当前线程不需要停止。 (线程的调度-合并)

image

除了以上三种方式外,还有下面几种特殊情况可能使线程离开运行状态:

  • 1、线程的run()方法完成。
  • 2、在对象上调用wait()方法(不是在线程上调用)。
  • 3、线程不能在对象上获得锁定,它正试图运行该对象的方法代码。
  • 4、线程调度程序可以决定将当前运行状态移动到可运行状态,以便让另一个线程获得运行机会,而不需要任何理由。
三、线程同步
  • 1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
  • 2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他同步方法。
  • 3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
  • 4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
  • 5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
  • 6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
  • 7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。
四、Java线程:线程的交互
void notify()——唤醒在此对象监视器上等待的单个线程。
void notifyAll()——唤醒在此对象监视器上等待的所有线程。
void wait()——导致当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法。
Condition

Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,Condition中,从而可以有选择的进行线程通知,在调度线程上更加灵活。

  • 1、Condition是个接口,基本的方法就是await()和signal()方法。

  • 2、Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition(),参考下图。

// 实例化一个ReentrantLock对象
    private ReentrantLock lock = new ReentrantLock();
    // 为线程A注册一个Condition
    public Condition conditionA = lock.newCondition();
    // 为线程B注册一个Condition
    public Condition conditionB = lock.newCondition();
    
.........................
            // 执行conditionA等待
            conditionA.await();
    
  • 3、调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用。

  • 4、Conditon中的await()对应Object的wait(),Condition中的signal()对应Object的notify(),Condition中的signalAll()对应Object的notifyAll()。

接下来,使用Condition来实现等待/唤醒,

五、线程池
1、线程池的优势

(1)、降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
(2)、提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
(3)方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
(4)提供更强大的功能,延时定时线程池。

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

1、corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)

2、maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。

3、keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。

4、workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。
ArrayBlockingQueue 使用列表,会将等待的线程放到队列中,当队列满了,会根据线程池来执行,创建新的线程
LinkBlockingQueue 使用列表,会将等待的线程放到队列中,当队列满了,会根据线程池来执行,创建新的线程
SynchronousQueue 没有队列,直接执行线程池的maximumPoolSize,创建新线程(okhttp使用)

5、threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。

6、handler(线程饱和策略):表示当拒绝处理任务时的策略,有以下四种取值:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 

image

线程池关闭:
  • shutdown() 不接收新任务,会处理已添加任务
  • shutdownNow() 不接受新任务,不处理已添加任务,中断正在处理的任务
Executors线程工厂类

Executors.newCachedThreadPool();
说明: 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.
内部实现:new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,new SynchronousQueue());

Executors.newFixedThreadPool(int);
说明: 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
内部实现:new ThreadPoolExecutor(nThreads, nThreads,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue());

Executors.newSingleThreadExecutor();
说明:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照顺序执行。
内部实现:new ThreadPoolExecutor(1,1,0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue())

Executors.newScheduledThreadPool(int);
说明:创建一个定长线程池,支持定时及周期性任务执行。
内部实现:new ScheduledThreadPoolExecutor(corePoolSize)

五、线程类型

1、线程的调度-守护线程:守护线程与普通线程写法上基本么啥区别,调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。( 前台线程是保证执行完毕的,后台线程还没有执行完毕就退出了。)

六、Android提供多线程方式:

为什么子线程中不允许对UI进行操作呢?

  • 因为Android的UI控件并不是线程安全,多线程的并发访问会带来UI控件的不可预期的状态,且考虑到加锁机制会带来性能上的问题,因此Android在设计初期就禁止子线程处理UI。UI操作时ViewRootImpl会对操作者所在的线程进行checkThread,如果非主线程,会抛出CalledFromWrongThreadException。

Android提供了四种常用的操作多线程的方式,分别是:

  1. Handler+Thread

  2. AsyncTask

  3. ThreadPoolExecutor

  4. IntentService

七、线程停止

在Java中有3种方法可以停止正在运行的线程:

(1)使用退出标志使线程正常终止,也就是当run方法完成后线程终止。

(2)使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend、resume一样,都是过期作废的方法。

(3)使用interrupt方法中断线程。调用thread.interrupt()方法,但是此方法并不会马上停止线程,只是在当前线程打了一个停止的标记,并不是真正的停止线程。

八、volatile

当把变量声明为 volatile 类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile 变量不会被缓存在寄存器或者对其他处理器不可见的地方,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步,因此在读取 volatile 类型的变量时总会返回最新写入的值。

当一个变量定义为 volatile 之后,将具备以下特性:

  • 保证此变量对所有的线程的可见性,不能保证它具有原子性(可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的)
  • 禁止指令重排序优化
  • volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行
  • AtomicInteger 中主要实现了整型的原子操作,防止并发情况下出现异常结果,其内部主要依靠 JDK 中的 unsafe 类操作内存中的数据来实现的。volatile 修饰符保证了 value 在内存中其他线程可以看到其值得改变。CAS(Compare and Swap)操作保证了 AtomicInteger 可以安全的修改value 的值。

底层实现:
1、Lock用于在多处理中执行指令时对共享内存的独占使用
2、他的作用是能够将当前处理器对应缓存的内容刷新到内容,并使其他处理器对应缓存失效。
3、还提供了有序的指令,无法越过这个内存屏障的作用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值