Java多线程系列(1)
本章主要内容有:
1.线程进程的区别
2.线程的生命周期
3.Java内存模型 原子性,可见性及有序性
4.线程池及Java实现
1. 线程进程的区别
线程:程序运行的最小单位 进程:资源分配的最小单位 一个进程可以有多个线程,多个线程共享进程里面的数据 线程间通讯相对更加方便,进程间通讯需要通过IPC(Inter-Process Communication,进程间通信)实现 JVM运行时数据区域是否线程共享: 程序计数器-线程私有 虚拟机栈-线程私有 本地方法栈-线程私有 堆-线程共享 方法区-线程共享
3. 线程的生命周期
1.sleep() 在指定时间内让当前正在执行的线程暂停执行,但不会释放“锁标志”。 sleep()使当前线程进入等待状态,在指定时间内不会执行。 2.wait() 在其他线程调用对象的notify或notifyAll方法前,导致当前线程等待。线程会释放掉它所占有的“锁标志”,从而使别的线程有机会抢占该锁。 3.yield() 暂停当前正在执行的线程对象。 yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。 yield()只能使同优先级或更高优先级的线程有执行的机会。 调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。 4.join() 等待调用join方法的线程结束,再继续执行。如:t.join();//主要用于等待t线程运行结束,若无此句,main则会执行完毕,导致结果不可预测。
4. Java线程几种创建方式
1. 实现Runnable或Callable接口 2. 实现Thread类 3. 通过线程池启动1,2创建的线程
5. Java内存模型(JMM)
Java内存模型定义了线程和内存的交互方式,在JMM抽象模型中,分为主内存、工作内存; 主内存是所有线程共享的,一般是实例对象、静态字段、数组对象等存储在堆内存中的变量。 工作内存是每个线程独占的,线程对变量的所有操作都必须在工作内存中进行, 不能直接读写主内存中的变量,线程之间的共享变量值的传递都是基于主内存来完成。
1) JMM中的8个原子操作
lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。 unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。 read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用 load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。 use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。 assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。 store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。 write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
2) Happen-before规则
Happens-before原则定义: 1. 如果A操作happens-before B操作,那么A操作的执行结果将对B操作可见,而且A操作的执行顺序排在B操作之前。 2. 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。 Happens-before规则(摘自《深入理解Java虚拟机第12章》): 1.程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作; 2.锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作; 3.volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作; 4.传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C; 5.线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作; 6.线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生; 7.线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行; 8.对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;
例:A线程调用set, B线程调用同一个对象的get,那么B线程返回什么? private int i = 0; public void set(int j ){ i = j; } public int get(){ return i; } 如何修改满足Happen-before规则?
6. 原子性,可见性,有序性特征
原子性 即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。原子性就像数据库里面的事务一样,他们是一个团队,同生共死 可见性 是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值 有序性 即程序执行的顺序按照代码的先后顺序执行 解决方案: 原子性: Java中提供了两个高级指令 monitorenter和 monitorexit,也就是对应的synchronized同步锁来保证原子性,也可以通过lock(),unlock()对保证代码块的原子性 可见性: volatile、synchronized、final都可以解决可见性问题 有序性: synchronized和volatile可以保证多线程之间操作的有序性,volatile会禁止指令重排序
7. 线程池及Java实现
线程池及为什么使用线程池
在程序启动的时候就创建若干线程来响应处理,它们被称为线程池,里面的线程叫工作线程 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。 第三:提高线程的可管理性。
Java线程池的参数
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 1.corePoolSize:
核心池的大小。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中; 2.maximumPoolSize:
线程池最大线程数。这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程; 3.keepAliveTime:
表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0; 4.unit:
参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性 5.workQueue:
一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:ArrayBlockingQueue; LinkedBlockingQueue; SynchronousQueue; PriorityBlockingQueue ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和SynchronousQueue。线程池的排队策略与BlockingQueue有关。 6.threadFactory:
线程工厂,主要用来创建线程 7.handler:
表示当拒绝处理任务时的策略。有四种取值: AbortPolicy:丢弃任务并抛出RejectedExecutionException异常;DiscardPolicy:也是丢弃任务,但是不抛出异常 DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程);CallerRunsPolicy:由调用线程处理该任务。
线程在线程池中的处理:
1、如果线程池的当前大小还没有达到基本大小(poolSize < corePoolSize),那么就新增加一个线程处理新提交的任务;
2、如果当前大小已经达到了基本大小,就将新提交的任务提交到阻塞队列排队,等候处理workQueue.offer(command);
3、如果队列容量已达上限,并且当前大小poolSize没有达到maximumPoolSize,那么就新增线程来处理任务;
4、如果队列已满,并且当前线程数目也已经达到上限,那么意味着线程池的处理能力已经达到了极限,此时需要拒绝新增加的任务。至于如何拒绝处理新增
Java线程池的实现
Executors.newFixedThreadPool(8) Executors.newCachedThreadPool() Executors.newSingleThreadExecutor() Executors.newScheduledThreadPool(8) Executors.newWorkStealingPool() Executors.newSingleThreadScheduledExecutor() 详见:https://www.cnblogs.com/barrywxx/p/8570558.html 问题:Executors.newCachedThreadPool()核心线程数设置为0,如何创建销毁线程的?
每天一点成长,欢迎指正!