1.线程与进程
进程:
是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程:
是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程。
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。
1.线程调度
分时调度
- 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度
- 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
- CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核芯而言,某个时刻,只能执行一个线程,而CPU在多个线程之间切换速度相对我们的感觉要块,看上去技术在同一时刻运行。其实多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
2.同步与并发&并发与并行
- 1.同步与异步 同步:排队执行,效率低但是安全。 异步:同时执行,效率高但是数据不安全。
- 2.并发与并行 并发:指两个或多个事件在同一个时间段内发生。 并行:指两个或多个事件在同一时刻发生(同时发生)。
3.继承Thread
继承一个Thread就是一个新的线程。
public static void main(String[] args){
Thread t = new Thread();
t.start();
}
public class MyRunnable extends Thread{
public void run(){
//这里的代码 就是一条新的执行路径,
//这个执行路径的出发方式,不是调用run方法,
//而是通过thread对象的start()来启动任务
for(int i=0;i<10;i++){
sout("床前明月光")
}
}
}
1.每个线程都拥有自己的栈空间,共用一份堆内存。
由一个线程执行的方法,这个方法也会执行在这个线程里面。
4.实现Runnable
//用于给线程进行执行的任务
public class MyRunnable implements Runnable{
public void run(){
//线程的任务
for(int i=0;i<10;i++){
sout("床前明月光")
}
}
}
//实现Runnable 与继承Thread相比又如下优势:
//1.通过创建任务,然后给线程分配的方式来实现的多线程。
//2.可以避免继承所带来的局限性。
//3.任务与线程本身是分离的,提高了程序的健壮性。
//4.后续学习的线程池技术,接收Runnable类型的任务,不接受Thread类型的任务。
public static void main(String[] args){
//实现Runnable
//1.创建一个任务对象
MyRunnable r = new MyRunnable();
//2.创建一个线程,并为其分配一个任务
Thread t = new Thread(r);
//3.执行这个线程
t.start();
for(int i=0;i<0;i++){
sout("疑是地上霜")
}
}
5.线程休眠sleep
public static void main(String[] args){
//线程的休眠 sleep,每隔一秒打印
for(int i=0;i<10;i++){
sout(i);
Thread.sleep(1000);
}
}
6.设置和获取线程名称
public static void main(String[] args){
//如何获取线程的名称
sout(Thread.currentThread().getName());
new Thread(new MyRunnable(),"锄禾日当午").start();//线程的名称是锄禾日当午
}
static class MuRunnable implements Runnable{
public void run(){
sout(Thread.currentThread().getName());
}
}
7.线程的中断
//线程的中断
//一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定。
Thread t1 = new Thread(new MyRunnable());
//给线程t1添加中断标记
t1.interrupt();
static class MuRunnable implements Runnable{
public void run(){
sout(Thread.currentThread().getName());
try{
Thread.sleep(1000);
}catch(InterruptedException e){
sout("发现中断标记");
//如果要让线程中断就return
return;
}
}
8.守护线程
//线程:分为守护线程和用户线程
//用户线程:当一个进程不包含任何的存活的用户线程时,进行结束。
//守护线程:守护用户线程,当最后一个用户线程结束时,所有守护线程自动死亡。
Thread t1 = new Thread(new MyRunnable());
//设置t1为守护线程
t1.setDaemon(true);
9.线程安全问题
//线程不安全
//解决方案1. 同步代码块
//格式: synchronized(锁对象){}
psvm{
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
//解决方案1. 同步代码块 隐式锁
//格式: synchronized(锁对象){}
static class Ticket implements Runnable{
//只会创建一次,3个线程同用一个锁对象。
private Object o = new Object();
//票数
private int count = 10;
public void run(){
while(true){
//三个线程看同一个锁对象o,排队执行。
synchronized(o){
if(count>0){
//卖票
sout("正在准备卖票");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
count--;
sout("出票成功,余票"+count)
}
}
}
}
//解决方案2:同步方法 隐式锁
//给方法增加synchronized修饰
static class Ticket implements Runnable{
//只会创建一次,3个线程同用一个锁对象。
private Object o = new Object();
//票数
private int count = 10;
public void run(){
while(true){
boolean flag = sale();
if(!flag){
break
}
}
//给方法增加synchronized修饰
public synchronized boolean sale(){
if(count>0){
//卖票
sout("正在准备卖票");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
count--;
sout("出票成功,余票"+count)
return true;
}
}else{
return false;
}
}
//解决方案3显示锁 l
//格式: lock l = new ReentrantLock();
static class Ticket implements Runnable{
//票数
private int count = 10;
//显示锁
lock l = new ReentrantLock();
public void run(){
while(true){
l.lock();
if(count>0){
//卖票
sout("正在准备卖票");
try{
Thread.sleep(1000);
}catch(InterruptedException e){
e.printStackTrace();
}
count--;
sout("出票成功,余票"+count)
}else{
break;
}
l.unlock();
}
}
10.公平锁与非公平锁
- 一起抢锁的是不公平锁,排队的是公平锁。
- 显示锁,同步代码块,同步方法都是不公平锁。
- 显示锁 l :fair参数为true 就表示公平锁
11.多线程通信问题
//多线程通信问题,生产者与消费者问题
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;
}
public void run(){
for(int i=0;i<100;i++){
//生成50份小米粥和50份煎饼果子
if(i%2==0){
f.setNameAndTaste("老干妈小米粥","香辣味");
}else{
f.setNameAndTaste("煎饼果子","甜辣味");
}
}
}
}
//服务生
static class Waiter extends Thread{
private Food f;
public Waiter(Food f){
this.f = f
}
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;
//标记是厨师做饭还是服务员端菜
private boolean flag = true;
public synchronized void setNameAndTaste(String name,String taste){
if(flag){
this.name = name;
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
this.taste = taste;
flag = false;
//唤醒在当前this下的所有线程
this.notifyAll();
//唤醒之后厨师线程睡着
this.wait();
}
}
public synchronized void get(){
if(!flag){
sout("服务员端走的菜的名称是:"+name+"味道:"+taste);
flag = true;
this.notifyAll();
this.wait();
}
}
}
12.线程的六种状态
- new: 线程刚被创建,尚未启动的线程处于此状态。
- Runnable(运行状态): Java虚拟机中执行的线程处于此状态。
- Blocked(在排队时的状态): 被阻塞等待监视器锁定的线程处于此状态。
- Waiting(在休眠的状态): 无期限等待另一个线程执行特定操作的线程处于此状态。
- TIMED WAITING: 正在等待另一个线程执行最多指定等待时间操作的线程处于此状态。
- TERMINATED: 已退出的线程处于此状态。
13.带返回值的线程Callable
1.//编写类实现Callable接口,实现call方法
class xxx implements Callable<T>{
public <T> call() throws Exception{
return T;
}
}
2.//创建FutureTask对象,并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);
3.//通过Thread,启动线程
new Thread(future).start();
14.线程池 Executors
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
1.线程池的好处
- 降低资源消耗。
- 提高响应速度。
- 提高线程的可管理性。
2.线程池类型
//缓存线程池(长度无限制)
//任务加入后的执行流程:
1.判断线程池是否存在空闲线程。
2.存在则使用。
3.不存在,则创建线程,并放入线程池,然后使用
public static void main(String[] args){
//向线程池中加入新的任务
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable(){
public void run(){
sout(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable(){
public void run(){
sout(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable(){
public void run(){
sout(Thread.currentThread().getName()+"锄禾日当午");
}
});
}
//定长线程池
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable(){
public void run(){
sout(Thread.currentThread().getName()+"锄禾日当午");
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
});
service.execute(new Runnable(){
public void run(){
sout(Thread.currentThread().getName()+"锄禾日当午");
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
});
service.execute(new Runnable(){
public void run(){
sout(Thread.currentThread().getName()+"锄禾日当午");
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
});
//单线程线程池
//执行流程:
1.判断线程池的那个线程是否空闲
2.空闲则使用
3.不空闲,则等待 池中的单个线程空闲后使用
public static void main(String[] args){
//向线程池中加入新的任务
ExecutorService service = Executors.newSingleThreadExector();
service.execute(new Runnable(){
public void run(){
sout(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable(){
public void run(){
sout(Thread.currentThread().getName()+"锄禾日当午");
}
});
service.execute(new Runnable(){
public void run(){
sout(Thread.currentThread().getName()+"锄禾日当午");
}
});
}
//周期任务 定长线程池
//执行流程
1.判断线程池是否存在空闲线程
2.存在则使用
3.不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池,然后使用
4.不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
public static void main(String[] args){
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
//1.定时执行一次
//参数1.定时执行的任务
//参数2.时长数字
//参数3.时长数字的时间单位,TimeUnit的常量指定
//下面代码5秒后打印我
service.schdule(new Runnable(){
public void run(){
sout("我")
}
},5,TimeUnit.SECONDS);
}
//周期性执行任务
//参数1.任务
//参数2.延迟时长数字(第一次执行在上面时间以后)
//参数3.周期时长数字(每隔多久执行一次)
//参数4.时长数字的单位
//下面的代码5秒后执行每次间隔一秒打印我
service.scheduleAtFixedRate(new Runnable(){
public void run(){
sout("我");
}
},5,1,TimeUnit.SECONDS);