1、进程和线程是什么
- 进程:是CPU的资源调度单位,一个运行的程序,软件就是一个进程,每个进程有自己独立的内存空间
- 线程:是在进程中的,是程序执行的路径,多个线程相当于是主线程main的分支,共享同一个内存空间
注意:main其实也是一个线程,主线程!
2、线程调度
分时调度
- 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
抢占式调度
- 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),
Java使用的为抢占式调度。
- CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使用率更高
3、同步与异步
- 同步: 相当于十个大汉,排队吃一桶饭,一个一个来,安全但是效率不高
- 异步:十个大汉,同时吃一桶饭,效率高,但是不安全,容易产生冲突
4、并发与并行
- 并发:同一个时间段,请求数量
- 并行:同一时间的请求数量
5、创建线程方法
- 创建一个类继承Thread类,重写run方法,再调用写的类.start()方法,调用start方法执行run
- 创建一个类实现Runnable,相当于写一个任务,然后new出该类,在new一个Thread,把该类传进Thread中,把任务交给Thread执行,再调用Thread的start()
=实现Runnable接口比Thread的好处是:=
- 实现Runnable接口方式,相当于创建一个任务,当需要多个线程执行同个任务的时候,直接把该类传进线程中就行,特别方便
- 类是单继承的,实现接口有利于类的多态化
- 线程池的管理时,是只接受Runnable类型的任务,不接收Thread类型的线程
- 任务与线程本身是分离的,降低程序的耦合,提高了程序的健壮性
6、线程分守护线程和用户线程
- 用户线程:当一个线程不包含任何的存活的用户线程是进行结束
- 守护线程:守护用户线程的,当最后一个用户线程结束的时候,所有线程自动死亡
人在塔在!!!
7、线程中断
package com.xzk.section04.chapter05;
/**
* @version 1.0
* @Author: lyq
* @Date: 2020/12/23
* @Description: TODO
**/
public class Demo_ThreadInterrupt {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyThread());
thread.start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+i);
Thread.sleep(1000);
}
thread.interrupt();
}
static class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+i);//打印当前线程名字
try {
Thread.sleep(1000);//等待一秒
} catch (InterruptedException e) {//接收到中断标志
//e.printStackTrace();
System.out.println("接收到中断标志,中断线程");
//...在这期间释放该释放的资源
return;//结束线程
}
}
}
}
}
8、锁
话不多说,上代码:
package com.xzk.section04.chapter05;
/**
* @version 1.0
* @Author: lyq
* @Date: 2020/12/23
* @Description: TODO 练习线程锁
**/
public class Demo_ThreadLock {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
new Thread(myThread).start();
new Thread(myThread).start();
new Thread(myThread).start();
}
/**
* TODO 假设一个简单的需求 卖票
*
* @param null
* @return:
**/
static class MyThread implements Runnable {
//10张票
private int count = 10;
@Override
public void run() {
while (true) {
if (count > 0) {
try {
Thread.sleep(1000);//休眠一秒
System.out.println("票卖出一张,余票:" + (--count));
} catch (InterruptedException e) {
return;
}
} else {
break;
}
}
}
}
}
最终结果出现:
票卖出一张,余票:-1
票卖出一张,余票:-2
原因,当第一个线程抢到了CPU抛出的时间偏,线程开始休眠一秒钟,在这期间,第二个第三个线程也抢到了时间偏,所以出现以上情况
加锁法一,加上一把对象锁:=(同步代码块=
static class MyThread implements Runnable {
//10张票
private int count = 10;
Object o = new Object();
@Override
public void run() {
while (true) {
synchronized (o) {
if (count > 0) {
try {
Thread.sleep(1000);//休眠一秒
System.out.println(Thread.currentThread().getName()
+"票卖出一张,余票:" + (--count));
} catch (InterruptedException e) {
return;
}
} else {
break;
}
}
}
}
}
结果正常,但是效率变慢,因为是变成排队同步了
加锁法二,给方法加锁:(同步方法)
static class MyThread implements Runnable {
//10张票
private int count = 10;
@Override
public void run() {
sale();
}
public synchronized void sale() {
while (true) {
if (count > 0) {
try {
Thread.sleep(1000);//休眠一秒
System.out.println(Thread.currentThread().getName()
+"票卖出一张,余票:" + (--count));
} catch (InterruptedException e) {
return;
}
} else {
break;
}
}
}
}
上面两种都是隐式锁
加锁方法三:*(显式锁Lock)
显式锁(synchronized)跟隐式锁(Lock)的区别:
1.显式锁需要手动控制开关
2.显式锁不可以中断,而隐式锁可以设置超时中断,或者直接Interrupt中断
Sync:非公平锁
lock:两者都可以的。默认是非公平锁。在其构造方法的时候可以传入Boolean值。
9、公平锁与非公平锁
公平锁,先申请的先得到锁,一个个排队
非公平锁,每一个都尝试去获取锁,获取到就锁,获取不到就进行等待
10、死锁
A持有B的锁,B持有A的锁。
案例(警察与罪犯):
package com.xzk.section04.chapter05;
/**
* @version 1.0
* @Author: lyq
* @Date: 2020/12/23
* @Description: TODO
**/
public class Demo_ThreadDieLock {
public static void main(String[] args) {
Police police = new Police();
Zuifan zuifan = new Zuifan();
new MyThread(zuifan,police).start();
zuifan.say(police);
}
public static class MyThread extends Thread {
private Zuifan zuifan;
private Police police;
public MyThread(Zuifan zuifan, Police police) {
this.zuifan = zuifan;
this.police = police;
}
@Override
public void run() {
//在此谈判
police.say(zuifan);
}
}
//警察类
static public class Police {
public synchronized void say(Zuifan zuifan) {
System.out.println("放人质,再放你");//警察谈判
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
zuifan.respond();//希望得到罪犯的回应
}
//警察回应
public synchronized void respond() {
System.out.println("你放了人质,你走吧");
}
}
//罪犯类
static public class Zuifan {
public synchronized void say(Police police) {
System.out.println("放过我,再放人质");//罪犯谈判
try {
Thread.sleep(100); //电脑太好锁不住,加大几率
} catch (InterruptedException e) {
e.printStackTrace();
}
police.respond();//希望得到警察的回应
}
//罪犯回应
public synchronized void respond() {
System.out.println("你放了我,我把人质放了");
}
}
}
11、多线程同信问题(生产者与消费者)
package com.xzk.section04.chapter05;
/**
* @version 1.0
* @Author: lyq
* @Date: 2020/12/23
* @Description: TODO 多线程通信问题,消费者与生产者
* <p>生产者生产过程中,消费者睡眠;
* 消费者消费过程中,生产者睡眠;
* </p>
**/
public class Demo_ThreadsCom {
public static void main(String[] args) {
Food food = new Food();
new Cooker(food).start();
new Waiter(food).start();
}
//厨师,生产菜品
static public class Cooker extends Thread {
private Food food;
public Cooker(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
food.setNameAndTaste("猪脚饭", "酸辣味");
} else {
food.setNameAndTaste("酸菜鱼", "苦甜味");
}
}
}
}
//服务员,端菜
static public class Waiter extends Thread {
private Food food;
public Waiter(Food food) {
this.food = food;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
food.get();//连续上菜,但需要等待100ms,不然厨师刚做好菜,可能服务员就端了一百次
}
}
}
//菜品
static public class Food {
private String name;
private String taste;
public Food() {
}
public String getName() {
return name;
}
public void setNameAndTaste(String name,String taste) {
this.name = name;
//加大多线程时候数据错乱的几率
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
}
public String getTaste() {
return taste;
}
public void get() {
System.out.println("端走:"+name+"味道"+taste);
}
}
}
简单的加锁并不能完全解决问题,因为第一个设置名字的锁,可能回首掏,刚自己释放锁有持有锁。
所以需要,让生产者生产完进行休眠,唤醒消费者,消费者消费完睡眠,唤醒生产者:
package com.java.demo;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo4 {
/**
* 多线程通信问题, 生产者与消费者问题
* @param args
*/
public static void main(String[] args) {
Food f = new Food();
new Cook(f).start();
new Waiter(f).start();
}
//厨师
static class Cook extends Thread{
private Food f;
public Cook(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
if(i%2==0){
f.setNameAndSaste("老干妈小米粥","香辣味");
}else{
f.setNameAndSaste("煎饼果子","甜辣味");
}
}
}
}
//服务生
static class Waiter extends Thread{
private Food f;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i=0;i<100;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
f.get();
}
}
}
//食物
static class Food{
private String name;
private String taste;
//true 表示可以生产
private boolean flag = true;
public synchronized void setNameAndSaste(String name,String taste){
if(flag) {
this.name = name;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.taste = taste;
flag = false;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void get(){
if(!flag) {
System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
flag = true;
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
12、线程的六种状态
-
线程状态。
线程可以处于以下状态之一:
NEW
尚未启动的线程处于此状态。RUNNABLE
在Java虚拟机中执行的线程处于此状态。BLOCKED
被阻塞等待监视器锁定的线程处于此状态。WAITING
无限期等待另一个线程执行特定操作的线程处于此状态。TIMED_WAITING
正在等待另一个线程执行最多指定等待时间的操作的线程处于此状态。TERMINATED
已退出的线程处于此状态。
13、创建线程的第三种方法—>Callable
Runnable 与 Callable
Callable使用步骤:
+ Runnable 与 Callable的相同点
1. 都是接口
2. 都可以编写多线程程序
3. 都采用Thread.start()启动线程
+ Runnable 与 Callable的不同点
1.Runnable没有返回值;Callable可以返回执行结果
2.Callable接口的call()允许抛出异常;Runnable的run()不能抛出
+ Callable获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行, 如果不调用不会阻塞。
接口定义
//Callable接口
public interface Callable {
V call() throws Exception;
}
//Runnable接口
public interface Runnable {
public abstract void run();
}
1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable {
@Override
public call() throws Exception {
return T;
}
}
2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask future = new FutureTask<>(callable);
3. 通过Thread,启动线程
new Thread(future).start();
14、线程池
线程池 Executors
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
线程池的好处
- 降低资源消耗。
- 提高响应速度。
- 提高线程的可管理性。
Java中的四种线程池 . ExecutorService
- 缓存线程池
/**
* 缓存线程池.
* (长度无限制)
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在,则创建线程 并放入线程池, 然后使用
*/
ExecutorService service = Executors.newCachedThreadPool();
//向线程池中 加入 新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
- 定长线程池
/**
* 定长线程池.
* (长度是指定的数值)
* 执行流程:
3. 单线程线程池
4. 周期性任务定长线程池
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*/
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:"+Thread.currentThread().getName());
}
});
-
单线程线程池
效果与定长线程池 创建时传入数值1 效果一致. /** * 单线程线程池. * 执行流程: * 1. 判断线程池 的那个线程 是否空闲 * 2. 空闲则使用 * 4. 不空闲,则等待 池中的单个线程空闲后 使用 */ ExecutorService service = Executors.newSingleThreadExecutor(); service.execute(new Runnable() { @Override public void run() { System.out.println("线程的名称:"+Thread.currentThread().getName()); } }); service.execute(new Runnable() { @Override public void run() { System.out.println("线程的名称:"+Thread.currentThread().getName()); } });
4.周期性任务定长线程池
public static void main(String[] args) {
/**
* 周期任务 定长线程池.
* 执行流程:
* 1. 判断线程池是否存在空闲线程
* 2. 存在则使用
* 3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
* 4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
*
* 周期性任务执行时:
* 定时执行, 当某个时机触发时, 自动执行某任务 .
*/
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
/**
* 定时执行
* 参数1. runnable类型的任务
* 参数2. 时长数字
* 参数3. 时长数字的单位
*/
/*service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("俩人相视一笑~ 嘿嘿嘿");
}
},5,TimeUnit.SECONDS);
*/
/**
* 周期执行
* 参数1. runnable类型的任务
* 参数2. 时长数字(延迟执行的时长)
* 参数3. 周期时长(每次执行的间隔时间)
* 参数4. 时长数字的单位
*/
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("俩人相视一笑~ 嘿嘿嘿");
}
},5,2,TimeUnit.SECONDS);
}