多线程

多线程

1、线程简介

1.1、Process与Thread

进程与线程

  • 说起进程,就不得不说一下程序。程序时指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
  • 而进程则是执行程序的一次执行的过程,它是一个动态的概念。是系统资源分配的单位
  • 通常在一个线程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
  • 总结:一个程序只有一个进程,一个进程必须至少包含一个线程,线程可以有多个,而程序执行的是线程
注意:很多多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错局。

1.2、核心概念

  • 线程就是独立的执行路径
  • 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
  • main()称之为主线程,为系统的入口,用于执行整个程序
  • 在一个进程中,如果开辟了多个线程,线程的运行由调度器(CPU)安排调度,调度器是与操作系统紧密相关的,先后顺序是不能认为的干预的。
  • 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
  • 线程会带来额外的开销,如CPU调度时间,并发控制开销
  • 每个线程在自己的工作内存交换,内存控制不当会造成和数据不一致。

2、线程创建

三种创建方式

Thread class		继承Thread类(重点)

Runnable接口		  实现Runnable接口

callable接口		  实现Callable接口(了解)

2.1、Thread继承实现

  • 自定义线程继承Thread类
  • 重写run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程

代码参考:

/**
 * @author 丶Anton
 * @date 2021/4/14 16:58
 * 总结:注意,线程开启不一定立即执行,由CPU调度执行。
 */
public class HelloThread extends Thread{

    @Override
    public void run(){
        //线程体
        for (int i = 0; i < 20; i++) {
            System.out.println("我在主线程敲代码~~~~~~~~~~~~");
        }
    }

    public static void main(String[] args) {
        //main线程,主线程

        //创建一个线程对象
        HelloThread HelloThread = new HelloThread();

        //调用线程对象中的start方法开启线程
        HelloThread.start();

        for (int i = 0; i < 200; i++) {
            System.out.println("我是主线程,我在敲代码!");
        }
    }
}

总结:注意,线程开启不一定立即执行,由CPU调度执行。

2.1.1、练习 网络图片下载

需要导入包 **commons-io-1.3.2.jar **

工具类 FileUtils

代码参考:

package lib;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

/**
 * @author 丶Anton
 * @date 2021/4/14 17:19
 * 联系Thread,实现多线程下载图片
 */
public class TestThread2 extends Thread {

    private String url;         //网络图片地址
    private String name;        //保存的文件名

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

    @Override
    public void run(){
        //线程体

        //调用网络文件下载方法下载
        WebDownloader.Downloader(url, name);
        System.out.println("下载了文件:" + name);
    }

    public static void main(String[] args) {
        TestThread2 t2 = new TestThread2("https://static.runoob.com/images/mix/plief.jpg", "程序人生.jpg");
        TestThread2 t3 = new TestThread2("https://static.runoob.com/images/mix/maxresdefault.jpg", "编程是一门艺术.jpg");
        TestThread2 t1 = new TestThread2("https://static.runoob.com/images/mix/plief.jpg", "程序人生1.jpg");

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

class WebDownloader{
    /**
     * 下载方法
     * @param url   网页URL
     * @param name  要保存的路径
     */
    public static void Downloader(String url, String name){
        try {
            //参数:  URL, 为文件取名
            FileUtils.copyURLToFile(new URL(url), new File(name));
        } catch (IOException e) {
            System.out.println("Downloader方法出现问题,IO异常!");
        }
    }
}

总结:执行顺序是按照CPU调度执行的,一句话一切看CPU心情。

2.2、Runnable接口实现 【推荐】

步骤:

  1. 创建一个类,实现Runnable接口
  2. 重写run方法
  3. 在主线程中创建一个Runnable实现类对象
  4. 创建Thread线程对象,将Runnable实现类对象丢进Thread线程对象中
  5. 调用start方法,开启线程
/**
 * @author 丶Anton
 * @date 2021/4/14 17:51
 */
public class TestRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println("我是小小天才程序员,我在学习多线程");
        }
    }

    public static void main(String[] args) {
        //创建一个Runnable实现类对象
        Runnable Runnable = new TestRunnable();
        //将Runnable实现类对象丢进 Thread构造器创建一条新线程
        Thread thread = new Thread(Runnable);
        //开启线程
        thread.start();

        for (int i = 0; i < 200; i++) {
            System.out.println("我是程序员,我在看小天才学习多线程~~~~~~");
        }
    }
}

总结:本质上还是跟继承Thread类一样。

2.3、小结

  • 继承Thread类
    • 子类继承Thread类具备多线程能力
    • 启动线程:子类对象.start()
    • 不建议使用:避免OOP单继承局限性
  • 实现Runnable接口
    • 实现Runnable接口具有多线程能力
    • 启动线程:传入目标对象+Thread对象.start()
    • 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
2.4、线程问题

代码:

package com.itanton;

/**
 * @author 丶Anton
 * @date 2021/4/14 19:46
 * 火车票案列
 */
public class RunnableCase implements Runnable{

    private int ticket;

    public RunnableCase(int ticket) {
        this.ticket = ticket;
    }

    public RunnableCase() {
    }

    public static void main(String[] args) {
        RunnableCase Case = new RunnableCase(10);

        //创建三个进程同时抢票
        new Thread(Case).start();
        new Thread(Case).start();
        new Thread(Case).start();
    }

    @Override
    public void run() {
        while(ticket > 1){
            System.out.println(Thread.currentThread().getName() + "拿到了第" + ticket + "张票");
            ticket--;
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

Thread-1拿到了第10张票
Thread-0拿到了第10张票
Thread-2拿到了第10张票
Thread-0拿到了第7张票
Thread-2拿到了第6张票
Thread-1拿到了第5张票
Thread-0拿到了第4张票
Thread-2拿到了第3张票
Thread-1拿到了第3张票

总结:如果多个线程同时使用同一个对象的话,会引发线程安全问题,此处就会用到 synchronized锁来解决,后面再仔细介绍锁的使用。

3、静态代理模式【扩展】

总结:可以使用静态代理的前提是代理方买家同时实现一个接口,买家将事情丢给代理方去做,代理方再将做事情,做事后交付即可。用现实生活中的例子可以叫做找中介

代码参考:

/**
 * @author 丶Anton
 * @date 2021/4/14 21:06
 * 静态代理
 */
public class buyACar implements buyACarInterface {
    @Override
    public void buy() {
        System.out.println("小罗买到车了,超级开心");
    }

    public static void main(String[] args) {
        //创建买车人
        buyACar buyACar = new buyACar();

        //买车人将买车的事情交给 4儿子店去做
        _4SShop _4SShop = new _4SShop(buyACar);
        //4儿子点帮忙买车
        _4SShop.buy();

    }
}
//买车接口
interface buyACarInterface {
    void buy();
}
//4儿子店
class _4SShop implements buyACarInterface{
    //用于接收客户的
    private buyACarInterface buyACarInterface;

    public _4SShop(com.itanton.buyACarInterface buyACarInterface) {
        this.buyACarInterface = buyACarInterface;
    }

    //买车前
    private void before(){
        System.out.println("4儿子进货");
    }

    //买车后
    private void after(){
        System.out.println("送保险");
    }

    //代理买车
    @Override
    public void buy() {
        before();
        this.buyACarInterface.buy();
        after();
    }
}

4、线程状态

线程有5大状态_看图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IwqnsxC0-1618477829332)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210414212128620.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NkhbEIUs-1618477829334)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210414212349022.png)]

4.1、线程方法

方法说明
setPriority(int newPriority)更改线程优先级
static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠
void join()等待该线程终止
static void yield()暂停当前正在执行的线程对象,并执行其他线程
boolean isAlive()测试线程是否处于活动状态

4.2、停止线程

  • 不推荐使用JDK提供的stop()、destroy()方法。【已废弃】
  • 推荐线程自己停止下来
  • 建议使用一个标志位进行终止变量,当flag=false,则终止线程运行
  • 建议线程正常停止 --> 利用次数,不建议死循环
/**
 * @author 丶Anton
 * @date 2021/4/14 21:38
 */
public class ThreadStop {
    //用于停止线程的标志位
    private static boolean flag = true;
    //设置停止标志位
    public static void stop(){
        flag = false;
    }

    public static void main(String[] args) {
        int i = 0;
        
        new Thread(() -> {
            while(flag){
                System.out.println("干活干活");
            }
        }).start();

        for(; i < 100; i++){
            System.out.println(Thread.currentThread().getName() + "当前:" + i);
            if(i == 90){
                stop();
                System.out.println("该线程停止了");
            }
        }
    }
}

4.3、线程休眠

  • sleep(时间)指定当前线程阻塞的毫秒数
  • sleep存在异常interruptedException
  • sleep时间达到后线程进入就绪状态
  • sleep可以模拟网络延时,倒计时,放大问题的发生性等
  • 每个对象都有一个锁,sleep不会释放锁
//模拟倒计时
public TestSleep{
    
    public static void main(){
        int i = 10;
        while(i > 0){
            //停止1秒
            try{
                Thread.sellp(1000);
            } catch(interruptedException e){
                e.printStackTrace();
            }
            i--;
            System.out.println(i);
        }
        
    }
    
}

4.4、线程礼让

  • 礼让线程,让当前正在执行的线程停止,但不阻塞
  • 将线程从运行状态转换位就绪状态
  • 让CPU重新调度,礼让不一定成功,看CPU心情

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YxLcg2AX-1618477829336)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20210414221731740.png)]

public class ThreadYield {
    public static void main(String[] args) {
        new Thread(()->{
            System.out.println("P-Go");
            Thread.yield();     //线程礼让,但不阻塞,当线程回来时继续执行下面代码
            System.out.println("P-END");
        }).start();

        new Thread(()->{
            System.out.println("C-Go");
            Thread.yield();     //线程礼让
            System.out.println("C-END");
        }).start();
    }
}

4.4、线程强制执行【插队】

  • Join合并线程,待此线程执行完毕后,再执行其他线程,其他线程阻塞
  • 可以想象成插队
/**
 * @author 丶Anton
 * @date 2021/4/14 22:22
 * 线程强制执行
 */
public class ThreadJoin implements Runnable {
    public static void main(String[] args) {
        Thread thread = new Thread(new ThreadJoin());

        thread.start();

        for (int i = 0; i < 10; i++) {
            if (i == 5) {
                try {
                    //等待该线程执行完毕才可以退出  插队
                    //所有线程都必须等待我执行完毕
                    thread.join();  
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(i);
        }


    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + ",i=" + i);
        }
    }
}

4.5、观测线程状态

  • NEW 初始状态
  • RUNNABLE 运行时状态
  • BLOCKED 阻塞于锁状态
  • WAITING 等待状态
  • TIMED_WAITING 等待超时状态
  • TERMINATED 死亡状态(当线程死亡后就不可再次启动了,一个线程只可以启动 一次)
/**
 * @author 丶Anton
 * @date 2021/4/15 11:24
 * 观察线程状态
 */
public class ThreadState {

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {
            for (int i = 0; i < 2; i++) {
                  Thread.sleep(100);  //线程睡眠
            }
            System.out.println("即将死亡");
        });


        Thread.State state = thread.getState(); //获取线程状态
        System.out.println(state);
        thread.start();     //启动线程


        state = thread.getState();  //更新线程状态
        System.out.println(state);

        //如果thread线程没有死亡则一直运行
        while( state != Thread.State.TERMINATED ){
            state = thread.getState();  //更新线程状态
            System.out.println(state);
            Thread.sleep(100);
        }
    }
}

总结:

  • NEW初始状态,在线程被创建时就会存在
  • RUNNABLE状态,当线程启动时(Thread.start())
  • TIMED_WAITING / WAITING状态,当CPU调度或则被线程插队时,则会切换至
  • BLOCKED状态,当两个线程使用同一个锁,其中一个阻塞时,则阻塞那个线程则会转成次状态
  • TERMINATED状态,当线程执行完所有代码

4.6、线程优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
  • 线程优先级用数字表示,范围从1~10.
    • ​ Thread.MIN_PRIORITY = 1
    • ​ Thread.MAX_PRIORITY = 10;
    • ​ Thread.NORM_PRIORITY = 5;
  • 使用一下方式改变获取优先级
    • ​ getPriority()
    • ​ setPriority(int x)
/**
 * @author 丶Anton
 * @date 2021/4/15 11:42
 * 测试线程优先级
 * 总结:线程优先级高的不一定会先执行,但是大多数情况下会先执行
 */
public class ThreadPriority implements Runnable {

    public static void main(String[] args) {

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

        //设置线程优先级(先设置再启动)
        t1.setPriority(1);t2.setPriority(5);t3.setPriority(10);

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

    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + "运行了,线程优先级:" + thread.getPriority());
    }
}

总结:线程优先级高的不一定会先执行,但是大多数情况下会先执行

4.7、守护(daemon)线程

  • 线程分为用户线程守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如:后天记录操作日志,监控内存,垃圾回收等待…
/**
 * @author 丶Anton
 * @date 2021/4/15 12:11
 * 线程守护     -->     上帝守护着你
 */
public class ThreadDefend {

    public static void main(String[] args) {
        //上帝
        Thread god = new Thread(() -> {
            while(true){
                System.out.println("上帝守护着你");
            }
        });


        //你
        Thread you = new Thread(() -> {
            for (int i = 10; i > 0; i--) {
                System.out.println("你还剩" + i + "天生命");
            }
            System.out.println("========Goodbye,World!========");
        });

        //设置守护线程(上帝)
        god.setDaemon(true);

        //启动线程
        god.start();
        you.start();
    }
}

总结:当用户线程死亡时,守护线程也会跟着死亡,不过守护死亡的时候会根据虚拟机关闭的时间产生一定延时。

5、线程同步【重点】

  • 多个线程操作同一个资源
  • 并发:同一个对象多个线程同时操作
  • 处理多个线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这个时候我们就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
  • 由于同一进程的多个线程共享同一块存储空间,在带啦地方病的同时,也带来了访问冲突问题,为了保证数据在方法被访问时的正确性,在访问时加入锁机制synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用后释放锁即可,存在以下问题:
    • 一个线程持有锁会导致其他所有需要此锁的线程挂起;
    • 在多线程竞争下,枷锁,释放锁会导致比较多的上下文切换 和 调度延迟,引发起性能问题;
    • 如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒置,引起性能问题。
/**
 * @author 丶Anton
 * @date 2021/4/15 13:24
 * 线程同步
 */
public class ThreadSyn {

    public static void main(String[] args) {
        Ticket ticket  = new Ticket(10);
        new Thread(ticket).start();
        new Thread(ticket).start();
        new Thread(ticket).start();
    }

}

class Ticket implements Runnable {

    private int ticketNum;		//票
    private boolean flag = true;    //停止标志位

    public Ticket(int ticketNum) {
        this.ticketNum = ticketNum;
    }
    //买票
    public void bug(){
        if(ticketNum == 0){ //当无票了,则停止线程
            flag = false;
            return;
        }
        System.out.println(Thread.currentThread().getName() + "买到第:" + ticketNum-- + "张票");
    }

    @Override
    public void run() {
        Thread.currentThread();
        while(flag){
            //同步代码块
            synchronized (this) {
                bug();
            }
        }
    }
    
    //方式二
    private synchronized void bug(){
        if(ticketNum == 0){
            flag = false;
            return;
        }
        System.out.println(Thread.currentThread().getName() + "买到第:" + ticketNum-- + "张票");
    }
    @Override
    public void run() {
        Thread.currentThread();
        while(flag){
            bug();
        }
    }
    
}
/**
 * @author 丶Anton
 * @date 2021/4/15 13:57
 * 银行取钱问题
 */
public class ThreadBankWithdrawal {

    public static void main(String[] args) {
        Account account = new Account(50);

        User user1 = new User(0,account, 50);
        User user2 = new User(0,account, 50);

        new Thread(user1).start();
        new Thread(user2).start();
    }

}

//账户
class Account{
    public int totalAmount;

    public Account(int totalAmount) {
        this.totalAmount = totalAmount;
    }
}

//用户
class User implements Runnable {
    //当前用户手里的钱
    private int money;
    //账户上的钱
    private Account account;
    //要取的钱
    private int getMonet;


    public User(int money, Account account, int getMonet){
        this.money = money;
        this.account = account;
        this.getMonet = getMonet;
    }


    @Override
    public void run() {
        synchronized (account) {
            if (account.totalAmount < getMonet) {
                System.out.println("余额不足,无法取钱.剩余:" + account.totalAmount);
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.totalAmount -= getMonet;
            money += getMonet;
            System.out.println(Thread.currentThread().getName() + "取钱成功,账户余额:" + account.totalAmount);
            System.out.println(Thread.currentThread().getName() + "手里的前:" + money);
        }

    }
}

总结:synchronized默认锁的是this,同步代码块可以锁任何对象

5.1、死锁

多个线程自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步块同时拥有“两个以上对象锁”时,就可能会发生“死锁”现象

一句话:多个线程互相抱着对方需要的资源,然后形成僵持。

产生死锁代码:

public class ThreadDeadlock {

    public static void main(String[] args) {
        String a = "挖掘机";
        String b = "跑车";

        new Thread(() -> {
            String threadName = Thread.currentThread().getName();
            synchronized (a) {
                System.out.println(threadName + "拿到:" + a + "\n准备拿:" + b);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //A锁中抱着Therda-0想要拿到的锁a,又想同时拿到b锁
                //线程执行到这里时就会产生僵持状态
                synchronized (b) {
                    System.out.println(threadName + "拿到:" + b);
                }
            }
        }).start();

        new Thread(() -> {
            String threadName = Thread.currentThread().getName();
            synchronized (b) {
                System.out.println(threadName + "拿到:" + b + "\n准备拿:" + a);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //B锁中抱着Therda-0想要拿到的锁b,又想同时拿到a锁
                //线程执行到这里时就会产生僵持状态
                synchronized (a) {
                    System.out.println(threadName + "拿到:" + a);
                }
            }
        }).start();
    }
}

解决方案

/**
 * @author 丶Anton
 * @date 2021/4/15 14:56
 */
public class ThreadDeadlock {

    public static void main(String[] args) {
        String a = "挖掘机";
        String b = "跑车";

        new Thread(() -> {
            String threadName = Thread.currentThread().getName();
            synchronized (a) {
                System.out.println(threadName + "拿到:" + a + "\n准备拿:" + b);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //让Thread-0释放a锁,  同时让Thread-1释放b锁,作为交换
            synchronized (b) {
                System.out.println(threadName + "拿到:" + b);
            }
        }).start();

        new Thread(() -> {
            String threadName = Thread.currentThread().getName();
            synchronized (b) {
                System.out.println(threadName + "拿到:" + b + "\n准备拿:" + a);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //让Thread-0释放a锁,  同时让Thread-1释放b锁,作为交换
            synchronized (a) {
                System.out.println(threadName + "拿到:" + a);
            }
        }).start();
    }
}

总结死锁避免方法:

  • 产生死锁的四个必要条件
    • 互斥条件:一个资源每次只能被一个进程使用
    • 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
    • 不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺
    • 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
  • 以上四个条件,只需要破其中的任意一种就可以避免死锁发生

5.2、Lock锁

  • 从JDK5.0开始,Java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock对象充当
  • Java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
  • ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁
/**
 * @author 丶Anton
 * @date 2021/4/15 15:21
 */
public class ThreadLock implements Runnable {

    private final ReentrantLock ReentrantLock = new ReentrantLock();
    private int ticket = 10;


    public static void main(String[] args) {
        ThreadLock ThreadLock = new ThreadLock();
        new Thread(ThreadLock).start();
        new Thread(ThreadLock).start();
        new Thread(ThreadLock).start();
    }

    @Override
    public void run() {
        try {
            //加锁
            ReentrantLock.lock();
            while (ticket > 0) {
                System.out.println(Thread.currentThread().getName() + ":获取票:" + ticket--);
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //解锁
            ReentrantLock.unlock();
        }
    }
}

总结:lock锁只可以锁代码块,不可以锁方法。标准方法必须在try findlly里写。不管运行如果线程都必须解锁。

5.3、Lock锁与sunchronized锁的对比

Lock是显式锁手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,除了作用于自动释放

Lock只有代码块锁,synchronized有代码块和方法锁

使用Lock锁,JVM将花费更少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)

优先顺序:

​ Lock --> 同步代码块(已经进入方法体,分配了相应资源) --> 同步方法(在方法体之外)

6、线程协作通信

6.1、生产者消费者问题

  • 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费
  • 如果仓库在红没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。
  • 如果仓库中放有产品,则消费则可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止
6.1、问题分析:

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

  • 对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费
  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。
  • 在生产者消费者问题中,仅有synchronized是不够的
    • ​ synchronized可组织并法更新同一个共享资源,实现了同步
    • ​ synchronized不能实现不同线程之间的消息传递(通信)

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

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

以上方法全部是Object类的方法

/**
 * @author 丶Anton
 * @date 2021/4/15 15:48
 * 生产者消费者问题解决
 */
public class ProductionAndConsumption {

    public static void main(String[] args){
        Depot Depot = new Depot();
        new Thread(new Producer(Depot)).start();
        new Thread(new Consumer(Depot)).start();
    }


}

//生产者
class Producer implements Runnable {
    private Depot depot;

    public Producer(Depot depot){
        this.depot = depot;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                depot.producer();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

//消费者
class Consumer implements Runnable {

    private Depot depot;

    public Consumer(Depot depot){
        this.depot = depot;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                depot.consumer();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


//仓库
class Depot{
    public boolean flag = false;
    public List list = new ArrayList();

    //生产
    public synchronized void producer() throws InterruptedException {
        //如果仓库中有产品
        if(flag){
            this.wait();    //睡眠
        }
        //生产
        for (int i = 0; i < 10; i++) {
            list.add(i);
            System.out.println(Thread.currentThread().getName() + "生产了:" + i);
        }
        flag = !flag;
        //生产完毕  唤醒用户
        this.notifyAll();
    }

    //消费
    public synchronized void consumer() throws InterruptedException {
        //如果仓库没中有产品
        if(!flag){
            this.wait();    //睡眠
        }
        //消费
        int i = 0;
        while(list.size() > 0){
            System.out.println(Thread.currentThread().getName() + "消费了:" + list.remove(i));
        }
        flag = !flag;
        //生产完毕  唤醒生产
        this.notifyAll();
    }
}

7、线程池

  • JDK5.0起提供了线程池相关API:ExecutorService和Executprs
  • ExecutorService:真正的线程池接口。常见子类ThreadPooExecutor
    • ​ void execute(Runnable command): 执行任务/命令,没有返回值,一般用来执行Runnable
    • ​ Future submit(Callable task):执行任务,有返回值,一般用来执行Callable
    • ​ void shutdown():关闭线程池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
/**
 * @author 丶Anton
 * @date 2021/4/15 16:57
 */
public class ThreadPool {

    public static void main(String[] args) {
        //在池子中放入10个线程
        ExecutorService service = Executors.newFixedThreadPool(10);
        //执行 任务1
        service.execute( () -> {
            System.out.println(1);
        });
        //执行 任务2
        service.execute( () -> {
            System.out.println(2);
        });

        //关闭连接池
        service.shutdown();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hello,Anton

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值