IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> Java多线程 -> 正文阅读

[Java知识库]Java多线程

程序,进程,线程的基本概念+并行与并发:

程序:

是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。

进程:

是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。-------生命周期

线程:

进程可进一步细化为线程,是一个程序内部的一条执行路径 即:线程《线程(一个程序可以有多个线程)

程序:

静态的代码 进程:动态执行的程序

线程:

进程中要同时干几件事时,每一件事的执行路径成为线程。

并行:

多个CPU同时执行多个任务,比如:多个人同时做不同的事

并发:

一个CPU(采用时间片)同时执行多个任务,比如秒杀平台,多个人做同件事

线程的调度

调度策略: 时间片:线程的调度采用时间片轮转的方式 抢占式:高优先级的线程抢占CPU Java的调度方法: 1.对于同优先级的线程组成先进先出队列(先到先服务),使用时间片策略 2.对高优先级,使用优先调度的抢占式策略

多线程的创建方式有两种

1.实现runbale接口重写run方法

2.继承Thread类

start与run方法的区别:

start方法的作用:

1.启动当前线程

2.调用当前线程的重写的run方法(在主线程中生成子线程,有两条线程)

调用start方法以后,一条路径代表一个线程,同时执行两线程时,因为时间片的轮换,所以执行过程随机分配,且一个线程对象只能调用一次start方法。 run方法的作用:在主线程中调用以后,直接在主线程一条线程中执行了该线程中run的方法。(调用线程中的run方法,只调用run方法,并不新开线程)

总结:我们不能通过run方法来新开一个线程,只能调用线程中重写的run方法(可以在线程中不断的调用run方法,但是不能开启子线程,即不能同时干几件事),start是开启线程,再调用方法(即默认开启一次线程,调用一次run方法,可以同时执行几件事)

Runnable接口应该由其实例旨在由线程执行的任何类实现。 该类必须定义一个名为run的无参数方法。 此接口旨在为希望在活动时执行代码的对象提供通用协议。 例如, Runnable是由类Thread实现的。 处于活动状态仅意味着线程已启动且尚未停止。 此外, Runnable提供了使类处于活动状态而不是子类化Thread 。 通过实例化Thread实例并将自身作为目标传入,实现Runnable的类可以在不继承Thread的情况下运行。 在大多数情况下,如果您只打算覆盖run()方法而不打算覆盖其他Thread方法,则应该使用Runnable接口。 这很重要,因为除非程序员打算修改或增强类的基本行为,否则类不应被子类化

多线程例子

1.实现Runnable接口重写run方法

package com.qf;

/**
 * @author Xuyijun
 * @classname runableTest.java
 * @create 2021-10-26, 星期二, 16:04:20
 */
public class runableTest {

    public static void main(String[] args) {
        Window3 window3 = new Window3();
        Thread t1 = new Thread(window3);
        Thread t2 = new Thread(window3);
        Thread t3 = new Thread(window3);
        t1.setName("售票员1");
        t2.setName("售票员2");
        t3.setName("售票员3");
        t1.start();
        t2.start();
        t3.start();
    }




}
class Window3 implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while (true){
            if (ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
                ticket--;
            }else {
                break;
            }
        }
    }
}

2.继承Thread类

package com.qf;

import java.awt.*;

/**
 * @author Xuyijun
 * @classname threadTest.java
 * @create 2021-10-26, 星期二, 15:52:10
 */
public class threadTest extends Thread {
    public static void main(String[] args) {
        Window window1 = new Window();
        Window window2 = new Window();
        Window window3 = new Window();
        window1.setName("售票口1");
        window2.setName("售票口2");
        window3.setName("售票口3");
        window1.start();
        window2.start();
        window3.start();

    }

static class Window extends Thread{
        private  int ticket=100;

    @Override
    public void run() {
        while (true){
            if (ticket>0) {
                try {
                    if (ticket>0){
                        sleep(100);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(getName()+"当前售出第"+ticket+"张票");
                ticket--;
            } else {
                break;
            }
        }
    }
}
}

比较创建线程的两种方式:
开发中,优先选择实现Runable接口的方式
原因
1:实现的方式没有类的单继承性的局限性
2:实现的方式更适合用来处理多个线程有共享数据的情况
联系:Thread也是实现自Runable,两种方式都需要重写run()方法,将线程要执行的逻辑声明在run中

3.新增的两种创建多线程方式

1.实现callable接口方式: 与使用runnable方式相比,callable功能更强大些: runnable重写的run方法不如callaalbe的call方法强大,call方法可以有返回值 方法可以抛出异常 支持泛型的返回值 需要借助FutureTask类,比如获取返回结果
package com.qf;

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

/**
 * @author Xuyijun
 * @classname CallableTest.java
 * @create 2021-10-28, 星期四, 13:35:23
 */
public class CallableTest  {
    public static void main(String[] args) {
        Window window = new Window();
//        通过futureTask对象的get方法来接收futureTask的值
        FutureTask futureTask=new FutureTask(window);
        Thread t1 = new Thread(futureTask);
        t1.setName("售票员1");
        t1.start();
        try {
            Object o = futureTask.get();
            System.out.println(o);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

    }





    static class Window implements Callable{
        private int ticket=100;

        @Override
        public Object call() throws Exception {
            while (true){
                if (ticket>0){
                    Thread.sleep(100);
                    System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
                    ticket--;
                }else {
                    break;
                }
            }
            return ticket;
        }
    }
}

4.使用线程池的方式:

背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

思路:提前创建好多个线程,放入线程池之,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。(数据库连接池)

好处:提高响应速度(减少了创建新线程的时间) 降低资源消耗(重复利用线程池中线程,不需要每次都创建) 便于线程管理
package com.qf;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

import java.util.concurrent.*;


/**
 * @author Xuyijun
 * @classname ThreadPoolTest.java
 * @create 2021-10-28, 星期四, 14:43:07
 */
public class ThreadPoolTest {
/**
 * 创建线程的方式四:使用线程池(批量使用线程)
 *1.需要创建实现runnable或者callable接口方式的对象
 * 2.创建executorservice线程池
 * 3.将创建好的实现了runnable接口类的对象放入executorService对象的execute方法中执行。
 * 4.关闭线程池
 */

public static void main(String[] args) {
//    创建线程工厂并设置线程名字,方便出错时回溯
//      ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
//        .setNameFormat("demo-pool-%d").build();
    ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("售票员-%d").build();

   /* corePoolSize – 要保留在池中的线??程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut
    maximumPoolSize – 池中允许的最大线程数
    keepAliveTime – 当线程数大于核心数时,这是多余空闲线程在终止前等待新任务的最长时间。
    unit – keepAliveTime参数的时间单位
    workQueue – 用于在执行任务之前保存任务的队列。 这个队列将只保存execute方法提交的Runnable任务。
    threadFactory – 执行程序创建新线程时使用的工厂
    handler – 执行被阻塞时使用的处理程序,因为达到了线程边界和队列容量*/
    ExecutorService executorService = new ThreadPoolExecutor(5,200,0L, TimeUnit.MILLISECONDS,new LinkedBlockingDeque<Runnable>(1024),namedThreadFactory,new ThreadPoolExecutor.AbortPolicy());
    Window1 window1 = new Window1();
    Window2 window2 = new Window2();
//    执行线程
    executorService.execute(window1);
    executorService.execute(window2);
//    关闭线程池
    executorService.shutdown();
}







 }

 class Window1 implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while (true){
            if (ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
                ticket--;
            }else {
                break;
            }

        }
    }
}

 class Window2 implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while (true){
            if (ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
                ticket--;
            }else {
                break;
            }

        }
    }
    
}


这里贴一张阿里Java开发规范
需要的java依赖

<dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>16.0.1</version>
</dependency>

在这里插入图片描述

线程通信方法:

wait()/ notify()/ notifayAll():此三个方法定义在Object类中的,因为这三个方法需要用到锁,而锁是任意对象都能充当的,所以这三个方法定义在Object类中。 由于wait,notify,以及notifyAll都涉及到与锁相关的操作

wait(在进入锁住的区域以后阻塞等待,释放锁让别的线程先进来操作)---- Obj.wait 进入Obj这个锁住的区域的线程把锁交出来原地等待通知

notify(由于有很多锁住的区域,所以需要将区域用锁来标识,也涉及到锁) ----- Obj.notify 新线程进入Obj这个区域进行操作并唤醒wait的线程 有点类似于我要上厕所,我先进了厕所关了门,但是发现厕所有牌子写着不能用,于是我把厕所锁给了别人,别人进来上厕所还是修厕所不得而知,直到有人通知我厕所好了我再接着用。 所以wait,notify需要使用在有锁的地方,也就是需要用synchronize关键字来标识的区域,即使用在同步代码块或者同步方法中,且为了保证wait和notify的区域是同一个锁住的区域,需要用锁来标识,也就是锁要相同的对象来充当

一个线程的生命周期


线程是一个动态执行的过程,它也有一个从产生到死亡的过程。

下图显示了一个线程完整的生命周期。
在这里插入图片描述

  • 新建状态:
    使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。

  • 就绪状态:
    当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。

  • 运行状态:
    如果就绪状态的线程获取 CPU 资源,就可以执行
    run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。

  • 阻塞状态:
    如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

  • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。

  • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。

  • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会

进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

  • 死亡状态:
    一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

线程的同步:在同步代码块中,只能存在一个线程。

线程的安全问题:

什么是线程安全问题呢?

线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。

上述例子中:创建三个窗口卖票,总票数为100张票

1.卖票过程中,出现了重票(票被反复的卖出,ticket未被减少时就打印出了)错票。

2.问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也来操作车票。(将此过程的代码看作一个区域,当有线程进去时,装锁,不让别的线程进去) 生动理解的例子:有一个厕所,有人进去了,但是没有上锁,于是别人不知道你进去了,别人也进去了对厕所也使用造成错误。

3.如何解决:当一个线程在操作ticket时,其他线程不能参与进来,直到此线程的生命周期结束

4.在java中,我们通过同步机制,来解决线程的安全问题。 方式一:同步代码块 使用同步监视器(锁) Synchronized(同步监视器){ //需要被同步的代码 } 说明: 1. 操作共享数据的代码(所有线程共享的数据的操作的代码)(视作卫生间区域(所有人共享的厕所)),即为需要共享的代码(同步代码块,在同步代码块中,相当于是一个单线程,效率低) 2. 共享数据:多个线程共同操作的数据,比如公共厕所就类比共享数据 3. 同步监视器(俗称:锁):任何一个的对象都可以充当锁。(但是为了可读性一般设置英文成lock)当锁住以后只能有一个线程能进去(要求:多个线程必须要共用同一把锁,比如火车上的厕所,同一个标志表示有人) Runable天生共享锁,而Thread中需要用static对象或者this关键字或者当前类(window。class)来充当唯一锁

方式二:同步方法

使用同步方法,对方法进行synchronized关键字修饰 将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。 对于runnable接口实现多线程,只需要将同步方法用synchronized修饰 而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一) 总结:

1.同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。

2.非静态的同步方法,同步监视器是this 静态的同步方法,同步监视器是当前类本身。继承自Thread。class

package com.qf;

/**
 * @author Xuyijun
 * @classname SynchronizedTest.java
 * @create 2021-10-29, 星期五, 10:16:30
 */
public class SynchronizedTest extends Thread {
    public static void main(String[] args) {
        Window window1 = new Window();
        Thread t1 = new Thread(window1);
        Thread t2 = new Thread(window1);
        Thread t3 = new Thread(window1);
        t1.setName("售票员1");
        t2.setName("售票员2");
        t3.setName("售票员3");
        t1.start();
        t2.start();
        t3.start();

    }

}
class Window implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while (true){
            synchronized (this) {
                if (ticket>0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
                    ticket--;
                }else {
                    break;
                }

            }
        }
    }
}

方式三:JDK5.0新增的lock锁方法

package com.qf;

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

/**
 * @author Xuyijun
 * @classname ReentrantLockTest.java
 * @create 2021-10-29, 星期五, 10:41:03
 */
public class ReentrantLockTest {
    public static void main(String[] args) {
        Window4 window1 = new Window4();
        Thread t1 = new Thread(window1);
        Thread t2 = new Thread(window1);
        Thread t3 = new Thread(window1);
        t1.setName("售票员1");
        t2.setName("售票员2");
        t3.setName("售票员3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Window4 implements Runnable{
    private int ticket=100;
//    使用给定的公平策略创建一个新的ReentrantLock 。
//
//参数:
//公平 - 如果此锁应使用公平排序策略,则为true
    private final ReentrantLock lock = new ReentrantLock(true);
    @Override
    public void run() {
        while (true){
            lock.lock();
            try {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "当前售出第" + ticket + "张票");
                    ticket--;
                } else {
                    break;
                }

            } finally {
                lock.unlock();
            }

        }
    }
}

总结:Synchronized与lock的异同?

相同:二者都可以解决线程安全问题

不同:synchronized机制在执行完相应的代码逻辑以后,自动的释放同步监视器 lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())(同时以为着lock的方式更为灵活) 优先使用顺序: LOCK-》同步代码块-》同步方法

死锁问题

甲现在要去吃饭他先去拿碗,然后等筷子,乙先去拿筷子,然后等碗,然后两个人就这样一直等着
package com.qf;

/**
 * @author Xuyijun
 * @classname DeadLockTest.java
 * @create 2021-10-29, 星期五, 11:22:54
 */
public class DeadLockTest {
    public static void main(String[] args) {
        final String lock1="碗";
        final String lock2="筷子";

        new Thread("线程1"){
            @Override
            public void run() {
                synchronized (lock1) {
                    System.out.println(Thread.currentThread().getName()+"拿到了"+lock1+"还差"+lock2);
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"睡眠5ms结束");
                    synchronized (lock2){
                        System.out.println(Thread.currentThread().getName()+"获得"+lock2);
                    }
                }
            }
        }.start();


        new Thread("线程2"){
            @Override
            public void run() {
                synchronized (lock2) {
                    System.out.println(Thread.currentThread().getName()+"拿到了"+lock2+"还差"+lock1);
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"睡眠5ms结束");
                    synchronized (lock1){
                        System.out.println(Thread.currentThread().getName()+"获得"+lock1);
                    }
                }
            }
        }.start();

    }

}

运行结果打印
在这里插入图片描述

死锁产生的4个必要条件

1、互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
2、占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3、不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4、循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-11-02 23:12:37  更:2021-11-02 23:12:57 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年10日历 -2024/10/28 6:30:45-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码