多线程

多线程技术概述

线程与进程

进程

  • 是指一个内存中运行的应用程序,每个进程都有一个独立的空间

线程

  • 是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行。一个进程最少有一个线程。
  • 线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分若干个线程。

线程调度

分时调度

  • 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。

抢占式调度

  • 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
  • CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核心而言,某个时刻,只能执行一个线程,而CPU在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。其实,多线程程度并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

同步与异步

同步:排队执行,效率低但是安全。

异步:同步执行,效率高但是数据不安全。

并发与并行

并发:指两个或多个事件在同一个时间段内发生。

并行:指两个或多个事件在同一时刻发生(同时发生)。


Thread

常用的构造方法:

构造器描述
Thread()分配新的Thread对象
Thread(Runnable target)分配新的Thread对象
Thread(Runnable target, String name)分配新的Thread对象
Thread(String name)分配新的Thread对象

常用的方法:

变量和类型方法描述
longgetId()返回此Thread的标识符
StringgetName()返回此线程的名称
intgetPriority()返回此线程的优先级
voidrun()如果此线程是使用单独的Runnable运行对象构造的,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回。
voidsetName(String name)将此线程的名称更改为等于参数 name 。
voidsetPriority(int newPriority)更改此线程的优先级。
static voidsleep(long millis)导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数,具体取决于系统计时器和调度程序的精度和准确性。
static voidsleep(long millis,int nanos)导致当前正在执行的线程休眠(暂时停止执行)指定的毫秒数加上指定的纳秒数,具体取决于系统定时器和调度程序的精度和准确性。
voidstart()导致此线程开始执行;Java虚拟机调用此线程的run()方法

线程的生命周期

线程的生命周期存在五个状态:新建、就绪、运行、阻塞、终止。

在这里插入图片描述

新建:采用new语句创建完成

就绪:执行start后

运行:占用CPU时间

阻塞:执行了wait语句、执行了sleep语句和等待某个对象锁,等待输入的场合

终止:退出了run()语句

创建线程

有两种方法可以创建新的执行线程。

一种是将类声明为Thread的子类,此子类可以覆盖类Thread的run方法,然后可以分配和启动子类的实例。

public class MyThread extends Thread {
    /**
     * run方法就是线程要执行的任务方法
     */
    @Override
    public void run(){
        //这里的代码 就是一条新的执行路径
        //这个执行路径的触发方式,不是调用run方法,而是通过thread对象的start()方法来启动任务
        for(int i=0;i<10;i++){
            System.out.println("锄禾日当午"+i);
        }
    }
}

public class Demo {
    /**
     * 多线程技术
     */
    public static void main(String[] args) {
        MyThread m = new MyThread();
        m.start();
        for(int i=0;i<10;i++) {
            System.out.println("汗滴禾下土" + i);
        }
    }
}

运行结果有很多种情况,主要是看谁先抢到时间片,因为Java使用的是抢占式调度。
运行结果(其中一种情况):
在这里插入图片描述

使用匿名内部类,上方代码可改为:

public class Demo2 {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run(){
                for(int i=0;i<10;i++){
                    System.out.println("锄禾日当午"+i);
                }
            }
        }.start();
        for(int i=0;i<10;i++){
            System.out.println("汗滴禾下土"+i);
        }
    }
}

另一种是声明一个实现Runnable接口的类,该类实现run方法,然后可以分配类的实例,在创建Thread时作为参数传递,然后启动。

/**
 * 用于给线程进行执行的任务
 */
public class MyRunnabe implements Runnable {
    @Override
    public void run() {
        //线程的任务
        for(int i=0;i<10;i++){
            System.out.println("床前明月光"+i);
        }
    }
}

public class Demo1 {
    public static void main(String[] args) {
        //实现Runnable
        //1.创建一个任务对象
        MyRunnabe r = new MyRunnabe();
        //2.创建一个线程,并为其分配一个任务
        Thread t = new Thread(r);
        //3.执行这个线程
        t.start();
        for(int i=0;i<10;i++){
            System.out.println("疑是地上霜"+i);
        }
    }
}

运行结果(其中一种情况):

在这里插入图片描述

实现Runnable 与 继承Thread相比有如下优势:

  1. 通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况
  2. 可以避免单继承所带来的局限性
  3. 任务与线程本身是分离的,提高了程序的健壮性
  4. 后续学习的线程池技术,接受Runnable类型的任务,不接收Thread类型的线程

设置和获取线程名称

Thread.currentThread().getName()

public class Demo3 {
    public static void main(String[] args) {
        //获取线程的名称
        System.out.println(Thread.currentThread().getName());
        new Thread(new MyRunnable(),"这是一个线程名").start();
        new Thread(new MyRunnable()).start();
        new Thread(new MyRunnable()).start();

    }

    static class MyRunnable implements Runnable{

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }
}

运行结果为:

在这里插入图片描述

在这里插入图片描述

线程休眠

Thread.sleep(毫秒数)

public class Demo4 {
    public static void main(String[] args) throws InterruptedException {
        //线程的休眠 sleep
        for(int i=0;i<10;i++){
            System.out.println(i);
            //每一秒输出i
            Thread.sleep(1000);
        }
    }
}

线程中断

public class Demo5 {
    public static void main(String[] args) {
        //线程的中断
        //一个线程是一个独立的执行路径,它是否应该结束,应该由其自身决定,外部不能干涉线程死亡
        Thread t1 = new Thread(new MyRunnable());
        t1.start();
        for(int i=1;i<=5;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //给线程t1添加中断标记
        t1.interrupt();
    }
    static class MyRunnable implements Runnable{

        @Override
        public void run() {
            for(int i=1;i<=10;i++){
                System.out.println(Thread.currentThread().getName()+":"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                    System.out.println("发现了中断标记,让这个线程自杀");
                    return;
                }
            }
        }
    }
}

运行结果(其中一种情况):

在这里插入图片描述

守护线程

public class Demo6 {


    public static void main(String[] args) {
        //线程:分为守护线程和用户线程
        //用户线程:当一个进程不包含任何的存活的用户线程时,进行结束。
        //守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
        //直接创建的线程都是用户线程
        Thread t1 = new Thread(new MyRunnable());
        //设置t1为守护线程,即使t1没有执行完,main的线程si掉后,它也跟着si掉
        t1.setDaemon(true);
        t1.start();
        for(int i=1;i<=5;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    static class MyRunnable implements Runnable{

        @Override
        public void run() {
            for(int i=1;i<=10;i++){
                System.out.println(Thread.currentThread().getName()+":"+i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println("让线程自杀");
                    return;
                }
            }
        }
    }
}

线程安全问题

public class Demo7 {
    public static void main(String[] args) {
        //线程不安全
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable{
        //票数
        private int count = 10;

        @Override
        public void run() {
            while(count>0){
                //卖票
                System.out.println("正在准备卖票");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println("出票成功,余票:"+count);
            }
        }
    }
}

运行结果(其中一种情况):

在这里插入图片描述

余票为负数的原因:当只有一张票时,一个线程判断count > 0为真,进入循环,然后线程休眠一秒,其他进程就可能在这一秒内判断count是否大于0,为真,进入循环。就出现多个进程count–,所以就出现有负数的情况,但这种情况是不符合常理的。

线程安全—同步代码块

public class Demo8 {
    /**
     * 线程同步:synchronized
     * @param args
     */
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable{
        //票数
        private int count = 10;
        private Object o = new Object();//只有一把锁,看同一把锁才能排队
        @Override
        public void run() {
            while(true){
                //Object o = new Object();//所有线程都有一把锁
                synchronized (o){//等待同一把锁
                    if(count>0){
                        //卖票
                        System.out.println("正在准备卖票");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println(Thread.currentThread().getName()+"出票成功,余票为:"+count);
                    }else{
                        break;
                    }
                }
            }
        }
    }
}

运行结果(其中一种情况):

在这里插入图片描述

线程安全—同步方法

public class Demo9 {
    /**
     * 线程同步:synchronized
     * @param args
     */
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable{
        //票数
        private int count = 10;
        private Object o = new Object();
        @Override
        public void run() {
            while(true){
                boolean flag = sale();
                if(!flag){
                    break;
                }
            }
        }
        public synchronized boolean sale(){
            if(count>0){
                //卖票
                System.out.println("正在准备卖票");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName()+"出票成功,余票为:"+count);
                return true;
            }
            return false;
        }
    }
}

线程安全—显式锁Lock

public class Demo10 {
    /**
     * 同步代码块 和 同步方法 都属于隐式锁
     * 线程同步:Lock
     * @param args
     */
    public static void main(String[] args) {
        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();
    }

    static class Ticket implements Runnable{
        //票数
        private int count = 10;
        //显示锁:fair参数为true就表示是公平锁,先到先得
        private Lock l = new ReentrantLock(true);

        @Override
        public void run() {
            while(true){
                l.lock();
                if(count>0){
                    //卖票
                    System.out.println("正在准备卖票");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    count--;
                    System.out.println(Thread.currentThread().getName()+"出票成功,余票为:"+count);
                }else{
                    break;
                }
                l.unlock();
            }
        }

    }
}

线程死锁

public class Demo11 {
    public static void main(String[] args) {
        //线程死锁
        Culprit c = new Culprit();
        Police p = new Police();
        new MyThread(c,p).start();
        c.say(p);
    }
    static class MyThread extends Thread{
        private Culprit c;
        private Police p;
        public MyThread(Culprit c,Police p){
            this.c = c;
            this.p = p;
        }
        @Override
        public void run(){
            p.say(c);
        }
    }
    //罪犯
    static class Culprit{
        public synchronized void say(Police p){
            System.out.println("罪犯:你放了我,我放了人质");
            p.fun();
        }
        public synchronized void fun(){
            System.out.println("罪犯被放走了,罪犯也放了人质");
        }
    }
    //警察
    static class Police{
        public synchronized void say(Culprit c){
            System.out.println("警察:你放了人质,我放过你");
            c.fun();
        }
        public synchronized void fun(){
            System.out.println("警察救了人质,但是罪犯跑了");
        }
    }
}

运行结果:

在这里插入图片描述

多线程通信问题

public class Demo12 {
    /**
     * 多线程通信,生产者与消费者问题
     * @param args
     */
    public static void main(String[] args) {
        Food f = new Food();
        new Cook(f).start();
        new Waiter(f).start();
    }
    //厨师
    static class Cook extends Thread{
        private Food f;
        public Cook(Food f){
            this.f = f;
        }
        @Override
        public void run(){
            for(int i=0;i<100;i++){
                if(i%2==0){
                    f.setNameAndState("老干妈小米粥","香辣味");
                }else{
                    f.setNameAndState("煎饼果子","甜辣味");
                }
            }
        }
    }
    //服务生
    static class Waiter extends Thread{
        private Food f;
        public Waiter(Food f){
            this.f = f;
        }
        @Override
        public void run(){
            for(int i=0;i<100;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                f.get();
            }
        }
    }
    //食物
    static class Food{
        private String name;
        private String taste;

        //true表示可以生产 false表示可以消费
        private boolean flag = true;
        public synchronized void setNameAndState(String name,String taste){
            if(flag){
                this.name = name;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.taste = taste;
                flag = false;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
        public synchronized void get(){
            if(!flag){
                System.out.println("服务员端走的菜的名称是"+name+",味道:"+taste);
                flag = true;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}

运行结果:

在这里插入图片描述

不会出现菜品与味道不符,或者端走的菜品顺序错乱。

带返回值的线程Callable

public class Demo13 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Callable<Integer> c = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(c);
        new Thread(task).start();
        Integer j = task.get();
        System.out.println("返回值为"+j);
        for(int i=0;i<10;i++){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);
        }
    }

    static class MyCallable implements Callable<Integer>{

        @Override
        public Integer call() throws Exception {
            //Thread.sleep(3000);

            for(int i=0;i<10;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(i);
            }
            return 100;
        }
    }
}

Runnable 与 Callable的相同点

  • 都是接口
  • 都可以编写多线程程序
  • 都采用Thread.start()启动线程

Runnable 与 Callable的不同点

  • Runnable没有返回值;Callable可以返回执行结果
  • Callable接口的call()允许抛出异常;Runnable的run()不能抛出

Callable获取返回值

Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

线程池Executors

原因

如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间,线程就是一个容纳多个线程的容器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。

线程池的好处

  • 降低资源消耗
  • 提高响应速度
  • 提高线程的可管理性

Java中的四种线程池:缓冲线程池、定长线程池、单线程线程池、周期性任务定长线程池

缓冲线程池

public class Demo14 {
    /**
     * 缓存线程池
     * (长度无限制)
     * 任务加入后的执行流程:
     *      1.判断线程池是否有空闲线程
     *      2.存在则使用
     *      3.不存在,则创建线程并加入线程池,然后使用
     */
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        //指挥线程池执行新的任务
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
    }
}

在这里插入图片描述

定长线程池

public class Demo15 {
    /**
     * 定长线程池
     * (长度是指定的数值)
     * 任务加入后的执行流程:
     *      1.判断线程池是否存在空闲线程
     *      2.存在则使用
     *      3.不存在空闲线程,且线程未满的情况下,则创建线程,并放入线程池,然后使用
     *      4.不存在空闲线程,且线程已满的情况下,则等待线程池存在空闲线程
     */
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(2);
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {

                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
    }
}

在这里插入图片描述

单线程线程池

public class Demo16 {
    /**
     * 单线程线程池
     * 执行流程:
     *      1.判断线程池的单个线程是否空闲
     *      2.空闲则使用
     *      3.不空闲,则等待池中的单个线程空闲后使用
     */
    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
        service.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"锄禾日当午");
            }
        });
    }
}

在这里插入图片描述

周期性任务定长线程池

public class Demo17 {
    /**
     * 周期任务定长线程池
     * 执行流程:
     *      1.判断线程池是否存在空闲线程
     *      2.存在则使用
     *      3.不存在空闲线程,且线程未满的情况下,则创建线程并放入线程池,然后使用
     *      4.不存在空闲线程,且线程已满的情况下,则等待线程池存在空闲线程
     * 周期性任务执行时:
     *      定时执行,当某个时机触发时,自动执行某任务。
     */
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
        /*service.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("锄禾日当午");
            }
        },5, TimeUnit.SECONDS);*/
        service.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("锄禾日当午");
            }
        },5,1,TimeUnit.SECONDS);
    }
}

Lambda表达式

public class Demo18 {
    public static void main(String[] args) {
        /**
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("锄禾日当午");
            }
        });
         **/
        
        //下方使用了Lambda表达式,运行结果等同于上方
        Thread t = new Thread(() -> {
            System.out.println("锄禾日当午");
        });
        t.start();
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值