0
点赞
收藏
分享

微信扫一扫

Java基础--->并发部分(3)【JUC、AQS】

木樨点点 2023-05-22 阅读 45

Java基础学习多线程

一、多线程

1.1 什么是多线程

线程

进程:
进程是程序的基本执行实体

多线程的应用场景:

1.2 多线程的两个概念

1.2.1 并发

并发:在同一时刻,有多个指令在单个CPU上交替执行
并行:在同一时刻,有多个指令在多个CPU上同时执行
在这里插入图片描述

1.3 多线程的实现方式

  • 继承Thread类的方式进行实现

多线程的第一种启动方式:

重写的Run方法:

package MyThreads;
//重写run方法(Thread)
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "HelloWorld!!");
        }
    }
}

启动的线程:

package MyThreads;
//新建Thread
public class Dom1 {
    public static void main(String[] args) {
    //创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        //给线程添加名字
        t1.setName("线程1");
        t2.setName("线程2");

        //启动两个线程
        t1.start();
        t2.start();
    }
}

  • 实现Runnable接口的方式进行实现

多线程的第二种启动方式:

执行代码:

package MyThreads;
//创建线程二
//利用runnable接口进行创建
/*
* 多线程的第二种启动方式:
* 1.自己定义一个类实现Runnable接口
* 2.重写里面的run方法
3.创建自己的类的对象
4.创建一个Thread类的对象,并开启线程
*
* */

public class Dom2 {
    public static void main(String[] args) {
        //创建线程需要执行的任务
        MyThread2 mr = new MyThread2();

        //创建线程执行mr任务
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);

        //给线程添加名字
        t1.setName("线程一");
        t2.setName("线程二");

        //开启线程
        t1.start();
        t2.start();
    }
}

接口代码:

package MyThreads;
//创建线程二
//利用runnable接口进行创建
/*
* 多线程的第二种启动方式:
* 1.自己定义一个类实现Runnable接口
* 2.重写里面的run方法
3.创建自己的类的对象
4.创建一个Thread类的对象,并开启线程
*
* */

public class Dom2 {
    public static void main(String[] args) {
        //创建线程需要执行的任务
        MyThread2 mr = new MyThread2();

        //创建线程执行mr任务
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);

        //给线程添加名字
        t1.setName("线程一");
        t2.setName("线程二");

        //开启线程
        t1.start();
        t2.start();
    }
}

  • 利用Callable接口和Future接口方式实现

多线程的第三种实现方式:
特点:可以获取到多线程运行的结果

对象:

package MyThreads;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

//线程创建3
/*
* 多线程的第三种实现方式:
特点:可以获取到多线程运行的结果
1。创建一个类MyCallable实现Callable接口
* 2。重写ca11   (是有返回值的,表示多线程运行的结果 )
3。 创建MyCallable的对象 (表示多线程要执行的任务)
* 4。创建FutureTask的对象 (作用管理多线程运行的结果)
* 5。创建Thread类的对象,并启动 (表示线程)
* */
public class Dom3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //创建MyCallable的对象 (表示多线程要执行的任务)
        MyCallable mr = new MyCallable();
        //创建FutureTask的对象 (作用管理多线程运行的结果)
        FutureTask<Integer> ft = new FutureTask<>(mr);
        //创建Thread类的对象,并启动 (表示线程)
        Thread t1 = new Thread(ft);
        //启动线程
        t1.start();
        //获取返回值
        Integer result = ft.get();
        System.out.println(result);
    }
}

接口:

package MyThreads;

import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 100; i++) {
            sum = sum + i;
        }
        return sum;
    }
}

总结:
按照不同的需求去完成我们的线程创建
在这里插入图片描述

1.4 多线程的成员方法

方法名称说明
String getName()返回此线程的名称
void setName(string name)设置线程的名字(构造方法也可以设置名字)
static Thread currentThread()获取当前线程的对象
static void sleep(long time)让线程休眠指定的时间,单位为毫秒
setPriority(int newPriority)设置线程的优先级
final int getPriority()获取线程的优先级
final void setDaemon(boolean on)设置为守护线程
public static void yield()出让线程/礼让线程
public static void join()插入线程/插队线程

细节:(前面四个成员方法)

测试代码:

package MyThreadThod;
//成员方法演示
/*
* 1. 设置名字,返回名字
* 2. 获取当前的线程名字
* 3. 让线程睡眠(单位毫秒)
* */
public class Dom1 {
    public static void main(String[] args) {
        //创建线程对象
        MyThread t1 = new MyThread("lisi");
        MyThread t2 = new MyThread("zhangsan");

        //开启线程
        t1.start();
        t2.start();


    }
}

继承Thread代码:

package MyThreadThod;

public class MyThread extends Thread{
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //睡眠
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(getName()+"@"+i);
        }
    }
}

成员方法优先级
范围: 1~10
优先级越高,就代表的执行他的概率就越高,但是不是代表优先级越高,就先执行该代码,执行的顺序是随机的,但是优先级越大,随机的概率就越大

package MyThreadThod;
//成员方法,优先级
public class Dom2 {
    public static void main(String[] args) {
        //创建多线程执行的任务
        MyRunnable mr = new MyRunnable();

        //创建Thread对象,执行任务
        Thread t1 = new Thread(mr);

        //设置优先级
        t1.setPriority(10);
        //启动
        t1.start();
    }
}

守护线程:

package MyThreadThod;
//成员方法:守护线程
public class Dom3 {
    public static void main(String[] args) {
        //创建Thread对象
        MyThread t1 = new MyThread();
        MyThread2 t2 = new MyThread2();

        //将第二个设置为守护线程
        t2.setDaemon(true);

        t1.setName("女神");
        t2.setName("二货");

        t2.start();
        t1.start();

    }
}

出让线程:

插入线程:

package MyThreadThod;

import MyThreads.MyThread1;

//插入代码
public class Dom4 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new MyThread1();
        t1.setName("洋芋");
        t1.start();

        t1.join();

        for (int i = 0; i < 10; i++) {
            System.out.println("main线程" + i);
        }
    }
}

1.5 线程的生命周期

在这里插入图片描述

二、线程安全

在这里插入图片描述
买票引发的安全问题:

解决方法:
在这里插入图片描述

  • 利用锁
    特点1: 锁默认打开,有一个线程进去了,锁自动关闭
    特点2:里面的代码全部执行完毕,线程出来,锁自动打开

代码实现

package MySafeThread.safe1;

public class MySafe  extends Thread{
    static int ticket = 0;
    //锁对象必须是唯一的
    static Object obj = new Object();
    @Override
    public void run() {
        //创建锁
        //作用:如果有一个进入到了线程当中就会,就不会让其他的线程进入,直到
        //当该线程完成过后,重写再抢夺CPU
        while (true){   
            //创建锁
            synchronized (obj){
                if (ticket<100){
                    //每个10毫秒在进行买票
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    ticket++;
                    System.out.println(getName() + "正在卖"+ticket+"张票了!!!!!!!!!");
                }else {
                    break;
                }
            }
        }
    }
}

锁对象必须得是唯一的

一般情况下用当前代码的字节码文件.class来表示唯一对象

1.6 同步方法

就是把synchronized关键字加到方法上

格式:
修饰符 synchronized 返回值类型 方法名(方法参数) {...}

特点1: 同步方法是锁住方法里面所有的代码
特点2:锁对象不能自己指定

里面在不同状态下调用的对象也不一样:

Stirngbuilder 与 Stringbuffer 的区别:

主要修改的代码

package MySafeThread.safe2;

import java.util.Timer;

public class MyRunnable2 implements Runnable{
    int ticket = 0;
    @Override
    public void run() {
        //1. 循环
        //2. 同步方法
        //3.判断是否完成,未完成,执行下列代码
        //4.判断是否完成,已完成,执行下列代码
        while (true){
            if (method()) break;
        }
    }

    //创建同步方法:这里是非静态的调用时为:this
    private synchronized boolean method() {
        if (ticket == 100) {
            return true;
        } else {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            ticket++;
            System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "票了!!!!!");
        }

        return false;
    }
}

1.7 锁lock

定义:

Lock中提供了获得锁和释放锁的方法

  • void lock(): 获得锁
  • void unlock(): 释放锁

手动上锁、手动释放锁

Lock是接口不能直接实例化

ReentrantLock的构造方法
ReentrantLock():创建一个ReentrantLock的实例

1.8 死锁

两个锁互相嵌套导致,两个锁都在等待着对方放下锁,但是他们释放锁的情况是执行该所下面的代码才能释放,这样就导致程序卡死出不去

死锁实列:

package MySafeThread.BugLock;

public class BUGLock extends Thread {
    static Object obj1 = new Object();
    static Object obj2 = new Object();
    @Override
    public void run() {
        if ("A".equals(getName())){
            synchronized (obj1){
                System.out.println("拿到了线程A,准备拿线程B");
                synchronized (obj2){
                    System.out.println("拿到了线程B,结束");
                }
            }
        } else if ("B".equals(getName())) {
            if ("B".equals(getName())){
                synchronized (obj2){
                    System.out.println("拿到了线程B,现在拿线程A");
                    synchronized (obj1){
                        System.out.println("拿到了线程A,现在拿线程B");
                    }
                }
            }
        }
    }
}

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

生产者消费者模式是一个十分经典的多线程协作的模式

形象化理解:

在这里插入图片描述

方法名称说明
void wait()当前线程等待,直到被其他线程唤醒
void notify()随机唤醒单个线程
void notifyAll()唤醒所有线程

启动窗口:

package MyLock.WaitAndNotify;

import java.io.FileOutputStream;

public class waitNotiyDom {
    public static void main(String[] args) {
        //创建厨师和吃货的对象
        Cook cook = new Cook();
        FoodAddict addict = new FoodAddict();

        //设置名字
        cook.setName("厨师");
        addict.setName("吃货");

        cook.start();
        addict.start();
    }
}

厨师窗口:

package MyLock.WaitAndNotify;

public class Cook extends Thread{
    @Override
    public void run() {
        //循环
        while (true){
            //创建同步代码块
            synchronized (Desk.lock){
                //判断,没有执行到尾部
                if (Desk.MaxFood == 0){
                    break;
                }else {
                    //执行到末尾
                    //首先判断状态值是否为0,是否有面条
                    if (Desk.Food == 1){
                        //是1,就执行等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else {
                        //是0,就做食物,释放所有等待代码
                        System.out.println("厨师正在做食物");
                        System.out.println("厨师已做完,请吃");
                        //将状态码变为1
                        Desk.Food =1;
                        //释放所以等待代码
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}

吃货窗口:

package MyLock.WaitAndNotify;

public class FoodAddict extends Thread{
    @Override
    public void run() {
        while (true){
            synchronized (Desk.lock){
                //判断是否还能吃下去
                if (Desk.MaxFood == 0){
                    //不能吃下去了
                    System.out.println("吃货吃不下了,要g了");
                    break;
                }else{
                    //还能继续吃
                    //判断桌子上面是否有吃的
                    if (Desk.Food == 0){
                        //没有吃的就执行等待
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }else {
                        //有吃的
                        //将吃的碗数减1
                        Desk.MaxFood--;
                        System.out.println("吃货正在吃面条,还可以吃"+Desk.MaxFood+"碗");
                        //已经吃完,释放所有的代码,叫厨师吃饭了
                        Desk.lock.notifyAll();
                        //食物清空
                        Desk.Food =0;
                    }
                }
            }
        }
    }
}

桌子窗口:

package MyLock.WaitAndNotify;
//表示桌子,来放置面条的状态,达到谁去执行的效果
public class Desk{
    //创建面的状态 , 0:没有面条 1:有面条
    public static int Food = 0;
    //创建吃货吃的最多碗
    public static int MaxFood = 10;

    //创建锁
    public static Object lock = new Object();

}

1.9 等待唤醒机制(阻塞队列方式实现)

在这里插入图片描述
实现类:
ArrayBlockingQueue底层是数组,有界限
LinkedBlockingQueue底层是链表,无界但不是真正的无界最大为int的最大值。

厨师类

package MyLock.ArrayBlockingQueues;

import java.util.concurrent.ArrayBlockingQueue;

public class queueCooker extends Thread{
    ArrayBlockingQueue<String> queue;

    public queueCooker(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true){
            //不需要再写锁了,因为put当中写了
            try {
                queue.put("面条");
                System.out.println("厨师做了面");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

吃货类

package MyLock.ArrayBlockingQueues;

import java.util.concurrent.ArrayBlockingQueue;

public class queueAddict extends Thread{
    ArrayBlockingQueue<String> queue;

    public queueAddict(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true){
            try {
                String food = queue.take();
                System.out.println("吃货在吃"+food);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

执行类:

package MyLock.ArrayBlockingQueues;

import java.util.Arrays;
import java.util.concurrent.ArrayBlockingQueue;

//阻塞队列方式实现等待唤醒任务
public class ArrayBlockingQueueDom {
    public static void main(String[] args) {
        //创建阻塞队列方式(唯一)
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

        //创建两个变量,赋名字,并且把阻塞队列传入
        queueAddict addict = new queueAddict(queue);
        queueCooker cooker = new queueCooker(queue);

        addict.setName("吃货");
        cooker.setName("厨师");

        //开启线程
        addict.start();
        cooker.start();
    }
}

1.10 线程状态

六种红色的状态
在这里插入图片描述
新建状态(NEW ) --> 创建线程对象

就绪状态(RUNNABLE ) --> start方法

阻塞状态( BLOCKED ) --> 无法获得锁对象

等待状态 (WAITING ) --> wait方法

计时等待 (TIMED WAITING ) --> sleep方法

结束状态(TERMINATED ) --> 全部代码运行完毕

二、线程池

2.1 线程池的概述

  • 以前写多线程的弊端

会造成资源的浪费

所以现在出现了线程池来存储线程

线程池的核心原理:

创建线程池的方法:
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象

方法名称说明
public static ExecutorService newCachedThreadPool()创建一个没有上限的线程池
public static ExecutorService newFixedThreadPool(int nThreads)创建有上限的线程池
package MythreadPool.dom1;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//创建线程池,初识线程池
public class Dom1 {
    public static void main(String[] args) {
        //创建无上限的线程池
        ExecutorService pool1 = Executors.newCachedThreadPool();
   //创建有上限的线程池(3个)
//        ExecutorService pool2 = Executors.newFixedThreadPool(3);

        //提交任务
        pool1.submit(new MyRunnable1());
        pool1.submit(new MyRunnable1());
        pool1.submit(new MyRunnable1());
        pool1.submit(new MyRunnable1());
    }
}

package MythreadPool.dom1;

public class MyRunnable1 implements Runnable{
    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() + "-----------" + i);
        }
    }
}

2.2 自定义线程池

任务拒绝策略说明
ThreadPoolExecutor.AbortPolicy 默认策略: 丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy丢弃任务,但是不抛出异常 这是不推荐的做法
ThreadPoolExecutor.DiscardoldestPolicy抛弃队列中等待最久的任务 然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy调用任务的run()方法绕过线程池直接执行

理解:
自定义线程池:

核心元素一: 正式员工数量 --------------> 核心线程数量(不能小于0)

核心元素二: 餐厅最大员工数 ---------------> 线程池中最大线程的数量(最大数量 >= 核心线程数量)

核心元素三: 临时员工空闲多长时间被辞退(值) --------> 空闲时间 (值) (不能小于0)

核心元素四: 临时员工空闲多长时间被辞退(单位) -------> 空闲时间(单位) (用TimeUnit指定)

核心元素五: 排队的客户 --------> 阻塞队列 (不能为null)

核心元素六: 从哪里招人 ---------> 创建线程的方式(不能为null)

核心元素七: 当排队人数过多,超出顾客请下次再来(拒绝服务) ------> 要执行的任务过多时的解决方案(不能为null)

package MythreadPool.dom2;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

//自定义线程池
public class dom2 {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3 , //核心线程数量(表示的正式员工)
                6, //最大线程数量(总员工数量)
                60,//空闲线程最大的存活时间
                TimeUnit.SECONDS, //表示当前最大存活的时间单位
                new ArrayBlockingQueue<>(3),//排队的客户
                Executors.defaultThreadFactory(),//创建线程工厂
                new ThreadPoolExecutor.AbortPolicy()//拒绝方法

        );
    }
}

总结:

不断的提交任务,会有以下三个临界点:

2.3 线程池到底多大才合适

CPU 密集型运算 --------> 最大并行数 +I

l/0 密集型运算 ----------> 最大并行数 * ”期望 CPU利用率 * 总时间(CPU计算时间+等待时间) / CPU计算时间

例如: 4核8线程

在这里插入图片描述

举报

相关推荐

0 条评论