文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

JAVA多线程详解(超详细)

2023-08-19 21:18

关注

一、线程简介

1、进程、线程

2、并发、并行、串行

3、进程的三态

进程在运行的过程中不断的改变其运行状态。通常一个运行的进程必须有三个状态,就绪态、运行态、阻塞态。

二、线程实现

1、继承Thread类

 步骤: - 自定义线程类继承Thread类 - 重写run()方法,编写线程执行体 - 创建线程对象,调用start()方法启动线程(启动后不一定立即执行,抢到CPU资源才能执行)

代码如下(示例):

// 自定义线程对象,继承Thread,重写run()方法public class MyThread extends Thread {    public MyThread(String name){        super(name);    }    @Override    public void run() {        // 线程执行体        for (int i = 0; i < 10; i++) {            System.out.println("我是自定义" + Thread.currentThread().getName() + "--" + i);        }    }    public static void main(String[] args) {        // main线程,主线程        // 创建线程实现类对象        MyThread thread = new MyThread("线程1");        MyThread thread2 = new MyThread("线程2");        // 调用start()方法启动线程        thread.start();        thread2.start();        for (int i = 0; i < 10; i++) {            System.out.println("我是主线程--" + i);        }    }}
执行结果:
我是自定义线程2--0我是自定义线程2--1我是主线程--0我是自定义线程1--0我是主线程--1我是主线程--2我是自定义线程2--2我是主线程--3我是自定义线程1--1我是主线程--4我是主线程--5我是主线程--6我是主线程--7我是主线程--8我是主线程--9我是自定义线程2--3我是自定义线程1--2我是自定义线程2--4我是自定义线程1--3我是自定义线程1--4我是自定义线程1--5我是自定义线程1--6我是自定义线程1--7我是自定义线程1--8我是自定义线程1--9我是自定义线程2--5我是自定义线程2--6我是自定义线程2--7我是自定义线程2--8我是自定义线程2--9

2、实现Runnable接口

 步骤: - 自定义线程类实现Runnable接口 - 实现run()方法,编写线程体 - 创建线程对象,调用start()方法启动线程(启动后不一定立即执行,抢到CPU资源才能执行)

代码如下(示例):

// 自定义线程对象,实现Runnable接口,重写run()方法public class MyRunnable implements Runnable {    @Override    public void run() {        // 线程执行体        for (int i = 0; i < 10; i++) {            System.out.println("我是自定义" + Thread.currentThread().getName() + "--" + i);        }    }    public static void main(String[] args) {        // main线程,主线程        // 创建实现类对象        MyRunnable myRunnable = new MyRunnable();        // 创建代理类对象        Thread thread = new Thread(myRunnable,"线程1");        Thread thread2 = new Thread(myRunnable,"线程2");        // 调用start()方法启动线程        thread.start();        thread2.start();        for (int i = 0; i < 10; i++) {            System.out.println("我是主线程--" + i);        }    }}

执行结果:

我是主线程--0我是自定义线程1--0我是自定义线程2--0我是自定义线程1--1我是主线程--1我是自定义线程1--2我是自定义线程2--1我是自定义线程1--3我是主线程--2我是主线程--3我是自定义线程1--4我是自定义线程2--2我是自定义线程2--3我是自定义线程2--4我是自定义线程1--5我是自定义线程1--6我是主线程--4我是自定义线程1--7我是自定义线程1--8我是自定义线程1--9我是自定义线程2--5我是自定义线程2--6我是自定义线程2--7我是自定义线程2--8我是主线程--5我是自定义线程2--9我是主线程--6我是主线程--7我是主线程--8我是主线程--9

3、实现Callable接口(不常用)

 步骤: - 实现Callable接口,先要返回值类型 - 重写call()方法,需要抛出异常 - 创建目标对象 - 创建执行服务:ExecutorService ser = Executor.newFixedThreadPool(1); - 提交执行:Future res = ser.submit(t1); - 获取结果:boolean r1 = res.get(); - 关闭服务:ser.shutdownNow();

代码如下(示例):

import java.util.concurrent.*;// 自定义线程对象,实现Callable接口,重写call()方法public class MyThread implements Callable<Boolean> {    @Override    public Boolean call() throws Exception {        // 线程执行体        for (int i = 0; i < 10; i++) {            System.out.println("我是自定义" + Thread.currentThread().getName() + "--" + i);        }        return true;    }    public static void main(String[] args) throws ExecutionException,        InterruptedException {        // main线程,主线程        // 创建线程实现类对象        MyThread thread = new MyThread();        MyThread thread2 = new MyThread();        // 创建执行服务,参数是线程池线程数量        ExecutorService ser = Executors.newFixedThreadPool(2);        // 提交执行        Future<Boolean> res = ser.submit(thread);        Future<Boolean> res2 = ser.submit(thread2);        // 获取结果        boolean r1 = res.get();        boolean r2 = res2.get();        // 关闭服务        ser.shutdownNow();    }}
执行结果:
我是自定义pool-1-thread-1--0我是自定义pool-1-thread-2--0我是自定义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我是自定义pool-1-thread-2--1我是自定义pool-1-thread-1--6我是自定义pool-1-thread-2--2我是自定义pool-1-thread-2--3我是自定义pool-1-thread-2--4我是自定义pool-1-thread-2--5我是自定义pool-1-thread-2--6我是自定义pool-1-thread-2--7我是自定义pool-1-thread-2--8我是自定义pool-1-thread-2--9我是自定义pool-1-thread-1--7我是自定义pool-1-thread-1--8我是自定义pool-1-thread-1--9

三、线程常用方法

1、线程的状态

2、线程常用方法

1、 sleep(Long time)方法: - 让线程阻塞的指定的毫秒数。 - 指定的时间到了后,线程进入就绪状态。 - sleep可研模拟网络延时,倒计时等。 - 每一个对象都有一个锁,sleep不会释放锁。

代码如下(示例):

public class MyThread implements Runnable {    @Override    public void run() {        // 模拟倒计时        for (int i = 10; i >= 0; i--) {            try {                System.out.println(i);                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    public static void main(String[] args) {        MyThread myThread = new MyThread();        Thread thread = new Thread(myThread);        thread.start();    }}
执行结果:
109876543210
2、yield()方法: - 提出申请释放CPU资源,至于能否成功释放取决于JVM决定。 - 调用yield()方法后,线程仍然处于RUNNABLE状态,线程不会进入阻塞状态。 - 调用yield()方法后,线程处于RUNNABLE状态,就保留了随时被调用的权利。

代码如下(示例):

public class MyThread implements Runnable {    @Override    public void run() {        System.out.println(Thread.currentThread().getName() + "线程开始执行");        Thread.yield();        System.out.println(Thread.currentThread().getName() + "线程结束执行");    }    public static void main(String[] args) throws InterruptedException {        MyThread myThread = new MyThread();        Thread thread = new Thread(myThread,"a");        Thread thread2 = new Thread(myThread,"b");        thread.start();        thread2.start();    }}
执行结果:从结果1看,a释放CPU成功后,b就抢到了CPU执行权,接着b也释放CPU成功,a抢到了CPU执行权;从结果2看,a并没有成功释放CPU。
结果1:a线程开始执行b线程开始执行a线程结束执行b线程结束执行结果2:a线程开始执行a线程结束执行b线程开始执行b线程结束执行
3、join()方法: - 将当前的线程挂起,当前线程阻塞,待其他的线程执行完毕,当前线程才能执行。 - 可以把join()方法理解为插队,谁插到前面,谁先执行。

代码如下(示例):

public class MyThread implements Runnable {    @Override    public void run() {        for (int i = 0; i < 10; i++) {            System.out.println(Thread.currentThread().getName() + "join()线程执行:" + i);        }    }    public static void main(String[] args) throws InterruptedException {        MyThread myThread = new MyThread();        Thread thread = new Thread(myThread,"a");        thread.start();        for (int i = 0; i < 10; i++) {            System.out.println("主线程执行:" + i);            if (i == 2) {                thread.join(); //主线程阻塞,等待thread一口气执行完,主线程才能继续执行            }        }    }}
执行结果:
主线程执行:0a线程join()执行:0主线程执行:1主线程执行:2a线程join()执行:1a线程join()执行:2a线程join()执行:3a线程join()执行:4a线程join()执行:5a线程join()执行:6a线程join()执行:7a线程join()执行:8a线程join()执行:9主线程执行:3主线程执行:4主线程执行:5主线程执行:6主线程执行:7主线程执行:8主线程执行:9
4、setPriority (int newPriority)、getPriority()- 改变、获取线程的优先级。- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。- 线程的优先级用数据表示,范围1~10。- 线程的优先级高只是表示他的权重大,获取CPU执行权的几率大。- 先设置线程的优先级,在执行start()方法。

代码如下(示例):

public class MyThread implements Runnable {    @Override    public void run() {        System.out.println(Thread.currentThread().getName() + "线程优先级:"            + Thread.currentThread().getPriority());    }    public static void main(String[] args) throws InterruptedException {        MyThread myThread = new MyThread();        Thread thread = new Thread(myThread,"a");        Thread thread2 = new Thread(myThread,"b");        Thread thread3= new Thread(myThread,"c");        Thread thread4= new Thread(myThread,"d");        thread3.setPriority(Thread.MAX_PRIORITY);        thread.setPriority(Thread.MIN_PRIORITY);        thread2.setPriority(Thread.NORM_PRIORITY);        thread4.setPriority(8);        thread.start();        thread2.start();        thread3.start();        thread4.start();    }}

执行结果:优先级高的线程不一定先执行

c线程优先级:10b线程优先级:5a线程优先级:1d线程优先级:8
5、stop()、destroy()。【已废弃】- JDK提供的上述两种方法已废弃,不推荐使用。- 推荐线程自动停止下来,建议使用一个标识位变量进行终止,当flag=false时,则终止线程运行。

代码如下(示例):

public class MyThread implements Runnable {        private boolean flag = true;    public boolean isFlag() {        return flag;    }    public void setFlag(boolean flag) {        this.flag = flag;    }    int i = 0;    @Override    public void run() {        while (flag) {            System.out.println(Thread.currentThread().getName() + "线程:" + i ++);        }    }    public static void main(String[] args) throws InterruptedException {        MyThread myThread = new MyThread();        Thread thread = new Thread(myThread,"a");        thread.start();        for (int i = 0; i < 10; i++) {            System.out.println("主线程:" + i);            if (i == 5) {                // 当主线程 i== 5时,标识位变为false,控制子线程停止                myThread.setFlag(false);             }        }    }}

执行结果:主线程 i== 5 之后,子线程就停止运行了

主线程:0主线程:1a线程:0a线程:1a线程:2a线程:3a线程:4a线程:5a线程:6主线程:2a线程:7主线程:3a线程:8主线程:4a线程:9主线程:5a线程:10主线程:6主线程:7主线程:8主线程:9

四、多线程

线程同步就是线程排队,就是操作共享资源要有先后顺序,一个线程操作完之后,另一个线程才能操作或者读取。

1、守护(Deamon)线程

代码如下(示例):

public class MyThread{    public static void main(String[] args) throws InterruptedException {        DeamonThread deamon = new DeamonThread();        UserThread user = new UserThread();        Thread deamonThread = new Thread(deamon);        deamonThread.setDaemon(true); // 设置为守护进程        deamonThread.start();        Thread userThread = new Thread(user);        userThread.start();    }}// 模拟守护线程class DeamonThread implements Runnable{    @Override    public void run() {        // 验证虚拟机不用等待守护线程执行完毕,只要用户线程执行完毕,程序就结束。        // 如果成功,怎下面的打印不会一直输出;如果成功,则下面的打印会一直输出        while (true) {            System.out.println("我是守护线程");        }    }}// 用户线程class UserThread implements Runnable{    @Override    public void run() {        for (int i = 0; i < 10; i++) {            System.out.println("我是用户线程 :" + i);        }    }}

执行结果:守护进程不会一直打印

我是守护线程我是用户线程 :0我是守护线程...我是用户线程 :1我是守护线程...我是用户线程 :2我是用户线程 :3我是用户线程 :4我是用户线程 :5我是用户线程 :6我是用户线程 :7我是用户线程 :8我是用户线程 :9我是守护线程...我是守护线程

2、多线程并发与同步

(1)、多线程并发
在多线程场景下,如果多个线程修改同一个资源,或者一个线程修改共享资源,另一个线程读取共享资源,可能会导致结果不对的问题,这就导致线程不安全,即并发。导致线程并发的原因:

下面以两个例子演示线程不安全问题。
示例1:买票问题

代码如下(示例):

// 模拟线程不安全问示例1:买票public class MyThread{    public static void main(String[] args) throws InterruptedException {        BuyTicker ticker = new BuyTicker();        Thread person1Thread = new Thread(ticker, "person1");        Thread person2Thread = new Thread(ticker, "person2");        Thread person3Thread = new Thread(ticker, "person3");        person1Thread.start();        person2Thread.start();        person3Thread.start();    }}class BuyTicker implements Runnable{    // 车票    private int tickerNum = 10;    // 停止线程标识    boolean flag = true;    @Override    public void run() {        while (flag) {            try {                buyTicker();            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    private void buyTicker() throws InterruptedException {        // 判断是否还有票        if (tickerNum <= 0) {            flag = false;            return;        }        // 模拟延时        Thread.sleep(100);        // 买票        System.out.println(Thread.currentThread().getName() + "买到第" + tickerNum -- + "张票");    }}

执行结果:可以看到第4、3张票卖了两次,还有人买到了第0张票

person3买到第8张票person2买到第10张票person1买到第9张票person1买到第7张票person3买到第5张票person2买到第6张票person1买到第4张票person2买到第4张票person3买到第3张票person1买到第2张票person2买到第2张票person3买到第1张票person1买到第0张票

示例2:银行取钱

代码如下(示例):

// 模拟线程不安全示例2:银行取钱public class MyThread{    public static void main(String[] args) throws InterruptedException {        Account account = new Account(1000, "旅游基金");        new Bank(account, 500, "你").start();        new Bank(account, 600, "女朋友").start();    }}// 账户class Account {    // 账户总余额    int money;    // 账户名    String name;    public Account(int money, String name) {        this.money = money;        this.name = name;    }}// 银行class Bank extends Thread{    // 客户账户    Account account;    // 取得钱数    int drawingMoney;    public Bank(Account account, int drawingMoney, String name) {        super(name);        this.account = account;        this.drawingMoney = drawingMoney;    }    @Override    public void run() {        if (account.money- drawingMoney <= 0) {            System.out.println(account.name+ "钱不够了,取不了了");            return;        }        try {            Thread.sleep(500);        } catch (InterruptedException e) {            e.printStackTrace();        }        // 卡内余额 = 余额 - 取得钱        account.money = account.money - drawingMoney;        System.out.println(Thread.currentThread().getName()  + "取了" + drawingMoney);        System.out.println(account.name + "余额为:" + account.money);    }}

执行结果:当你取500时,线程执行到account.money = account.money - drawingMoney之前,另一个线程抢到了CPU执行权,也执行到account.money = account.money - drawingMoney之前,现在余额还是1000,继续执行1000-500-900=-400.

你取了500女朋友取了900旅游基金余额为:-400旅游基金余额为:-400

(2)、同步(解决并发问题)

解决线程并发问题的方法是线程同步线程同步就是让线程排队,就是操作共享资源要有先后顺序,一个线程操作完之后,另一个线程才能操作或者读取。

解决并发问题的两种方法:
同步方法:public synchronized void method(int args){执行体…}

同步代码块:synchronized (Obj){执行体…}

示例1:买票问题(使用同步方法改造成线程安全)

代码如下(示例):

// 模拟线程不安全问示例1:买票public class MyThread{    public static void main(String[] args) throws InterruptedException {        BuyTicker ticker = new BuyTicker();        Thread person1Thread = new Thread(ticker, "person1");        Thread person2Thread = new Thread(ticker, "person2");        Thread person3Thread = new Thread(ticker, "person3");        person1Thread.start();        person2Thread.start();        person3Thread.start();    }}class BuyTicker implements Runnable{    // 车票    private int tickerNum = 10;    // 停止线程标识    boolean flag = true;    @Override    public void run() {        while (flag) {            try {                buyTicker();            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    private synchronized void buyTicker() throws InterruptedException {        // 判断是否还有票        if (tickerNum <= 0) {            flag = false;            return;        }        // 模拟延时        Thread.sleep(100);        // 买票        System.out.println(Thread.currentThread().getName() + "买到第" + tickerNum -- + "张票");    }}

执行结果:

person1买到第10张票person1买到第9张票person1买到第8张票person1买到第7张票person1买到第6张票person1买到第5张票person3买到第4张票person3买到第3张票person3买到第2张票person2买到第1张票

示例2:银行取钱(使用同步代码块改造成线程安全)

代码如下(示例):

// 模拟线程不安全示例2:银行取钱public class MyThread{    public static void main(String[] args) throws InterruptedException {        Account account = new Account(1000, "旅游基金");        new Bank(account, 500, "你").start();        new Bank(account, 600, "女朋友").start();    }}// 账户class Account {    // 账户总余额    int money;    // 账户名    String name;    public Account(int money, String name) {        this.money = money;        this.name = name;    }}// 银行class Bank extends Thread{    // 客户账户    Account account;    // 取得钱数    int drawingMoney;    public Bank(Account account, int drawingMoney, String name) {        super(name);        this.account = account;        this.drawingMoney = drawingMoney;    }    @Override    public void run() {        synchronized (account) {            if (account.money- drawingMoney < 0) {                System.out.println(account.name+ "钱不够了,取不了了");                return;            }            try {                Thread.sleep(500);            } catch (InterruptedException e) {                e.printStackTrace();            }            // 卡内余额 = 余额 - 取得钱            account.money = account.money - drawingMoney;            System.out.println(Thread.currentThread().getName()  + "取了" + drawingMoney);            System.out.println(account.name + "余额为:" + account.money);        }    }}

执行结果:你取了500后,你女朋友取600,就提示余额不足,不会出现余额为负数的情况了。这里的同步监视器是account,account才是操作的共享资源,而不是bank。

你取了500旅游基金余额为:500旅游基金钱不够了,取不了了

示例3:模拟集合ArrayList<>()是线程不安全的

代码如下(示例):

import java.util.ArrayList;import java.util.List;// 模拟集合ArrayList<>()是线程不安全的public class MyThread{    public static void main(String[] args) throws InterruptedException {        List<String> list = new ArrayList<>();        for (int i = 0; i < 1000; i++) {            new Thread(() -> {                list.add(Thread.currentThread().getName());            }).start();        }// sleep可研模拟网络延迟        Thread.sleep(5000);        System.out.println(list.size());    }}

执行结果:list的大小应该是1000,结果是999

999

使用同步代码块改造成线程安全的

代码如下(示例):

import java.util.ArrayList;import java.util.List;// 模拟线程不安全示例2:银行取钱public class MyThread{    public static void main(String[] args) throws InterruptedException {        List<String> list = new ArrayList<>();        for (int i = 0; i < 1000; i++) {            new Thread(() -> {                // 加锁                synchronized (list) {                    list.add(Thread.currentThread().getName());                }            }).start();        }        Thread.sleep(5000);        System.out.println(list.size());    }}

执行结果:

1000

3、死锁

(1)死锁形成的原因:多个线程各自占有一个资源,并且相互等待其他线程占有的资源才能运行,从而导致另个或者多个线程都在等待对方释放资源,都停止了执行。某一个同步代码块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。

代码如下(示例):

// 死锁例子:鱼和熊不可兼得public class MyThread{    public static void main(String[] args) throws InterruptedException {        Person personA = new Person(0, "猎人A");        Person personB = new Person(1, "猎人B");        personA.start();        personB.start();    }}// 熊掌class Bear {}// 鱼class Fish {}// 人class Person extends Thread {    // 保证资源只有一份    public static Bear bear = new Bear();    public static Fish fish = new Fish();    int choose;    String personName;    public Person (int choose, String personName) {        this.choose = choose;        this.personName = personName;    }    @Override    public void run() {        // 捕猎        try {            this.hunting();        } catch (InterruptedException e) {            e.printStackTrace();        }    }    // 捕猎方法    private void hunting() throws InterruptedException {        if (choose == 0) {            synchronized (bear) {                System.out.println(personName + "想捕捉熊");                Thread.sleep(1000);                synchronized (fish) {                    System.out.println(personName + "想捕捉鱼");                }            }        } else {            synchronized (fish) {                System.out.println(personName + "想捕捉鱼");                Thread.sleep(1000);                synchronized (bear) {                    System.out.println(personName + "想捕捉熊");                }            }        }    }}

执行结果:两个线程一直阻塞,都在等在对方释放锁,结果导致死锁。

猎人A想捕捉熊猎人B想捕捉鱼

(2)解决死锁的方法:同步代码块中不要相互嵌套,即,不要相互嵌套锁。

代码如下(示例):

// 死锁例子:鱼和熊不可兼得public class MyThread{    public static void main(String[] args) throws InterruptedException {        Person personA = new Person(0, "猎人A");        Person personB = new Person(1, "猎人B");        personA.start();        personB.start();    }}// 熊掌class Bear {}// 鱼class Fish {}// 人class Person extends Thread {    // 保证资源只有一份    public static Bear bear = new Bear();    public static Fish fish = new Fish();    int choose;    String personName;    public Person (int choose, String personName) {        this.choose = choose;        this.personName = personName;    }    @Override    public void run() {        // 捕猎        try {            this.hunting();        } catch (InterruptedException e) {            e.printStackTrace();        }    }    // 捕猎方法    private void hunting() throws InterruptedException {        if (choose == 0) {            synchronized (bear) {                System.out.println(personName + "想捕捉熊");                Thread.sleep(1000);            }            // 把嵌套的代码块拿到外面,两个代码块并列            synchronized (fish) {                System.out.println(personName + "想捕捉鱼");            }        } else {            synchronized (fish) {                System.out.println(personName + "想捕捉鱼");                Thread.sleep(1000);            }            // 把嵌套的代码块拿到外面,两个代码块并列            synchronized (bear) {                System.out.println(personName + "想捕捉熊");            }        }    }}

执行结果:两个线程即捕到了熊,有捕到了鱼,解决了死锁问题。

猎人B想捕捉鱼猎人A想捕捉熊猎人A想捕捉鱼猎人B想捕捉熊

4、Lock(锁)

Lock 锁也称同步锁,java.util.concurrent.locks.Lock 机制提供了⽐ synchronized 代码块和 synchronized ⽅法更⼴泛的锁定操作,同步代码块 / 同步⽅法具有的功能 Lock 都有,除此之外更强⼤,更体现⾯向对象。
创建对象 Lock lock = new ReentrantLock() ,加锁与释放锁⽅法如下:
public void lock() :加同步锁
public void unlock() :释放同步锁

synchronized和Lock的对比:

代码如下(示例):

import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;// 测试 Lock锁public class MyThread{    public static void main(String[] args) throws InterruptedException {        TestLock testLock = new TestLock();        new Thread(testLock,"a").start();        new Thread(testLock,"b").start();        new Thread(testLock,"c").start();    }}class TestLock implements Runnable {    // 车票    private static int tickerNum = 10;    // 创建一个Lock锁    private final Lock lock = new ReentrantLock();    @Override    public void run() {        while (true) {            lock.lock(); // 加锁            try {                // 判断是否还有票                if (tickerNum > 0) {                    // 模拟延时                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    // 买票                    System.out.println(Thread.currentThread().getName() + "线程买到第" + tickerNum -- + "张票");                } else {                    break;                }            } finally {                lock.unlock(); // 解锁            }        }    }}

执行结果:

a线程买到第10张票a线程买到第9张票a线程买到第8张票a线程买到第7张票a线程买到第6张票a线程买到第5张票b线程买到第4张票b线程买到第3张票b线程买到第2张票b线程买到第1张票

5、线程协作

应用场景:生产者和消费者问题

场景分析:这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。

在生产者消费者问题中,没生产出产品之前,消费者是不能消费的,反之,消费者没消费完之前,生产者是不能生产的。这就需要来实现线程之间的同步。仅有同步还不行,还要实现线程之间的消息传递,即通信

Java提供了解决线程之间通信问题的方法:

方法名作用
wait ()表示线程一直等待,直到其他线程通知,与 sleep 不同会释放锁
wait (long timeOut)指定等待的毫秒数
notify ()唤醒一个处于等待状态的线程
notifyAll()唤醒同一个对象所有的调用 wait()方法的线程,优先级高的优先调度

注意:均是Object的方法,均只能在同步方法或者同步代码块中使用,否则会抛出异常IIIegalMonitorStageException。

解决线程之间通信的方式管程法信号灯法

管程法:生产者把生产好的数据放入缓存区,消费者从缓存区中拿出数据。
在这里插入图片描述

代码如下(示例):

// 线程通信:生产消费模式-管程法public class MyThread{    public static void main(String[] args) {        SynContainer container = new SynContainer();        new Productor(container).start();        new Consumer(container).start();    }}// 产品class Chicken {    int id;    public Chicken (int id) {        this.id = id;    }}// 生产者class Productor extends Thread {    SynContainer synContainer;    public Productor(SynContainer synContainer) {        this.synContainer = synContainer;    }    @Override    public void run() {        for (int i = 0; i < 20; i++) {            synContainer.pushTo(new Chicken(i));        }    }}// 消费者class Consumer extends Thread {    SynContainer synContainer;    public Consumer(SynContainer synContainer) {        this.synContainer = synContainer;    }    @Override    public void run() {        for (int i = 0; i < 20; i++) {            synContainer.popTo();        }    }}// 容器class SynContainer {    // 定义一个大小为10的容器    Chicken[] chickens = new Chicken[10];    // 容器计数器    int count;    // 生产者生产产品方法    public synchronized void pushTo(Chicken chicken) {        // 如果容器满了,就停止生产        if (chickens.length == count) {            try {                this.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        // 如果容器没满,就往容器中放入产品        chickens[count] = chicken;        System.out.println("生产了" + chicken.id + "个鸡腿");        count ++;        // 通知消费者消费        this.notifyAll();    }    // 消费者消费产品方法    public synchronized Chicken popTo() {        // 如果容器中没有产品了,就停止消费        if (count == 0) {            try {                this.wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        // 如果容器有产品,就可以消费        count --;        Chicken chicken = chickens[count];        System.out.println("消费了第" + chicken.id + "个鸡退");        //只要消费了,就通知生产者生产        this.notifyAll();        return chicken;    }}

执行结果:

生产了0个鸡腿生产了1个鸡腿生产了2个鸡腿生产了3个鸡腿生产了4个鸡腿生产了5个鸡腿生产了6个鸡腿生产了7个鸡腿生产了8个鸡腿生产了9个鸡腿消费了第9个鸡退生产了10个鸡腿消费了第10个鸡退生产了11个鸡腿消费了第11个鸡退生产了12个鸡腿消费了第12个鸡退生产了13个鸡腿消费了第13个鸡退生产了14个鸡腿消费了第14个鸡退生产了15个鸡腿消费了第15个鸡退生产了16个鸡腿消费了第16个鸡退生产了17个鸡腿消费了第17个鸡退消费了第8个鸡退消费了第7个鸡退消费了第6个鸡退消费了第5个鸡退消费了第4个鸡退消费了第3个鸡退消费了第2个鸡退消费了第1个鸡退消费了第0个鸡退生产了18个鸡腿生产了19个鸡腿消费了第19个鸡退消费了第18个鸡退

信号灯法:设置一个标识位,标识位来判断线程是等待还是执行。

代码如下(示例):

// 线程通信:生产消费模式-信号灯法public class MyThread{    public static void main(String[] args) {        CCTV cctv = new CCTV();        new Player(cctv).start();        new Watcher(cctv).start();    }}// 演员class Player extends Thread {    CCTV cctv;    public Player(CCTV cctv) {        this.cctv = cctv;    }    @Override    public void run() {        for (int i = 0; i < 10; i++) {            if (i%2 == 0) {                cctv.play("快乐大本营");            } else {                cctv.play("天天向上");            }        }    }}// 观众class Watcher extends Thread {    CCTV cctv;    public Watcher(CCTV cctv) {        this.cctv = cctv;    }    @Override    public void run() {        for (int i = 0; i < 10; i++) {            cctv.watch();        }    }}// 电视class CCTV {    // 表演的节目    String voice;    // 标识    boolean flag = true;    // 表演节目    public synchronized void play(String voice) {        if (!flag) {            try {                this.wait();            } catch (Exception e) {                e.printStackTrace();            }        }        this.voice = voice;        System.out.println("演员表演了:" + voice);        // 通知观众观看        this.notifyAll();        this.flag = !flag;    }    // 观看节目    public synchronized void watch () {        if (flag) {            try {                this.wait();            } catch (Exception e) {                e.printStackTrace();            }        }        // 如果容器有产品,就可以消费        System.out.println("观众观看了:" + voice);        // 通知演员表演节目        this.notifyAll();        this.flag = !flag;    }}

执行结果:

演员表演了:快乐大本营观众观看了:快乐大本营演员表演了:天天向上观众观看了:天天向上演员表演了:快乐大本营观众观看了:快乐大本营演员表演了:天天向上观众观看了:天天向上演员表演了:快乐大本营观众观看了:快乐大本营演员表演了:天天向上观众观看了:天天向上演员表演了:快乐大本营观众观看了:快乐大本营演员表演了:天天向上观众观看了:天天向上演员表演了:快乐大本营观众观看了:快乐大本营演员表演了:天天向上观众观看了:天天向上

6、线程池

背景:经常创建和销毁线程,消耗特别大的资源,比如并发的情况下的线程,对性能影响很大。线程池就是问题为了解决这个问题,提前创建好多个线程,放在线程池中,使用时直接获取,使用完放回线程池中,可以避免频繁的创建、销毁,实现重复利用。
优点

线程池相关的API:

代码如下(示例):

import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;// 线程池public class ThreadPool {    public static void main(String[] args) {        // 1、创建服务,创建线程池        ExecutorService service = Executors.newFixedThreadPool(10);        MyThread myThread = new MyThread();        // 执行        service.execute(myThread);        service.execute(myThread);        service.execute(myThread);        service.execute(myThread);        // 关闭连接        service.shutdown();    }}// 演员class MyThread implements Runnable {    @Override    public void run() {        System.out.println(Thread.currentThread().getName());    }}

执行结果:

pool-1-thread-2pool-1-thread-4pool-1-thread-3pool-1-thread-1

来源地址:https://blog.csdn.net/qq_29141913/article/details/125964815

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯