Java多线程学习小结

多线程
1.继承Thread类
线程只能单继承,不能再继承其他类(受限于Java类的单继承)
业务实现在run方法中,业务和线程耦合
多个子类之间业务不能共享
2.实现Runnable接口
解决了单继承问题
业务实现和线程实现解耦
业务可以共享
作为一个函数接口,可以使用lambda表达式和匿名内部类的匿名对象的方式进行创建Runnable
run方法返回值是void,无法处理有返回值结果的业务(可以通过数据库等方式进行弥补)
多线程共享,线程不安全,
3.实现Callable接口
可以处理返回结果
使用Callable的业务对象时,通过FutureTask来获取执行结果

	主程序的线程---main
	thread.currentThread.getName();
	修改线程名称:
	setName/Thread构造方法
	线程名称要设置请避免重复,同时中间不要修改
	
	java程序运行,创建jvm进程,并至少创建一个线程--主线程
	程序运行结束,所有线程结束,进程随之结束
	GC(线程)---自动回收内存
	
	//阻塞和就绪---挂起和唤醒
	
	sleep--线程休眠:指的是让线程暂缓执行一下,等到了预计时间之后再恢复执行。(以毫秒为单位)
		线程休眠会交出CPU,让CPU去执行其他的任务。但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当
	前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象
	yield--线程让步:意思就是调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放
	锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间
	的机会。
	注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时
	间,这一点是和sleep方法不一样的。
	
	Thread.sleep(ms)
		线程暂停执行,进入阻塞状态
		交出cpu
		不释放占用的资源锁
		休眠时间到了回到就绪状态
	Thread.yield()(不需要抛出异常)
		线程暂停执行,进入就绪状态
		交出cpu,但不确定具体时间
		不释放占用的资源锁
		相同优先级的线程拥有交出的CPU时间片的权限
	thread.join()
		在线程a中调用线程b的join方法,线程a暂停执行,直到线程b执行完毕,线程a才能继续执行
		
		Date date=new Date();
		SimpleDateFormat formate=new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
		
		LocalDateTime now =LocalDateTime.now();
		System.out.println(now.formate
		
		线程中有三种方式可以停止线程。
		1. 设置标记位,可以是线程正常退出。(boolean类型的标识)myThread.setFlag(false);		
		2. 使用stop方法强制使线程退出,但是该方法不太安全所以已经被废弃了。
		3. 使用Thread类中的一个interrupt() 可以中断线程。
			*1.改变线程的中断标识
			*2.如果非阻塞,true
			*3.如果阻塞,wait/sleep/join引起,抛出InterruptedExeception,清理中断标识
			false,通过触发中断,开发者决定如何处理线程业务(退出或是继续或是其他处理措施)
			
			中断在java中主要有3个方法,interrupt(),isInterrupted()和interrupted()。
			interrupt(),在一个线程中调用另一个线程的interrupt()方法,即会向那个线程发出信号——线程中断状态已被设置。
				至于那个线程何去何从,由具体的代码实现决定。
			isInterrupted(),用来判断当前线程的中断状态(true or false)。
			interrupted()是个Thread的static方法,用来恢复中断状态。
	
	线程的优先级指的是,线程的优先级越高越有可能先执行,但仅仅是有可能而已。
	
	在Thread类中提供有如下优先级方法(1--5--10  低--中--高):
		主线程优先级(默认优先级):5   主线程中创建的线程默认:5
		子线程优先级和父线程优先级相同
	设置优先级:
	public final void setPriority(int newPriority)
	取得优先级:
	public final int getPriority()
	对于优先级设置的内容可以通过Thread类的几个常量来决定
		1. 最高优先级:public final static int MAX_PRIORITY = 10;
		2. 中等优先级:public final static int NORM_PRIORITY = 5;
		3. 最低优先级:public final static int MIN_PRIORITY = 1;
		
		线程  isDaemon()	
			thread.setDaemon(true);//在线程启用之前调用
			用户线程(之前所有包括主线程)
			守护线程(陪伴用户线程)(如GC)(一般都是死循环)
				*当一个JVM进程中最后一个用户线程退出,守护线程将伴随JVM一起结束
				*run执行完成/run执行任务周期性死循环
				
	synchronized处理同步问题:
		使用synchronized关键字处理有两种模式:同步代码块、同步方法
		使用同步代码块 : 如果要使用同步代码块必须设置一个要锁定的对象,所以一般可以锁定当前对象:this
			synchronized(对象)->对象	
			全局锁:synchronized(xxx.class)->锁类(只要是该类实例化的对象,全部加锁)
			synchronized(Object object)->对任意对象进行加锁,String之类等等
		同步方法(成员)synchronized->类的实例化对象->this
			如果加在了静态方法上,也相当于加了全局锁
			
					*1*锁中锁的加锁过程不受上一层锁的限制
						通过monitorenter-exit实现
								重复锁时,锁对象有一个计数器
					*2*加锁但未释放锁会导致死锁状态
		
	ReentrantLock进行同步处理(重入锁)
		this.lock.lock->业务逻辑->this.lock.unlock    **易因为函数体异常而导致无法释放锁
			此时,使用finally{this.lock.unlock();}  **使用finally可以使释放锁在任何情况下都需执行
			**加锁,解锁,异常情况正常解锁
			**开始事务 CURD (回滚、提交) -> 提交事务
			
	synchronized(内建锁)的优化
		CAS(无锁操作 compare and swap/比较和交换**依赖CPU指令集CMPXCHG实现两步绑定以保证两步同时成功或者失败):
			V(堆上的实际值)O(预期值)N(更新的值)
			**V==O 实际值和期望值相等,没有被其他值修改	V=N
			**V!=O 实际值和期望值不相等,被其他线程修改了V,导致V!=O,返回V
			**当多个线程使用CAS操作一个变量时,只有一个线程会成功,并成功更新,其余会失败。失败的线程会重新尝试(retry),当然也可以选择挂起线程。
				CAS三个问题:ABA、自旋的资源浪费(自适应自旋)、公平性	
				//JVM通过自适应自旋(根据经验进行动态判定)来决定到底自旋多少次
				//不公平的锁机制。处于阻塞状态的线程,无法立刻竞争被释放的锁。然而,处于自旋状态的线程,则很有可能优先获得这把锁。	
				//内建锁无法实现公平机制,而lock体系可以实现公平锁。
		对象头:在同步的时候是获取对象的monitor,即获取到对象的锁。那么对象的锁怎么理解?无非就是类似对对象的一个标志,
		那么这个标志就是存放在Java对象的对象头。Java对象头里的Mark Word里默认的存放的对象的Hashcode,分代年龄和锁标记位
			/*锁一共有四种状态: Java SE 1.6中,锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、
		轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁
		升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。*/
			
			//线程、时间、资源(锁)**三种维度区分锁
			偏向锁是四种状态中最为轻量(最乐观)的一种锁:从始至终只有一个线程请求某一把锁。
			/*如何关闭偏向锁:
			偏向锁在JDK6之后是默认启用的,但是它在应用程序启动几秒钟之后才激活,如有必要可以使用JVM参数来关闭延
			迟:-XX:BiasedLockingStartupDelay=0。如果你确定应用程序里所有的锁通常情况下处于竞争状态,可以通过
			JVM参数关闭偏向锁:-XX:-UseBiasedLocking=false,那么程序默认会进入轻量级锁状态。*/
		
			轻量级锁:多线程在不同时间访问同一把锁
			//轻量级锁+偏向锁足够应付大多数情况
			
			重量级锁:多线程同一时间访问同一个锁
			
			Java虚拟机中synchronized关键字的实现,按照代价由高到低可以分为重量级锁、轻量锁和偏向锁三种。
			**1. 重量级锁会阻塞、唤醒请求加锁的线程。它针对的是多个线程同时竞争同一把锁的情况。JVM采用了自适应自
			旋,来避免线程在面对非常小的synchronized代码块时,仍会被阻塞、唤醒的情况。
			**2. 轻量级锁采用CAS操作,将锁对象的标记字段替换为一个指针,指向当前线程栈上的一块空间,存储着锁对象
			原本的标记字段。它针对的是多个线程在不同时间段申请同一把锁的情况。
			**3. 偏向锁只会在第一次请求时采用CAS操作,在锁对象的标记字段中记录下当前线程的地址。在之后的运行过程
			中,持有该偏向锁的线程的加锁操作将直接返回。它针对的是锁仅会被同一线程持有的情况。
			
			锁粗化:减少高频次的无端加锁,将多个连续的锁扩展成为一个范围更大的锁
			//使用同步对象时一般加上final
			锁消除:代码逃逸技术,堆上的对象不会被除当前线程以外的其他线程访问,不需要加锁
			//StringBuffer->StringBuilder
			//使用一个API时需注意该API是否线程安全,以及是否被多个线程访问。(尽量在哪使用,在哪实例化对象)
			
			死锁:多线程要执行完成,相互之间等待对方持有的锁,便会发生死锁,死锁一旦出现之后,整个程序就将中断执行,
			所以死锁属于严重性问题。过多的同步会造成死锁,不要让多线程访问资源成“环”
			
		ThreadLocal:通过创建变量副本,实现该变量在各线程中独享。//set:ThreadLocal.ThreadLocalmap->(ThreadLocal.value)
			/*在 set,get,initialValue 和 remove 方法中都会获取到当前线程,然后通过当前线程获取到 ThreadLocalMap,如果
						ThreadLocalMap 为 null,则会创建一个 ThreadLocalMap,并给到当前线程*/
					/*在 ThreadLocalMap 的 set(),get() 和 remove() 方法中,都有清除无效 Entry 的操作,这样做是为了降低内存泄漏发生的可能。
					Entry 中的 key 使用了弱引用的方式,这样做是为了降低内存泄漏发生的概率,但不能完全避免内存泄漏。
						在调用 ThreadLocal 的 get(),set() 和 remove()的时候都会清除当前线程 ThreadLocalMap 中所有 key
					为 null 的 value。这样可以降低内存泄漏发生的概率。所以我们在使用 ThreadLocal 的时候,每次用完 
					ThreadLocal 都调用 remove() 方法,清除数据,防止内存泄漏。*/
					
生产者与消费者模型
		1.生产和消费的速率相等
		2.生产速率>消费速率,容器元素到达一定容量,停止生产,增加消费者
		3.生产速率<消费速率,容器为空,停止消费
	容器用队列实现
		生产者生产的商品数量超过容器限制,停止生产
		消费者消费商品时,容易为空,停止消费
	生产者:多个线程 Producer
	消费者:多个线程  Consumer
	容器:队列	Queue
	元素:Object	Goods(编号,名称,价格)
	生产者 等待 消费者 wait
	消费者 通知 生产者 notify
	wait和notify成对出现,除非用notifyAll
	出现阻塞的情况大体分为如下5种:
	1. 线程调用 sleep方法,主动放弃占用的处理器资源。
	2. 线程调用了阻塞式IO方法,在该方法返回前,该线程被阻塞。
	3. 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。
	4. 线程等待某个通知。
	5. 程序调用了 suspend方法将该线程挂起。此方法容易导致死锁,尽量避免使用该方法。
	
	/*可配置:	1.scanner键盘输入
				2.file
				3.properties(key=value)
				4.command line arguments 命令行参数 java Main.class a b class
				5.数据库 mysql(JDBC)
				6.System.env("")*/
	请解释sleep()与wait()的区别:
		1. sleep()是Thread类中定义的方法,到了一定的时间后该线程自动唤醒,不会释放对象锁。
		2. wait()是Object类中定义的方法,要想唤醒必须使用notify()、notifyAll()方法才能唤醒,会释放对象锁
		
线程池
		Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或者并发执行任务的程序都可以使用线程池。开发中
	使用线程池的三个优点如下:
		1. 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁带来的消耗。
		2. 提高响应速度:当任务到达时,任务可以不需要等待线程创建就能立即执行。
		3. 提高线程的可管理性:使用线程池可以统一进行线程分配、调度和监控。
		
			/*当向线程池提交了一个任务之后,线程池是如何处理这个任务的呢?下面来看线程池的主要处理流程如下:
			1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心
			线程池里的线程都在执行任务,则进入下个流程。
			2)线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队
			列满了,则进入下个流程。
			3)线程池判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满
			了,则交给饱和策略来处理这个任务。*/
			
			
			/*ThreadPoolExecutor执行execute()方法的流程	如下:
			1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。
			2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
			3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取
			全局锁)。
			4)如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用
			RejectedExecutionHandler.rejectedExecution()方法。*/
	
	线程池的创建
	我们可以通过ThreadPoolExecutor来创建一个线程池:
							public ThreadPoolExecutor(int corePoolSize,
							int maximumPoolSize,
								BlockingQueue<Runnable> workQueue,
							RejectedExecutionHandler handler)
							long keepAliveTime,
							TimeUnit unit,
				/*创建一个线程池时需要输入几个参数,如下:
								1)corePoolSize(线程池的基本大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其
							他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调
							用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程。
								2)runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。
							·ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
							·LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于
							ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列。
							·SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操
							作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool
							使用了这个队列。
							PriorityBlockingQueue:一个具有优先级的无限阻塞队列。
								3)maximumPoolSize(线程池最大数量):线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小
							于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界的任务队列这个参数就没什么
							效果。
								4) keepAliveTime(线程活动保持时间):线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且
							每个任务执行的时间比较短,可以调大时间,提高线程的利用率。
								5) TimeUnit(线程活动保持时间的单位):可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫
							秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微
							秒)。
								6) RejectedExecutionHandler(饱和策略):当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种
							策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。在JDK 1.5中Java线
							程池框架提供了以下4种策略。 
							·AbortPolicy:直接抛出异常。(默认采用此策略) 
							·CallerRunsPolicy:只用调用者所在
							线程来运行任务。 
							·DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。 
							·DiscardPolicy:不处理,丢弃掉。*/
	向线程池提交任务
					可以使用两个方法向线程池提交任务,分别为execute()和submit()方法。
					execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。
					submit()方法用于提交需要返回值的任务。线程池会返回一个future类型的对象,通过这个future对象可以判
				断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用
				get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
	关闭线程池
					以通过调用线程池的shutdown或shutdownNow方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后
				逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。但是它们存在一定的区别。
					**shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行
				任务的列表。(暴力的)
					**shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。(优雅的)
						**只要调用了这两个关闭方法中的任意一个,isShutdown方法就会返回true。
						**当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回true。
						至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用shutdown方法来关闭线程池,
					如果任务不一定要执行完,则可以调用shutdownNow方法。
	 合理配置线程池
				想合理地配置线程池,就必须首先分析任务特性,可以从以下几个角度来分析。
				任务的性质:CPU密集型任务、IO密集型任务和混合型任务。
				任务的优先级:高、中和低。
				任务的执行时间:长、中和短。
				任务的依赖性:是否依赖其他系统资源,如数据库连接。
			性质不同的任务可以用不同规模的线程池分开处理。CPU密集型任务应配置尽可能小的线程,如配置Ncpu+1个线程
		的线程池。由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如2*Ncpu。混合型的任务,
		如果可以拆分,将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,
		那么分解后执行的吞吐量将高于串行执行的吞吐量。如果这两个任务执行时间相差太大,则没必要进行分解。
			可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。
			/*优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先执行。
				**如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。*/
				执行时间不同的任务可以交给不同规模的线程池来处理(优先),或者可以使用优先级队列,让执行时间短的任务先执行。
				/*依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,等待的时间越长,则CPU空闲时间就越
				长,那么线程数应该设置得越大,这样才能更好地利用CPU。*/
		Executor框架的两级调度模型
			任务---Executor框架---线程池(线程)---OSKernel(操作系统)---CPU
			|-----------应用层---------|---------------操作系统层----------|
			
	Executor有一个工具类Executors:
					Executor框架最核心的类是ThreadPoolExecutor,它是线程池的实现类。通过Executor框架的工具类Executors,可
				以创建3种类型的ThreadPoolExecutor。
				1. 创建无大小限制的线程池:public static ExecutorService newCachedThreadPool()			
				/*CachedThreadPool是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者负载较轻的服务器。
			这里把keepAliveTime设置为60L,意味着CachedThreadPool中的空闲线程等待新任务的最长时间为60秒,空闲线程超过60秒后将会被终止。
			用来帮助销毁无用任务以防内存不够或者线程池线程数达到Intager.MAX_VALUE*/
				2. 创建固定大小的线程池:public static ExecutorService newFixedThreadPool(int nThreads)
				/*FixedThreadPool适用于为了满足资源管理的需求,而需要限制当前线程数量的应用场合,适用于负载比较重的服务器*/
				3. 单线程池:public static ExecutorService newSingleThreadExecutor() 					
				/*SingleThreadExecutor适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。*/
				
				/*FixedThreadPool和SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作队列。
				CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但CachedThreadPool的
				maximumPool是无界的。这意味着,如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,
				CachedThreadPool会不断创建新线程。极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU和内存资
				源。*/
				
				 4. 周期性线程池:ScheduledThreadPoolExecutor
				 /*DelayQueue是一个无界队列,所以ThreadPoolExecutor的maximumPoolSize在ScheduledThreadPoolExecutor中没
				有什么意义(设置maximumPoolSize的大小没有什么效果)。ScheduledThreadPoolExecutor的执行主要分为两大部分。
				1)当调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法时,会
				向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFutur接口的ScheduledFutureTask。
				2)线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。
				
				ScheduledThreadPoolExecutor为了实现周期性的执行任务,对ThreadPoolExecutor做了如下的修改。
						a.使用DelayQueue作为任务队列。
						b.获取任务的方式不同
						c.执行周期任务后,增加了额外的处理*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值