Java多线程02(内存模型 死锁 等待唤醒机制 Lock锁 线程池)

Java 内存模型(面试)

1、什么是Java内存模型?
大家不要认为是我们之前讲解的JVM的内存模型,堆和栈、方法区这些概念它是JVM里面。
Java内存模型简称JMM,定义了一个线程对另一个线程可见。共享变量存放在主内存中,每个线程都有自己的本地内存(本地内存主内存的一个副本),当多个线程同时访问一个数据的时候,可能本地内存没有及时刷新到主内存,所以就会发生线程安全问题。
在这里插入图片描述
解读:当线程1取得cpu的控制权,先会修改本地内存,x修改为1,然后把主内存中的共享变量修改成1;主内存再次将x的值同步给本地内存,但是这个时候线程1还没来得及把共享变量同步到本地内存,cpu的控制权就被线程2获取,线程2先把共享变量同步到本地内存,x修改成2,然后线程2修改共享变量,再次同步到本地内存,线程2结束;线程1再次获取cpu的控制权,继续同步共享变量到本地内存,这个时候就变成了2,这和预期的1 是不一样的,就会造成线程安全问题。

死锁

同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。
锁套锁

等待唤醒机制

输入线程和输出线程交替执行
需求:用两个线程一个线程输入、一个线程输出,输入一个用户完毕,然后马上输出一个用户的信息。而不是出现同时输入用户和输出用户?

实现步骤:
1)定义一个标记: flag = false;
2)如果是输入线程获取到CPU的使用权,先去判断flag是否是false,如果是false,就输入username和sex,输入完成之后,就把flag修改成true;然后输入线程就唤醒输出线程去打印属性信息,自己进入等待状态。
3)如果输出线程获取到CPU的使用权,先去判断flag是否是true,如果是true,就打印username和sex的信息,打印完成之后,就把flag修改成false,然后输出线程就唤醒输入线程去输入属性信息,自己进入等待状态。

在这里插入图片描述

  • 线程之间的通信
    多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即—―等待唤醒机制,

  • 等待唤醒机制所涉及到的方法
    wait () :等待,将正在执行的线程释放其执行资格和执行权,并存储到线程池中。
    notify () :唤醒,唤醒线程池中被wait ()的线程,一次唤醒一个,而且是任意的。
    notifyAll ()︰唤醒全部:可以将线程池中的所有wait()线程都唤醒。

  • 什么是唤醒
    所谓唤醒的意思就是让线程池中的线程具备执行资格。必须注意的是,这些方法都是在同步中才有效。同时这些方法在使用时必须标明所属锁,这样才可以明确出这些方法操作的到底是哪个锁上的线程。
    仔细查看JavaAPI之后,发现这些方法并不定义在Thread中,也没定义在Runnable接口中,却被定义在了Object类中,为什么这些操作线程的方法定义在Object类中?
    因为这些方法在使用时,必须要标明所属的锁,而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中。

package com.m.demo;

public class Person {
	public String name;
	public char sex;
	//标记
	boolean flag=false;
}

package com.m.demo;

public class InputTh implements Runnable{
	Person p;
	int x=0;
	
	public InputTh(Person p) {
		super();
		this.p = p;
		
	}

	@Override
	public void run() {
		
		
//			System.out.println("in"+flag);
				while(true) {
					synchronized (p) {
					if(p.flag==false) {
						if(x%2==0) {
							p.name="a";
							p.sex='女';
						}else {
							p.name="b";
							p.sex='男';
						}
						x++;
						p.flag=true;
						p.notify();
					}else {
						try {
							p.wait();
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
					
				
				
				
			}
			
		}
		
	}
	
}

package com.m.demo;

public class OutputTh implements Runnable{
	Person p;
	
	
	
	public OutputTh(Person p) {
		super();
		this.p = p;
		
	}

	@Override
	public void run() {
		
//			System.out.println("op"+flag);
		while(true) {
			synchronized (p) {
				if(p.flag==true) {
					System.out.println(p.name+" "+p.sex);
					p.flag=false;
					p.notify();
				}else {
					try {
						p.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				
			}
		}
	}

}

package com.m.demo;

public class Test {

	public static void main(String[] args) {
		//公共变量
		Person p=new Person();
		
		///
		InputTh it=new InputTh(p);
		OutputTh ot=new OutputTh(p);
		Thread t1=new Thread(it,"t1");
		Thread t2=new Thread(ot,"t2");
		
		t1.start();
		t2.start();
		

	}

}

Lock锁

1、Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。
2、使用Lock锁来实现等待唤醒机制,实现线程中通信

void lock() 
获得锁。 
void unlock() 
释放锁。   
 Lock l = ...; 
 l.lock(); 
 try { // access the resource protected by this lock 
 } finally { 
 l.unlock(); 
 } 

Condition因素出Object监视器方法( wait , notify和notifyAll )成不同的对象,以得到具有多个等待集的每个对象,通过将它们与使用任意的组合的效果Lock个实现。 Lock替换synchronized方法和语句的使用, Condition取代了对象监视器方法的使用。

void await() 
导致当前线程等到发信号或 interrupted 。 
void signal() 
唤醒一个等待线程。  
void signalAll() 
唤醒所有等待线程。  

Lock锁相当于代替了synchronized,Conditon相当于代替了Object

  • Lock锁实现步骤
1)如何创建一个Lock对象?
Lock lock = new ReentrantLock();
2)如何获得Lock对象关联的Condition接口?
Condition condition= lock.newCondition();
condition.await()等待,
condition.signal(唤醒)

package com.m.demo;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Person {
	public String name;
	public char sex;
	//标记
	boolean flag=false;
	Lock lock=new ReentrantLock();
	Condition condition=lock.newCondition();
}

package com.m.demo;

public class InputTh implements Runnable{
	Person p;
	int x=0;
	
	public InputTh(Person p) {
		super();
		this.p = p;
		
	}

	@Override
	public void run() {
				while(true) {
					p.lock.lock();
					if(p.flag) {
						try {
							p.condition.await();
						} catch (InterruptedException e) {
							
							e.printStackTrace();
						}
						
					}
					
					
					if(x%2==0) {
						p.name="a";
						p.sex='女';
					}else {
						p.name="b";
						p.sex='男';
					}
					x++;
					p.flag=true;
					p.condition.signal();
					p.lock.unlock();
					
				
			}
			
		}
		
	}
	


package com.m.demo;

public class OutputTh implements Runnable{
	Person p;
	
	
	
	public OutputTh(Person p) {
		super();
		this.p = p;
		
	}

	@Override
	public void run() {
		

		while(true) {
			try {
				p.lock.lock();
				if(p.flag==false) {
				  try {
					p.condition.await();
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
				}
				System.out.println(p.name+" "+p.sex);
				p.flag=false;
				p.condition.signal();
				
			}finally {
				p.lock.unlock();
			}
			
			}
		}
	}



线程池

之前我们使用Thread类来创建线程,现在我们使用线程池来创建和使用线程!使用线程池创建和使用线程是高效的。

1、什么是线程池?
Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

2、线程池的工作原理
在这里插入图片描述

3、为什么要使用线程池?
在java中,如果每个请求到达就创建一个新线程,开销是相当大的。在实际使用中,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程,可能会使系统由于过度消耗内存或""切换过度"而导致系统资源不足。为了防止资源不足,需要采取一些办法来限制任何给定时刻处理的请求数目,尽可能减少创建和销毁线程的次数,特别是一些资源耗费比较大的线程的创建和销毁,尽量利用已有对象来进行服务。线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程,线程创建的开销就被分摊到了多个任务上了,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使用应用程序响应更快。另外,通过适当的调整线程中的线程数目可以防止出现资源不足的情况。
4、线程池的优点
1)降低资源消耗。痛过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行.
3)提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用
5、线程池的分类

  1. newCachedhreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

  2. **newFixedThreadPool:**创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

  3. newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。
    4)创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。
    6、参数的含义=了解一下
    在这里插入图片描述
    在这里插入图片描述

  4. corePoolSize :线程池的核心池大小,在创建线程池之后,线程池默认没有任何线程。
    当有任务过来的时候才会去创建创建线程执行任务。换个说法,线程池创建之后,线程池中的线程数为0,当任务过来就会创建一个线程去执行,直到线程数达到corePoolSize 之后,就会被到达的任务放在队列中。(注意是到达的任务)。换句更精炼的话: corePoolSize表示允许线程池中允许同时运行的最大线程数。如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。

  5. maximumPoolSize :线程池允许的最大线程数,他表示最大能创建多少个线程。maximumPoolSize肯定是大于等于corePoolSize.

  6. keepAliveTime :表示线程没有任务时最多保持多久然后停止。默认情况下,只有线程池中线程数大于
    corePoolSize时l keepAliveTime才会起作用。换句话说,当线程池中的线程数大于corePoolSize,并且一个线程空闲时间达到了keepAliveTime,那么就是shutdown.

  7. Unit:keepAliveTime的单位。

  8. workQueue :一个阻塞队列,用来存储等待执行的任务,当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能

  9. threadFactory :线程工厂,用来创建线程。

  10. handler :表示当拒绝处理任务时的策略。

  • 线程池的创建和使用
  1. Executors:是创建线程池的工厂类,它专门用来创建线程池。
static ExecutorService newFixedThreadPool(int nThreads) 
创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。 
参数:池中的线程数
返回:新创建的线程池

  1. ExecutorService:线程池对象,用来执行任务。
void shutdown() 
启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。
<T> Future<T> submit(Callable<T> task) 
提交值返回任务以执行,并返回代表任务待处理结果的Future。   
Future<?> submit(Runnable task) 
提交一个可运行的任务执行,并返回一个表示该任务的未来。 

submit方法类似于Start方法 执行的就是run()方法
  1. Future接口:用来记录线程任务执行完毕后产生的结果
v get(
如有必要。等待计算完成,然后获取其结果。

B)如何使用
需求:使用线程池,在线程中执行输出任务“我要一个教练”
实现步骤:
1)Executors创建线程池
2)创建一个实现了Runnable接口的线程类
3)提交线程类到线程池中,线程池中调度的线程执行线程类中的run()方法
4)关闭线程池

package com.m.dem3;

public class Resource implements Runnable {

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName()+" "
	+"我需要一个教练");
		
	}

}

package com.m.dem3;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {

	public static void main(String[] args) {
		//定义一个线程池
		ExecutorService tp = Executors.newFixedThreadPool(3);
		//线程类
		Resource t1=new Resource();
		Resource t2=new Resource();
		Resource t3=new Resource();
		Resource t4=new Resource();
		//
		tp.submit(t1);
		tp.submit(t2);
		tp.submit(t3);
		tp.submit(t4);
		
		tp.shutdown();
		

	}

}

在这里插入图片描述

8、如何获得线程方法的返回值?

线程池可以获得线程执行后的返回值:而采用原来的继承Thread类或者是实现Runnable接口是无法获得线程执行方法run ()中的返回值的。
实现步骤如下:
1)创建线程池对象,调用submit
2)实现Callable接口,接口中的泛型类型决定了返回值的类型
3) submit()提交后会有一个接口Future
4) Future的get()方法就可以获得返回值

自定义线程类的方式t
1. extends Thread= run()方法
2.  implements Runnab1e接口=run()方法
3. implements ca1lable接门= ca11()方法=线程池

package com.m.dem3;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Test2 {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		//创建线程池
		ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(2);
		//使用匿名callable
		String fu=
		newFixedThreadPool.submit(new Callable<String>() {

			@Override
			public String call() throws Exception {
				System.out.println("执行内容");
				return "返回内容";
			}}).get();
		
		System.out.println(fu);
		newFixedThreadPool.shutdown();
	}

}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值