欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

Java多线程

最编程 2024-10-15 07:28:55
...

目录

前言

一、创建线程

1.1 继承Thread类

1.1.1 练习

1.2 实现Runnable接口

1.2.1 练习

1.3 两种方式的对比

二、线程的常用结构

2.1 线程中的构造器

2.2 线程中的常用方法:

2.3 线程的优先级:

2.4 线程的生命周期

三、线程的同步机制

3.1 同步代码块

3.1.1 练习

3.2 同步方法

3.2.1 练习

3.3 synchronized

3.4 小练习

四、懒汉式、死锁和ReentranLock

4.1 懒汉式的线程安全问题

4.2 死锁

4.3 ReentranLock

4.4 思考

五、线程间的通信机制

5.1 详解

5.2 使用实例

5.2.1 实例1

5.2.2 实例2

5.3 wait()和 slemp()的区别?

六、实现Callable和线程池

6.1 Callable

6.2.1 代码示例

6.2 线程池

6.2.1 什么是线程池?

6.2.2 线程池的主要组成部分

6.2.3 如何创建线程池

6.2.4 代码实现


前言

程序、进程和线程的区分:

程序(program):

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

进程(process):

  • 程序的一次执行过程,或是正在内存中运行的应用程序。程序是静态的,进程是动态的
  • 进程作为操作系统调度和分配资源的最小单位。

线程(thread):

  • 进程可进一步细化为线程,是程序内部的一条执行路径。
  • 线程作为CPU调度和执行的最小单位

线程调度:

  • 分时调度:通过将CPU时间划分为多个小的时间片,使得多个用户进程或线程可以“共享”CPU资源。每个进程在其时间片内运行,时间片用完后,操作系统会将该进程挂起,并调度下一个进程。
  • 抢占式调度:允许操作系统在任何时刻中断正在执行的线程或进程,强制其释放CPU,将CPU控制权转移给另一个线程或进程。为了确保高优先级任务能够获得及时的执行。

一、创建线程

1.1 继承Thread类

步骤:

  1. 创建一个继承于Thread类的子类
  2. 重写Thread类的run()--->将此线程要执行的操作,声明在此方法体中
  3. 创建当前Thread的子类的对象
  4. 通过对象调用start():1.启动线程 2.调用当前线程的run()

1.1.1 练习

创建两个分线程,其中一个线程输出100以内的奇数,另一个线程输出100以内的偶数

package duoxiancheng;

class numberTest1 extends Thread {//输出100以内的奇数
    /*@Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName() + ":"+ "--->" + i);
            }
        }
    }*/
}

class numberTest2 extends Thread {//输出100以内的偶数
    /*@Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }*/
}

public class numberTest{
    public static void main(String[] args) {
        new numberTest1(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i % 2 == 0) {
                        System.out.println(Thread.currentThread().getName() + ":"+ "--->" + i);
                    }
                }
            }
        }.start();
        new numberTest2(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i % 2 != 0) {
                        System.out.println(Thread.currentThread().getName() + ":" + i);
                    }
                }
            }
        }.start();
        //分线程
        //分线程
//        n1.run();
//        使用run则,还是单线程,先执行run以后继续执行下面的代码
        /*主线程
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName()+ ":" + i);
            }
        }*/
    }
}

1.2 实现Runnable接口

步骤:

  1. 创建一个实现Runnable接口的类
  2. 实现接口中的run()-->将此线程要执行的操作,声明在此方法体中
  3. 创建当前实现类的对象
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
  5. Thread类的实例调用start():1.启动线程 2.调用当前线程的run()

1.2.1 练习

创建两个分线程,其中一个线程输出100以内的奇数,另一个线程输出100以内的偶数

package duoxiancheng;

import org.junit.Test;

class numberprint implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName()+":"+ "-->"+ i);
            }
        }
    }
}
public class RunnableTest {
    public static void main(String[] args) {
        numberprint a1 = new numberprint();
        Thread t1 = new Thread(a1);
        t1.start();


//        Thread t2 = new Thread(a1);
//        t2.start();

//        创建Runnable接口的匿名实现类的匿名对象
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if (i % 2 != 0) {
                        System.out.println(Thread.currentThread().getName()+":"+ "-->"+ i);
                    }
                }
            }
        }).start();

        for (int i = 0; i < 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName()+":"+ "-->"+ i);
            }
        }
    }
}

1.3 两种方式的对比

共同点:

  • 启动线程,使用的都是Thread类中定义的start()
  • 创建的线程对象,都是Thread类或其子类的实例。

不同点:

  • 一个是类的继承,一个是接口的实现。
  • 建议:建议使用实现Runnable接口的方式。

Runnable方式的好处:

  • 实现的方式,避免的类的单继承的局限性
  • 更适合处理有共享数据的问题。 
  • 实现了代码和数据的分离。

联系: public class Thread implements Runnable (代理模式)

二、线程的常用结构

2.1 线程中的构造器

  • public Thread():分配一个新的线程对象。
  • public Thread(String name):分配一个指定名字的新的线程对象。
  • public Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
  • public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

2.2 线程中的常用方法:

  • start():①启动线程 ②调用线程的run()
  • run():将线程要执行的操作,声明在run()中。
  • currentThread():获取当前执行代码对应的线程
  • getName():获取线程名
  • setName():设置线程名
  • sleep(long millis):静态方法,调用时,可以使得当前线程睡眠指定的毫秒数
  • yield():静态方法,一旦执行此方法,就释放CPU的执行权
  • join():在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行
  • isAlive():判断当前线程是否存活

2.3 线程的优先级:

  • getPriority():获取线程的优先级
  • setPriority():设置线程的优先级。范围[1,10]

Thread类内部声明的三个常量:

  • MAX_PRIORITY(10):最高优先级
  • MIN_PRIORITY(1):最低优先级
  • NORM_PRIORITY(5):普通优先级,默认情况下main线程具有普通优先级

注意:

优先级高或者低,只是有较大的概率被先使用,不代表要先执行完高的才执行低的,(毕竟多核)

代码示例:

package duoxiancheng;

import org.junit.Test;

class numberprint implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(Thread.currentThread().getName()+":"+ "-->"+ i);
            }
//          if (i % 20== 0) {
//              Thread.yield();
//           }
//            效果不是很明显
        }
    }
}
public class RunnableTest {
    public static void main(String[] args) {
        numberprint a1 = new numberprint();
        Thread t1 = new Thread(a1);
        t1.setName("子线程1");
        t1.setPriority(Thread.MIN_PRIORITY);
        t1.start();


//        Thread t2 = new Thread(a1);
//        t2.start();

//        创建Runnable接口的匿名实现类的匿名对象
        new Thread(new Runnable() {
            @Override
            public void run() {
                Thread.currentThread().setName("子线程2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 100; i++) {

                    if (i % 2 != 0) {
                        System.out.println(Thread.currentThread().getName()+":"+ "-->"+ i);
                    }
                }
            }
        }).start();
        
        Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
        Thread.currentThread().setName("主线程");
        for (int i = 0; i < 100; i++) {
            if (i % 2 != 0) {
                System.out.println(Thread.currentThread().getName()+":"+ "-->"+ i);
            }
            if (i == 9) {
                try {
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println("子线程1是否存活: "+t1.isAlive());
    }
}

2.4 线程的生命周期

三、线程的同步机制

3.1 同步代码块

格式

synchronized(同步监视器){
//需要被同步的代码

}

说明:

  • 需要被同步的代码,即为操作共享数据的代码。
  • 共享数据:即多个线程多需要操作的数据。比如:ticket
  • 需要被同步的代码,在被synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其它线程必须等待
  • 同步监视器,俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码。
  • 同步监视器,可以使用任何一个类的对象充当。但是,多个线程必须共用同一个同步监视器

3.1.1 练习

三个窗口,出售100张观影票 包括(继承Thread和实现接口)

package duoxiancheng;

//使用实现接口的方式,实现买票,使用同步代码块解决线程安全问题
/*class SaleTicket implements Runnable{
    int ticket = 100;
    @Override
    public void run() {

        while(true){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (this){

                if (ticket > 0) {

                    System.out.println(Thread.currentThread().getName()+"售票,票号" + ticket);
                    ticket--;

                }else
                    break;
            }
        }
    }
}

public class tongbusuo {
    public static void main(String[] args) {
        SaleTicket s1 = new SaleTicket();

        Thread t1 = new Thread(s1);
        Thread t2 = new Thread(s1);
        Thread t3 = new Thread(s1);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}*/

//使用继承的方法,实现买票,使用同步代码块解决线程安全问题
class SaleTicket extends Thread{
    static int ticket = 100;
    @Override
    public void run() {

        while(true){
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (SaleTicket.class){
                if (ticket > 0) {

                    System.out.println(Thread.currentThread().getName()+"售票,票号" + ticket);
                    ticket--;

                }else
                    break;
            }
        }
    }
}

public class tongbusuo {
    public static void main(String[] args) {
        SaleTicket s1 = new SaleTicket();
        SaleTicket s2 = new SaleTicket();
        SaleTicket s3 = new SaleTicket();

        s1.setName("窗口1");
        s2.setName("窗口2");
        s3.setName("窗口3");

        s1.start();
        s2.start();
        s3.start();
    }
}

3.2 同步方法

说明:

  • 如果操作共享数据的代码完整的声明在了一个方法中,那么可以将此方法声明为同步方法即可。
  • 非静态的同步方法,默认同步监视器是this
  • 静态的同步方法,默认同步监视器是当前类本身。

3.2.1 练习

三个窗口,出售100张观影票 包括(继承Thread和实现接口)

package duoxiancheng;

//使用实现接口的方法,实现买票,使用同步方法,解决线程安全问题
/*
class SaleTicket1 implements Runnable{
    int ticket = 100;
    static boolean isFlag = false;
    @Override
    public void run() {

        while(isFlag){
           show();
        }
    }

    public synchronized void show(){//此时只有一个对象,所有这里的锁指的是 s1
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

            if (ticket > 0) {

                System.out.println(Thread.currentThread().getName()+"售票,票号" + ticket);
                ticket--;

            }else {
                isFlag =false;
            }
    }
}

public class fangfatongbu {
    public static void main(String[] args) {
        SaleTicket s1 = new SaleTicket();

        Thread t1 = new Thread(s1);
        Thread t2 = new Thread(s1);
        Thread t3 = new Thread(s1);

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}
*/

//使用继承的方法,实现买票,使用同步方法,解决线程安全问题
class SaleTicket1 extends Thread{
    static int ticket = 100;
    static boolean isFlag = false;
    @Override
    public void run() {
        while(isFlag){
            show();
        }
    }

    public static synchronized void show(){
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        if (ticket > 0) {

            System.out.println(Thread.currentThread().getName()+"售票,票号" + ticket);
            ticket--;

        }else {
            isFlag =false;
        }
    }
}

public class fangfatongbu {
    public static void main(String[] args) {
        SaleTicket s1 = new SaleTicket();
        SaleTicket s2 = new SaleTicket();
        SaleTicket s3 = new SaleTicket();

        s1.setName("窗口1");
        s2.setName("窗口2");
        s3.setName("窗口3");

        s1.start();
        s2.start();
        s3.start();
    }
}

3.3 synchronized

好处:解决线程的安全问题

弊端:操作共享数据时,多线程的串行执行,说明性能低

3.4 小练习

甲,乙两个用户,分别存款1000.,每个人存3000,最后账户余额为6000

package duoxiancheng;

import java.util.concurrent.Callable;

class Customer extends Thread{
    Account account;

    public Customer(Account account) {
        this.account = account;
    }

    public Customer(Account account,String name) {
        super(name);
        this.account = account;
    }

    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            account.deposit(1000);
        }

    }
}

class Account{
    private double balance;
    public synchronized void deposit(double amt){
        if (amt > 0) {
            balance += amt;

        }
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+ "存款1000,余额为:" + balance);
    }
}

public class AccountTest {
    public static void main(String[] args) {
        Account acct = new Account();

        Customer c1 = new Customer(acct,"甲");
        Customer c2 = new Customer(acct,"乙");

        c1.start();
        c2.start();
    }
}

四、懒汉式、死锁和ReentranLock

4.1 懒汉式的线程安全问题

懒汉式的线程安全问题

结果:

地址不一样,所以使用同步监视器,解决

结果:

代码实现:

package Lock;

public class lanhanshi {

    static Bank b1 = null;
    static Bank b2 = null;

    public static void main(String[] args) {
        Thread t1 = new Thread(){
            @Override
            public void run() {
                b1 = Bank.getInstance();
            }
        };

        Thread t2 = new Thread(){
            @Override
            public void run() {
                b2 = Bank.getInstance();
            }
        };

        t1.start();
        t2.start();

        try {
            t1.join();
        } catch (InterruptedException e) {
            System.out.println(e);
        }

        try {
            t2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println(b1);
        System.out.println(b2);
        System.out.println(b1 == b2);
    }
}

class Bank {

    private Bank(){}
    private static Bank instance = null;
    //方式1
    public static synchronized Bank getInstance(){//同步监视器为Bank类本身
        if (instance == null) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println(e);
            }
            instance = new Bank();
        }
        return instance;
    }

    //方式2
    /*public static  Bank getInstance(){//同步监视器为Bank类本身
        synchronized (Bank.class){
            if (instance == null) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println(e);
                }
                instance = new Bank();
            }
            return instance;
        }

    }*/
    //方式3  优于方式1和方式2
    /*public static  Bank getInstance(){//同步监视器为Bank类本身
        if (instance == null) {
            synchronized (Bank.class) {
                if (instance == null) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        System.out.println(e);
                    }
                    instance = new Bank();
                }
            }

        }
        return instance;
    }*/
}

4.2 死锁

死锁概念:

诱发死锁的原因:

  • 互斥条件
  • 占用且等待
  • 不可抢夺(或不可抢占)
  • 循环等待

以上4个条件,同时出现就会触发死锁。

解决死锁:
死锁一旦出现,基本很难人为干预,只能尽量规避。可以考虑打破上面的诱发条件。

  • 针对条件1:互斥条件基本上无法被破坏。因为线程需要通过互斥解决安全问题。
  • 针对条件2:可以考虑一次性申请所有所需的资源,这样就不存在等待的问题。
  • 针对条件3:占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源。
  • 针对条件4:可以将资源改为线性顺序。申请资源时,先申请序号较小的,这样避免循环等待问题

死锁例子:

package Lock;

public class Deadlock {
    // 定义两个锁
    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {
        // 创建第一个线程
        Thread thread1 = new Thread(() -> {
            synchronized (lock1) { // 先获得锁1
                System.out.println("线程1: 握住lock1...");

                // 模拟一些操作
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("线程1:等待线程2...");
                synchronized (lock2) { // 尝试获得锁2
                    System.out.println("线程1: 获得锁 2!");
                }
            }
        });

        // 创建第二个线程
        Thread thread2 = new Thread(() -> {
            synchronized (lock2) { // 先获得锁2
                System.out.println("线程 2: 握住锁 2...");

                // 模拟一些操作
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println("线程 2: 等待锁 1...");
                synchronized (lock1) { // 尝试获得锁1
                    System.out.println("线程 2: 获得锁 1!");
                }
            }
        });

        // 启动两个线程
        thread1.start();
        thread2.start();
    }
}

4.3 ReentranLock

代码实现:

package Lock;

import java.util.concurrent.locks.ReentrantLock;

//使用继承的方法,实现买票,使用同步代码块解决线程安全问题
class SaleTicket1 extends Thread{
    static int ticket = 100;
    //创建lock的实例,需要确保多个子线程共用一个lock,考虑为final
    private static final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {

        while(true){
            try {
                //锁定堆共享数据局的调用
                lock.lock();

                if (ticket > 0) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"售票,票号" + ticket);
                    ticket--;

                }else
                    break;

            }finally {
                //释放共享数据的锁定
                lock.unlock();
            }
        }
    }
}

public class Deadlock {
    public static void main(String[] args) {
        SaleTicket1 s1 = new SaleTicket1();
        SaleTicket1 s2 = new SaleTicket1();
        SaleTicket1 s3 = new SaleTicket1();

        s1.setName("窗口1");
        s2.setName("窗口2");
        s3.setName("窗口3");

        s1.start();
        s2.start();
        s3.start();
    }
}

4.4 思考

synchronized同步方法 与 Lock的对比:

  • synchronized不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放对同步监视器的调用。
  • Lock是通过两个方法控制需要被同步的代码,更灵活一些。
  • Lock作为接口,提供了多种实现类,适合更多更复杂的场景,效率更高。

五、线程间的通信机制

5.1 详解

1理解

  • 为了让多个线程有规律地共同完成任务,需要使用通信机制来协调它们的工作,以实现对共享数据的操作

2,涉及到三个方法的使用:

  • wait():线程一旦执行此方法,就进入等待状态。同时,会释放对同步监视器的调用
  • notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的那一个线程。(如果被wait()的多个线程的优先级相同随机唤醒一个)。被唤醒的线程从当初被wait的位置继续执行。
  • notifyALL():一旦执行此方法,就会唤醒所有被wait的线程。

注意点:

  • 此三个方法的使用,必须是在同步代码块或同步方法中
  • 此三个方法的调用者,必须是同步监视器。否则,会报IllegalMonitorstateException异常
  • 此三个方法声明在0bject类中。

5.2 使用实例

5.2.1 实例1

使用线程1和线程2 交替打印 1到100

package Lock;


import java.util.concurrent.locks.ReentrantLock;

//使用继承的方法,实现买票,使用同步代码块解决线程安全问题
class number extends Thread{
    static int ticket = 1;
    //创建lock的实例,需要确保多个子线程共用一个lock,考虑为final
    private static final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {

        while(true){

            synchronized (this){

                notify();//唤醒

                if (ticket <= 100) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    System.out.println(Thread.currentThread().getName()+":" + ticket);
                    ticket++;

                    try {
                        wait();//线程执行此方法就进入等待,同时释放对共享资源的调用
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                }else
                    break;
            }

        }
    }
}

public class lockTest {
    public static void main(String[] args) {
        number s = new number();

        Thread t1 = new Thread(s,"线程1");
        Thread t2 = new Thread(s,"线程2");
        t1.start();
        t2.start();
    }
}

5.2.2 实例2

代码实现:

package Lock;


class Clerk{
    private int produces;

    public synchronized void addProduce() {
        if (produces >= 20) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            produces++;
            System.out.println(Thread.currentThread().getName()+"生产了第" + produces+ "个产品");
            //唤醒消费者
            notify();
        }

    }


    public synchronized void minusProduce() {
        if (produces <= 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else{
            System.out.println(Thread.currentThread().getName()+"消费了第" + produces+ "个产品");
            produces--;
            //唤醒生产者
            notify();
        }


    }
}
class Producer extends Thread{
    private Clerk clerk;
    public Producer(Clerk clerk){
        this.clerk = clerk;
    }

    @Override
    public void run() {
        while(true){
            System.out.println("生产者开始生产......");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.addProduce();
        }

    }
}

class Customer extends Thread{

    private Clerk clerk;
    public Customer(Clerk clerk){
        this.clerk = clerk;
    }
    @Override
    public void run() {
        while(true){
            System.out.println("消费者开始消费......");
            try {
                Thread.sleep(250);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.minusProduce();
        }
    }
}

public class Produce {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();

        Producer p1 = new Producer(clerk);
        Customer c1 = new Customer(clerk);
        Customer c2 = new Customer(clerk);

        c1.setName("消费者1");
        c2.setName("消费者2");
        p1.setName("生产者1");

        p1.start();
        c1.start();
        c2.start();

    }
}


5.3 wait()和 slemp()的区别?

相同点:一旦执行,当前线程都会进入阻塞状态

不同点:
声明的位置:

  • wait():声明在0bject类中
  • sleep():声明在Thread类中,静态的

使用的场景不同:

  • wait():只能使用在同步代码块或同步方法中
  • sleep():可以在任何需要使用的场景

使用在同步代码块或同步方法中:

  • wait():一旦执行,会释放同步监视器
  • sleep():一旦执行,不会释放同步监视器

结束阻塞的方式:

  • wait():到达指定时间自动结束阻塞 或 通过被notify唤醒,结束阻塞
  • sleep():到达指定时间自动结束阻塞

六、实现Callable和线程池

6.1 Callable

Callable与Runnable方式的对比

好处

  • call()可以有返回值,更灵活
  • call()可以使用throws的方式处理异常,更灵活
  • Callable使用了泛型参数,可以指明具体的call()的返回值类型,更灵活

缺点:

  • 如果在主线程中需要获取分线程call()的返回值,则此时的主线程是阻塞状态的

6.2.1 代码示例

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class CallableExample {

    public static void main(String[] args) {
        // 创建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        // 创建一个 Callable 任务
        Callable<Integer> task = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                int sum = 0;
                // 计算从1到10的和
                for (int i = 1; i <= 10; i++) {
                    sum += i;
                    Thread.sleep(100); // 模拟一些计算时间
                }
                return sum; // 返回结果
            }
        };

        // 提交任务并获取 Future 对象
        Future<Integer> future = executorService.submit(task);

        try {
            // 获取任务执行的结果
            Integer result = future.get(); // 阻塞直到任务完成
            System.out.println("Sum from 1 to 10 is: " + result);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally {
            executorService.shutdown(); // 关闭线程池
        }
    }
}

6.2 线程池

6.2.1 什么是线程池?

线程池是一个预先创建一定数量的线程,并在需要时复用这些线程的机制。当需要执行任务时,线程池会从池中获取一个空闲线程来执行任务,而不是每次都创建新线程。这种方式可以显著减少资源的消耗和提高性能。

6.2.2 线程池的主要组成部分

  • Executor:执行任务的接口,负责线程的创建、管理和调度。
  • ExecutorServiceExecutor的一个子接口,提供了更高级的功能,如任务提交和生命周期管理。
  • ThreadPoolExecutorExecutorService的一个具体实现,提供了更灵活的线程池配置。

6.2.3 如何创建线程池

使用Java的Executors工厂类来创建不同类型的线程池:

  • 固定大小线程池Executors.newFixedThreadPool(int nThreads)
  • 缓存线程池Executors.newCachedThreadPool()
  • 单线程池Executors.newSingleThreadExecutor()
  • 定时线程池Executors.newScheduledThreadPool(int corePoolSize)

6.2.4 代码实现

package Lock;

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

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        // 提交多个任务
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            executorService.submit(() -> {
                System.out.println("任务 " + taskId + " 正在线程上运行 " + Thread.currentThread().getName());
                try {
                    // 模拟任务执行时间
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("任务 " + taskId + " 已完成");
            });
        }

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

结果: