java高级特性多线程

提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档


总结

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

一、多线程基础

1、多线程的概念,好处和实现方式
2、实现Runnable和Callable的区别
3、多线程的状态

1、多线程概念

多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器*。
在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理“。 *

1.2、并发(Concurrent)

在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。 同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行

交替利用cpu执行的多个程序运行过程被称为并发(假并行 耗时内存)

1.3、并行(Parallel)

当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。其实决定并行的因素不是CPU的数量,而是CPU的核心数量,比如一个CPU多个核也可以并行。

多个CPU核心同时运行多个程序的过程

1.4、进程(Process)

是正在运行的程序实体,并且包括这个运行的程序中占据的所有系统资源,比如说CPU(寄存器),IO,内存,网络资源等。同样一个程序,同一时刻被两次运行了,那么他们就是两个独立的进程。

程序运行的过程 是进程

1.5、线程(Thread)

是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

程序进程中运行的具体程序任务
java.lang.Thread类的一个实例;

1.6、进程与线程的区别

  1. 线程是程序最小执行单位,进程是操作系统分配资源的最小单位进程是系统资源分配的单位,线程是系统调度的单位。
  2. 一个进程由一个或者多个线程,线程是一个进程中代码的不同执行路线
  3. 进程之间互相独立,不能共享资源。但线程可以共享所在进程地址空间和其他资源同时线程还有自己的栈和栈指针,程序计数器等寄存器。
  4. 调度和切换:线程上下文切换比进程上下文切换要快得多。

1.7、线程的优缺点

优点
  1. 提高程序运行速度
  2. 提高cpu利用率
    代码实例
   /** 单线程的方式进行 时分的耗费时间
     * 封装方法
     * @param ip
     */
    public static void pingIp(String ip){
    
    
        InputStream inputStream=null;
        BufferedReader bufferedReader=null;
        try {
    
    
            /*每个Java应用程序都有一个Runtime类的Runtime ,允许应用程序与运行应用程序的环境进行接口。 当前运行时可以从getRuntime方法获得 */
            Runtime runtime = Runtime.getRuntime();
            // 使用与环境接口的类exec方法 可以得到当前运行类
            Process exec = runtime.exec("ping "+ip);
            //获取到执行结果流
            inputStream = exec.getInputStream();
            // BufferedReader 使用按行读取字符流 并提高效率   缓冲阅读器
            //InputStreamReader  把字节流转换字符流的类    输入流阅读器
            bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "GBK"));//GB18030    GB2312   GBK   国标扩展码
            //定义变量接收字符串
            String charLine=null;
            // 定义是否平通的标识符
            boolean isSuc=false;
            //按行读
            while((charLine=bufferedReader.readLine())!=null){
    
    
                /*System.out.println(charLine);*/
                if (charLine.indexOf("TTL")!=-1){
    
    
                    isSuc=true;
                    break;
                }
            }
            if (isSuc){
    
    
                System.out.println(ip+"ping的通");
            }else {
    
    
                System.out.println(ip+"ping不通");
            }

        } catch (IOException e) {
    
    
            e.printStackTrace();
        }finally {
    
    
            if (inputStream!=null){
    
    
                try {
    
    
                    inputStream.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
            if (bufferedReader!=null){
    
    
                try {
    
    
                    bufferedReader.close();
                } catch (IOException e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
    
    
        /*pingIp("192.168.41.1");*/
        /*  1970 时间戳*/
        long l = System.currentTimeMillis();
        for (int i = 0; i <=20; i++) {
    
    
            pingIp("192.168.41."+i);
        }
        long l1 = System.currentTimeMillis();
        System.out.println((l1-l)/1000);
    }

多线程

/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2021/11/30 15:50
 */
public class MTPPingIP extends Thread {
    
    

    private String ip;

    public MTPPingIP(String ip) {
    
    
        this.ip = ip;
    }

    @Override
    public void run() {
    
    
        PingIP.pingIp(ip);
    }
}
/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2021/11/30 16:12
 */
public class Test {
    
    
    public static void main(String[] args) {
    
    
        System.out.println("单前线程名称"+Thread.currentThread().getName());
        for (int i = 0; i <=50; i++) {
    
    
            new MTPPingIP("192.168.41."+i).start();
        }
    }
}

多线程的方式可以快速完成程序

缺点
  1. 如果是使用被共享的资源时可能造成程序的运行速度变慢。如一个打印机 这种独占资源
  2. 对线程管理需要额外的cpu开销 ,线程使用会给系统带来上下文切换的额外负担
  3. 线程的死锁。即对共享资源加锁实现同步的过程中可能会死锁。

2、多线程实现方式

在这里插入图片描述

2.1、继承Thread

package com.aaa.mt2.demo01;

import org.junit.jupiter.api.Test;

/**
 * @author zhangyifan
 * @version 8.0
 * @description: 继承Thread多线程
 * @date 2021/11/30 16:19
 */
public class MTExtendsThread extends Thread{
    
    
    @Override
    public void run() {
    
    //   run() 是//执行线程业务的方法
        for (int i = 0; i < 10; i++) {
    
    
//Thread.currentThread()返回正在执行的线程实例 也可以  Thread thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName()+"这是一个多线程");
        }
    }
    @Test
    public void MTThread(){
    
    
        //*启动线程  区别 start()启动线程 run()  start运行之后的就是 run里面的方法 run线程执行业务
        MTExtendsThread mtExtendsThread=new MTExtendsThread();
        mtExtendsThread.start();
    }
}

2.2、实现Runnable

Thread 里面的方法可以直接吧Runnable当参数
在这里插入图片描述


/**
 * @author zhangyifan
 * @version 8.0
 * @description: 接口Runnable
 * @date 2021/11/30 16:32
 */
public class MTImplementRunnable implements Runnable{
    
    
    @Override
    public void run() {
    
    
        for (int i = 0; i < 10; i++) {
    
    
//Thread.currentThread()返回正在执行的线程实例 也可以  Thread thread = Thread.currentThread();
            System.out.println(Thread.cur`在这里插入代码片`rentThread().getName()+"这是一个多线程");
        }
    }
    @Test
    public void implementRunnable(){
    
    
        MTImplementRunnable mtImplementRunnable=new MTImplementRunnable();
        //多态表现
        new Thread(mtImplementRunnable,"实现Runnable").start();
        new Thread(mtImplementRunnable,"实现Runnable2").start();
    }
}

2.3、实现Callable

package com.aaa.mt2.demo01;

import org.junit.jupiter.api.Test;

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

/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2021/11/30 16:41
 */
public class MTImplementCallable implements Callable {
    
    
    @Override
    public Object call() throws Exception {
    
    
        for (int i = 0; i < 10; i++) {
    
    
//Thread.currentThread()返回正在执行的线程实例 也可以  Thread thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName()+"这是一个多线程");
        }
        return null;
    }
    @Test
    public void implementCallable(){
    
    
        MTImplementCallable mtImplementRunnable = new MTImplementCallable();
        /*1、 因为该类是Runnable的字类  所有借助他启动线程
         *2、通过FutureTask 中提供的get方法获取线程执行的方法执行的结果和异常处理*/
        FutureTask objectFutureTask = new FutureTask(mtImplementRunnable);
        //借助于中间类来启动线程
        new Thread( objectFutureTask,"Callable").start();
    }
}

2.4、线程池

package com.aaa.mt2.demo01;

import com.aaa.mt.demo1.MTPool;
import org.junit.jupiter.api.Test;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;/* juc 并发包*/

/**
 * @author zhangyifan
 * @version 8.0
 * @description: 线程池实现多线程
 * @date 2021/11/30 17:05
 */
public class MTPooI implements Runnable{
    
    
    @Override
    public void run() {
    
    
        /* 执行多线程业务的方法  运行*/
        for (int i=0;i<10;i++){
    
    
            /* 返回正在执行的线程实例*/
            Thread thread = Thread.currentThread();
            /* 获取单前线程实例的名称*/
            System.out.println(thread.getName()+"执行打印");
        }
    }
    @Test
    public void ljcRunnable(){
    
    
        MTPool mtPool=new MTPool();//实例化
        ExecutorService executorService= Executors.newFixedThreadPool(2);//线程池长度
        //调用execute方法启动线程
        executorService.execute(mtPool);
        executorService.execute(mtPool);
        executorService.execute(mtPool);
        executorService.execute(mtPool);//只能执行两个线程 其他的是死了
    }
}

3、实现runnable和callable的区别

  1. 都是执行的多线程,但是方法名称不同run() 和 cal()
  2. 实现Runnable方法是没有返回值 无法获取线程业务方法执行结果 而Callable相反
  3. Runnable方法没有抛出异常,而Callable有异常处理 ,并且获取异常

4、实现runnable和继承thread

一个类只能继承一个父类,存在局限;一个类可以实现多个接口。在实现Runnable接口的时候调用Thread的Thread(Runnable run)或者Thread(Runnable run,String Name)构造方法创建进程时,使用同一个Runnable实例,建立的多线程的实例也是共享的;**但是继承Thread类是不能用一个实例建立多个线程,古而实现Runnable接口适合于资源共享;**当然继承 Thread 类也可以共享变量 能共享Thread类的static变量

/**
 * @author zhangyifan
 * @version 8.0
 * @description: 多线程卖票 thread
 * @date 2021/11/30 17:21
 */
public class SellTicketExtendsThread extends Thread{
    
    
    /* 票数*/
    private int ticknum=20;
    //姓名
    private String name;
    public SellTicketExtendsThread(String name) {
    
    
        this.name = name;
    }
    @Override
    public void run() {
    
    
        while (ticknum>0) {
    
    
            System.out.println(name+"在卖票,剩余"+(--ticknum)+"张");
        }
    }
    @Test
    public void maip(){
    
    
        SellTicketExtendsThread sellTicketExtendsThread=new SellTicketExtendsThread("张三");
        sellTicketExtendsThread.start();
    }
}

/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2021/11/30 19:13
 */
public class SellTicketImplementRunnable implements Runnable{
    
    

    private int ticknum=20;
    //姓名
    private String name;
    @Override
    public void run() {
    
    
        while (ticknum>0) {
    
    
            System.out.println(Thread.currentThread().getName()+"在卖票,剩余"+(--ticknum)+"张");
        }
    }
}

/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2021/11/30 19:06
 */
public class Test {
    
    
    public static void main(String[] args) {
    
    
/*        SellTicketExtendsThread sellTicketExtendsThread=new SellTicketExtendsThread("张三");
        sellTicketExtendsThread.start();
        SellTicketExtendsThread sellTicketExtendsThread1=new SellTicketExtendsThread("李四");
        sellTicketExtendsThread1.start();*/


        SellTicketImplementRunnable sellTicketImplementRunnable=new SellTicketImplementRunnable();
        new Thread(sellTicketImplementRunnable,"马云").start();
        new Thread(sellTicketImplementRunnable,"马化腾").start();
    }
}

5、多线程匿名内部类用法

/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2021/11/30 15:05
 */
public class AnonymousInternalClass {
    
    
    public static void main(String[] args) {
    
    
        //继承Thred
        new Thread(){
    
    
            @Override
            public void run() {
    
    
                /* 执行多线程业务的方法  运行*/
                for (int i=0;i<10;i++){
    
    
                    /* 返回正在执行的线程实例*/
                    Thread thread = Thread.currentThread();
                    /* 获取单前线程实例的名称*/
                    System.out.println(thread.getName()+"执行打印");
                }
            }
        }.start();
        System.out.println("======================================");
        /* 实现 Runnable*/
        new  Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                /* 执行多线程业务的方法  运行*/
                for (int i=0;i<10;i++){
    
    
                    /* 返回正在执行的线程实例*/
                    Thread thread = Thread.currentThread();
                    /* 获取单前线程实例的名称*/
                    System.out.println(thread.getName()+"执行打印");
                }
            }
        }).start();
        System.out.println("======================================");
        new Thread(new FutureTask<String>(new Callable<String>() {
    
    
            @Override
            public String call() throws Exception {
    
    
                /* 执行多线程业务的方法  运行*/
                for (int i=0;i<10;i++){
    
    
                    /* 返回正在执行的线程实例*/
                    Thread thread = Thread.currentThread();
                    /* 获取单前线程实例的名称*/
                    System.out.println(thread.getName()+"执行打印");
                }
                return null;
            }
        })).start();
    }
}

7、Thread常用方法

currentThread()返回对当前正在执行的线程对象的引用
setName(String name)将此线程的名称改名为等于参数name(具体用法参考项目demo4包)
getName() 返回线程名称
start()调用线程执行 run方法
run()如果这个线程使用单独的Runnable运行对象构造,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回。 线程执行业务的方法。
setPriority(int newPriority)更改此线程的优先级(不靠谱 不能确定)

/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2021/11/30 19:52
 */
public class ThreadProiorty {
    
    
    public static void main(String[] args) {
    
    
        Thread t1=new Thread(){
    
    
            @Override
            public void run() {
    
    
                System.out.println("线程1执行");
            }
        };
        Thread t2=new Thread(){
    
    
            @Override
            public void run() {
    
    
                System.out.println("线程2执行");
            }
        };
        // 这个只是一个几率执行 的  不能 z真的用来优先级
        t1.setPriority(Thread.MAX_PRIORITY);
        t2.setPriority(Thread.MIN_PRIORITY);
        t1.start();
        t2.start();
    }
}

getPriority()返回此线程的优先级
sleep(long millis) 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
stop() 强制结束当前线程。Deprecated


/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2021/11/30 20:05
 */
public class ThreadSetName {
    
    

    public static void main(String[] args) {
    
    

        Thread thread = new Thread() {
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 10; i++) {
    
    
                    try {
    
    
                        Thread.sleep(1000);//延迟函数
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "执行" + i);
                }
            }
        };
        thread.setName("lll");
        thread.start();
        try {
    
    
            Thread.sleep(3000);/* main 方法延迟3秒直接关闭*/
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        thread.stop();
    }
}

join() 等待这个线程死亡。一旦调用一个线程的join方法后,当前线程产生阻塞,直到在调用 join方法的线程执行完毕后,再去执行。

/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2021/11/30 20:27
 */
public class JoinTest2 {
    
    
    //包正实现先后顺序执行
    public static void main(String[] args) {
    
    
        final Thread t1 = new Thread() {
    
    
            @Override
            public void run() {
    
    
                System.out.println(Thread.currentThread().getName()+"t1");
            }
        };  final Thread t2 = new Thread() {
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    t1.join();//join  t1启动后执行
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"t2");
            }
        };
        Thread t3 = new Thread() {
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    t2.join();//join  t2启动后执行
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"t3");
            }
        };//这里三个线程的启动顺序可以任意,大家可以试下!
        t1.start();
        t2.start();
        t3.start();
    }
}

yield() 对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。当前让出执行权。

8、多线程的状态

  1. 新建
  2. 就绪
  3. 运行
  4. 阻塞
  5. 终止

在这里插入图片描述

二、多线程

1、线程同步Synchronized 和ReentrantLock用法

同步异步
如果数据将在线程间共享。列如正在写的数据被一个线程读到,或者正在读的数据已经被另一个线程写过了那么这些数据就是共享数据,必须同步存取
应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。
相似点

这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一
个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行 线程阻塞和唤醒的代价是比较高的.

Synchronized

修饰实例方法,作用于当前对象实例加锁,进入同步代码前要获得当前对象实例的锁
修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁 。
修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

synchronized经过编译会在同步块的前后分别形成monitorenter和monitorexit这个两个字节码指令。
在执行monitorenter指令时 ,首先尝试获取对象锁,如果这个对象没有被锁定,或者已经拥有对象锁 ,就把锁的计算器加1 相应的 monitorexit指令时会把锁计算器减一
当计算器为0时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释为止

/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2021/12/1 9:17
 */
public class SellTicketimpelmentsRunnable extends Thread{
    
    

        private int tickeNum=20;
        //修饰实例方法 ,当前对象实例加锁, 进入代码前①获得当前对象实例的锁
        public synchronized void sync(){
    
    
            //业务代码块 同步
        }
        //修饰静态方法 ,作用于当前类对象加锁,进入同步代码要先获得当前对象的锁
        public synchronized static void sync1(){
    
    
            //业务代码块 同步
        }
        @Override
        public void run() {
    
    
          while (tickeNum>0){
    
    
              synchronized (this){
    
    
                  if (tickeNum>0){
    
    
                      System.out.println(Thread.currentThread().getName()+"卖票"+(--tickeNum)+"张");
                  }
              }
          }
        }
}

ReentrantLock(互斥锁)
JDK1.5提供的API层面的互斥锁 需要lock 和unlock方法配合(上锁,开锁),try/finally语句块完成

ReentranLock是java。util。concurrent包下的互斥锁(juc包简称这样叫 牛逼点) 相比Synchronized,ReentrantLock提供了高级点的功能

  1. 等待可以中断, 就是不会一直等待线程 ,可以选择放弃等待 可以避免死锁的情况
  2. 公平锁, 可以开启线程获取锁时的顺序为先来后到顺序获得锁

默认是no 公平 可以当传入true就为公平了在这里插入图片描述
3. . 锁绑定多个条件,一个ReentrantLock对象可以同时绑定对个对象。

/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2021/12/1 9:17
 */
public class SellTicketimpelmentsRunnableA extends Thread{
    
    
    private int ticketNum=20;
    private String name;

    ReentrantLock reentrantLock=new ReentrantLock();//默认为不公平 分配   写入true就为 公平分配线程顺序先来后到的顺序
    @Override
    public void run() {
    
    

        while (ticketNum>0){
    
    
        try {
    
    
                reentrantLock.lock();//锁
            if (ticketNum>0){
    
    //需要多加一个循环
                System.out.println(Thread.currentThread().getName()+"在卖票+还有"+(--ticketNum)+"张");
            }
        } finally {
    
    
            reentrantLock.unlock();//开锁
        }
    }
    }
}

2、sleep和wait的区别

1、sleep在不在同步块中都可以执行(不在同步块时会直接让出cpu,在同步块不会释放锁) 而wait必须在同步块执行
2、sleep 是Thread类中的静态方法 wait 是OBject的方法
3、 都在同步块中时 sleep不会释放锁 wait释放锁产生阻塞 值到其他锁拿锁换醒才具备拿锁资格 但不是立刻拿到锁

sleep是正在执行的线程让出cpu 给其他线程 但是到指定时间过后,cpu会在回到这个线程上面执行 ,如果开启同步锁
就不会释放锁了,就算让出cpu,但是没有放锁,其他在同步锁中的线程也无法执行

wait是指在一个同步锁的线程中,自己先通过.wait()暂时让出同步锁 ,让其他等待这个锁的线程可以得到同步锁并运行
当有其他线程调用notify()方法是 之前调用wait的方法就可以继续参与到锁的竞争(如果 notify方法后面的代码还有很多,需要这些代码执行完后才会释放锁,可以在notfiy方法后增加一个等待和一些代码,看看效果)


/**
 * @author zhangyifan
 * @version 8.0
 * @description: sleep和wait的区别  :
 *          1、sleep在不在同步块中都可以执行(不在同步块时会直接让出cpu,在同步块不会释放锁) 而wait必须在同步块执行
 *          2、sleep 是Thread类中的静态方法  wait 是OBject的方法
 *          3、 都在同步块中时  sleep不会释放锁 wait释放锁产生阻塞 值到其他锁拿锁换醒才具备拿锁资格  但不是立刻拿到锁
 * @date 2021/12/1 9:52
 */
public class SleepAndWaitDemo {
    
    
    private static Object lock=new Object();
    public static void main(String[] args) {
    
    
        Thread t1=new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                synchronized  (lock){
    
    
                    System.out.println(" 浮世三千,");
                    try {
    
    
                       // Thread.sleep(2000);
                        lock.wait();//
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    System.out.println("  吾爱有三。");
               }

            }
        });
        Thread t2=new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                 synchronized (lock){
    
    
                    System.out.println("日,月与卿。日为朝,");
                    lock.notify();//唤醒  是否唤醒t1 ,
                    try {
    
    
                        Thread.sleep(5000);
                     /*   lock.wait();*/
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                    System.out.println("月为暮,卿为朝朝暮暮。");
                }
            }
        });
        t1.start();
        t2.start();
    }
}

3、死锁和防止死锁

死锁的原因
账户 1: $200
账户 2: $300
线程 1: 从账户 1 转移 $300 到账户 2
线程 2: 从账户 2 转移 $400 到账户 1

线程1 拥有 资源A 线程2拥有 资源B
线程1想拿到资源B 线程2想拿到资源A
实例显示死锁

/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2021/12/1 10:47
 */
public class Son {
    
    
    /**
     * 说方法
     */
    public void say(){
    
    
        System.out.println("给我玩具我给你成绩单");
    }
    /**
     * 获取方法
     */
    public void get(){
    
    
        System.out.println("得到玩具");
    }
}
public class Fathre {
    
    
    /**
     * 说方法
     */
    public void say(){
    
    
        System.out.println("给我成绩单我给你玩具");
    }

    /**
     * 获取方法
     */
    public void get(){
    
    
        System.out.println("得到成绩单");
    }
}
/**
 * @author zhangyifan
 * @version 8.0
 * @description: 死锁 和 防止死锁
 * @date 2021/12/1 10:49
 */
public class DeadLock extends Thread{
    
    
    private Fathre fathre=new Fathre();
    private Son son =new Son();
    //标识符  线程是否是父亲
    private Boolean isFather=true;
    //锁
    private static Object LockA=new Object();
    private static Object LockB=new Object();
    @Override
    public void run() {
    
    
        //判断是否是父亲线程
        if (isFather){
    
    //父亲
            synchronized (LockA){
    
    
                fathre.say();
                //线程休眠
                try {
    
    
                    Thread.sleep(200);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                //获取儿子的锁
                synchronized (LockB){
    
    
                    fathre.get();
                }
            }
        }else {
    
    //儿子
            synchronized (LockB){
    
    
                son.say();
                try {
    
    
                    Thread.sleep(200);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                //获取父亲的锁
                synchronized (LockA){
    
    
                    son.get();
                }
            }
        }
    }
    public static void main(String[] args) {
    
    
        DeadLock deadLock1=new DeadLock();
        deadLock1.isFather=true;
        DeadLock deadLock2=new DeadLock();
        deadLock2.isFather=false;
        deadLock1.start();
        deadLock2.start();
    }
}

  • 遗憾的是,Java 编程语言中没有任何东西可以避免或打破这种死锁现象。必须仔细设计 程序, 以确保不会出现死锁。

如何防止死锁

  1. 避免多次锁定

  2. 具有相同的加锁顺序
    如果多个线程需要对多个 Lock 进行锁定,则应该保证它们以相同的顺序请求加锁。。比如上面的死锁程序,主线程先对 A 对象的 Lock 加锁,再对 B 对象的 Lock 加锁;而副线程则先对 B 对象的 Lock 加锁,再对 A 对象的 Lock 加锁。这种加锁顺序很容易形成嵌套锁定,进而导致死锁。如果让主线程、副线程按照相同的顺序加锁,就可以避免这个问题。

  3. 使用定时锁 程序在调用 acquire() 方法加锁时可指定 timeout 参数,该参数指定超过 timeout 秒后会自动释放对 Lock 的锁定,这样就可以解开死锁了

  4. 死锁监测。死锁检测是一种依靠算法机制来实现的死锁预防机制,它主要是针对那些不可能实现按序加锁,也不能使用定时锁的场景的。

4、线程池的概念 和优点

概念
线程池顾名思义就是事先创建若干个可执行的线程放入一个 池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。

为了更好使用线程的容器

作用

  • 降低资源消耗。-通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度===当任务到达时,任务可以不需要的等到线程创建就能立即执行。
  • 提高线程的可管理性===线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系 统的稳定性,使用线程池可以进行统一的分配,调优和监控。

5、线程池的四种

本质了解:
ThreadPoolExecutor(int corePoolSize,//核心线程池大小
int maximumPoolSize,//最大线程池大小
long keepAliveTime,//线程池中超过corePoolSize数目的空闲线程最大存活时间
TimeUnit unit,//时间单位
BlockingQueue workQueue)//线程等待队列
corePoolSize,maximumPoolSize,workQueue之间关系。

1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

  1. newSingleThreadExecutor 译文:单线程执行器
    corePoolSize=maximumPoolSize=1,无界阻塞队列LinkedBlockingQueue;

创建一个单线程的线程池,这个线程池只有一个线程工作。如果这个线程出现异常,会有一个新的线程来替代他。。此线程保证所有的任务执行顺序是按照提交顺序执行。

/**
 * @author zhangyifan
 * @version 8.0
 * @description: 数据库
 * @date 2021/12/1 11:19
 */
public class SingleThreadExecutorDemo {
    
    
    public static void main(String[] args) {
    
    
        //实例化了一个单线程池
        /*创建一个单线程的线程池。这个线程池只有一个线程工作。如果这个线程出现异常,会有一个新的线程来替代它。此线程保证所有的任务执行顺序是按照提交顺序执行。 */
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 1; i <10; i++) {
    
    
            final int j=i;//final  变量编译的过程中 ,变量已经被赋值
            executorService.execute(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    System.out.println(Thread.currentThread().getName()+"执行打印"+j);
                }
            });

        }
        //关闭线程池
        executorService.shutdown();
    }
}

  1. newFixedThreadPool

创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一但达到最大值就会保持不变,如果某个线程出现异常,那么会补充一个新的线程。


/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2021/12/1 13:58
 */
public class FIxedThreadPoolDemo {
    
    
    public static void main(String[] args) {
    
    
        //创建带有长度的线程池
        /*创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。 线程池的大小一旦达到最大值就会保持不变,如果某个线程出现异常,那么会补充一个新的线程。*/
        ExecutorService executorService = Executors.newFixedThreadPool(3);//固定的线程大小
        for (int i=1;i<=10;i++){
    
    
            try {
    
    
                Thread.sleep(200);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            final int j= i;
            executorService.execute(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    System.out.println(Thread.currentThread().getName()+"执行打印"+j);
                }
            });
        }
        executorService.shutdown();//注意关闭线程池
    }
}

  1. newCachedThreadPool

创建一个可缓存的线程池。如果线程池的大小超过处理任务所需要的线程数, 那么会回收部分空闲线程,当任务数
增加时,线程会智能添加新线程来处理任务。此线程不会对线程池的大小做限制,线程池大小完全依赖于操作系统
或 JVM)能够创建最大线程的大小。 (如果间隔时间长,下一个任务运行时,上一个任务已经完成,所以线程可以继续复用,如果间隔时间调短,那么部分线程将会使用新线程来运行。)corePoolSize = 0,maximumPoolSize = Integer.MAX_VALUE,即线程数量几乎无限制;keepAliveTime = 60s,线程空闲60s后自动结束。workQueue 为 SynchronousQueue 同步队列,这个队列类似于一个接力棒,入队出队必须同时传递,因为CachedThreadPool线程创建无限制,不会有队列等待,所以使用SynchronousQueue;适用场景:快速处理大量耗时较短的任务


/**
 * @author zhangyifan
 * @version 8.0
 * @description: 可缓存线程池
 * @date 2021/12/1 14:05
 */
public class CachedThreadPoolDemo {
    
    
    public static void main(String[] args) {
    
    
        ExecutorService executorService = Executors.newCachedThreadPool();
        /*创建一个可缓存的线程池。如果线程池的大小超过处理任务所需要的线程数,
         那么会回收部分空闲线程,当任务数 增加时,线程会智能添加新线程来处理任务。
         此线程不会对线程池的大小做限制,线程池大小完全依赖于操作系统*/
        for (int i=1;i<=5;i++){
    
    
            final int b=i;//final 编译过程中,变量已经被赋值
            try {
    
    
                Thread.sleep(100); //当有延迟时 会重复调用已经缓存好的线程使用 方便线程
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        executorService.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                    System.out.println(Thread.currentThread().getName()+"执行"+b);
                }
            });
        }
        executorService.shutdown();//关闭线程池
        /*pool-1-thread-1执行1
            pool-1-thread-1执行2
            pool-1-thread-1执行3
            pool-1-thread-1执行4
            pool-1-thread-1执行5*/
    }
}
  1. newScheduledThreadPool

创建一个无限大小的线程池。此线程池支持定时及周期性执行任务的需求。适应场景:定时执行任务

/**
 * @author zhangyifan
 * @version 8.0
 * @description: 线程池
 * @date 2021/12/1 14:13
 */
public class ScheduledThreadPoolDemo {
    
    
    public static void main(String[] args) {
    
    //预定执行服务
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
        /*创建一个无限大小的线程池。此线程池支持定时及周期性执行任务的需求。适应场景:定时执行任务*/
        scheduledExecutorService.schedule(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("10 秒 执行");
            }
        },10, TimeUnit.SECONDS);//10 秒

        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("执行》》》》");
            }
        },5,2,TimeUnit.SECONDS);//5秒后开始执行 然后每2秒执行一次

        scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("执行 scheduleWithFixedDelay");
            }
        },7,3,TimeUnit.SECONDS);
    }   //  开始时间    间隔时间    单位
}

5、线程池submit和execute的区别

两个方法都可以向线程池提交任务,execute()方法的返回类型是void,它定义在Executor接口中 而submit()方法可以返回持有计算结果的Future对象,它定义在ExecutorService接口中,它扩展了Executor接口,其它线程池类像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有这些方法。

1,submit即支持Runnable还支持Callable execute 只支持Runnable
2,submit 因为支持Callable,所以就可以支持获取返回值
3,submit 因为支持Callable,所以就可以支持获取异常处理

/**
 * @author zhangyifan
 * @version 8.0
 * @description:
 * @date 2021/12/1 18:43
 */
public class MTPoolSubmitAndExecuteDemo {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        executorService.submit(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("submitRunnable");
            }
        });
        Future<Object> submitandCallable = executorService.submit(new Callable<Object>() {
    
    
            @Override
            public Object call() throws Exception {
    
    
                System.out.println("submitandCallable");
                return 1;
            }
        });
        System.out.println(submitandCallable.get());//抛延迟
        executorService.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("execute and 只有 Runnable");
            }
        });
    }
}

猜你喜欢

转载自blog.csdn.net/qq_45438019/article/details/121624292