2023-05-07 多线程的学习

23.5.07总专注时长02:02:07;
多线程的学习Java文件位置
番茄钟启动:开始学习
相关详细资料
![[Pasted image 20230508002340.png|200]]

多线程

多线程是多任务的一种特别的形式,但多线程使用了更小的资源开销。

这里定义和线程相关的另一个术语 - 进程:一个进程包括由操作系统分配的内存空间,包 含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。

多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。

为什么需要多线程

众所周知,CPU、内存、I/O 设备的速度是有极大差异的,为了合理利用 CPU 的高性能,平衡这三者的速度差异,计算机体系结构、操作系统、编译程序都做出了贡献,主要体现为:

  • CPU 增加了缓存,以均衡与内存的速度差异;// 导致 可见性问题
  • 操作系统增加了进程、线程,以分时复用 CPU,进而均衡 CPU 与 I/O 设备的速度差异;// 导致 原子性问题
  • 编译程序优化指令执行次序,使得缓存能够得到更加合理地利用。// 导致 有序性问题

线程的生命周期

![[Pasted image 20230508003512.png]]
新建状态(New)
创建后尚未启动
可运行(Runnable)(就绪状态)
当线程对象调用了Thread.start方法后进入就绪状态,等待JVM的调动
阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
1、等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
2、同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
3、 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
无限期等待(Waiting)
等待其他线程使用notify方法唤醒否则不会被分配资源
限期等待(TimeWaiting):
在一定时间过后自动被系统唤醒sleep()方法
死亡状态(Terminated):
可能是线程结束后任务自己结束,或者产生了异常导致结束

多线程的启动方式

第一种:自己定义一个类继承   Thread类本身
	1、定义类继承Thread类
	2、重新定义run方法
	3、创建子类对象并启动线程
public class Mythread extends Thread{  
  
    @Override  
    public void run() {  
        //书写线程要执行的代码  
        for (int i = 0; i < 100; i++) {  
            System.out.println(getName()+"Hello");  
        }  
    }  
}
public class threadDemo1 {  
    public static void main(String[] args) {  
        /*  
        多线程的第一种启动方式  
        1、自己定义一个类继承thread  
        2、重写run方法  
        3、创建子类的对象,并启动线程  
         */  
        Mythread t1= new Mythread();  
        Mythread t2= new Mythread();  
  
        //给线程取名  
        t1.setName("线程一");  
        t2.setName("线程二");  
        //开启线程  
        t1.start();  
        t2.start();  
    }  
}
第二种:定义一个类实现Runnable接口
	1、定义类实现Runnable接口
	2、重写run方法
	3、创建自己定义的类的对象
	4、创建Thread类的对象,并启动线程

public class MyRunnable implements Runnable {
    public void run() {
        // ...
    }
}
public static void main(String[] args) { 
   MyRunnable instance = new MyRunnable();
   Thread thread = new Thread(instance);
   thread.start(); }
多线程的第三种实现方式:  
    特点:可以获取到多线程运行的结果  
    与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。
    1、创建一个类MyCallable实现Callable接口  
    2、重写call(有返回值,表示多线程运行的结果)  
    3、创建MyCallable的对象(表示多线程要执行的任务)  
    4、创建FutureTask的对象 用于管理多线程运行的结果  
    5、创建Thread类的对象,并启动(表示线程)
public class MyCallable implements Callable<Integer> {  
    @Override  
    public Integer call() throws Exception {  
        int sum=0;  
        for (int i = 1; i <=100 ; i++) {  
            sum=sum+i;  
        }  
        return sum;  
    }  
}
public class threadDemo {  
    public static void main(String[] args) throws ExecutionException, InterruptedException {  
        //创建MyCallable对象 表示多线程要执行的任务  
        MyCallable mc=new MyCallable();  
        //创建FutureTask的对象 用于管理多线程运行的结果  
        FutureTask<Integer> ft=new FutureTask<>(mc);  
        Thread t=new Thread(ft);  
        t.start();  
        Integer result = ft.get();  
        System.out.println(result);  
    }  
}

实现接口 VS 继承 Thread

实现接口会更好一些,因为:

  • Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
  • 类可能只要求可执行就行,继承整个 Thread 类开销过大。

线程的相关方法

String getName()    返回此线程的名称  
void setName(String name)   设置该线程的名称  
	细节:  
    1、如果没有给线程设置名字,线程的默认名字为  
    Thread-x (从0开始)  
    2、给线程设置名字可以用set方法也可以用构造方法  

static Thread currentThread()   获取当前线程的对象  
	//哪条线程执行到这个方法,此时获取的就是该线程的名称
	Thread t=Thread.currentThread();
	细节:  
    当JVM虚拟机启动之后,会自动的启动多条线程  
    其中有一条线程就叫做main线程  
    它的作用就是去调用main方法并执行里面的代码  
    在以前,我们写的所有的代码,其实都是运行在main线程中  

static void sleep(long time) 让线程休眠指定的时间

	细节  
	    1、哪条线程执行到这个方法,哪条线程就会在这里停留相应的时间  
	    2、方法的参数:表示睡眠的时间,单位毫秒  
	    1秒=1000毫秒  
	    3、当时间到了之后,线程会自动的醒来,继续执行下面的代码
setPriority(int newPriority)设置线程的优先级  
final int getPriority()     获取线程的优先级
线程的优先级0-10 线程具有随机的特点

Daemon

守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。
当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。
main() 属于非守护线程。
使用 setDaemon() 方法将一个线程设置为守护线程。
细节:当其他的非守护线程执行完毕之后,守护线程会陆续结束(不一定会执行完
public static void main(String[] args) { 
    Thread thread = new Thread(new MyRunnable());
    thread.setDaemon(true); 
    }
public static void yield()      出让线程/礼让线程  
	//出让当前cpu的执行权  

public final void join() 插入线程/插队线程  
	表示把线程插入到当前线程之前

线程安全(锁和相关机制)

同步代码块

	把操作共享数据的代码锁起来
	格式
	synchronized(锁){
	     操作共享数据的代码
	}
特点1:锁默认打开,有一个线程进去了,锁自动关闭
特点2:里面的代码全部执行完毕,线程出来,锁自动打开。
主类
public class threadsafeDemo {  
    public static void main(String[] args) {  
        /*  
        需求:三个窗口卖一共一百张票  
         */        
        MyThread t1=new MyThread();  
        MyThread t2=new MyThread();  
        MyThread t3=new MyThread();  
        
        t1.setName("窗口1");  
        t2.setName("窗口2");  
        t3.setName("窗口3");  
          
        t1.start();  
        t2.start();  
        t3.start();  
    }  
}
public class MyThread extends Thread{  
    //表示这个类所有的对象,都共享ticket数据  
    static int ticket=0;  
  
    //锁对象,一定是唯一的  
    //    static Object obj = new Object();  
    @Override  
    public void run() {  
        while (true){  
            //同步代码块  
            synchronized (MyThread.class){  
                if (ticket<100){  
                    try {  
                        Thread.sleep(10);  
                    } catch (InterruptedException e) {  
                        throw new RuntimeException(e);  
                    }  
                    ticket++;  
                    System.out.println(getName()+"正在卖第"+ticket+"张票!!!");  
  
                }else{  
                    break;  
                }  
            }  
        }  
    }  
}

同步方法

就是把synchronized关键字加到方法上
public class MyRunnable implements Runnable{  
    int ticket=0;  
  
    @Override  
    public void run() {  
  
        while (true){  
            //同步代码块  
                if (method()) break;  
  
        }  
    }  
//非静态时锁对象 this    
private synchronized boolean method() {  
        if (ticket==100) {  
            return true;  
        }else {  
            try {  
                Thread.sleep(10);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            ticket++;  
            System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票!!!");  
  
        }  
        return false;  
    }  
}

Lock锁

jdk5下的Lock
虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里 加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作Lock中提供了获 得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法
ReentrantLock(): 创建一个ReentrantLock的实例

public class MyThread extends Thread{  
    //表示这个类所有的对象,都共享ticket数据  
    static int ticket=0;  
    //jdk5的lock  
    static Lock lock = new ReentrantLock();  
  
    @Override  
    public void run() {  
        while (true){  
            //同步代码块  
            //synchronized (MyThread.class){  
            lock.lock();  
            try {  
                if (ticket==100){  
                    break;  
  
                }else{  
  
                    ticket++;  
                    System.out.println(getName()+"正在卖第"+ticket+"张票!!!");  
                }  
            } catch (Exception e) {  
                throw new RuntimeException(e);  
            } finally {  
                lock.unlock();  
            }  
  
        }  
    }  
}

线程池

为什么要有线程池

线程池能够对线程进行统一分配,调优和监控:

  • 降低资源消耗(线程无限制地创建,然后使用完毕后销毁)
  • 提高响应速度(无须创建线程)
  • 提高线程的可管理性

简单创建一个线程池

public class MyThreadPoolDemo1 {  
    public static void main(String[] args) {  
        /*  
        ExecutorService newCachedThreadPool()       创建一个没有上线的线程池  
        ExecutorService newFixedThreadPool(int nThreads)创建有上限的新线程池  
         */  
        //获取线程池对象  
        ExecutorService pool1= Executors.newCachedThreadPool();  
  
        //提交线程任务  
        pool1.submit(new MyRunnable());  
        pool1.submit(new MyRunnable());  
        pool1.submit(new MyRunnable());  
        pool1.submit(new MyRunnable());  
  
        //销毁线程池  
        pool1.shutdown();  
  
  
    }  
}

等待和唤醒机制(生产者与消费者)

实现线程轮流交换执行的效果

厨师类 生产者
public class Cook extends Thread{  
    @Override  
    public void run() {  
        /*  
        1、循环  
        2、同步代码块  
        3、判断共享数据是否到了末尾(到了  
        4、判断共享数据是否到了末尾(没到 ->执行核心逻辑)  
         */        while(true){  
            synchronized (Desk.lock){  
                if (Desk.count==0){  
                    break;  
                }else {  
                    //1、判断桌子上是否有食物  
                    //有就等待  
                    if (Desk.foodFlag==1){  
                        try {  
                            Desk.lock.wait();//将线程与所绑定   方便之后唤醒  
                        } catch (InterruptedException e) {  
                            throw new RuntimeException(e);  
                        }  
                    }else {  
                        //2、没有就制作  
                        System.out.println("厨子做了一碗面");  
                        //制作完了修改食物状态  
                        Desk.foodFlag=1;  
                        //叫醒等待的消费者开吃  
                        Desk.lock.notifyAll();  
                    }  
                }  
            }  
        }  
    }  
}
桌子 控制生产者和消费者的执行
public class Desk {  
    /*  
    控制生产者和消费者的执行  
     */    
     //是否有面条:0:没有面条  1:有面条  
    public static int foodFlag = 0;  
    //总个数  
    public static int count = 10;  
    //锁对象  
    public static  Object lock = new Object();  
}
消费者
public class Foodie extends Thread{  
    @Override  
    public void run() {  
        /*  
        1、循环  
        2、同步代码块  
        3、判断共享数据是否到了末尾(到了  
        4、判断共享数据是否到了末尾(没到 ->执行核心逻辑)  
         */  
        while(true){  
            synchronized (Desk.lock){  
                if (Desk.count==0){  
                    break;  
                }else {  
                    //先判断桌子上是否有面条  
                    //如果没有就等待  
                    if (Desk.foodFlag==0){  
                        try {  
                            Desk.lock.wait();//让当前线程跟锁进行绑定  
                        } catch (InterruptedException e) {  
                            throw new RuntimeException(e);  
                        }  
                    }else{  
                        //有就开吃  
                        //把剩下的面减一  
                        Desk.count--;  
                        System.out.println("正在吃面,还能吃"+Desk.count+"碗");  
                        //吃完后唤醒厨师继续做面  
                        Desk.lock.notifyAll();  
                        //修改桌子的状态  
                        Desk.foodFlag = 0;  
                    }  
  
  
  
                }  
            }  
        }  
    }  
}
主类
public class ThreadDemo {  
    public static void main(String[] args) {  
        /*  
        生产者和消费者(等待和唤醒机制)  
        实现线程轮流交替执行的效果  
         */        Cook c= new Cook();  
        Foodie f=new Foodie();  
        c.setName("厨师");  
        f.setName("吃货");  
        c.start();  
        f.start();  
  
    }  
}

自定义线程池![[Pasted image 20230507230156.png]]

自定义线程池小结:

1、创建一个空的池子
2、有任务提交时,线程池会创建线程去执行任务,执行完毕归还线程

不断的提交任务,会有以下三个临界点:
当核心线程满时,再提交任务就会排队
当核心线程满,队伍满时,会创建临时线程
当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值