Java -- 多线程

news2024/9/20 18:39:55

多线程

并发

在同一时刻,有多个指令在单个CPU上交替执行

CPU在多个线程之间交替执行

并行

在同一时刻,有多个指令在多个CPU上同时执行

多线程的实现方式

  1. 继承Thread类的方法进行实现
  2. 实现Runnable接口的方式进行实现
  3. 利用Callable接口和Future接口方式实现

继承于Thread类

//Thread子类
public class MyThread extends Thread {

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

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(this.getName() + "Hello world!");
        }
    }
}


public class Main {

    public static void main(String[] args) {
        MyThread thread1 = new MyThread("thread1: ");
        MyThread thread2 = new MyThread("thread2: ");
        thread2.start();
        thread1.start();
    }

}

Runnable方法实现

  1. 自定义一个类实现Runnable接口
  2. 重写里面的run方法
  3. 创建自己的类对象
  4. 创建一个Thread对象开启线程

public class MyThread implements Runnable {

    public MyThread() {

    }

    @Override
    public void run() {
        Thread curThread = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            System.out.println(curThread.getName() + " Hello");
        }
    }
}


public class Main {

    public static void main(String[] args) {
        MyThread thread1 = new MyThread();
        MyThread thread2 = new MyThread();
        Thread thread3 = new Thread(thread1);
        Thread thread4 = new Thread(thread2);
        thread3.start();
        thread4.start();
    }

}

Callable与Future接口

特点:可以获取多线程运行的结果

  1. 创建一个MyCallable实现Callable接口
  2. 重写call(有返回值的,表示多线程运行的结果)
  3. 创建MyCallable对象,表示多线程要执行的任务
  4. 创建Future对象(用来管理多线程运行的结果)
  5. Future是一个接口,要创建它的实现类FutureTask
  6. 创建Thread类对象然后启动线程
import java.util.concurrent.Callable;

public class MyCallable implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        return 100;
    }
}

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

public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable mc = new MyCallable();
        FutureTask<Integer> fc = new FutureTask<>(mc);
        Thread thread = new Thread(fc);
        thread.start();
        System.out.println(fc.get());
    }

}

Thread常见的成员方法

方法作用
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()插入/插队线程

get/setName上面已经使用过

需要注意的是,如果不设置线程的名字,线程有默认的名字Thread-X格式(x从0开始计数)

关于主线程的一点:

当JVM虚拟机启动之后,会自动启动多条线程,其中有一条叫做main线程,它的作用就是去调用main方法并执行里面的代码,我们以前所写的所有代码都是运行在main线程中

优先级

抢占式调度:随机性

非抢占式调度:顺序性

随机等级:1-10

默认优先级都为5

main线程的优先级也为5

优先级越高抢到cpu的概率也越高

可以使用对应的get/setPriority方法调整优先级

守护线程

setDaemon设置为守护线程(备胎线程)

其他非守护线程执行完毕后,守护线程也会陆续结束


public class Main {

    public static void main(String[] args) {
        MyCallable thread1 = new MyCallable();
        MyCallable thread2 = new MyCallable();
        Thread thread3 = new Thread(thread1, "3: ");
        Thread thread4 = new Thread(thread2, "4: ");
        thread4.setPriority(1);
        thread3.setPriority(10);
        thread4.setDaemon(true);
        thread3.start();
        thread4.start();
    }

}

线程3执行完后就会告诉线程4他不需要继续执行了,然后他就会停止

比如说聊天窗口和文件发送,聊天窗口关闭后,正在发送的文件也没必要继续发送了

礼让线程

出让线程的执行权,让当前抢到CPU的线程让出其执行权,再来让所有的线程竞争执行权。

尽可能的让线程更加的均匀。

    public void run() {
        Thread curThread = Thread.currentThread();
        for (int i = 0; i < 100; i++) {
            System.out.println(curThread.getName() + " Hello" + i);
            Thread.yield();
        }
    }

插入线程

把对应线程插入到当前线程之前,对应线程执行完毕后再执行当前线程

        Thread thread = new Thread(new MyCallable());
        thread.start();

        thread.join();

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

线程的执行周期

start方法
抢到CPU执行权
run完毕
sleep或者其他阻塞方法
sleep完毕或者其他阻塞方式
创建线程对象--新建
有执行资格
没有执行权--就绪
有执行资格
有执行权--运行
线程死亡
变为垃圾--死亡
没有执行资格
没有执行权--阻塞等待
sleep
时间到
wait
notify
无法获取锁
获得锁
运行
就绪
计时等待
等待
阻塞

同步代码块

把操作共享数据的代码块锁起来

格式如下:

synchronized(){
    操作共享数据的代码
}
  • 锁默认打开,当有一个线程进去了,锁自动关闭
  • 里面的代码全部执行完毕,线程出来,锁自动打开

其中的锁对象要是唯一的,可以是任意一个对象,但是在多个线程中也要是唯一的,可以在类中声明一个静态的对象作为锁。


public class MyCallable extends Thread {
    static private int tickets = 100;
    static private Object lock = new Object();

    @Override
    public void run() {
        synchronized (lock) {
            while (true) {
                if (tickets <= 0)
                    break;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                tickets--;
                System.out.println("卖出了一张票,还有" + tickets + "张票");
            }
        }
    }
}


上面的代码中有一点小问题,其中synchronized块不可以放在循环的外边,因为如果放在外边,在第一个线程抢夺到CPU的执行权后就会开始循环,就会将票全部卖出去,因此需要放到循环内部,卖出一张票就解开锁。

  public void run() {

        while (true) {
            synchronized (lock) {
                if (tickets <= 0)
                    break;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                tickets--;
                System.out.println("卖出了一张票,还有" + tickets + "张票");
            }
        }
    }

还需要注意,锁对象一定要唯一,如果不唯一,相当于没有写

这个锁一般来写本文件的字节码对象:

类名.class
 synchronized (MyCallable.class) {
                if (tickets <= 0)
                    break;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                tickets--;
                System.out.println("卖出了一张票,还有" + tickets + "张票");
            }
}

同步方法

如果要把一个方法中的所有代码块都锁起来,可以把关键字加到方法上

修饰符 synchronized 返回类型 方法名(参数){
    
}
  • 同步方法是锁住方法中的所有代码
  • 锁对象不能自己指定
    • 非静态时锁对象为this
    • 静态时锁对象为当前类的字节码文件对象

把锁住的代码块做成一个方法,然后加上修饰符即可

StringBuilder对象是多线程不安全的,如果要多线程构造字符串请使用StringBuffer

Lock锁

JDK5以后提供了一个新的锁对象Lock,方便我们更加清晰地表达如何加锁释放锁

void lock();获得锁

void unlock();释放锁

手动上锁,手动释放锁

Lock是一个接口,我们需要使用它的实现类ReentrantLock

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyCallable extends Thread {
    private int tickets = 100;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();
            if (saleTicket()) break;
        }
    }

    private boolean saleTicket() {
        try {
            if (tickets <= 0)
                return true;
            Thread.sleep(10);
            tickets--;
            System.out.println(Thread.currentThread().getName() + " 卖出了一张票,还有" + tickets + "张票");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return false;

    }
}

要注意锁不打开直接break出循环,造成其他两个线程在锁前等待的情况。所以通常要用try catch块将代码包围,然后使用finally解锁

死锁

是一种书写代码的错误

比如说下面的情况

等待唤醒机制

生产者消费者是典型的多线程协作模式

消费者机制:

  1. 判断桌子上是否有食物
  2. 如果没有就等待
  3. 如果有就开吃
  4. 吃完之后唤醒厨师继续做

生产者机制:

  1. 判断桌子上是否有食物
  2. 有则等待
  3. 无则制作
  4. 把食物放在桌子上
  5. 叫醒等待的消费者开吃
方法作用
void wait()当前线程等待,直到被其他线程唤醒
void notify()随机唤醒单个线程
void notifyAll()唤醒所有线程
//生产者
public class Productor extends Thread {

    public void run() {
        while (true) {
            synchronized (tempFlag.lock){

                if (tempFlag.count == 0)
                    break;
                else {
                    if (tempFlag.tempFlag == 1) {
                        try {
                            tempFlag.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    } else {
                        System.out.println("厨师做了一碗面条");
                        tempFlag.tempFlag = 1;

                        tempFlag.lock.notifyAll();
                    }
                }
            }
        }
    }
}

//消费者
public class Consumer extends Thread {

    public void run() {
        while (true) {
            synchronized (tempFlag.lock) {
                if (tempFlag.count == 0)
                    break;
                else {
                    if (tempFlag.tempFlag == 0) {
                        try {
                            tempFlag.lock.wait();
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    } else {
                        tempFlag.count--;
                        System.out.println("还可以再吃" + tempFlag.count + "碗");
                        tempFlag.tempFlag = 0;
                        tempFlag.lock.notifyAll();

                    }
                }
            }
        }
    }
}

//桌子
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class tempFlag {
    public static int tempFlag = 1;
    public static int count = 10;
    public static final Lock lock = new ReentrantLock();
}

//测试类

public class Main {

    public static void main(String[] args) throws InterruptedException {

        Productor productor = new Productor();
        Consumer consumer = new Consumer();
        productor.setName("生产者");
        consumer.setName("消费者");
        productor.start();
        consumer.start();

    }

}

以上是一种实现等待唤醒机制的方式,下面还有第二种方式,阻塞队列方式实现:

需要注意生产者和消费之需要使用同一个阻塞队列

阻塞的话是不需要锁的,底层自动有锁和释放(put和take)

线程池

  1. 创建一个池子,池子是空的
  2. 提交任务时,池子会创建新的线程对象,任务执行完毕后,线程归还给池子,下次再提交任务时,不需要创建新的线程,直接复用已有的线程即可
  3. 但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
方法作用
public static ExecutorService newCachedThreadPool()创建一个没有上限的线程池
public static ExecutorService newFixedThreadPool(int nThreads)创建有上限的线程池
submit(参数)将任务交给线程池
shutdown()销毁线程池
        ExecutorService pool = Executors.newFixedThreadPool(2);
        pool.submit(new A());
        pool.submit(new A());
        pool.submit(new A());
        pool.submit(new A());
        pool.submit(new A());
        pool.submit(new A());
        pool.shutdown();

自定义线程池

任务拒绝策略

策略说明
ThreadPoolExecutor.
AbortPolicy
默认策略,抛出异常
ThreadPoolExecutor.
DiscardPolicy
丢弃任务但是不抛弃异常
ThreadPoolExecutor.
DiscardOldestPolicy
丢弃队列中等待最久的任务,然后把当前任务加入队列中

自定义线程池可以使用ThreadPoolExecutor对象,在创建时定义指定的参数即可。

ThreadPoolExecutor();
//参数一:核心线程数量
//参数二:最大线程数
//参数三:空闲线程最大存活时间
//参数四:时间的单位
//参数五:任务队列
//参数六:创建线程工厂
//参数七:任务的拒绝策略,注意此为静态内部类
    ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3, 6, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
 

然后使用submit提交shutdown关机即可

最大并行数

可以使用下面的代码查看电脑的最大并行数

System.out.println(Runtime.getRuntime().availableProcessors());

线程池多大合适

如果为CPU密集型运算:最大并行数加一

数据运算较多,IO较少

加一是为了预防前面的线程出问题,可以当备用线程

IO密集型运算:
最大并行数 ∗ 期望 C P U 利用率 ∗ 总时间 C P U 计算时间 总时间 = C P U 计算时间 + 等待时间 最大并行数*期望CPU利用率*\frac{总时间}{CPU计算时间}\\ 总时间=CPU计算时间+等待时间 最大并行数期望CPU利用率CPU计算时间总时间总时间=CPU计算时间+等待时间
CPU计算时间等需要使用工具测试,比如说thread dump

读取数据库或者其他IO操作较多

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/462885.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据库基础篇 《17.触发器》

数据库基础篇 《17.触发器》 在实际开发中&#xff0c;我们经常会遇到这样的情况&#xff1a;有 2 个或者多个相互关联的表&#xff0c;如商品信息和库存信息分别存放在 2 个不同的数据表中&#xff0c;我们在添加一条新商品记录的时候&#xff0c;为了保证数据的完整性&#…

【刷题之路】LeetCode 203. 移除链表元素

【刷题之路】LeetCode 203. 移除链表元素 一、题目描述二、解题1、方法1——在原链表上动刀子1.1、思路分析1.2、代码实现 2、方法2——使用额外的链表2.1、思路分析2.2、代码实现 一、题目描述 原题连接&#xff1a; 203. 移除链表元素 题目描述&#xff1a; 给你一个链表的…

跨数据中心下的 Kafka 高可用架构分析

导语 本文介绍了 Kafka 跨数据中心的两种部署方式&#xff0c;简要分析两种方式下的不同架构以及优缺点&#xff0c;对这些架构可能碰到的问题也提供了一些解决思路&#xff1b;同时也说明了 Kafka 跨数据中心部署的社区解决方案和商业化解决方案。 背景 Kafka 作为世界上最…

laravel5.6.* + vue2 创建后台

本地已经安装好了composer 1.新建 Laravel5.6.*项目 composer create-project --prefer-dist laravel/laravel laravel5vue2demo 5.6.* 2. cd laravel5vue2demo 3. npm install /routes/web.php 路由文件中, 修改 Route::get(/, function () {return view(index); });新建…

第三方jar包引入项目,发布到本地和远程仓库

在开发过程中&#xff0c;往往会遇到对接其他公司的系统。然后对接公司会提供API对接方式&#xff0c;就是给一个jar包。我们只需要把jar包引入到项目中直接用即可。本地引用jar的话可以有两种方式。第一种就是本地包引用&#xff0c;如下将包放下工程下&#xff0c;然后maven指…

【五一劳动节来了】

今年“五一”&#xff0c;4月29日至5月3日放假调休&#xff0c;共5天。 如果你在5月4日到5月6日请假3天&#xff0c;加上5月7日周日&#xff0c;就可以形成9天的假期。 一&#xff0c;五一劳动节的由来⭐ 国际劳动节又称“五一国际劳动节”“国际示威游行日”&#xff08;英语…

抢先看,甘特图工具DHTMLX gantt 灯箱编辑器通过套件 UI 小部件进行了扩展

DHTMLX Gantt是用于跨浏览器和跨平台应用程序的功能齐全的Gantt图表。可满足项目管理应用程序的大部分开发需求&#xff0c;具备完善的甘特图图表库&#xff0c;功能强大&#xff0c;价格便宜&#xff0c;提供丰富而灵活的JavaScript API接口&#xff0c;与各种服务器端技术&am…

【STL十四】函数对象(function object)_仿函数(functor)——lambda表达式

【STL十四】函数对象&#xff08;function object&#xff09;_仿函数&#xff08;functor&#xff09;——lambda表达式 一、函数对象&#xff08;function object&#xff09;二、函数对象优点三、分类四、头文件五、用户定义函数对象demo六、std::内建函数对象1、 算术运算函…

YARN 远程代码执行(RCE)安全漏洞问题分析与解决方案

YARN 远程代码执行&#xff08;RCE&#xff09;安全漏洞问题分析与解决方案 1 YARN RCE 漏洞问题问题现象 某客户使用Tenable.sc扫描安全漏洞后反馈&#xff0c;YARN 存在Remote code execution (RCE) 安全漏洞问题&#xff0c;攻击者可在未经过身份验证的情况下通过该漏洞在…

【21】核心易中期刊推荐——人工智能 | 遥感图像识别

🚀🚀🚀NEW!!!核心易中期刊推荐栏目来啦 ~ 📚🍀 核心期刊在国内的应用范围非常广,核心期刊发表论文是国内很多作者晋升的硬性要求,并且在国内属于顶尖论文发表,具有很高的学术价值。在中文核心目录体系中,权威代表有CSSCI、CSCD和北大核心。其中,中文期刊的数…

psql在建表时,分为常规、外部、分区,三者有什么区别?如何从建表语句中区分?

在 PostgreSQL 中&#xff0c;常规表、外部表和分区表都可以通过 CREATE TABLE 语句进行创建&#xff0c;它们的创建语法略有不同&#xff0c;通过创建语句可以很明显地区分它们的类型。 以下是常规表、外部表和分区表的创建语法及示例&#xff1a; 1. 常规表 常规表是最常见…

Spring核心与设计思想、创建与使用

文章目录 一、Spring是什么二、为什么要学习框架三、IoC和DI&#xff08;一&#xff09;IoC1. 认识IoC2. Spring的核心功能 &#xff08;二&#xff09;DI 四、Spring项目的创建&#xff08;一&#xff09;使用 Maven 方式创建一个 Spring 项目 五、Spring项目的使用&#xff0…

少年与阿童木:一场软件竞技赛背后的智能未来

1961年&#xff0c;手冢治虫创办了虫制作株式会社&#xff0c;带领团队开始尝试将此前的漫画作品进行动画化。1963年的元旦&#xff0c;他们的首部作品一经播出就引发轰动&#xff0c;这部动画的名字叫做——《铁臂阿童木》。 一晃数十年&#xff0c;阿童木已经成为了几代人对A…

2023年6月DAMA-CDGA/CDGP数据治理工程师认证报名及费用

目前6月DAMA-CDGA/CDGP数据治理认证考试开放报名地区有&#xff1a;北京、上海、广州、深圳、长沙、呼和浩特。目前南京、济南、西安、杭州等地区还在接近开考人数中&#xff0c;打算6月考试的朋友们可以抓紧时间报名啦&#xff01;&#xff01;&#xff01; 5月初&#xff0c;…

大数据 | 实验二:文档倒排索引算法实现

文章目录 &#x1f4da;实验目的&#x1f4da;实验平台&#x1f4da;实验内容&#x1f407;在本地编写程序和调试&#x1f955;代码框架思路&#x1f955;代码实现 &#x1f407;在集群上提交作业并执行&#x1f955;在集群上提交作业并执行&#xff0c;同本地执行相比即需修改…

蓝牙耳机怎么挑选?鹏鹏数码盘点2023口碑蓝牙耳机排行榜

大家好&#xff0c;欢迎来到鹏鹏数码频道。 上次测评发布后网友们评论不知道蓝牙耳机怎么挑选&#xff0c;为此我购入了市面上主流品牌的蓝牙耳机共计三十款&#xff0c; 经过两周的地狱式测评&#xff0c;总结了口碑蓝牙耳机排行榜&#xff0c;看看表现最好的是哪几款蓝牙耳机…

Linux操作系统命令大全

Linux是一种操作系统 Operating System 简称 OS &#xff0c;是软件的一部分&#xff0c;它是硬件基础上的第一层软件&#xff0c;是硬件和其它软件沟通的桥梁。 操作系统会控制其他程序运行&#xff0c;管理系统资源&#xff0c;提供最基本的计算功能&#xff0c;如管理及配置…

SSM整合(一) | SSM创建项目配置整合 - 添加功能模块

文章目录 SSM整合SSM配置整合SSM功能模块 SSM整合 SSM配置整合 SSM整合流程: 创建工程SSM整合 Spring SpringConfig MyBatis MybatisConfigJdbcConfigjdbc.properties SpringMVC ServletConfigSpringMvcConfig 创建工程 基于Maven创建项目, 选择webapp模版并补全缺失的目录 …

最优化方法Python计算:一元函数导数的数值计算

定义1 给定连续函数 f ( x ) f(x) f(x)&#xff0c; x ∈ Ω ⊆ R x\in\Omega\subseteq\text{ℝ} x∈Ω⊆R。设 x , x 1 ∈ Ω x,x_1\in\Omega x,x1​∈Ω&#xff0c; Δ x x − x 1 \Delta xx-x_1 Δxx−x1​ 称为变量 x x x的差分。此时&#xff0c; x x 1 Δ x xx_1\De…

黑盒测试过程中【测试方法】详解5-输入域,输出域,猜错法

在黑盒测试过程中&#xff0c;有9种常用的方法&#xff1a;1.等价类划分 2.边界值分析 3.判定表法 4.正交实验法 5.流程图分析 6.因果图法 7.输入域覆盖法 8.输出域覆盖法 9.猜错法 黑盒测试过程中【测试方法】讲解1-等价类&#xff0c;边界值&#xff0c;判定表_朝一…