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、线程池的分类
-
newCachedhreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
-
**newFixedThreadPool:**创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
-
newScheduledThreadPool创建一个定长线程池,支持定时及周期性任务执行。
4)创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO,优先级)执行。
6、参数的含义=了解一下
-
corePoolSize :线程池的核心池大小,在创建线程池之后,线程池默认没有任何线程。
当有任务过来的时候才会去创建创建线程执行任务。换个说法,线程池创建之后,线程池中的线程数为0,当任务过来就会创建一个线程去执行,直到线程数达到corePoolSize 之后,就会被到达的任务放在队列中。(注意是到达的任务)。换句更精炼的话: corePoolSize表示允许线程池中允许同时运行的最大线程数。如果执行了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有核心线程。 -
maximumPoolSize :线程池允许的最大线程数,他表示最大能创建多少个线程。maximumPoolSize肯定是大于等于corePoolSize.
-
keepAliveTime :表示线程没有任务时最多保持多久然后停止。默认情况下,只有线程池中线程数大于
corePoolSize时l keepAliveTime才会起作用。换句话说,当线程池中的线程数大于corePoolSize,并且一个线程空闲时间达到了keepAliveTime,那么就是shutdown. -
Unit:keepAliveTime的单位。
-
workQueue :一个阻塞队列,用来存储等待执行的任务,当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。通过workQueue,线程池实现了阻塞功能
-
threadFactory :线程工厂,用来创建线程。
-
handler :表示当拒绝处理任务时的策略。
- 线程池的创建和使用
- Executors:是创建线程池的工厂类,它专门用来创建线程池。
static ExecutorService newFixedThreadPool(int nThreads)
创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。
参数:池中的线程数
返回:新创建的线程池
- ExecutorService:线程池对象,用来执行任务。
void shutdown()
启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。
<T> Future<T> submit(Callable<T> task)
提交值返回任务以执行,并返回代表任务待处理结果的Future。
Future<?> submit(Runnable task)
提交一个可运行的任务执行,并返回一个表示该任务的未来。
submit方法类似于Start方法 执行的就是run()方法
- 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();
}
}