多线程详解

多线程详解,第1张

线程详解 多线程详解
  • 多条执行路径,主线程和子线程并行交替执行
线程简介
  • 程序,进程,线程
    • 程序:是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
    • 进程:程序的一次执行过程,动态概念,是系统资源分配的单位
    • 线程:是独立执行的路径,一个进程中包含若干个线程,最少一个,不然没有存在的意义,线程是 CPU 调度和执行的单位
  • 现在很多的多线程都是模拟出来的,真正的多线程指多个 CPU,即多核
  • 模拟出的多线程,在同一时间点,CPU 只能执行一个代码,因为切换的很快,所以有同时执行的错觉
  • 在程序执行时,即使没有自己创建线程,后台也会有多个线程,如主线程 main,gc 线程
  • 线程执行的先后顺序不能人为干涉
  • 对同一份资源 *** 作时,会存在资源抢夺问题,需要加入并发控制
  • 线程会带来额外的开销,如 cpu 调度时间,并发控制时间
  • 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
  • 多个线程在 *** 作同一个资源的情况下,线程不安全,会造成数据紊乱
线程实现(重点)
Thread 类(重点)
  1. 自定义类继承 Thread 类成为线程类
  2. 重写 run() 方法,编写 run() 方法线程体
  3. 创建线程对象,调用 start() 方法开启线程
package Dxc;

public class Text01 extends Thread{
    
    @Override
    public void run() {
        //run()方法线程体
        for (int i = 0; i < 2000; i++) {
            System.out.println("--------====------");
        }
    }
    
    public static void main(String[] args) {
        //main线程,主线程
        Text01 text01 = new Text01();
        text01.start();       //调用start开启线程
        for (int i = 0; i < 2000; i++) {
            System.out.println("147852369");
        }
    }
}

注意:线程开启不一定立即执行,由 CPU 调度执行

​ 不建议使用,避免OOP单继承局限性

  • 小例子:下载图片
package Dxc;

import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;

public class Text02 extends Thread{

    private String url;
    private String name;

    public Text02(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @Override
    public void run(){
        DownLoader dl = new DownLoader();
        dl.down(url,name);
        System.out.println("下载了文件,名为"+name);
    }

    public static void main(String[] args) {
        Text02 text1 = new Text02("https://img-bss.csdn.net/1636073674079.png","x1.png");
        Text02 text2 = new Text02("https://img-bss.csdn.net/1636073674079.png","x2.png");
        Text02 text3 = new Text02("https://img-bss.csdn.net/1636073674079.png","x3.png");
        text1.start();
        text2.start();
        text3.start();
    }
}

class DownLoader{
    public void down(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("异常!方法出现问题");
        }
    }
}
Runnable 接口(重点)
  1. 定义 MyRunnable 类实现 Runnable 接口
  2. 实现 run() 方法,编写线程执行体
  3. 创建线程对象,使用 start() 方法启动线程
package Dxc;

public class Text03 implements Runnable{
    @Override
    public void run() {
        //run()方法线程体
        for (int i = 0; i < 2000; i++) {
            System.out.println("--------====------");
        }
    }

    public static void main(String[] args) {
        //main线程,主线程
        Text03 text1 = new Text03();
        //创建线程对象,通过线程对象来开启线程
//        Thread thread = new Thread(text1);
//        thread.start();
        //上面两行可简写为下面一行
        new Thread(text1).start();       //调用start开启线程
        for (int i = 0; i < 2000; i++) {
            System.out.println("147852369");
        }
    }
}

注意:推荐使用,避免单继承局限性,灵活方便,方便同一个对象被多线程使用

  • 小例子:抢票问题
package Dxc;

public class Text04 implements Runnable{
    
    private int ticketNums=10;

    @Override
    public void run() {
        while (true){
            if (ticketNums<0){
                break;
            }
            //模拟延时
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"买了票后还有"+ticketNums--+"张票");
        }
    }

    public static void main(String[] args) {
        Text04 text = new Text04();
        new Thread(text,"小白").start();
        new Thread(text,"小红").start();
        new Thread(text,"小黑").start();
    }
}
  • 小练习:龟兔赛跑
  • 龟兔同速版
package Dxc;

public class Running implements Runnable{

    private static String winner;

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            //设置兔子每跑25步休息一毫秒
            if (Thread.currentThread().getName().equals("兔子")&& i%25==0){
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            boolean flag=gameOver(i);       //调用gameOver方法判断游戏是否结束
            if (flag){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
        }
    }
    public boolean gameOver(int length){
        if (winner!=null){
            return true;
        }else {
            if (length>=100){
                winner=Thread.currentThread().getName();
                System.out.println("胜利者是"+winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Running running = new Running();
        new Thread(running,"乌龟").start();
        new Thread(running,"兔子").start();
    }
}
  • 龟兔异速版
package Dxc;

public class Running implements Runnable{

    private static String winner;

    @Override
    public void run() {
        //为兔子设定规则
        if (Thread.currentThread().getName().equals("兔子")){
            for (int i1=1;i1<=10;i1++){
                //每跑5步休息1毫秒
                if (i1%5==0){       
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                boolean flag=gameOver(i1);
                if (flag){
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"跑了"+i1+"步");
            }
        }
        //为乌龟指定规则
        for (int i = 1; i <= 100; i++) {
            boolean flag=gameOver(i);
            if (flag){
                break;
            }
            if (Thread.currentThread().getName().equals("乌龟")){
                    System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
            }
        }
    }
    public boolean gameOver(int length){
        if (winner!=null){
            return true;
        }{
            if (Thread.currentThread().getName().equals("乌龟")&&length>=100){
                winner=Thread.currentThread().getName();
                System.out.println("胜利者是"+winner);
                return true;
            }
            if (Thread.currentThread().getName().equals("兔子")&&length>=10){
                winner=Thread.currentThread().getName();
                System.out.println("胜利者是"+winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Running running = new Running();
        new Thread(running,"乌龟").start();
        new Thread(running,"兔子").start();
    }
}
Callable 接口(了解)
  • 实现 Callable 接口,需要返回值类型
  • 重写 call() 方法,需要抛出异常
  • 创建目标对象
  • 创建执行服务:ExecutorService ser=Executors.newFixedThreadPool(1);
  • 提交执行:Future result1=ser.submit(t1);
  • 获取结果:boolean r1=result1.get();
  • 关闭服务:ser.shutdownNow();
package Dxc;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

public class Text05 implements Callable {

    private String url;
    private String name;

    public Text05(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @Override
    public Boolean call(){
        DownLoader1 dl = new DownLoader1();
        dl.down(url,name);
        System.out.println("下载了文件,名为"+name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Text05 text1 = new Text05("https://img-bss.csdn.net/1636073674079.png","x1.png");
        Text05 text2 = new Text05("https://img-bss.csdn.net/1636073674079.png","x2.png");
        Text05 text3 = new Text05("https://img-bss.csdn.net/1636073674079.png","x3.png");
        ExecutorService ser= Executors.newFixedThreadPool(3);       //创建执行服务,创建大小合适的线程池
        //提交执行
        Future r1=ser.submit(text1);
        Future r2=ser.submit(text2);
        Future r3=ser.submit(text3);
        //获取结果
        boolean rr1=r1.get();
        boolean rr2=r2.get();
        boolean rr3=r3.get();
        ser.shutdownNow();       //关闭服务
    }
}
class DownLoader1{
    public void down(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("异常!方法出现问题");
        }
    }
}
Lambda 表达式
  • 函数式编程
  • 避免匿名内部类定义过多
  • 使代码看起来更简洁
  • 去掉没有意义的代码,只留下核心的逻辑
  • 前提:接口为函数式接口
函数式接口
  • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口
  • 对于函数式接口,我们可以通过 lambda 表达式来创建该接口的对象
Lambda 表达式的演进
  • 一般
package Dxc.lib;

public class Text07 {
    public static void main(String[] args) {
        Love l1=new mylove();
        l1.love();
    }
}

interface Love{
    void love();
}

class mylove implements Love{

    @Override
    public void love() {
        System.out.println("1");
    }
}
  • 静态内部类
package Dxc.lib;

public class Text07 {

    //静态内部类
    static class mylove implements Love{

        @Override
        public void love() {
            System.out.println("2");
        }
    }
    public static void main(String[] args) {
        Love l2 = new mylove();
        l2.love();
    }
}

interface Love{
    void love();
}
  • 局部内部类
package Dxc.lib;

public class Text07 {

    public static void main(String[] args) {

        //局部内部类
        class mylove implements Love{

            @Override
            public void love() {
                System.out.println("3");
            }
        }
        Love l2 = new mylove();
        l2.love();
    }
}

interface Love{
    void love();
}
  • 匿名内部类
package Dxc.lib;

public class Text07 {

    public static void main(String[] args) {

        //匿名内部类
        Love l2 = new Love() {
            @Override
            public void love() {
                System.out.println("4");
            }
        };
        l2.love();
    }
}

interface Love{
    void love();
}
  • Lambda 表达式
package Dxc.lib;

public class Text07 {

    public static void main(String[] args) {

        //lambda表达式
        Love l2 = ()->{ System.out.println("5");};
        l2.love();
    }
}

interface Love{
    void love();
}
  • 当方法有多行时,不能去掉 Lambda 表达式中的花括号,一行则可以去掉

  • 当方法带多个参数时,转换为 Lambda 表达式可以去掉参数类型 ( 全部去掉 ) ,但不能去掉括号,各个参数之间用逗号隔开

    当方法带一个参数时,转换为 Lambda 表达式可以去掉参数类型和括号, 变为:参数 - > { 方法体 } ;

静态代理模式
  • 真实对象和代理对象都要实现一个接口
  • 代理对象要代理真实角色
  • 优点
    • 代理对象可以增加很多真实对象没有的功能
    • 真实对象可以专注做自己的事
package Dxc;

public class Text06 {
    public static void main(String[] args) {
        Wedding wedding = new Wedding(new My());
        wedding.marry();
    }
}

interface Marry{
    void marry();
}

//真实角色
class My implements Marry{

    @Override
    public void marry() {
        System.out.println("结婚");
    }
}

//代理角色
class Wedding implements Marry{

    private Marry name;       //代理真实角色

    public Wedding(Marry name) {
        this.name = name;
    }

    @Override
    public void marry() {
        before();
        this.name.marry();       //调用真实角色方法
        after();
    }

    private void before() {
        System.out.println("布置现场");
    }

    private void after() {
        System.out.println("打扫现场");
    }

}
线程状态
  • 创建状态

    new Thread()

    尚未启动的线程出于此状态

  • 就绪状态

    调用 start() 方法,立即进入就绪状态,但不意味立即调度执行

  • 运行状态

    调度,进入运行状态,线程开始执行线程体的代码块

    在 java 虚拟机中执行的线程处于此状态

  • 阻塞状态

    当调用 sleep , wait 或同步锁定时,线程进入阻塞状态,代码不再向下执行,阻塞事件解除后,重新进入就绪状态

    被阻塞等待监视器锁定的线程处于此状态

  • 死亡状态

    线程中断或者结束,一旦进入死亡状态就不能再启动

    以退出的线程处于此状态

死亡的线程不能再次启动

线程方法 setPriority(int newPriority) 方法

更改线程优先级

static void sleep(long millis) 方法

在指定的毫秒数内让当前正在执行的线程休眠

  • sleep (时间) 指定当前线程阻塞的毫秒数
  • sleep 存在异常 InterruptedException
  • sleep 时间到达后线程进入就绪状态
  • sleep 可以模拟网络延时,倒计时等
    • 模拟网络延时,放大问题的发生性
  • 每一个对象都有一个锁,sleep 不会释放锁

倒计时

package Dxc;

public class TextSleep {
    public static void main(String[] args) {
        try {
            Down();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void Down() throws InterruptedException {
        int i=10;
        while (true){
            Thread.sleep(1000);
            System.out.println(i--);
            if (i<=0){
                break;
            }
        }
    }
}

读取系统时间

package Dxc;import java.text.SimpleDateFormat;import java.util.Date;public class TextSleep {    public static void main(String[] args) {        Date date = new Date(System.currentTimeMillis());       //获取系统当前时间        while (true){            try {                //打印的是前一秒的时间                Thread.sleep(1000);                System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));                date = new Date(System.currentTimeMillis());       //更新当前时间            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }
void join() 方法
  • 插队
  • 待此线程执行完成后,在执行其他线程,其他线程阻塞
package Dxc;

import java.text.SimpleDateFormat;
import java.util.Date;

public class TextSleep {
    public static void main(String[] args) {
        Date date = new Date(System.currentTimeMillis());       //获取系统当前时间
        while (true){
            try {
                //打印的是前一秒的时间
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
                date = new Date(System.currentTimeMillis());       //更新当前时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
static void yield() 方法

暂定当前正在执行的线程并将其返回就绪状态

  • 线程礼让,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态,保留执行进度
  • 让CPU重新调度,依然随机执行
boolean isAlive() 方法

测试线程是否处于活动状态

停止线程方法
  • 线程自己停止,利用次数,不建议死循环

  • 建议使用一个标志位进行终止变量,当 flag=false 时,终止线程执行

  • 不要使用 stop 或者 destroy 等过时或者 JDK 不建议使用的方法

package Dxc;

public class Text08 implements Runnable{

    private boolean flag=true;       //设置一个标志位

    @Override
    public void run() {
        int i=0;
        while (flag){
            System.out.println("生产机器人"+i+++"号");
        }
    }

    //设置一个公开的方法停止线程,转换标志位
    public void stop(){
        this.flag=false;
    }

    public static void main(String[] args) {
        Text08 text08 = new Text08();
        new Thread(text08).start();
        for (int i = 0; i < 100; i++) {
            System.out.println("生产机器"+i+"号");
            //当主线程到90时,停止副线程
            if (i==90){
                //调用自己写的stop方法切换标志位,让线程停止
                text08.stop();
                System.out.println("停止生产机器人");
            }
        }
    }
}
观测线程状态
package Dxc;

public class TextState{
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("结束");
        });

        //观察状态
        Thread.State state=t.getState();
        System.out.println(state);

        //观察启动后状态
        t.start();
        state=t.getState();
        System.out.println(state);

        //观察阻塞和结束状态
        while (state!=Thread.State.TERMINATED){       //判断线程是不是终止状态
            Thread.sleep(200);
            state=t.getState();       //更新线程状态
            System.out.println(state);
        }
    }
}
线程优先级
  • 线程的优先级用数字表示,范围从1~10
    • Thread.MIN_PRIORITY=1;
    • Thread.MAX_PRIORITY=10;
    • Thread.NORM_PRIORITY=5;
  • 使用一下方法改变或获取优先级
    • 改变:setPriority(int xxx)
    • 获取:getPriority()
  • 优先级的设定建议在 start() 调度之前
  • 优先级高低并不能决定调度的先后,知识决定被优先调度的概率
守护线程
  • 守护 (daemon)
  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 例子:记录 *** 作日志,监控内存,垃圾回收等待
  • 用户线程结束后,守护线程会在执行一段时间,因为虚拟机停止需要时间
package Dxc;

public class TextDaemon {
    public static void main(String[] args) {
        Thread t = new Thread(new god());
        t.setDaemon(true);       //默认为false表示用户线程,正常的线程都是用户线程,改为true则为守护线程
        t.start();
        new Thread(new person()).start();
    }
}

class god implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("世界还在运转");
        }
    }
}

class person implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i <=100; i++) {
            System.out.println("今年我"+i+"岁");
            if (i==100){
                System.out.println("再见了,世界!");
            }
        }
    }
}
线程同步(重点)

并发:同一个对象被多个线程同时 *** 作

  • 多个线程 *** 作同一个资源
  • 解决方法:队列
  • 是一种等待机制
  • 多个线程进入对象等待池形成队列,前面线程使用完毕,下一个线程再使用
  • 形成条件:队列 + 锁
  • 关键词:锁机制 synchronized
  • 每个对象都有一个锁,当一个线程获得该锁后,独占资源,其他线程必须等待,使用后释放锁供后一个线程使用
    • 一个线程持有锁会导致其他需要此锁的线程挂起,降低性能
    • 在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引发性能问题
同步方法
  • 用法包括两种

    synchronized 方法和 synchronized 块

同步方法:public synchronized void method(int args){}
  • 缺点:如果将一个大的方法声明为 synchronized 将会影响效率
  • 方法里面包括只读代码块和修改代码块,需要修改的代码块才需要锁,锁的太多,浪费资源
  • 默认锁的是本身修饰的方法
package Dxc;

public class TextS {
    public static void main(String[] args) {
        BuyTicket bt = new BuyTicket();
        new Thread(bt,"小白").start();
        new Thread(bt,"小红").start();
        new Thread(bt,"小黑").start();
    }
}
class BuyTicket implements Runnable{

    private int ticketNums=10;
    boolean flag=true;       //外部停止方式

    @Override
    public void run() {
        while (flag){

            try {
                Thread.sleep(200);
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //同步方法
    private synchronized void buy() throws InterruptedException {       
        if (ticketNums<=0){
            flag=false;
            return;
        }
        System.out.println(Thread.currentThread().getName()+"买了票后还有"+ticketNums--+"张票");
    }
}
同步块:synchronized(Obj){}
  • Obj 称为同步监视器
    • Obj 可以是任何对象 (需要修改的对象) ,但是推荐使用共享资源作为同步监视器
    • 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是对象本身,或 class
package Dxc;

public class TextM {
    public static void main(String[] args) {
        Zh zh = new Zh(100,"未来基金");
        Drawing d1 = new Drawing(zh,50,"小白");
        Drawing d2 = new Drawing(zh,100,"小黑");
        d1.start();
        d2.start();
    }
}
class Zh{
    int money;
    String name;
    public Zh(int money, String name) {
        this.money = money;
        this.name = name;
    }
}
class Drawing extends Thread{
    Zh zh;
    int drawingMoney;
    int haveMoney;

    public Drawing(Zh zh, int drawingMoney, String name) {
        super(name);
        this.zh = zh;
        this.drawingMoney = drawingMoney;
    }

    @Override
    public void run() {
        //同步代码块
        synchronized (zh){
            if (zh.money-drawingMoney<0){
                System.out.println("您的账户余额不足");
                return;
            }
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            zh.money= zh.money-drawingMoney;
            haveMoney=haveMoney+drawingMoney;
            System.out.println(zh.name+"的余额为"+zh.money);
            System.out.println(this.getName()+"的现金为"+haveMoney);
        }

    }
}
JUC安全类型集合
  • CopyOnWriteArrayList
package Dxc;

import java.util.concurrent.CopyOnWriteArrayList;

public class Text22 {
    public static void main(String[] args) {
        CopyOnWriteArrayList s = new CopyOnWriteArrayList();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                    s.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(s.size());
    }
}
死锁
  • 多个线程各自占用一些公共资源,并且互相等待其他资源占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都

    停止执行的情形

  • 某一个同步块同时拥有 ”两个以上对象的锁“ 时,就可能会发生 ”死锁” 的问题

产生死锁的四个必要条件
  • 互斥条件:一个资源每次只能被一个进程使用
  • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺
  • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系

上面列出的死锁的四个条件中,只要破坏其中的任意一个或多个条件就可以避免死锁发生

Lock 锁
  • Lock 接口是控制多个线程对共享资源进行访问的工具,

  • 同步锁使用 Lock 对象充当

  • 每次只能有一个线程对 Lock 对象加锁,线程开始访问共享资源之前应先获得 Lock 对象

  • ReentrantLock 类实现了 Lock,拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常见的是

    ReentrantLock,可以显示加锁,释放锁

格式

class Tick{

    private final ReentrantLock lock=new ReentrantLock();

    public void run() {
                lock.lock();       //加锁
            try{
                       //保证线程安全的代码
                }
            }finally {
                lock.unlock();       //解锁,如果同步代码有异常,要求unlock()写入finally语句块中
            }
        }
}
package Dxc;

import java.util.concurrent.locks.ReentrantLock;

public class TextBuyTicket {
    
    public static void main(String[] args) {
        Tick tick = new Tick();
        new Thread(tick,"小白").start();
        new Thread(tick,"小红").start();
        new Thread(tick,"小黄").start();
    }
}

class Tick implements Runnable{

    int ticke=100;
    boolean flag=true;

    private final ReentrantLock lock=new ReentrantLock();

    @Override
    public void run() {
        while (flag){
            try{
                lock.lock();       //加锁
                if (ticke<0){
                    System.out.println("票卖光了,欢迎下次光临");
                    flag=false;
                }else {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"买完后还剩余"+ticke--+"张票");
                }
            }finally {
                lock.unlock();       //解锁
            }
        }
    }
}}
synchronized 与 Lock 的对比
  • Lock 是显示锁 (手动开启和关闭锁,别忘记关闭锁) synchronized 是隐式锁,除了作用域自动释放
  • Lock 只是代码块锁,synchronized 有代码块锁和方法锁
  • 使用 Lock 锁,JVM 将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性 (提高更多的子类)
  • 优先使用顺序
    • Lock > 同步代码块 (已经进入了方法体,分配了相应资源) > 同步方法 (在方法体之外)
线程协作
生产者消费者模式
  • 应用场景:生产者和消费者问题
    • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费
    • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
    • 如果产品中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
  • synchronized 可以阻止并发更新同一个共享资源,实现了同步,但不能用来实现不同线程之间的信息传递 (通信)
线程通信方法
  • 所有方法均为 Object 类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常
wait()

表示线程一直等待,直到其他线程通知,与 sleep 不同,会释放锁

wait(long timeout)

指定等待的毫秒数

notify()

唤醒一个处于等待状态的线程

notifyAll()

唤醒同一个对象上所有调用 wait() 方法的线程,优先级别高的线程优先调度

解决方法1

并发协作模型 “生产者/消费者模式” —>管程法

  • 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
  • 消费者:负责处理数据的模块(可能是方法,对象,线程,进程)
  • 缓冲区:消费者不能直接使用生产者的数据,他们之间有一个 “缓冲区”

生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据

package Dxc;

public class TextCk {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        new Producer(buffer).start();
        new Consumer(buffer).start();
    }
}

class Producer extends Thread{

    Buffer container;       //创建一个Buffer类型的变量

    public Producer(Buffer container) {
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            container.push(new Card(i));       //调用Buffer类中的push方法,循环传入参数
            System.out.println("往卡池注入了第"+i+"张卡");
        }
    }
}

class Consumer extends Thread{

    Buffer container;       //创建一个Buffer类型的变量

    public Consumer(Buffer container) {
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("抽出了第"+ container.get().id+"张卡");
        }
    }
}

//产品
class Card{

    int id;

    public Card(int id) {
        this.id = id;
    }
}

class Buffer{

    Card[] cards=new Card[10];       //创建一个Card类型的数组cards,容量为10
    int number=0;       //定义容器内的产品的数量number

    public synchronized void push(Card card){
        if (number==cards.length){       //如果容器内产品的数量等与容量,容器满了
            //通知消费者消费,生产者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果容器没满
        cards[number]=card;       //将产品放入容器的相对位置中
        number++;       //容器内产品的数量加1
        this.notifyAll();       //通知消费者消费
    }

    public synchronized Card get(){
        if (number==0){       //如果容器内产品的数量等于0
            //通知生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果容器内产品的数量不等于0
        number--;       //容器内的产品数量减1
        Card card=cards[number];       //将容器中相对位置的产品取出
        this.notifyAll();       //通知生产者生产
        return card;       //返回取出的产品
    }
}

代码执行中出现的先消费后生产的问题

  • 原因:push 方法中刚刚执行完 notifyAll 唤醒 get 线程时刚好 push 的时间片轮转完了
  • 解决方法:在 get 方法中加一个短暂的睡眠,保证 push 方法整个执行完
解决方法2

并发协作模型 “生产者/消费者模式" —>信号灯法

  • 标志位解决问题
  • 本质上可以看成缓冲区大小为 1 的管程法
  • 生产者,消费者通过标志位的变化交替执行
package Dxc;

public class TextSl {
    public static void main(String[] args) {
        Games games=new Games();
        new WY(games).start();
        new Player(games).start();
    }
}

class WY extends Thread{

    Games games;

    public WY(Games games) {
        this.games = games;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if (i%2==0){
                this.games.make("阴阳师"+i+"年");
            }else {
                this.games.make("王者荣耀"+i+"年");
            }
        }
    }
}

class Player extends Thread{

    Games games;

    public Player(Games games) {
        this.games = games;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            this.games.play();
        }
    }
}

class Games{

    String game;
    boolean flag=true;

    public synchronized void make(String game){
        if (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("游戏厂商制作了"+game);
        this.notifyAll();
        this.game=game;
        this.flag=!this.flag;
    }

    public synchronized void play(){
        if (flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("玩家玩了"+game);
        this.notifyAll();
        this.flag=!this.flag;
    }
}
线程池
  • 提前创建好线程,放入线程池中,使用时直接获取,使用完后放回池中。可以避免频繁创建销毁,实现重复利用。
  • 好处
    • 提高响应速度 (减少创建新线程的时间)
    • 降低资源消耗 (重复利用线程池中的线程,不需要每次都创建)
    • 便于线程管理
      • corePoolSize:核心池的大小
      • maxmumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多保持多长时间才会终止
ExecutorService
  • 真正的下次线程池接口,常见子类 ThreadPoolExcutor
    • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行 Runnable
    • Future submit(Callable task):执行任务,有返回值,一般用来执行 Callable
    • void shutdown():关闭线程池
Executors
  • 工具类,线程池的工厂类,用于创建并返回不同类型的线程池
package Dxc;

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

public class TextC {
    public static void main(String[] args) {
        ExecutorService es= Executors.newFixedThreadPool(3);       //创建一个容量大小为3的线程池
        es.execute(new Cz());
        es.execute(new Cz());
        es.execute(new Cz());
        es.shutdown();
    }
}
class Cz implements Runnable{
    @Override
    public void run() {
            System.out.println(Thread.currentThread().getName());
    }
}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5482384.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-12
下一篇 2022-12-12

发表评论

登录后才能评论

评论列表(0条)

保存