Java多线程

2023-05-16

文章目录

  • 前言
  • 一、多线程
    • 1.进程,线程,多线程
    • 2.继承Thread类
      • 1)同时下载多个网图通过继承Thread类
    • 3.实现Runnable接口
      • 1)下载多个网图通过实现Runnable接口
      • 2)实现Runnable接口与继承Thread的区别
    • 4.实现Callable接口
    • 5.静态代理
    • 6.Lambda表达式
    • 7.线程的五个状态
      • 1)线程的新生态
      • 2)线程的就绪态
      • 3)线程的运行态
      • 4)线程的阻塞态
      • 5)线程的死亡态
    • 8.观测线程状态
      • 1)线程的停止
      • 2)线程的阻塞--sleep
      • 3)线程的阻塞--礼让
      • 4)线程的阻塞--join
    • 9.线程的优先级
    • 10.守护线程
    • 11.线程同步和锁
      • 1)同步方法和方法块
      • 2)死锁
      • 3)lock锁
    • 12.管程法
    • 13.线程池
      • 1)线程池的好处
      • 2)线程池的创建
  • 总结


前言

一次只跑一条程序,已经不能满足于我们的日常编程,而我们都能够一遍吃饭一遍看电视,那么程序为什么不能执行着主线程的同时再多跑几条线程呢?


一、多线程

1.进程,线程,多线程

进程:当程序运行的时候就会形成一个进程,而一个进程里面又根据程序的功能可以形成多个线程,每一个线程对应着程序所提供的功能,而当我们同时进行两个线程的时候就是多线程。

2.继承Thread类

在Java程序中如果要实现多线程有三种方法,其为一个类两个接口,类就是Thread类,接口则是Runnable接口和Callable接口。这里主要是通过继承Thread类来进行多线程的操作

示例代码
public class Test2 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 40; i++) {
            System.out.println("我在学习++日语"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException{
        Test2 test2 = new Test2();
        test2.start();

        for (int i = 0; i < 60; i++) {
            System.out.println("我在学习中文"+i);
        }
    }
}

部分结果:
我在学习中文43
我在学习++日语37
我在学习中文44
我在学习++日语38
我在学习中文45
我在学习++日语39

由部分结果我们可以看到,线程不一定立即执行,而是会等待cpu的调用。需要注意的是,开启线程是调用start方法而不是直接调用run()方法

1)同时下载多个网图通过继承Thread类

通过Io工具类的FileUtils.copyURLToFile()方法来把网上的图片下载下来,然后通过多线程进行多张图片的同时下载

示例代码
public class Test2 extends Thread {
        private String url = null;
        private String name = null;

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

    @Override
    public void run() {

        DownLoad downLoad = new DownLoad();
        try {
            downLoad.fileDownLoad(url,name);
            System.out.println("已下载了"+name);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Test2 test1 = new Test2("https://i0.hdslb.com/bfs/live/3cbafec446f49594a98fe8becbdc72d644438cf1.jpg@672w_378h_1c.webp","图片1.webp");
        Test2 test2 = new Test2("https://i0.hdslb.com/bfs/archive/d37e475c854e82f1a6c29ad3cfdf6c3bc1dd7887.jpg@672w_378h_1c.webp", "图片2.webp");

        test1.start();
        test2.start();
    }

}



class DownLoad{
    public void fileDownLoad(String url,String name) throws IOException{
        FileUtils.copyURLToFile(new URL(url),new File(name));
    }
}

3.实现Runnable接口

与继承Thread时的代码并没有太大区别。实现Runnable接口来进行多线程的步骤一般是 实现Runnable接口,重写run()方法,通过创建线程对象去启动

示例代码
public class Test2 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 40; i++) {
            System.out.println("我在学习++日语"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException{
        Test2 test2 = new Test2();
        new Thread(test2).start();

        for (int i = 0; i < 60; i++) {
            System.out.println("我在学习中文"+i);
        }
    }
}

1)下载多个网图通过实现Runnable接口

不需要修改其他的地方只需要把启动的地方改为创建Thread对象再来使用start方法即可

示例代码
public class Test2 implements Runnable{
    private String url = null;
    private String name = null;

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

    @Override
    public void run() {

        DownLoad downLoad = new DownLoad();
        try {
            downLoad.fileDownLoad(url,name);
            System.out.println("已下载了"+name);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Test2 test1 = new Test2("https://i0.hdslb.com/bfs/live/3cbafec446f49594a98fe8becbdc72d644438cf1.jpg@672w_378h_1c.webp","图片1.webp");
        Test2 test2 = new Test2("https://i0.hdslb.com/bfs/archive/d37e475c854e82f1a6c29ad3cfdf6c3bc1dd7887.jpg@672w_378h_1c.webp", "图片2.webp");

        new Thread(test1).start();
        new Thread(test2).start();
    }

}
class DownLoad{
    public void fileDownLoad(String url,String name) throws IOException{
        FileUtils.copyURLToFile(new URL(url),new File(name));
    }
}

2)实现Runnable接口与继承Thread的区别

继承Thread的方法是子类继承Thread类实现多线程能力,而实现Runnable接口则是实现Runnable接口来获得多线程的能力,但是两者启动线程的方法有别,前者是通过子类对象.statrt()来开启多线程,后者则是通过创建一个Thread对象并传入目标对象.statrt()来开启多线程。而实现Runnable接口比继承Thread类的一个好处就是避免了单继承的局限性


4.实现Callable接口

与实现Runnable接口的时候时,从重写run()方法变更到了重写call()方法,开启线程也从new Thread().start()复杂化到了四个步骤

示例代码
//创建执行服务
        ExecutorService pool = Executors.newFixedThreadPool(1);
        //提交执行
        Future<Boolean> submit = pool.submit(test2);
        //返回结果
        Boolean result = submit.get();
        //关闭服务器
        pool.shutdownNow();

5.静态代理

一个类中有一个方法,但是这个方法只能做一件事的时候就可以使用代理。而代理类则比目标类功能强大,但又互不干扰。

示例代码
public class Test2{
    public static void main(String[] args) {
        //
        ProxyComp proxyComp = new ProxyComp(new TargetMen());
        proxyComp.happyMarry();
    }

}


//目标接口
interface Marry{
    void happyMarry();
}

//创建需要代理的类,并实现接口
class TargetMen implements Marry{
    //重写方法
    @Override
    public void happyMarry() {
        System.out.println("代理人");
    }
}



//创建代理类
class ProxyComp implements Marry{
    private TargetMen targetMen;
    //创建有参构造,传入需要代理的类
    public ProxyComp(TargetMen targetMen) {
        this.targetMen = targetMen;
    }

    @Override
    //重写方法
    public void happyMarry() {
        Before();
        System.out.println("========");
        this.targetMen.happyMarry();
        System.out.println("========");
        After();
    }

    //创建代理类做不到的方法
    private void Before(){
        System.out.println("在代理前需要处理的事务");
    }
    private void After(){
        System.out.println("在代理前需要处理的事务");
    }

}

6.Lambda表达式

本质是用来简化匿名内部类的,其使用条件是必须位函数接口,且接口中的方法不多于一个

未使用lambda表达式示例代码
public class Test2{
    public static void main(String[] args) {
        //使用匿名方式实现接口
        Marry marry =new Marry(int a) {
            @Override
            public void happyMarry(int a) {
                System.out.println("我是匿名"+a);
            }
        };
        marry.happyMarry(2);
    }
}

//目标接口
interface Marry{
    void happyMarry(int a);
}

使用lambda表达式示例代码

public class Test2{
    public static void main(String[] args) {
        //使用lambda表达式
        Marry marry =a-> System.out.println("我是匿名"+a);
        marry.happyMarry(2);
    }
}
//目标接口
interface Marry{
    void happyMarry(int a);
}

7.线程的五个状态

在这里插入图片描述

1)线程的新生态

当我们创建线程的时候,这时候就是处于新生状态

2)线程的就绪态

当我们使用.start()方法时为就绪态

3)线程的运行态

线程获取cpu的权限开始运行的时候为运行态

4)线程的阻塞态

当线程因为某种原因放弃cpu的使用权的时候,暂停运行,此时为阻塞态。而阻塞态有三种情况:

1、等待阻塞:通过调用线程的wait()方法,让线程等待某工作的完成

2、同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态

3、其他阻塞:调用了sleep()或join()时,或发出了I/O请求时候。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

5)线程的死亡态

当线程运行完成就进入了死亡态,转让cpu资源,让其他线程使用cpu

8.观测线程状态

上面已经讲述了线程的五个状态,而现在就通过实际代码来更直观的查看线程从新生到死亡的状态

示例代码
public class Test1 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("啊哈哈哈线程等待咯~"+i);
        }
    }
}

class ThreadStart{
    public static void main(String[] args) throws InterruptedException{
        Test1 test1 = new Test1();
        Thread thread = new Thread(test1);
        //创建线程,此时为新生态
        System.out.println(thread.getState());

        //线程启动
        thread.start();
        //此时为进行态
        Thread.State state = thread.getState();
        System.out.println(state);

        while (state!=Thread.State.TERMINATED){
            Thread.sleep(100);
            state = thread.getState();
        }

        //线程执行完成,死亡
        System.out.println(thread.getState());

    }
}

1)线程的停止

在Java中我们查看Thread 的源码的时候,我们可以看到stop方法已经被废弃了,所以自然状态下,要结束一个线程只能等其自动完成后停止。当然我们也可以手动变更标识位的值来结束一个线程

示例代码
public class Test2 implements Runnable{
    //创立标识位并设置为活跃状态
    private boolean flag = true;

    @Override
    public void run() {
        int i=0;
        while(flag){
            System.out.println(Thread.currentThread().getName()+"线程运行在"+i++);
        }
    }

//创建停止线程方法
    public void stop(){
    //将标识位改为停止态
        this.flag = false;
    }

    public static void main(String[] args) {

        Test2 test2 = new Test2();
        //创建一个新进程
        new Thread(test2,"线程1").start();
        //开始一千次循环,当循环到900次的时候终止循环
        for (int i = 0; i <1000 ; i++) {
            System.out.println("main"+i);
            if(i==900){
            test2.stop();
            }
        }
    }
}

2)线程的阻塞–sleep

通常是让线程进入假堵塞的状态,比如模拟接收信息需要延迟一点时间之类的任务

示例代码
public class Test1 implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(i+1);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Test1 test1 = new Test1();
        Thread thread = new Thread(test1);
        thread.start();

    }
}

这个代码就是模拟从1s数到10s的任务,其中关键之处就是Thread.sleep(1000);这里的代码就

3)线程的阻塞–礼让

礼让就是指,有两个线程A\B,此时A在cpu上运行,然后A礼让cpu的位置,然后和B一起重新让cpu进行调度,此时的结果就是A再次上cpu或者B得到礼让然后上cpu进行运行

示例代码
import com.galgame.eriya.pojo.Admin;
import com.galgame.eriya.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.http.HttpResponse;

public class Test1 implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"号线程启动");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"号线程停止");
    }
}

class TestYield{
    public static void main(String[] args) {
        Test1 test1 = new Test1();
        new Thread(test1,"线程1").start();
        new Thread(test1,"线程2").start();
    }
}
线程2号线程启动
线程1号线程启动
线程1号线程停止
线程2号线程停止

这里我们可以看到当2号线程启动后就开始礼让,并礼让成功,然后1号线程上cpu运行并一直运行到结束并未再次礼让,最后当1号结束后2号继续运行

4)线程的阻塞–join

简单来说,就是比如你在做核酸检测的时候明明快要排到你了,但是却来了一群胡搅蛮缠的二流子,因为你打不过他们所以只能等待他们做完后你才能接着做。而转换到线程来说就是,当主线程开始执行后,接收到join的指令后,vip线程就开始上cpu执行而只有等到vip线程执行结束后,主线程才能继续执行

示例代码
import com.galgame.eriya.pojo.Admin;
import com.galgame.eriya.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSenderImpl;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.http.HttpResponse;

public class Test1 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("啊哈哈哈鸡汤来咯~"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Test1 test1 = new Test1();
        Thread thread = new Thread(test1);
        thread.start();

        for (int i = 0; i < 500; i++) {
            if(i==200){
                thread.join();
            }
            System.out.println("主线程"+i);
        }
    }
}


9.线程的优先级

在生活中,我们做任何事都有一个优先级的顺序,比如我们得先把肚子填饱才能去做其他的事情,所以能吃饱饭是优先级最高的事情,其他的事情就比吃饱饭的优先级要低。而换到程序上来看也是一样的,每个线程都有自己的优先级,优先级的不同代表着执行的顺序不同,而优先级又是从1-10这样排序的,而我i们也可以通过getPriority()和setPriority()来查看一个线程的优先级和修改一个线程的优先级。当然,在我们设置了线程的优先级顺序后cpu也不一定完全按照这个优先级来调度

示例代码
public class Test1 implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
    }
}

class ThreadStart{
    public static void main(String[] args){

        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
        Test1 test1 = new Test1();
        Thread thread = new Thread(test1);
        Thread thread1 = new Thread(test1);
        Thread thread2 = new Thread(test1);
        Thread thread3 = new Thread(test1);
        Thread thread4 = new Thread(test1);

        thread.setPriority(4);
        thread.start();

        thread1.setPriority(Thread.MAX_PRIORITY);
        thread1.start();

        thread2.setPriority(6);
        thread2.start();

        thread3.setPriority(2);
        thread3.start();

        thread4.setPriority(Thread.MIN_PRIORITY);
        thread4.start();

    }
}

main-->5
Thread-0-->4
Thread-1-->10
Thread-2-->6
Thread-4-->1
Thread-3-->2

这里我们可以看到主线程一定会在所有线程执行前执行,但是我们之前给线程1设置了最高优先级后任然没有在主线程之后执行,而是由线程0最先执行。由此可见,对于cpu来说线程的优先级更多的是一种参考,真正执行的时候还得看它的心情

10.守护线程

1、线程分为守护线程和用户线程

2、jvm虚拟机必须等待用户线程结束才能结束

3、jvm虚拟机不要要等待守护线程结束就可以结束

示例代码

public class Test1 implements Runnable {
    @Override
    public void run() {
        while (true){
            System.out.println("守护还活着");
        }
    }
}

class You implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("用户线程活了"+i+"天了");
        }
        System.out.println("用户线程死了");
    }
}

class ThreadStart{
    public static void main(String[] args){
        Test1 test1 = new Test1();
        You you = new You();

        Thread thread = new Thread(test1);
        thread.setDaemon(true);
        thread.start();

        Thread thread1 = new Thread(you);
        thread1.start();
    }
}

这段程序的结果是当用户线程结束后没过多久,守护线程也结束了。按理说,守护线程会永远执行下去,但是当jvm停止后它也就停止了,所以这正印证了jvm虚拟机不要要等待守护线程结束就可以结束


11.线程同步和锁

1)同步方法和方法块

我们知道,在实现runnable接口后的线程是不安全的,就比如写一个买票的程序,一共有10张票,三个人去抢,但是可以知道的是一张票三个人都会看到,所以会导致三个人可能都会拿到同一张票在没有同步的程序里,而更直观的是在银行取钱的业务中,你有着100元,你和你的兄弟都要取出这一百元,然后你们同时登录取钱都会显示账户有100元,当两人同时点击取钱按钮后,在没有同步的线程中,你们兄弟二人都会拿到100元。

所以给需要的方法同步,才能有效解决这样的问题。而给方法加上同步的方法则是 public synchronized void way(){}在返回值前添加synchronized即可,而同步方法块只需要在方法里面添加一个synchronized(){}在里面写要同步的方法即可

示例代码--买票
public class Test1 implements Runnable {
    //设置线程状态标志
    private boolean flag =true;
    //设置票数
    int trick=10;


    @Override
    public void run() {
        while(flag){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            buy();
        }
    }

    //购票方法
    public synchronized void buy(){
        //如果票清空则把线程状态设为结束
        if (trick<=0){
            flag=false;
            return;
        }
        System.out.println(Thread.currentThread().getName()+"买到票");
        trick--;
    }
}

class BuyTrick{
    public static void main(String[] args) {
        Test1 test1 = new Test1();
        Thread thread = new Thread(test1,"线程1号");
        Thread thread1 = new Thread(test1, "线程二号");

        thread.start();
        thread1.start();
    }
}

这里我在buy这个方法前面加了synchronized ,这样就会在某一个人买票的时候锁住票,只有当这个人确确实实的拿到了票。且总票数减少后才会再次开放购买

2)死锁

死锁的本质其实就是一个进程占用了一个资源,但是这个资源一次只能被一个进程使用,而另一个进程也占用着一个资源。最后两个进程都需要对方的资源,却又都锁着对方需要的资源,此时就会发生死锁

示例代码
public class Test1 implements Runnable {
    //设置一个进程一次只能被一个进程所调用
    static Phone phone = new Phone();
    static Cpu cpu = new Cpu();

    int choose;//选择
    String name;//选择的进程

    public Test1(int choose, String name) {
        this.choose = choose;
        this.name = name;
    }

    @Override
    public void run() {
        try {
            use();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //进程使用方法
    public void use()throws InterruptedException{
        if(choose==0){
            //当第一个进程开始
            synchronized (phone){//拿到了phone这个资源并上锁
                System.out.println(Thread.currentThread().getName()+"拿到了电话锁");
                Thread.sleep(1000);

                synchronized (cpu){//在拿到phone后想要继续拿到cpu这个资源
                    System.out.println(Thread.currentThread().getName()+"拿到了cpu锁");
                }
            }
        }else{
            synchronized (cpu){//拿到了cpu这个资源并上锁
                System.out.println(Thread.currentThread().getName()+"拿到了cpu锁");
                Thread.sleep(1000);
                synchronized (phone){//在拿到cpu后想要继续拿到phone这个资源
                    System.out.println(Thread.currentThread().getName()+"拿到了电话锁");
                }
            }
        }
    }


}

class Phone{}
class Cpu{}

class Main{
    public static void main(String[] args) {
        Test1 t1 = new Test1(0, "进程1");
        Test1 t2= new Test1(1, "进程2");

        //启动线程
        new Thread(t1).start();
        new Thread(t2).start();
    }
}

Thread-0拿到了电话锁
Thread-1拿到了cpu锁

当执行这段程序时,我们会发现一直卡在Thread-0拿到了电话锁 Thread-1拿到了cpu锁 这里然后就没有动静了,但是程序并没有结束此时就发生了死锁

想要结束死锁,只有破除一下条件的任意一种即可

1、互斥条件:一个资源一次只能被一个进程所调用

2、请求与保持条件:一个进程在请求资源阻塞的时候,对其所持有的资源不会释放

3、不剥夺条件:在进程使用完资源前不会强行剥夺资源

4、若干进程形成头尾相接的循环等待资源的关系

而上面代码想要解除死锁状态很简单,只需要把嵌套顺序改变即可

示例代码
//当第一个进程开始
            synchronized (phone){//拿到了phone这个资源并上锁
                System.out.println(Thread.currentThread().getName()+"拿到了电话锁");
                Thread.sleep(1000);
            }
            synchronized (cpu){//在拿到phone后想要继续拿到cpu这个资源
                System.out.println(Thread.currentThread().getName()+"拿到了cpu锁");
            }

3)lock锁

相比于synchronized方法来说

1、lock是显示锁,没有关闭不会自动关闭,synchronized是隐式锁出了作用域后自动关闭

2、lock只能锁代码块不能锁方法,而synchronized是可以锁方法的

3、lock更高效

示例代码-之前的购票系统的lock版
public class Test1 implements Runnable {
    //
    private final ReentrantLock lock = new ReentrantLock();
    //设置线程状态标志
    private boolean flag =true;
    //设置票数
    int trick=10;


    @Override
    public void run() {
        while(flag){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            buy();
        }
    }

    //购票方法
    public void buy(){
        lock.lock();
        try {
            //如果票清空则把线程状态设为结束
            if (trick<=0){
                flag=false;
                return;
            }
            System.out.println(Thread.currentThread().getName()+"买到票");
            trick--;
        }finally {
            lock.unlock();
        }

    }
}

class BuyTrick{
    public static void main(String[] args) {
        Test1 test1 = new Test1();
        Thread thread = new Thread(test1,"线程1号");
        Thread thread1 = new Thread(test1, "线程二号");

        thread.start();
        thread1.start();
    }
}

12.管程法

本质就是生产者和消费者的问题,进程在原则上是互不干扰的,偶尔是能够在一个进程中设置flag然后停止一个进程。而要两个进程之间又关联就需要一个缓存区,这里就拿鸡的生产和消费来举例子

示例代码
public class Test1 {
    public static void main(String[] args) {
        Buffered buffer = new Buffered();
        //生产者开始
        new Producer(buffer).start();
        //消费者开始
        new Consumer(buffer).start();
    }
}

//生产者类
class Producer extends Thread{
    Buffered buffer;

    public Producer(Buffered buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        //开始生产
        for (int i = 0; i <100 ; i++) {
            try {
                buffer.push(new Chicken(i));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("生产了"+i+"只鸡");
        }
    }
}

//消费者类
class Consumer extends Thread{
    Buffered buffer;

    public Consumer(Buffered buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        //开始消费
        for (int i = 0; i <100 ; i++) {
            try {
                buffer.buy();
                System.out.println("消费了"+buffer.buy().id+"只鸡");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//鸡类
class Chicken{
    int id;

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

//创建缓存区
class Buffered{
    //需要大小
    Chicken[] chickens = new Chicken[10];

    //计数器
    int count=0;
    //需要生产者放入产品
    public synchronized void push (Chicken chicken)throws InterruptedException{
        //如果容器满了,通知消费
     while (count==chickens.length){
         this.wait();
     }
     //如果容器没满,把鸡放进去
     chickens[count]=chicken;
     count++;
     this.notifyAll();
    }

    public synchronized Chicken buy() throws InterruptedException{
        //判断能否消费
        while(count==0){
            System.out.println("不能消费");
            this.wait();
        }

        //缓存区不为0则能消费
        count--;
        Chicken chicken =chickens[count];
        this.notifyAll();
        //吃完了通知在生产
        return chicken;
    }

}

13.线程池

1)线程池的好处

1、把线程都放入一个线程池中后,不需要再频繁的创建和销毁,能够实现用完后再放回

2、效率高

3、降低了资源消耗,并且便于线程的管理

2)线程池的创建

要使用线程池就需要创建ExecutorService接口并使用Executors类中的newFixedThreadPool方法

示例代码
public class Test1 {
    public static void main(String[] args) throws InterruptedException{
        MyThread myThread = new MyThread();
        ExecutorService service = Executors.newFixedThreadPool(10);
        service.execute(myThread);
        service.execute(myThread);
        service.execute(myThread);


        Thread.sleep(3000);
        service.shutdown();

        System.out.println("是否已经关闭"+service.isShutdown());

    }
}

class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"号进程");
    }
}

pool-1-thread-1号进程
pool-1-thread-3号进程
pool-1-thread-2号进程
是否已经关闭true

总结

这估计是我Java篇的最后的文章了,不是不想深入,在校的时候已经自学到微服务了。但是找工作很是碰壁,无奈之下只能转运维了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java多线程 的相关文章

  • python中如何用for循环语句1加到100?

    计算机是现代一种用于高速计算的电子计算机器 xff0c 是一种高级的计算工具 可以进行数值计算 xff0c 又可以进行逻辑计算 xff0c 还具有存储记忆功能 是能够按照程序运行 xff0c 自动 高速处理海量数据的现代化智能电子设备 计算
  • controller层配置全局配置拦截器

    大家好 xff0c 我是一名在算法之路上不断前进的小小程序猿 xff01 体会算法之美 xff0c 领悟算法的智慧 希望各位博友走过路过可以给我点个免费的赞 xff0c 你们的支持是我不断前进的动力 xff01 xff01 加油吧 xff0
  • 用python写DFS和BFS算法

    前言 xff1a 菜鸟学算法 加油 xff01 一 什么是DFS和BFS xff1f 1 BFS算法 xff1a 宽度优先搜索算法 xff08 又称广度优先搜索 xff09 是最简便的图的搜索算法之一 xff0c 这一算法也是很多重要的图的
  • 进程同步 生产者消费者问题

    题目 xff1a 某超级市场 xff0c 可容纳100人同时购物 入口处备有篮子 xff0c 每个购物者可持一只篮子入内购物 出口处结帐 xff0c 并归还篮子 xff08 出 入口仅容一人通过 xff09 请试用P xff08 S xff
  • Windows-取消锁屏密码

    开始 gt 设置 gt 账户 gt 登录选项 gt 密码 gt 输入当前密码 gt 更改密码 xff08 默认为空就行 xff09 gt 下一步 gt 完成
  • 腾讯云4核服务器和2核区别大吗?性能差异

    腾讯云服务器2核和4核性能有什么区别 xff1f 云服务器核数指的是vCPU处理器 xff0c 云服务器CPU核心数如何选择主要取决于用户实际应用情况 xff0c 如果当前应用对CPU计算能力要求不高 xff0c 2核完全可以胜任 xff0
  • 最详细的手工LAMP环境搭建

    环境 xff1a 阿里云服务器ECS xff0c Alibaba Cloud Linux 3 2104 LTS 64位 xff0c 2核 vCPU 2 GiB LAMP 是搭建Web应用时最常用的环境 xff0c LAMP 分别表示 Lin
  • python 语音播报 简单入门

    coding utf 8 import pyttsx3 import time 初始化 pt 61 pyttsx3 init 说什么 pt say 34 你好 xff0c dbirder 34 开始说吧 pt runAndWait time
  • 【optimizer详解】

    optimizer 定义 optimizer就是在深度学习反向传播过程中 xff0c 指引损失函数 xff08 目标函数 xff09 的各个参数往正确的方向更新合适的大小 xff0c 使得更新后的各个参数让损失函数 xff08 目标函数 x
  • docker网络配置

    开放容器端口 执行docker run的时候有个 p选项 xff0c 可以将容器中的应用端口映射到宿主机中 xff0c 从而实现让外部主机可以通过访问宿主机的某端口来访问容器内应用的目的 p选项能够使用多次 xff0c 其所能够暴露的端口必
  • Ubuntu使用SSH工具默认用root用户连接;解决SSH不能用root用户连接。

    ROOT是什么意思 xff1f Root xff0c 也称为根用户 xff0c 是Unix 如 Solaris AIX BSD xff09 和类UNIX系统 如 Linux QNX 等 xff0c 及Android和iOS移动设备系统中的唯
  • LeetCode55. 跳跃游戏(贪心)

    力扣 解题思路 xff1a 1 设想一下 xff0c 对于数组中的任意一个位置 y xff0c 我们如何判断它是否可以到达 xff1f 根据题目的描述 xff0c 只要存在一个位置 x xff0c 它本身 可以到达 xff0c 并且它跳跃的
  • LeetCode300. 最长递增子序列(动态规划 / 贪心)

    力扣 解题思路 xff1a 1 动态规划 状态定义 xff1a dp i 的值代表 nums 以 nums i 结尾的最长子序列长度 转移方程 xff1a 设 j 0 i j 0 i xff0c 考虑每轮计算新 dp i 时 xff0c 遍
  • LeetCode299. 猜数字游戏

    力扣 解题思路 1 使用一个vector flag 记录哪个位置的下的字符相同 xff0c 使用一个Hash map记录secret中剩余字符与次数的映射 2 遍历guess 找到guess中的字符与 secret 相同但是在不同位置的字符
  • 八大排序 - (详解)

    目录 一 直接插入排序 1 思想 2 实现 3 特性总结 二 希尔排序 1 思想 2 实现 3 特性总结 三 选择排序 1 思想 2 实现 3 特性总结 四 堆排序 1 思想 2 实现 3 特性分析 五 冒泡排序 1 思想 2 实现 3 特
  • 牛客 - 另类加法

    另类加法 解题思路 1 二进制位异或运算相当于对应位相加 xff0c 不考虑进位 比如 xff1a 1 1 61 0 gt 1 43 1 61 0 当前位值为0 xff0c 进一位 1 0 61 1 gt 1 43 0 61 1 当前位值为
  • Jmeter添加MD5方法插件

    1 xff1a 下载 https jmeter plugins org install Install 2 xff1a jmeter plugins manager 1 3 jar放到 apache jmeter 5 0 lib ext目录
  • Linux常用指令(详解)

    目录 1 ls指令 2 pwd 3 clear 4 whoami 5 cd 6 tree 7 mkdir 8 touch 9 rmdir 10 rm 11 man 12 cp 13 mv 14 cat 15 more 16 less 17
  • 进程间通信详解

    目录 一 进程间通信介绍 1 进程间通信的目的 2 进程间通信的本质 3 进程间通信分类 二 什么是管道 三 匿名管道 1 匿名管道只能用于具有亲缘关系的进程之间进行通信 xff0c 常用于父子 2 pipe函数 3 匿名管道的使用 4 管
  • 大厂笔试真题

    1 复数相乘 2 K个一组翻转链表 include lt iostream gt include lt vector gt include lt string gt using namespace std void Reverse vect

随机推荐

  • 文件系统概念

    1 文件逻辑结构 1 有结构文件和无结构文件 定长记录 可变长记录 2 顺序文件 3 索引文件 4 索引顺序文件 5 多级索引顺序文件 2 文件目录 1 文件控制块 2 目录结构 3 索引节点 3 文件的物理结构 1 文件块 xff0c 磁
  • Makefile

    1 基本规则 目标 依赖 目标 要生成的目标文件 tab 命令 依赖 目标文件由那些文件生成 命令 通过执行该命令由依赖文件生成目标 举例 add o add c gcc c add c o add c 1 其他规则 目标的时间必须晚于依赖
  • 计算机组成原理测试题

    随堂测试 1 单项选择题 第1题 主频为10MHZ xff0c 则时钟周期为 10ns 100ns xff08 答案 xff09 1000ns 第2题 冯 诺伊曼机工作方式的基本特点是 D 存储器按内容选择地址 C 堆栈操作 B 按地址访问
  • js练习题(3)

    1 序列 xff1a 1 xff0c 2 xff0c 3 xff0c 5 xff0c 8 xff0c 13 找出第20个数是多少 得出前20个数之和是多少 xff1f span class token keyword function sp
  • JAVA 两数求商

    题目描述 xff1a 给定两个整数 a 和 b xff0c 求它们的除法的商 a b xff0c 要求不得使用乘号 除号 以及求余符号 注意 xff1a 整数除法的结果应当截去 xff08 truncate xff09 其小数部分 xff0
  • 约瑟夫环总结

    约瑟夫环 N个人围成一圈 xff0c 从第一个人开始报数 xff0c 报到m的人出圈 xff0c 剩下的人继续从1开始报数 xff0c 报到m的人出圈 xff0c 如此往复 问题一 xff1a 所有人都出圈 xff0c 求出圈的人的编号顺序
  • Linux操作系统实验:生产者和消费者问题

    一 实验目的及要求 生产者消费者 问题是一个著名的同时性编程问题的集合 通过编写经典的 生产者消费者 问题的实验 xff0c 读者可以进一步熟悉 Linux 中多线程编程 xff0c 并且掌握用信号量处理线程间的同步互斥问题 二 实验仪器设
  • COCO数据集解析

    1 简介 官方网站 xff1a http cocodataset org 全称 xff1a Microsoft Common Objects in Context xff08 MS COCO xff09 支持任务 xff1a Detecti
  • U盘启动盘cmd制作

    U盘 移动硬盘启动盘cmd手工制作 插入 gt 61 8G的u盘 移动硬盘 1 win 43 r打开运行窗口 xff0c 输入cmd回车打开命令行 xff08 命令提示符不分大小写 xff09 2 在DOS命令行窗口中输入 diskpart
  • 系统调用的概念和作用

    一 什么是系统调用 xff0c 有何作用 1 概念 用户接口 命令接口 允许用户直接使用 程序接口 允许用户通过程序间接使用 xff1a 由一组系统调用组成 系统调用 系统调用 是操作系统提供给应用程序 xff08 程序员 编程人员 xff
  • linux系统下键盘按键的重新映射——xmodmap工具和xev工具

    虽然linux下小键盘还是不能用 xff0c 但是找到一篇好文章 linux系统下键盘按键的重新映射 xmodmap工具和xev工具 文章目录 1 xev工具 xff1a 2 xmodmap工具 大家会不会有时候 xff0c 感觉键盘上的某
  • Sorry, you have been blocked(Chatgpt登录被屏蔽)

    解决办法 xff1a 关闭浏览器 xff0c 重新打开 vpn换个地区 xff0c 换个美国的vpn
  • Linux查看ip地址出错,ens33不显示inet解决

    在我的私人博客中也有写 xff0c 大家去看看哦 Linux 查看 ip 地址出错 xff0c ens33 不显示 inet 解决 白都 baidu2001 top 问题 xff1a 在 CentOS7中输入 ip addr 时 xff0c
  • Java多线程(超详解)

    目录 1 线程简介 1 1 程序 1 2 进程 1 3 线程 1 4 多线程 1 5 普通方法调用和多线程 2 线程创建 2 1 继承Thread类 2 2 实现Runnable接口 2 3 实现Callable接口 xff08 了解 xf
  • 学习Java的路线

    JavaSE 18 20天 xff09 数据库 xff08 4天 xff09 前端 xff08 7天 xff09 Javaweb xff08 7天 xff09 SSM框架 xff08 9天 xff09 Linux xff08 7天 xff0
  • Java常用类的使用方法小结

    文章目录 前言一 常用类有哪些 xff1f 二 常用类使用方法1 Object类0 xff09 所用的对象1 xff09 getClass方法2 xff09 hasCode方法3 xff09 toString方法4 xff09 equals
  • Java I/O流

    文章目录 前言一 什么是流 xff1f 二 流的分类1 根据流向来分1 xff09 输入流2 xff09 输出流 2 根据单位来分1 xff09 字节流2 xff09 字符流 3 根据功能来分1 xff09 节点流2 xff09 过滤流 三
  • Java 网络编程

    文章目录 前言一 什么是计算机网络 xff1f 二 网络通信的两个重要因素1 通讯双方的地址1 xff09 ip2 xff09 端口号 2 规则 xff1a 网络协议 三 Ip地址1 InetAddress2 Ip的分类1 xff09 ip
  • ScrumMaster的教练职责

    ScrumMaster是Scrum团队的敏捷教练 Ken Rubin说 xff0c 类似于运动团队的教练 xff0c ScrumMaster观察团队使用Scrum的过程 xff0c 帮助团队提高工作绩效 教练不是顾问 xff0c 不提供解决
  • Java多线程

    文章目录 前言一 多线程1 进程 xff0c 线程 xff0c 多线程2 继承Thread类1 xff09 同时下载多个网图通过继承Thread类 3 实现Runnable接口1 xff09 下载多个网图通过实现Runnable接口2 xf