说出进程的概念
进程:进程指在内存中正在运行的程序
说出线程的概念
线程:进程内部的一个独立执行单元(就是应用程序一个功能通向cpu的路径)
能够理解并发与并行的区别
并行:多个事件在同一时刻发生(同时发生)。
并发:多个事件在某个时间段内(交替)发生。
继承的方式开启新线程:
public clsss MyThread extends Thread{
//重写run方法,设置线程任务
run(){
//方法内部写线程任务
}
}
main(){
new MyThread().start();//开启新线程
}
能够描述Java中多线程运行原理
调用start方法会开辟一个栈空间运行run方法
多个栈空间的方法都是独立的,互相不影响
能够使用继承类的方式创建多线程
public clsss MyThread extends Thread{
//重写run方法,设置线程任务
run(){
}
}
main(){
new MyThread().start();//开启多线程
}
能够使用实现接口的方式创建多线程
public class RunnableImpl implements Runnable{
//重写run方法,设置线程任务
run(){
}
}
new Thread(new RunnableImpl()).start();//开启多线程
能够说出实现接口方式的好处
1.避免了实现类单继承的局限性(实现了Runnable接口,还可以继承其它的类,实现其它的接口使用)
2.降低了设置线程任务和执行线程之间的耦合性,增强了扩展性
能够解释安全问题的出现的原因,电影院卖票的案例:
多线程访问了同一个共享的数据
能够使用同步代码块解决线程安全问题
synchronized(锁对象){
可能出现安全问题的代码
(访问了共享数据的代码)
}
注意:保证锁对象要唯一
能够使用同步方法解决线程安全问题
1.把访问共享数据的代码,提取出来放在一个方法中
2.在方法上增加一个同步关键字synchronized
语法:
修饰符 synchronized 返回值类型 方法名(参数列表){
出现了安全问题的代码
(使用了共享数据的代码)
}
synchronized 锁的详解图:
JDK1.5之后,提供有锁的接口,可以有效监测锁的取和还,用多态new一个锁出来
Lock lock = new ReentrantLock();
使用:
在方法开头,加入取锁方法,lock.lock();
在方法结束,加入还锁方法,lock.unlock();
具体代码案例:
public class Ticket implements Runnable{
private int ticket = 100;
Lock lock = new ReentrantLock();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
lock.lock();
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket‐‐);
}
lock.unlock();
}
}
}
死锁:(拓展,理解:就是尽量不要写锁中锁,程序容易卡死)
同步锁使用的弊端:当线程任务中出现了多个同步(多个锁)时,如果同步中嵌套了其他的同步。这时容易引发一种现象:程序出现无限等待,这种现象我们称为死锁。这种情况能避免就避免掉。
能够说出线程6个状态的名称:
NEW(新建)
Runnable(可运行)
Blocked(锁阻塞)
Waiting(无限等待)
TimedWaiting(计时等待,睡眠)
Teminated(被终止/死亡)
能够理解等待唤醒案例
吃包子案例
一个生产者:生产者生产包子–>唤醒消费者吃包子
一个消费者:消费者吃完包子–>唤醒生产者生产包子
BaoZi.java
/*
资源类:包子类
属性:皮,陷,包子状态
*/
public class BaoZi {
//皮
String pi;
//陷
String xian;
//包子状态 初始值为false没有包子
boolean flag = false;
}
BaoZiPu.java
/*
包子铺类:是一个线程类
线程任务: 生产包子
对包子的状态进行判断
true:有包子
包子铺线程调用wait等待
false:没有包子
包子铺线程开始生产包子
生产x皮x陷的包子
生产包子花费3秒钟
生产完包子,修改包子的状态为true
包子铺线程唤醒吃货线程,吃包子
注意:
1.生产包子和吃包子,只能有一个在执行,需要使用同步技术
2.同步技术需要使用锁对象,可以使用包子对象
在成员位置创建一个包子变量
使用构造方法为包子变量赋值
*/
//包子铺类:是一个线程类
public class BaoZiPu implements Runnable{
//在成员位置创建一个包子变量
BaoZi bz;
//使用构造方法为包子变量赋值
public BaoZiPu(BaoZi bz) {
this.bz = bz;
}
//线程任务: 生产包子
@Override
public void run() {
//包子铺一直生产包子
while (true){
/*
1.生产包子和吃包子,只能有一个在执行,需要使用同步技术
2.同步技术需要使用锁对象,可以使用包子对象
*/
synchronized (bz){
//对包子的状态进行判断
if(bz.flag == true){
//true:有包子 包子铺线程调用wait等待
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*
包子铺被吃货唤醒之后执行的代码
*/
//false:没有包子 包子铺线程开始生产包子
//生产x皮x陷的包子
bz.pi = "薄皮";
bz.xian = "牛肉大葱陷";
System.out.println("包子铺正在生产"+bz.pi+bz.xian+"的包子!");
//生产包子花费3秒钟
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//生产完包子,修改包子的状态为true
bz.flag = true;
//包子铺线程唤醒吃货线程,吃包子
bz.notify();//唤醒的是bz锁对象上等待的吃货线程
System.out.println("包子铺已经生产好了"+bz.pi+bz.xian+"的包子,吃货赶紧来吃吧!");
}
}
}
}
ChiHuo.java
/*
吃货类:是一个线程类
线程任务:吃包子
对包子的状态进行判断
false:没有包子
吃货线程调用wait等待
true:有包子
吃货线程开始吃包子
打印吃x皮x陷的包子
吃完包子,修改包子的状态为false
吃货线程唤醒包子铺线程,做包子
注意:
1.生产包子和吃包子,只能有一个在执行,需要使用同步技术
2.同步技术需要使用锁对象,可以使用包子对象
在成员位置创建一个包子变量
使用构造方法为包子变量赋值
*/
//吃货类:是一个线程类
public class ChiHuo implements Runnable{
//在成员位置创建一个包子变量
BaoZi bz;
//使用构造方法为包子变量赋值
public ChiHuo(BaoZi bz) {
this.bz = bz;
}
//线程任务:吃包子
@Override
public void run() {
//吃货一直吃包子
while(true){
/*
1.生产包子和吃包子,只能有一个在执行,需要使用同步技术
2.同步技术需要使用锁对象,可以使用包子对象
*/
synchronized (bz){
//对包子的状态进行判断
if(bz.flag==false){
//false:没有包子 吃货线程调用wait等待
try {
bz.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/*
吃货线程被包子铺线程唤醒之后执行的代码
*/
//true:有包子 吃货线程开始吃包子
//打印吃x皮x陷的包子
System.out.println("吃货正在吃"+bz.pi+bz.xian+"的包子!");
//吃完包子,修改包子的状态为false
bz.flag = false;
//吃货线程唤醒包子铺线程,做包子
bz.notify();//唤醒bz对象上等待的包子铺线程
System.out.println("吃货已经吃完了"+bz.pi+bz.xian+"的包子,包子铺赶紧生产包子吧!");
System.out.println("------------------------------------------------------------");
}
}
}
}
Demo01WaitAndNotify.java
/*
等待与唤醒案例:
Object类中的方法:
void wait()
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
被唤醒的线程,会执行wait方法之后的代码
void notify() 唤醒在此对象监视器(对象锁)上等待的单个线程。
注意:
1.wait和notify方法必须由锁对象调用,而且必须是同一个锁
锁对象-->wait() 锁对象-->notify()
2.wait和notify方法一般都是写在同步中
测试类:
创建一个包子对象
创建一个包子铺线程,生产包子
创建一个吃货线程,吃包子
包子铺正在生产薄皮牛肉大葱陷的包子!
包子铺已经生产好了薄皮牛肉大葱陷的包子,吃货赶紧来吃吧!
吃货正在吃薄皮牛肉大葱陷的包子!
吃货已经吃完了薄皮牛肉大葱陷的包子,包子铺赶紧生产包子吧!
------------------------------------------------------------
包子铺正在生产薄皮牛肉大葱陷的包子!
包子铺已经生产好了薄皮牛肉大葱陷的包子,吃货赶紧来吃吧!
吃货正在吃薄皮牛肉大葱陷的包子!
吃货已经吃完了薄皮牛肉大葱陷的包子,包子铺赶紧生产包子吧!
------------------------------------------------------------
*/
public class Demo01WaitAndNotify {
public static void main(String[] args) {
//创建一个包子对象
BaoZi bz = new BaoZi();
//创建一个包子铺线程,生产包子
new Thread(new BaoZiPu(bz)).start();
//创建一个吃货线程,吃包子
new Thread(new ChiHuo(bz)).start();
}
}
多线程常用方法:
获取当前线程对象的名字
String name = Thread.currentThread().getName();
实现线程接口:
implements Runnable
让线程休眠多少100毫秒:
Thread.sleep(100)
线程池的相关用法:
使用线程池无返回值得用法:
/*
线程池
在JDK1.5的时候java提供了线程池
java.util.concurrent.Executors类:线程池的工厂类,用来生产线程池
静态方法:
static ExecutorService newFixedThreadPool(int nThreads)
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。
参数:
int nThreads:创建线程池中线程的个数
返回值:
ExecutorService:是生产线程池,类型是一个接口,newFixedThreadPool返回的就是ExecutorService接口的实现类对象
注意:我们无需关注ExecutorService的实现类是谁,我们只需要会使用ExecutorService接口来接收这个实现类即可(多态)
这叫面向接口编程
java.util.concurrent.ExecutorService:线程池
Future<?> submit(Runnable task) 提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。
把线程任务传递给submit方法,submit方法会在线程池中获取一个线程用于执行任务;执行完毕会自动把线程在归还给线程池
Future<T> submit(Callable<T> task) 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
void shutdown() 用于销毁线程池,一般不建议使用
*/
public class Demo01ThreadPool {
public static void main(String[] args) {
//1.使用线程池工厂类Executors提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
ExecutorService ex = Executors.newFixedThreadPool(2);
//2.调用线程池ExecutorService中的方法submit,传递线程任务,执行线程任务
//new Thread(new Runnable(){}).start();
ex.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程任务1执行了!");//pool-1-thread-2线程任务执行了!
}
});
ex.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程任务2执行了!");//pool-1-thread-1线程任务执行了!
}
});
ex.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程任务3执行了!");//pool-1-thread-2线程任务执行了!
}
});
//void shutdown() 用于销毁线程池,一般不建议使用
ex.shutdown();
//线程池销毁之后,就在内存中消失了,就不能在执行线程任务了
ex.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程任务3执行了!");//RejectedExecutionException
}
});
}
}
使用线程池有返回值得用法:
/*
Future<T> submit(Callable<T> task) 提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。
java.util.concurrent.Callable<T>接口:用于设置线程任务
V call() 计算结果,如果无法计算结果,则抛出一个异常。
重写call方法,返回一个执行泛型类型的数据
*/
public class Demo02ThreadPool {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.使用线程池工厂类Executors提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
ExecutorService ex = Executors.newFixedThreadPool(2);
//2.调用线程池ExecutorService中的方法submit,传递线程任务,执行线程任务,接收线程任务的返回值
Future<Double> f1 = ex.submit(new Callable<Double>() {
@Override
public Double call() throws Exception {
return 1.1;
}
});
System.out.println(f1);//java.util.concurrent.FutureTask@7006c658
//使用Future接口中的方法V get()获取线程任务的返回值
Double d = f1.get();
System.out.println(d);
Future<Integer> f2 = ex.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
//返回一个0-100之间的随机数 [0,100)
return new Random().nextInt(100);
}
});
System.out.println(f2.get());
}
}
有返回值的实操代码累加1-100的和:
/*
使用Callable接口,计算1-100或者1-200之间所有整数的和
*/
public class Demo03Test {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.使用线程池工厂类Executors提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
ExecutorService ex = Executors.newFixedThreadPool(2);
//使用Scanner获取一个整数
System.out.println("请输入一个整数");
int i = new Scanner(System.in).nextInt();
//2.调用线程池ExecutorService中的方法submit,传递线程任务,执行线程任务,接收线程任务的返回值
Future<Integer> f = ex.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
//计算1-i之间的和
for (int j = 1; j <= i; j++) {
sum += j;
}
return sum;
}
});
System.out.println(f.get());
}
}
Lambda表达式的标准格式:
/*
Lambda表达式的标准格式:
(参数)->{重写的方法体}
一些参数,一个箭头,一段代码
()->接口中的抽象方法的参数,有就写上,没有空着 (String s) (int a,int b)
->传递:把参数传递给{}中的方法体
{}:重写接口中的抽象方法的方法体
*/
public class Demo02Lambda {
public static void main(String[] args) {
//使用匿名内部类的方式,实现多线程程序
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->执行了线程任务");
}
}).start();
//使用lambda表达式实现多线程程序
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"-->执行了线程任务");
}).start();
//使用Lambda的省略格式
new Thread(()->System.out.println(Thread.currentThread().getName()+"-->执行了线程任务")).start();
}
}
使用Arrays数组工具类中的方法sort对象Person按照年龄进行降序排序
/*
创建一个数组,类型使用Person
存储Person对象
使用Arrays数组工具类中的方法sort对象Person按照年龄进行降序排序
java.util.Arrays:
static <T> void sort(T[] a, Comparator<? super T> c)
根据指定比较器产生的顺序对指定对象数组进行排序。
*/
public class Demo01Lambda {
public static void main(String[] args) {
//创建一个数组,类型使用Person.存储Person对象
Person[] arr = {
new Person("迪丽热巴",18),
new Person("古力娜扎",17),
new Person("马尔扎哈",30),
};
//使用Arrays数组工具类中的方法sort对象Person按照年龄进行降序排序
Arrays.sort(arr, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
// o1-o2升序,o2-o1降序
return o2.getAge()-o1.getAge();
}
});
//使用Lambda表达式,简化匿名Comparator内部类
Arrays.sort(arr,(Person o1, Person o2)->{
return o2.getAge()-o1.getAge();
});
//使用Lambda表达式的简化格式
Arrays.sort(arr,(o1,o2)->o2.getAge()-o1.getAge());
//遍历数组
for (Person p : arr) {
System.out.println(p);
}
}
}
Lambda表达式的使用前提:
/*
Lambda表达式的使用前提:
1.必须用来简化接口重写的匿名内部类
2.接口中只能有一个抽象方法(函数式接口),才能使用Lambda表达式简化
Lambda表达式是可推导,可能省略
接口中只有一个抽象方法,所以Lambda重写的就是这个抽象方法
Lambda表达式的省略格式:
():()中的参数,数据类型是可以省略的 (int a,int b)-->(a,b)
Lambda重写的就是这个抽象方法,抽象方法的参数的类型是固定的,所以可以推导出来,就可以省略了
():()中如果只有一个参数,那么类型和()都可以省略 (int a)-->a
注意:如果没有参数()不能省略
{}:{}中的方法体,无论是否有返回值,如果只有一行代码
那么{},return,;都可以省略,但是必须3个一起省略
*/
public class Demo01Lambda {
public static void main(String[] args) {
//创建集合对象,必须指定集合的泛型,在JDK1.7之前集合前后的泛型都必须写出来
ArrayList<String> list01 = new ArrayList<String>();
//在JDK1.7之后,后边的泛型可以根据前边的泛型推导出来,所以可以省略
ArrayList<String> list02 = new ArrayList<>();
}
}
函数式接口:
能够使用Supplier函数式接口
Supplier接口用于生产数据,接口指定什么类型,就会调用get方法生产什么类型的数据返回
能够使用Consumer函数式接口
Consumer接口是一个消费型接口,指定接口是什么泛型,accept方法就会消费什么类型的数据
传递什么指定的类型的数据,想怎么消费数据都可以,看心情
能够使用Function函数式接口
Function<T,R>用来进行类型转换,可以根据一个类型的数据得到另一个类型的数据
常用的方法:
R apply(T t),根据类型T的参数获取类型R的结果。
使用的场景例如:将String类型转换为Integer类型。
能够使用Predicate函数式接口
Predicate对某种类型的数据进行判断,从而得到一个boolean值结果。
常用的方法:
boolean test(T t):用来对指定泛型类型的数据进行判断
符合条件,方法返回true;不符合条件,就返回false