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 小米 华为 单反 装机 图拉丁
 
   -> 开发工具 -> 多线程基础 -> 正文阅读

[开发工具]多线程基础

多线程

1.1并发与并行

并发:指两个或多个时间在同一个时间段内发生
并行:指两个或多个事件在同一时刻发生(同时发生)

1.2进程与线程

进程:进入到内存中的程序;指一个内存中运行的应用程序;每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建,运行到消亡的过程.

线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程.一个进程中是可以有多个线程,这个应用程序也可以称之为多线程程序.

简而言之一个程序运行后至少有一个进程,一个进程中可以包含多个线程.

线程调度:
  • 分时调度:

    所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间.

  • 抢占式调度:

    优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度.

    • 设置线程的优先级:进入任务管理器-_–>详细信息,右键进程,设置线程优先级.

    • 抢占式调度详解:大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序,比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,doc窗口等软件。此时,这些程序是在同时运行,“感觉这些软件好像在同一时刻运行着”。

      实际上CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换,对于CPU的一个核而言,某个时刻,只能执行一个进程,而CPU在多个线程切换速度相对于我们的感觉要快,看上去就是在同一时刻进行。

      其实多线程程序并不能提高运行速度,但能够提高程序运行效率,让CPU的使用率更高。

主线程:执行主(main)方法的线程.

单线程程序:java程序中只有一个线程.

执行从main方法开始,从上到下依次执行.

JVM执行main方法,main方法会进入到栈内存

JVM会找操作系统开辟一条main方法通向CPU的执行路径

CPU就可以通过这个路径来执行main方法

而这个路径有一个名字,叫main(主)线程

public class Person {
    private String name;

    public void run(){
        for(int i = 0; i < 20 ; i++){
            System.out.println(name + i);
        }
    }

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
public class MainThread {
    public static void main(String[] args) {
        Person p1 = new Person("小强");
        p1.run();

        Person p2 = new Person("旺财");
        p2.run();
    }
}

小强9
小强10
小强11
小强12
小强13
小强14
小强15
小强16
小强17
小强18
小强19
旺财0
旺财1
旺财2
旺财3
旺财4
旺财5
旺财6
旺财7
旺财8
旺财9
旺财10
旺财11
旺财12
旺财13
旺财14
旺财15
旺财16
旺财17
旺财18
旺财19

Process finished with exit code 0
public class MainThread {
    public static void main(String[] args) {
        Person p1 = new Person("小强");
        p1.run();
        
		System.out.println(0/0);
        
        Person p2 = new Person("旺财");
        p2.run();
    }
}
D:\Java\jdk1.8.0_171\bin\java.exe "(略)spark.day01.MainThread
小强0
小强1
小强2
小强3
小强4
小强5
小强6
小强7
小强8
小强9
小强10
小强11
小强12
小强13
小强14
小强15
小强16
小强17
小强18
小强19
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at spark.day01.MainThread.main(MainThread.java:8)

Process finished with exit code 1

2.1多线程原理

public class demo01Thread {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("main:" + i);
        }
    }
}
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("run:" + i);
        }
    }
}

2.2Thread类

构造方法:
  • public Thread():分配一个新的线程对象。
  • public Thread(String name):分配一个指定名字的新的线程对象。
  • public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。

常用方法:

  • public String getName():获取当前线程名称。
  • public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法。
  • public void run():此线程要执行的任务在此处定义代码。
  • public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
  • public static Thread currentThread():返回对当前正在执行的线程对象的引用。
获取线程名称
public class Demo01GetThreadName {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}
public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(getName());
    }
}
Thread-0

Process finished with exit code 0

线程的名称:
主线程:main
新线程: Thread-0, Thread-1, Thread-2

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
设置线程名称

1.使用Thread类中的方法setName(名字)
void setName(String name)改变线程名称,使之与参数name相同。

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
public class Demo01SetThreadName {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.setName("小强");
        myThread.start();
    }
}

2.创建- -个带参数的构造方法,参数传递线程的名称;调用父类的带参构造方法,把线程名称传递给父类,让父类( Thread)给子线程起一-个名字
Thread(String name) 分配新的Thread 对象。

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

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

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
public class Demo01SetThreadName {
    public static void main(String[] args) {
        new MyThread("旺财").start();
    }
}
sleep方法
public class demo01sleep {
    public static void main(String[] args) {
        for (int i = 1; i <= 60; i++) {
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2.3

创建多线程程序的第二种方式:实现Runnable接口

java. Lang. Runnable
Runnable接口应该由那些打算通过某- -线程执行其实例的类来实现。类必须定义一一个称为 run 的无参数方法。
java. lang. Thread类的构造方法
Thread(Runnable target)分配新的Thread 对象。
Thread(Runnable target, String name) 分配新的Thread 对象。

实现步骤:

1.创建一个Runnable接口的实现类
2.在实现类中重写Runnable接口的run方法,设置线程任务
3.创建一个Runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程执行run方法

public class Demo01Runnable {
    public static void main(String[] args) {
        RunnableImpl runnable = new RunnableImpl();
        Thread thread = new Thread(runnable);
        thread.start();
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}
public class RunnableImpl implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}
实现Runnaate接口创建多线程程序的好处:
  1. 避免了单继承的局限性

一个类只能继承一个类- 一个人只能有一个亲爹),类继承了Thread类就不能继承其他的类
实现了Runnable接口,还可以继承其他的类,实现其他的接口

  1. 增强了程序的扩展性。降低了程序的耦合性(解耦)

实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法:用来开启新线程

2.4匿名内部类方式实现线程的创建

匿名:没有名字
内部类:写在其他类内部的类
匿名内部类作用:简化代码
把子类继承父类,重写父类的方法,创建子类对象合-步完成
把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
格式:

? new 父类/接口(){

? 重写父类/接口的方法

? };

public class Demo01InnerClassThread {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName()+"黑马");
                }
            }
        }.start();
        Runnable runnable = new Runnable(){
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println(Thread.currentThread().getName()+"程序员");
                } 
            }
        };
        new Thread(runnable).start();
    }
}

3.1 线程安全问题的概述

public class Demo01Ticket {
    public static void main(String[] args) {
        RunnableImpl runnable = new RunnableImpl();
        Thread thread0 = new Thread(runnable);
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread0.start();
        thread1.start();
        thread2.start();
    }
}
public class RunnableImpl implements Runnable {
    private int ticket  = 100;
    @Override
    public void run() {
        while (true){
            if (ticket > 0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
                ticket--;
            }
        }
    }
}

3.2线程同步

当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制
(synchronized)来解决。
根据案例简述∶

窗口1线程进入操作的时候,窗口2和窗口3钱程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码
去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU
资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。

为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。
那么怎么去使用呢?有三种方式完成同步操作:

  1. 同步代码块。
  2. 同步方法。
  3. 锁机制。

3.3同步代码块

同步代码块:synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:

synchronized(同步锁){
	需要同步操作的代码
}

同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

注意:

  1. 通过代码块中的锁对象, 可以使用任意的对象
  2. 但是必须保证多个线程使用的锁对象是同一个
  3. 锁对象作用:
    把同步代码块锁住,只让一个线程在同步代码块中执行

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着
(BLOCKED)。
使用同步代码块解决代码︰

public class Demo01Ticket {
    public static void main(String[] args) {
        RunnableImpl runnable = new RunnableImpl();
        Thread thread0 = new Thread(runnable);
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread0.start();
        thread1.start();
        thread2.start();
    }
}
public class RunnableImpl implements Runnable {
    private int ticket  = 100;

    Object obj = new Object();

    @Override
    public void run() {

        while (true){
            synchronized (obj){
                if (ticket > 0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
                    ticket--;
                }
            }
        }
    }
}

3.4同步技术原理

使用了一个锁对象这个锁对象叫同步锁,也叫对象锁,也叫对象监视器

3个线程一起抢夺cpu的执行权,谁抢到了谁执行run方法进行卖票

t0抢到了cpu的执行权,执行run方法,遇到synchronized代码块

这时t0会检查synchronized代码块是否有锁对象

发现有,就会获取到锁对象,进入到同步中执行

t1抢到了cpu的执行权,执行run方法,遇到synchronized代码块

这时t1会检查synchronized代码块是否有锁对象

发现没有,t1就会进入到阻塞状态,会一直等待t0线程归还锁对象

一直到t0线程执行完同步中的代码块,会把锁对象归还给同步代码块

t1才能获取到锁对象进入到同步中执行

总结:同步中的线程,没有执行完毕不会释放锁,同步外的线程,没有锁进不去同步

3.5同步方法

解决线程安全问题的二种方案:使用同步方法
使用步骤:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2.在方法上添加synchronized修饰符
格式:定义方法的格式
修饰符synchronized 返回值类型(参数列表){

? 可能会出现线程安全问题的代码(访问了共享数据的代码)

}

public class RunnableImpl implements Runnable {
    private int ticket  = 100;

    Object obj = new Object();

    @Override
    public void run() {
        while (true){
            synchronized (obj){
                payTicket();
            }
        }
    }

    public synchronized void payTicket() {
        if (ticket > 0) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
            ticket--;
        }
    }
}
public class Demo01Ticket {
    public static void main(String[] args) {
        RunnableImpl runnable = new RunnableImpl();
        Thread thread0 = new Thread(runnable);
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread0.start();
        thread1.start();
        thread2.start();
    }
}

定义一个同步方法,

同步方法也会把方法内部的代码锁住,

只让一个线程执行,

同步方法的锁对象是谁?

就是Runnable实现类对象 new RunnableImpl();

也是就是this

3.6静态同步方法

锁对象是谁?

不能是this

this是创建对象之后产生的,静态方法优于对象

静态方法的锁对象是本类的class属性–>class文件对象(反射)

public class RunnableImpl implements Runnable {
    private static int ticket  = 100;

    Object obj = new Object();

    @Override
    public void run() {
        while (true){
            synchronized (obj){
                payTicketStatic();
            }
        }
    }

    public static void payTicketStatic() {
        synchronized (RunnableImpl.class){
            if (ticket > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                ticket--;
            }
        }
    }
}
public static synchronized void payTicketStatic() {
            if (ticket > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                ticket--;
            }
}

3.7Lock锁

解决线程安全问题的三种方案:使用Lock锁
java. util. concurrent. locks. Lock接口
Lock.实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作。
Lock接口中的方法:
void lock( )获取锁。
void unlock()释放锁。

java. util. concurrent. locks . Reentrantlock implements Lock接口
使用步骤:

  1. 在成员位置创建一个Reentrantlock对象
  2. 在可能会出现安全问题的代码前调用ock接口中的方法lock获取锁
  3. 在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
public class Demo01Ticket {
    public static void main(String[] args) {
        RunnableImpl runnable = new RunnableImpl();
        Thread thread0 = new Thread(runnable);
        Thread thread1 = new Thread(runnable);
        Thread thread2 = new Thread(runnable);
        thread0.start();
        thread1.start();
        thread2.start();
    }
}
package heima.demo11;

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

public class RunnableImpl implements Runnable {
    private static int ticket  = 100;

    Object obj = new Object();
    Lock l = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            l.lock();
            if (ticket > 0) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                ticket--;
            }
            l.unlock();
        }
    }
}

优化代码:

public class RunnableImpl implements Runnable {
    private static int ticket  = 100;

    Object obj = new Object();
    Lock l = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            l.lock();
            if (ticket > 0) {
                try {
                    Thread.sleep(10);
                    System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    l.unlock();
                }
            }
        }
    }
}

4.1线程状态概述

下图源自《Java并发编程艺术》图4-1

线程状态图

线程有六种状态:

  • NEW:初始化态(创建线程时)
  • RUNNABLE:运行态(start后运行时)
    • Ready:准备态(刚start,没有获取CPU时)
    • Running:执行态(获取到CPU)
  • BLOCKED:阻塞态(没有拿到锁)
  • WAITING:等待态(拿到锁由于没有获取到需要的资源等待,释放锁)
  • TIMED_WAITING:超时等待(拿到锁后,在规定时间没有获取到资源超时等待)
  • TERMINATED:结束态(线程任务结束)

NEW
至今尚未启动的线程处于这种状态。

RUNNABLE
正在 Java虚拟机中执行的线程处于这种状态。

BLOCKED
受阻塞并等待某个监视器锁的线程处于这种状态。

WAITING
无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。

TIMED_WAITING
等待另—个线程来执行取决于指定等待时间的操作的线程处于这种状态。

TERMINATED
已退出的线程处于这种状态。

4.2等待唤醒案例分析

等待唤醒案例:线程之间的通信

创建一个顾客线程 (消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到NAITING状态(无限等待)
创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
注意:
顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一-个在执行
同步使用的锁对象必须保证唯一
只有锁对象才能调用wait和notify方法
obejct类中的方法
void wait()
在其他线程调用此对象的notify()方法或notifyAll() 方法前,导致当前线程等待。
void notify()
唤醒在此对象监视器上等待的单个线程。
会继续执行wait方法之后的代码

public class Demo01WaitAndNotify {
    public static void main(String[] args) {
        Object obj = new Object();
        new Thread(){
            @Override
            public void run() {
                while (true){
                    synchronized (obj){
                        System.out.println("告知老板演的包子种类和数量");
                        try {
                            obj.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("包子已经做好了开吃");
                    }
                }
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                while (true){
                    try {
                        Thread.sleep(5000);

                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (obj){
                        System.out.println("老板做好包子告知顾客,可吃包子了");
                        obj.notify();
                    }
                }
            }
        }.start();
    }
}

4.3object类中的wait带参方法和notifyAll方法

进入到TimeWaiting(计时等待)有两种方式
1.使用sleep(long m)方法, 在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2.使用wait(iong m)方法, wait方法如果在毫秒值结束之后,还没有被notify唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态
唤醒的方法:
void notify() 唤醒在此对象监视器上等待的单个线程。
void notifyALl()
唤醒在此对象监视器上等待的所有线程。

5.1线程间通信

概念:多个线程在处理同-一个资源,但是处理的动作(线程的任务)却不相同。
比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个
是生产,一个是消费,那么线稈A与线程B之间就存在线程通信问题。

为什么要处理线程间通信:

多个线程并发执行时,在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们
希望他们有规律的执行,那么多线程之间需要一些协调通信 ,以此来帮我们达到多线程共同操作一份数据。

如何保证线程间通信有效利用资源:

多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一一个变量的使用或操作。就是多个线程在操作同一份数据时,避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即一等待唤醒机制。

5.2等待唤醒机制

什么是等待唤醒机制

这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争( race) , 比如去争夺锁,但这并不
是故事的全部,线程间也会有协作机制。就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多
时候你们更多是一起合作以完成某些任务。
就是在一个线程进行了规定操作后,就进入等待状态( wajt()),等待其他线程执行完他们的指定代码过后再将
其唤醒( notify() ) ;在有多个线程进行等待时,如果需要,可以使用notifyl()来唤醒所有的等待线程。
wait/notify就是线程间的一-种协作机制。

等待唤醒中的方法

等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:

  1. wait :线程不再活动,不再参与调度,进入waitset中,因此不会浪费CPU资源,也不会去竞争锁了,这时
    的线程状态即是WAITING。它还要等着别的线程执行一个特别的动作,也即是通知( notify)”在这个对象上
    等待的线程从wait set中释放出来,重新进入到调度队列( ready queue )中
  2. notify :则选取所通知对象的wait set中的一一个线程释放;例如,餐馆有空位置后,等候就餐最久的顾客最先
    入座。
  3. notifyAll :则释放所通知对象的wait set.上的全部线程。

注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而
此刻它已经不持有锁,所以她需要再次尝试去获取锁(很可能面临其它线程的竞争) , 成功后才能在当初调
用wait方法之后的地方恢复执行。
总结如下:
● 如果能获取锁,线程就从WAITING状态变成RUNNABLE状态;
● 否则,从waitset出来,又进入entry set ,线程就从WAITING状态又变成BLOCKED状态
调用wait和notify方法需要注意的细节

  1. wait方法与notify方法必须要由同一一个 锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一一个锁对
    象调用的wait方法后的线程。
  2. wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继
    承了Object类的。
  3. wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方
    法。

5.3等待唤醒机制需求分析

等待唤醒机制其实就是经典的“生产者与消费者的问题。
就拿生产包子消费包子来说等待唤醒机制如何有效利用资源:
包子铺线程生产包子,吃货线程消费包子。当包子没有时(包子状态为false ),吃货线程等待,包子铺线程生产包子(即包子状态为true ),并通知吃货线程(解除吃货的等待状态),因为已经有包子了,那么包子铺线程进入等待状态。接下来,吃货线程能否进一步执行则取决于锁的获取情况。如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。包子铺线程能否进一步执行则取决于锁的获取情况。

public class Baozi {
    private String pier;
    private String xianer;
    private boolean flag = false;

    public String getPier() {
        return pier;
    }

    public void setPier(String pier) {
        this.pier = pier;
    }

    public String getXianer() {
        return xianer;
    }

    public void setXianer(String xianer) {
        this.xianer = xianer;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }
}

public class Baozipu extends Thread {
    private  Baozi baozi;

    public Baozipu(Baozi baozi) {
        this.baozi = baozi;
    }

    @Override
    public void run() {
        int count = 0;
        while(true){
            synchronized (baozi){
                if (baozi.isFlag() == true){
                    try {
                        baozi.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if(count % 2 == 0){
                    //生产薄皮三鲜馅包子
                    baozi.setPier("薄皮");
                    baozi.setXianer("三鲜馅");
                }else{
                    //生产冰皮牛肉大葱馅包子
                    baozi.setPier("冰皮");
                    baozi.setXianer("牛肉大葱馅");
                }
                count++;
                System.out.println("包子铺正在生产" + baozi.getPier() + baozi.getXianer() + "包子");
                //生产包子需要三秒钟
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                baozi.setFlag(true);
                //唤醒吃货线程吃包子
                baozi.notify();
                System.out.println("包子铺已经生产好了" + baozi.getPier() + baozi.getXianer() + "包子,吃货可以开始吃了");
            }
        }
    }
}
public class Chihuo extends Thread{
    private Baozi baozi;

    public Chihuo(Baozi baozi) {
        this.baozi = baozi;
    }

    @Override
    public void run() {
        while (true){
            synchronized (baozi){
                if (baozi.isFlag()  == false){
                    try {
                        baozi.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("吃货正在吃" + baozi.getPier() + baozi.getXianer() + "包子");
                baozi.setFlag(false);
                baozi.notify();
                System.out.println("吃货已经把" + baozi.getPier() + baozi.getXianer() + "包子吃完了,包子铺开始生产包子了");
                System.out.println("===============================");

            }
        }
    }
}
public class Test {
    public static void main(String[] args) {
        Baozi baozi = new Baozi();
        Baozipu baozipu = new Baozipu(baozi);
        Chihuo chihuo = new Chihuo(baozi);
        baozipu.start();
        chihuo.start();
    }
}

6.1线程池

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低
系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池。

线程池;容器—>集合(ArrayList,HashSet,LinkedList,HashMap)

6.2线程池的概念

●线程池:其实就是一一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无
需反复创建线程而消耗过多资源。
由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多赘述。我们通过一-张图来 了解线程池的工作原

合理利用线程池能够带来三个好处:
1.降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3.提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内
存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

6.3线程池的实现

线程池:JDK1.5之后提供的
java. util. concurrent . Executors :线程池的工厂类,用来生成线程池
Executors类中的静态方法:
static ExecutorService newFixedThreadPool (int nThreads) 创建一个可重用固定线程数的线程池
参数:
int nThreads :创建线程池中包含的线程数量
返回值:
ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
java. util. concurrent . ExecutorService:线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务
submit(Runnable task) 提交一个Runnable 任务用于执行
关闭/销毁线程池的方法
void shutdown()
线程池的使用步骤:
1.使用线程池的工厂类Executors里边提供的静态方法newF ixedThreadPool生产- -个指定线程数量的线程池
2.创建-一个类,实现Runnable接口,重写run方法,设置线程任务
3.调用ExecutorService中的方法submit ,传递线程任务(实现类),开启线程,执行run方法
4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行)

public class Demo01ThreadPool {
    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(2);
        es.submit(new RunnableImpl());
        es.submit(new RunnableImpl());
        es.submit(new RunnableImpl());
        es.shutdown();
    }
}
public class RunnableImpl implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"创建了一个新的线程执行");
    }
}

程池:JDK1.5之后提供的
java. util. concurrent . Executors :线程池的工厂类,用来生成线程池
Executors类中的静态方法:在这里插入代码片
static ExecutorService newFixedThreadPool (int nThreads) 创建一个可重用固定线程数的线程池
参数:
int nThreads :创建线程池中包含的线程数量
返回值:
ExecutorService接口,返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
java. util. concurrent . ExecutorService:线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务
submit(Runnable task) 提交一个Runnable 任务用于执行
关闭/销毁线程池的方法
void shutdown()
线程池的使用步骤:
1.使用线程池的工厂类Executors里边提供的静态方法newF ixedThreadPool生产- -个指定线程数量的线程池
2.创建-一个类,实现Runnable接口,重写run方法,设置线程任务
3.调用ExecutorService中的方法submit ,传递线程任务(实现类),开启线程,执行run方法
4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行)

public class Demo01ThreadPool {
    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(2);
        es.submit(new RunnableImpl());
        es.submit(new RunnableImpl());
        es.submit(new RunnableImpl());
        es.shutdown();
    }
}
public class RunnableImpl implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"创建了一个新的线程执行");
    }
}
  开发工具 最新文章
Postman接口测试之Mock快速入门
ASCII码空格替换查表_最全ASCII码对照表0-2
如何使用 ssh 建立 socks 代理
Typora配合PicGo阿里云图床配置
SoapUI、Jmeter、Postman三种接口测试工具的
github用相对路径显示图片_GitHub 中 readm
Windows编译g2o及其g2o viewer
解决jupyter notebook无法连接/ jupyter连接
Git恢复到之前版本
VScode常用快捷键
上一篇文章      下一篇文章      查看所有文章
加:2021-09-13 09:28:08  更:2021-09-13 09:30:17 
 
开发: 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年11日历 -2024/11/16 5:46:45-

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