Skip to content

十九、多线程

一、什么是线程

1、进程

2、线程

3、多线程

应用软件中互相独立,可以同时运行的功能

4、并发和并行

  1. 并发:在同一时刻,有多个指令在单个CPU上交替执行
  2. 并行:在同一时刻,有多个指令在多个CPU上同时执行

二、多线程的实现方式

1、继承Thread类

  1. 定义一个类继承Thread
  2. 重写run方法
  3. 创建子类的对象,并启动线程

MyThread继承Thread

java
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(this.getName()+"     helloworld");
        }
    }
}

main方法调用

java
MyThread mt1=new MyThread();
MyThread mt2=new MyThread();

mt1.setName("线程1");
mt2.setName("线程2");

mt1.start();
mt2.start();

2、实现Runnable接口

  1. 定义一个类实现Runnable接口
  2. 重写run方法
  3. 创建自己类的对象
  4. 创建一个Thread对象,并开启线程

MyRun

java
public class MyRun implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"     helloworld");
        }
    }
}

main方法

java
MyRun mr=new MyRun();

Thread t1=new Thread(mr);
Thread t2=new Thread(mr);

t1.setName("线程1");
t2.setName("线程2");

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

3、利用Callable接口和Future接口

特点:可以获取到多线程运行的结果

  1. 创建一个类MyCallable实现Callable接口

  2. 重写call(是有返回值的,表示多线程运行的结果)

  3. 创建MyCallable的对象(表示多线程要执行的任务)

  4. 创建FutureTask的对象(作用管理多线程运行的结果)

  5. 创建Thread类的对象,并启动(表示线程)

  6. 创建MyCallable的对象

    java
    import java.util.concurrent.Callable;
    
    public class MyCallable implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            int sum=0;
            for (int i = 0; i < 100; i++) {
                sum+=i;
            }
            return sum;
        }
    }
  7. main方法

    java
    MyCallable mc=new MyCallable();
    
    FutureTask<Integer> ft=new FutureTask<>(mc);
    
    Thread t1=new Thread(ft);
    t1.start();
    
    Integer result=ft.get();
    System.out.println(result);

三、常见的成员方法

方法名称说明
String getName()返回此线程的名称
void setName(String name)设置线程的名字(构造方法也可以设置名字)
static Thread currentThread()获取当前线程的对象
static void sleep(long time)让线程休眠指定的时间,单位为毫秒
setPriority(int newPriority)设置线程的优先级
final int getPriority()获取线程的优先级
final void setDaemon(boolean on)设置为守护线程
public static void yield()出让线程/礼让线程
public static void join()插入线程/排队线程

1、getName

四、线程安全问题

1、同步代码块

把操作共享数据的代码锁起来

格式

java
synchronized (锁){		//一般来说,锁是当前类的字节码文件   MyThread.class
    操作共享数据的代码
}
  • 特点1:锁默认打开,有一个进程进去了,锁自动关闭
  • 特点2:里面的代码全部执行完毕,线程出来,锁自动打开

2、同步方法

synchronized关键字加到方法上

格式

java
修饰符 synchronized 返回值类型 方法名 (方法参数) {...}

特点

  1. 同步方法是锁住方法里面所有的代码
  2. 锁对象不能自己指定
    • 非静态的方法,锁是this
    • 静态的方法,锁是当前类的字节码文件对象类名.class

3、Lock锁

Lock提供了获得锁和释放锁的方法

  1. void lock():获得锁
  2. void unlock():释放锁
java
static Lock lock = new ReentrantLock();

@Override
public void run() {
    while(true) {
        lock.lock();
        
        try{
            //操作
        }catch(...){
            
        }finally{
            lock.unlock();
        }
    }
}

五、生产者和消费者(等待唤醒机制)

生产者消费者模式是一个十分经典的多线程协作的模式

1、常见方法

方法名称说明
void wait()当前线程等待,直到被其他线程唤醒
void notify()随机唤醒单个线程
void notifyAll()唤醒所有线程

2、案例一

消费者(消费数据)

  1. 判断桌子上是否有食物
  2. 如果没有就等待
  3. 如果有就开吃
  4. 吃完之后,唤醒厨师继续做

生产者(生产数据)

  1. 判断桌子上是否有食物
  2. 有:等待
  3. 没有:制作食物
  4. 把食物放在桌子上
  5. 叫醒等待的消费者开吃

Desk

java
package a01;

public class Desk {
//    作用:控制生产者和消费者的执行

    //    控制面条的状态,1 有食物 0 无食物
    public static int foodFlag = 0;

    //    面条的个数
    public static int count = 10;

    //    锁对象
    public static Object lock = new Object();
}

Cook

java
package a01;

public class Cook extends Thread {


    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
                if (Desk.count == 0) break;
                else {
                    if (Desk.foodFlag == 1) {
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        Desk.foodFlag = 1;
                        System.out.println(Thread.currentThread().getName() + "做了一碗面条");
                        Desk.lock.notifyAll();
                    }
                }
            }

        }
    }
}

Foodie

java
package a01;

public class Foodie extends Thread {

    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
//                面条上完了
                if (Desk.count == 0) break;
                else {
//                    如果桌子上没有面条,就等待
                    if (Desk.foodFlag == 0) {
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        Desk.count--;
                        Desk.foodFlag = 0;
                        System.out.println(Thread.currentThread().getName() + "吃了一碗面条,还剩" + Desk.count + "碗面条");
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}

ThreadDeom

java
package a01;

public class ThreadDemo {
    public static void main(String[] args) {
        Cook c = new Cook();
        Foodie f = new Foodie();

        c.setName("厨师");
        f.setName("吃货");

        System.out.println("一共" + Desk.count + "碗面条");

        c.start();
        f.start();
    }
}

3、阻塞队列实现等待唤醒机制

Cook

java
package a02;

import java.util.concurrent.ArrayBlockingQueue;

public class Cook extends Thread{

    ArrayBlockingQueue<String> queue;

    public Cook(ArrayBlockingQueue<String> queue){
        this.queue=queue;
    }

    @Override
    public void run() {
        while (true){
            try{
                queue.put("面条");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

Foodie

java
package a02;

import java.util.concurrent.ArrayBlockingQueue;

public class Foodie extends Thread {
    ArrayBlockingQueue<String> queue;

    public Foodie(ArrayBlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                String food = queue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

ThreadDemo

java
package a02;

import java.util.concurrent.ArrayBlockingQueue;

public class ThreadDemo {
    public static void main(String[] args) {
        ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);

        Cook c = new Cook(queue);
        Foodie f = new Foodie(queue);

        c.start();
        f.start();
    }
}

六、线程的状态

  1. 新建状态:创建线程对象
  2. 就绪对象:start方法
  3. 阻塞状态:无法获得锁对象
  4. 等待状态:wait方法
  5. 计时等待:sleep方法
  6. 结束状态:全部代码运行完毕

七、多线程练习题

1、卖电影票

一共有1000张电影票,可以在两个窗口领取,假设每次领取的时间为3000毫秒,要求:请用多线程模拟卖票过程并打印剩余电影票的数量

MyThread

java
package a03;

public class MyThread extends Thread {

    static int total = 1000;

    @Override
    public void run() {
        while (true) {
            synchronized (MyThread.class) {
                if (total == 0) {
                    break;
                } else {
                    try {
                        Thread.sleep(30);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    total--;
                    System.out.println(Thread.currentThread().getName() + "卖出一张票,还剩" + (1000 - total) + "张票");
                }

            }
        }
    }
}

ThreadDemo

java
package a03;

public class ThreadDemo {
    public static void main(String[] args) {
        MyThread m1 = new MyThread();
        MyThread m2 = new MyThread();

        m1.setName("窗口1");
        m2.setName("窗口2");

        m1.start();
        m2.start();
    }
}

2、送礼品

有100份礼品,两人同时发送,当剩下的礼品小于10份的时候则不再送出,利用多线程模拟该过程并将线程的名字和礼物的剩余数量打印出来

MyRunnable

java
public class MyThread implements Runnable {

    int total = 100;

    @Override
    public void run() {
        while (true) {
            synchronized (MyThread.class) {
                if (total < 10) break;
                else {
                    total--;
                    System.out.println(Thread.currentThread().getName() + "发送了礼品,还剩" + total + "份");
                }
            }
        }
    }
}

RunnableDemo

java
public class ThreadDemo {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr, "窗口1");
        Thread t2 = new Thread(mr, "窗口2");

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

3、打印奇数数字

同时开启两个线程,共同获取1-100之间的所有数字。 要求:将输出所有的奇数。

MyCallable

java
package a05;

import java.util.ArrayList;
import java.util.concurrent.Callable;

public class MyCallable implements Callable<ArrayList<Integer>> {

    static ArrayList<Integer> result = new ArrayList<>();

    @Override
    public ArrayList<Integer> call() throws Exception {
        for (int i = 0; i < 100; i++) {
            synchronized (MyCallable.class) {
                if ((i + 1) % 2 == 1) result.add(i + 1);
            }
        }
        return result;
    }
}

ThreadDemo

java
package a05;

import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class ThreadDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable mc = new MyCallable();
        FutureTask<ArrayList<Integer>> ft = new FutureTask<>(mc);
        Thread t1 = new Thread(ft);
        Thread t2 = new Thread(ft);

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

        System.out.println(ft.get());


    }
}

4、抢红包

微信中的抢红包也用到了多线程。

假设:100块,分成了3个包,现在有5个人去抢。

其中,红包是共享数据。

5个人是5条线程。

打印结果如下:

XXX抢到了XXX元

XXX抢到了XXX元

XXX抢到了XXX元

XXX没抢到

XXX没抢到

MyThread

java
package a06;

import java.util.Random;

public class MyThread extends Thread {

    static int count = 3;
    static double money = 100;

    static final double MIN = 0.01;

    @Override
    public void run() {
        synchronized (MyThread.class) {
            if (count == 0) {
                System.out.println(getName() + "没有抢到红包!!!");
            } else {
                double price = 0;
                if (count == 1) {
                    price = money;

                } else {
                    Random r = new Random();
//                    指定范围
                    double bounds = money - (count - 1) * MIN;
                    price = r.nextDouble(bounds);

                    if (price < MIN) price = MIN;


                }
                money -= price;
                count--;
                System.out.println(getName() + "抢到了" + price + "元红包");

            }

        }
    }
}

main

java
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
MyThread mt3 = new MyThread();
MyThread mt4 = new MyThread();
MyThread mt5 = new MyThread();

mt1.setName("同学1");
mt2.setName("同学2");
mt3.setName("同学3");
mt4.setName("同学4");
mt5.setName("同学5");

mt1.start();
mt2.start();
mt3.start();
mt4.start();
mt5.start();

八、线程池

1、Executors

Executors :线程池的工具类通过调用方法返回不同类型的线程池对象

方法名称说明
public static ExecutorService newCachedThreadPool创建一个没有上限的线程池
public static ExecutorService newCachedThreadPool(int nThreads)创建有上限的线程池

MyRunnable

java
public class MyRunnable implements Runnable{}

PoolDemo

java
//创建任务池
ExecutorService pool = Executors.newFixedThreadPool();

//提交任务
pool.submit(new MyRunnable());

//销毁线程池
pool.shutdown();

2、ThreadPoolExecutor

格式

java
ThreadPoolExecutor pool = new ThreadPoolExecutor(
	3,//corePoolSize:核心线程数量,能小于0
    6,//maximumPoolSize:最大线程数,不能小于0,最大数量>=核心线程数量
    TimeUnit.SECONDS,//空闲线程最大存活时间
    new ArrayBlockingQueue<>(3),//任务队列
    Executors.defaultThreadFactory(),//创建线程工厂
    new ThreadPoolExecutor.AbortPolicy() //任务的拒绝策略
)

3、最大并行数

java
int i = Runtime.getRuntime().availableProcessors();
System.out.println(i);

4、线程池多少合适

  1. CPU密集型运算:最大并行数+1
  2. I/O密集型运算:最大并行数*期望CPU利用率*总时间(CPU计算时间+等待时间)/CPU计算时间