多线程入门你看这一篇就够了

线程与进程

进程:是指一个内存中运行的应用程序,拥有独立的空间,你可以理解为每个进程都有自己的堆栈互不共享
线程:
是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分
成若干个线程

多个线程共享一块内存空间,n件事情在一个进程中,当a线程创建一个变量,只要作用域匹配,bc都能找到他,线程可以自由切换,让某一个线程停止执行,另一个开始,也可以同时开始。一个进程至少一个线程,一个进程没有线程,也要马上被关闭。线程,是进程更细的划分,进程就是一个软件,线程就是软件里面的一个执行路径

什么是多线程:我们以前 写的程序 从第一行到第十行顺序执行,如果我们想要写一个软件即可以同时播放音乐又可以同时接受用户输入,这样的程序就又两个需要执行的路径,一个程序就在做两个事情了,在java用多线程,线程就是执行路径,多线程就是多条执行路径

线程调度

在早期的dos电脑,一个电脑只能做一件事情,后面出了一些多进程的操作系统如windows,更合理的将CPU的时间分成n份,然后分给各个软件,这样给人造成一种假象,好像多个程序同时再走。电脑只有一个脑子,一次只能做一个事情,但可以把时间分很多份,把一秒分成一千份额,前五千分分给音乐播放,后五千分分给页面跳转。我们学习多线程,不是让电脑可以拥有多个脑子,更是更合理的调度时间。举个例子,从宏观角度来说比如一个人一年当中,买了车,买了房子,结了婚,生了孩子,这些事情并不是一个人一下子就完成了,而是在一年的某些时刻来完成。你的一年就好像CPU的一秒一样(也许还不如一秒),CPU执行速度很快。其实多线程,并不能提高程序的运行速度,但能够提高运行效率,让CPU的使用率更高。

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

抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为

抢占式调度CPU使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核新而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的 使用率更高。

同步与异步

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

并发和并行

并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。
服务器5000个用户并行太可怕了,cpu就8个如何实现,记得区分

多线程在java的实现

继承thread的子类就是线程类,重写run方法(run方法就是线程要执行的任务方法)

主线成 下面都是分支线程。并发执行的

继承Thread

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 static void main(String[] args) {
     
        MyThread m = new MyThread();
        m.start();
        for (int i = 0; i <10 ; i++) {
     
            System.out.println("汗滴禾下土"+i);
        }
      }

多线程入门你看这一篇就够了_第1张图片
多线程入门你看这一篇就够了_第2张图片
多线程入门你看这一篇就够了_第3张图片

2.实现Runnable接口(推荐)

public class MyRunnable implements Runnable{
     

    @Override
    public void run() {
     
        for (int i = 0; i <10 ; i++) {
     
            System.out.println("床前明月光"+i);
        }
    }
}

public class Demo {
     
    public static void main(String[] args) {
     
       

        /**
         * 实现Runnable(更多使用) 与 继承Thread相比有如下优势
         * 1.通过创建任务,然后给线程分配的多线程,更适合多个线程同时执行相同的任务
         * 2.可以避免单继承的局限性
         * 3.任务与线程本身是分离的提高类程序的健壮性
         * 4.后续学习线程池技术,只接收Runnable类型的任务,而不接收Thread线程
         */

        //实现Runnable
        //1.创建一个任务对象
        MyRunnable r = new MyRunnable();
        //2.为任务对象分配一个线程
        Thread t = new Thread(r);
        //3.执行这个线程
        t.start();
        for (int i = 0; i <10 ; i++) {
     
            System.out.println("疑是地上霜"+i);
        }
    }

}

多线程入门你看这一篇就够了_第4张图片

也并不是说继承Thread就没有优点,继承Thread也有一些简单的创建方法。

public class Demo2 {
     
    public static void main(String[] args) {
     
        //通过匿名内部类实现Thread,比较简单
        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);
        }
    }
}

Thread类

不管我们通过哪种方式使用多线程技术,我们都需要用到Thread类
多线程入门你看这一篇就够了_第5张图片
多线程入门你看这一篇就够了_第6张图片
多线程入门你看这一篇就够了_第7张图片

注意:
stop不能停止线程,因为有的操作并没执行完,这种方法是不安全的,现在已经过时。现在都用变量标记的方法让线程自己关闭。

在这里插入图片描述

设置和获取线程名称

public class Demo {
     
    public static void main(String[] args) {
     
        //打印主线程名字
        System.out.println(Thread.currentThread().getName());
        //调用Thread类的有参构造方法并取名
        new Thread(new MyRunnable(),"线程1").start();
        new Thread(new MyRunnable(),"线程2").start();
        new Thread(new MyRunnable(),"线程3").start();
        //也可以通过对象.setName
        Thread t = new Thread(new MyRunnable());
        t.setName("线程4");
        t.start();


    }

    static class MyRunnable implements Runnable{
     

        @Override
        public void run() {
     
            //获取当前线程的名字,如果不指定默认(Thread-0)
            System.out.println(Thread.currentThread().getName());
        }
    }
}

多线程入门你看这一篇就够了_第8张图片
注意:线程也有可能不是顺序执行的,线程不保证那个先输出。

线程休眠Sleep

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

}

多线程入门你看这一篇就够了_第9张图片
可以选择毫秒,也可以选择毫秒加纳秒。
多线程入门你看这一篇就够了_第10张图片
可以try catch , 也可以直接抛

线程阻塞(耗时操作)

线程阻塞不只是线程休眠,我们知道线程是一条执行路径,从一行到一百行,比如说某一行正在进行读取操作,后面在等待他执行完的这个过程就叫线程阻塞。你可以理解为,只要稍微消耗时间的过程就是线程阻塞。在举个例子,比如我们最开始学的Scanner 等待用户操作,只要用户没有输入,程序就会一直等待,这个等待就是线程阻塞。

线程的中断

一个线程是一个独立的执行路径,他是否应该结束,应该由其自身决定。
一个线程就好像一个人,他的结局应该由自己决定。
使用stop可以停止,但就像从外部掐死一个线程,非常不安全。

目的:主线程执行完之后,让子线程死亡
原理:使用interrupt()方法标记要中断的线程,在以下方法执行的时候会检查线程是否被标记,如果被标记则进入run方法中的catch块执行我们想要对线程执行的操作。
多线程入门你看这一篇就够了_第11张图片

此时在catch块中只是提醒,并未操作

public class Demo {
     
    public static void main(String[] args) throws InterruptedException {
     
        //线程休中断
        //一个线程是独立的执行路径,它是否应该结束,应该由自身决定
        Thread t1 = new Thread(new MyRunnable());
        t1.start();
        for (int i = 0; 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 = 0; i < 10; i++) {
     
                System.out.println(Thread.currentThread().getName()+":"+i);
                try {
     
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
     
//                    e.printStackTrace();
                    System.out.println("发现了中断标记,但就是不死亡");

                }
            }
        }

    }
}


结果:
多线程入门你看这一篇就够了_第12张图片
我们发现子线程并没有死亡,原因是我们没有使用return结束

public class Demo {
     
    public static void main(String[] args) throws InterruptedException {
     
        //线程休中断
        //一个线程是独立的执行路径,它是否应该结束,应该由自身决定
        Thread t1 = new Thread(new MyRunnable());
        t1.start();
        for (int i = 0; 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 = 0; i < 10; i++) {
     
                System.out.println(Thread.currentThread().getName()+":"+i);
                try {
     
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
     
//                    e.printStackTrace();
                    System.out.println("发现了中断标记,线程选择自杀");
                    return;

                }
            }
        }

    }
}


我们对run方法(也就是线程的任务)中的catch块进行return,也就是说发现标记后就结束了该方法,也就是中断了该线程的任务。
多线程入门你看这一篇就够了_第13张图片

守护线程

线程:分为守护线程和用户线程
用户线程:也就是我们直接创建的线程,当一个进程不包含任何存活的用户线程时,进行结束。
守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。

public class Demo {
     
    public static void main(String[] args) throws InterruptedException {
     
        //线程:分为守护线程和用户线程
        //用户线程:也就是我们直接创建的线程,当一个进程不包含任何存活的用户线程时,进行结束。
        //守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
        Thread t1 = new Thread(new MyRunnable());
        //一定要在启动之前设置守护线程
        //把t1设置为守护线程,只要是主线程结束了,无论守护线程是否运行完,都要结束
        t1.setDaemon(true);
        t1.start();
        for (int i = 0; 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 = 0; i < 10; i++) {
     
                System.out.println(Thread.currentThread().getName()+":"+i);
                try {
     
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();

                }
            }
        }

    }
}


多线程入门你看这一篇就够了_第14张图片

线程安全、不安全问题

多个线程在同时运行时,就很有可能发生线程不安全问题

public class Demo3 {
     
    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("正在卖票ing.....");

                    try {
     
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
     
                        e.printStackTrace();
                    }
                    count--;
                System.out.println("卖出票了,余票:"+count);

            }
        }
    }
}

多线程入门你看这一篇就够了_第15张图片
我们明明规定定了while(count>0)的时候才卖票,那为什么会出现coutn = -1 呢
多线程入门你看这一篇就够了_第16张图片
比如当count = 1 的时候 a进去while 但是 a丢失了时间篇 被 b抢到了,b也进了循环 当 b 买了票,count 就变为了0 此时 a 再买票 就导致了count变为 -1

线程安全1–同步代码块

操作:代码块加加synchronized
之前线程不安全的问题我们已经看到,接下来看看我们怎么去处理。
原理很简单:只需要由原来多个线程争抢一个数据去操作,变成排队去操作即可。

import java.io.ObjectOutputStream;

public class Demo3 {
     
    public static void main(String[] args) {
     
        //线程不安全
        //解决方案1.同步代码块
        //格式: synchronized(锁对象){
     
        //
        //       }
        //java中任何对象都可以成为锁对象,即被打上锁标记
        //比如有一个锁对象在执行,其他的对象就会排队等待,执行完了才执行下一个
        //锁是同一把锁
        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) {
     
                    synchronized (o) {
     
                    if (count > 0) {
     
                        //卖票
                        System.out.println("正在卖票ing.....");

                        try {
     
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
     
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println(Thread.currentThread().getName()+"卖出票了,余票:" + count);

                    }else {
     
                        break;
                    }
                }
            }
        }
    }
}

多线程入门你看这一篇就够了_第17张图片
我们发现数据再也没有出现-1的情况,这时候锁就起作用。但是因为0线程离锁比较近,每次0线程执行完就解锁,然后又马上上锁了,导致一直全是都是0线程。

注意:锁的位置很重要,下面演示不合理的锁,每个线程开启的时候都会调用run方法,所以三个线程在调用run方法开启任务时,就创建了三个锁的对象,导致了三个线程三把锁。

多线程入门你看这一篇就够了_第18张图片
如果我们把锁对象放在线程任务(run()方法里面),每个线程执行任务都会有自己的锁,这样就会造成问题
多线程入门你看这一篇就够了_第19张图片

线程安全2-同步方法

操作:给方法加 synchronized

import java.io.ObjectOutputStream;

public class Demo3 {
     
    public static void main(String[] args) {
     
        //线程不安全
        //解决方案2.同步方法

        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 (true) {
     
                boolean flag = sale();
                if (!flag){
     
                    break;
                }
            }
        }

        public synchronized boolean  sale(){
     
            if (count > 0) {
     
                //卖票
                System.out.println("正在卖票ing.....");

                try {
     
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName() + "卖出票了,余票:" + count);
                return true;
            }
               return false;

        }
    }
}

多线程入门你看这一篇就够了_第20张图片
注意一个问题,此时我们创建了一个对象,通过一个对象开辟了三个线程。如果我们此时有三个对象,那么

import java.io.ObjectOutputStream;

public class Demo3 {
     
    public static void main(String[] args) {
     
        //线程不安全
        //解决方案2.同步方法

//        Runnable run = new Ticket();
//        new Thread(run).start();
//        new Thread(run).start();
//        new Thread(run).start();

        //此时创建了三个对象
        new Thread(new Ticket()).start();
        new Thread(new Ticket()).start();
        new Thread(new Ticket()).start();

    }

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

        @Override
        public void run() {
     
            while (true) {
     
                boolean flag = sale();
                if (!flag){
     
                    break;
                }
            }
        }

        public synchronized boolean  sale(){
     
            if (count > 0) {
     
                //卖票
                System.out.println("正在卖票ing.....");

                try {
     
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
                count--;
                System.out.println(Thread.currentThread().getName() + "卖出票了,余票:" + count);
                return true;
            }
               return false;

        }
    }
}

多线程入门你看这一篇就够了_第21张图片
很明显不是排队的,因为我们new了三个对象,即三个任务,即三个线程各自调用三个run方法,不是三个线程公用一个run方法。

线程安全3-显示锁

同步代码块和同步方法都属于隐式锁
线程同步:Lock
一般使用子类ReentrantLock

import java.io.ObjectOutputStream;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo3 {
     
    public static void main(String[] args) {
     
        //线程不安全
        //解决方案2.同步方法

        Runnable run = new Ticket();
        new Thread(run).start();
        new Thread(run).start();
        new Thread(run).start();

        //此时创建了三个对象
//        new Thread(new Ticket()).start();
//        new Thread(new Ticket()).start();
//        new Thread(new Ticket()).start();

    }

    static class Ticket implements Runnable {
     
        private int count = 10;
        //显示锁 l
        private Lock l = new ReentrantLock();

        @Override
        public void run() {
     
            while (true) {
     
                //自己创建一把锁,当代码执行到这个位置的时候锁住
                l.lock();
                while (true){
     
                    if (count > 0) {
     
                        //卖票
                        System.out.println("正在卖票ing.....");

                        try {
     
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
     
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println(Thread.currentThread().getName() + "卖出票了,余票:" + count);

                    }else {
     
                        break;
                    }
                    //执行完毕解开锁
                    l.unlock();
                }
            }
        }


    }
}

显式锁和隐式锁区别

多线程入门你看这一篇就够了_第22张图片

一、构成不同

SyncLock 的出身(原始的构成)不同:

**Sync:**Java中的关键字,是由JVM来维护的。是JVM层面的锁。
Lock:是JDK5以后才出现的具体的类。使用 Lock 是调用对应的API。是API层面的锁。
Sync 底层是通过 monitorenter 进行加锁(底层是通过 monitor 对象来完成的,其中的wait/notify等方法也是依赖于 monitor 对象的。只有在同步代码块或者同步方法中才可以调用wait/notify等方法。因为只有在同步代码块或者是同步方法中,JVM才会调用 monitory 对象);通过 monitorexit 来退出锁。
而 Lock 是通过调用对应的API方法来获取锁和释放锁。

二、使用方法不同

Sync是隐式锁;Lock是显示锁。

所谓的显示和隐式就是在使用的时候,使用者要不要手动写代码去获取锁和释放锁。

在使用sync关键字的时候,程序能够自动获取锁和释放锁。那是因为当sync代码块执行完成之后,系统会自动的让程序释放占用的锁。Sync是由系统维护的,如果非逻辑问题的话,是不会出现死锁的。
在使用lock的时候,我们使用者需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。手动获取锁方法:lock();释放锁:unlock()

三、等待是否可中断

Sync是不可中断的。除非抛出异常或者正常运行完成。

Lock是可以中断的。中断方式:

调用设置超时方法tryLock(long timeout ,timeUnit unit)
调用**lockInterruptibly()**放到代码块中,然后调用interrupt()方法可以中断

四、加锁的时候是否公平

Sync:非公平锁。

Lock:两者都可以。默认是非公平锁,在其构造方法的时候可以传入Boolean值(true:公平锁;false:非公平锁)

五、锁绑定多个条件来condition

Sync:没有。要么随机唤醒一个线程;要么是唤醒所有等待的线程。

Lock:用来实现分组唤醒需要唤醒的线程,可以

公平锁和非公平锁

公平锁:先来先到
非公平锁:一块抢,咱们学的三种都是默认非公平锁

我们如何使用公平锁呢?
在我们使用显式锁的时候,可以通过其构造方法 true就是 公平锁

  private Lock l = new ReentrantLock(true);

线程死锁

举个例子:A和B两个人在两个试衣间换衣服,A进A试衣间发现很冷想去B试衣间,但是发现B试衣间锁门了,于是等待B试衣结束再去B。B进去B试衣间发现地上一滩水,想去A试衣间,发现A试衣间锁门了。于是两个人互相等待,这一过程就叫线程死锁。

代码演示(情景:警察等待绑匪放人再放他走 绑匪等待警察放他走再放人)

package view;

public class Demo4 {
     
    //线程死锁
    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("警察救了人质,但是罪犯跑了");
        }
    }
}

未死锁状态
多线程入门你看这一篇就够了_第23张图片
死锁状态
多线程入门你看这一篇就够了_第24张图片
解决办法:在任何有可能导致锁的方法里不要再调用另一个有锁的方法,导致锁产生。

多线程通信问题(生产者消费者问题)

A线程网上下载音乐 B 线程播放
如何实现连个线程交互的问题

我们想让厨师生成一份菜,服务员就端走一份菜,那么下面我们的这段代码是否能完成呢

package view;

public class Demo4 {
     
    /**
     * 多线程通信问题,生产者与消费者问题
     * @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.setNameAndTaste("老干妈小米粥","香辣味");
                }else {
     
                    f.setNameAndTaste("煎饼果子","甜辣味");
                }
            }
        }
    }
   //服务员
    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;

        public String getName() {
     
            return name;
        }

        public void setNameAndTaste(String name,String taste) {
     
            this.name = name;
            try {
     
                //休眠0.1秒
                Thread.sleep(100);
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
            this.taste = taste;
        }

        public void get() {
     
            System.out.println("服务员端走菜的名称是:"+name+",味道:"+taste);
        }
    }

  
}


多线程入门你看这一篇就够了_第25张图片
我们看到竟然出现了甜辣味的老干妈和香辣的煎饼果子,还有一些错乱的数据,这就说明了两个线程之间合作没有协调,如果我们在两个线程的方法加锁呢结果会怎么样

package view;

public class Demo4 {
     
    /**
     * 多线程通信问题,生产者与消费者问题
     * @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.setNameAndTaste("老干妈小米粥","香辣味");
                }else {
     
                    f.setNameAndTaste("煎饼果子","甜辣味");
                }
            }
        }
    }
   //服务员
    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;

        public String getName() {
     
            return name;
        }

        public synchronized void setNameAndTaste(String name,String taste) {
     
            this.name = name;
            try {
     
                //休眠0.1秒
                Thread.sleep(100);
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
            this.taste = taste;
        }

        public synchronized void get() {
     
            System.out.println("服务员端走菜的名称是:"+name+",味道:"+taste);
        }
    }


}


多线程入门你看这一篇就够了_第26张图片
我们生产是交替的,按理说应该一份小米粥一份煎饼果子,可见光加线程安全是没有用的。因为synchronized是非公平锁,厨师上完菜有可能服务员没有端走就又上菜了。那我们应该怎么解决呢?

package view;

public class Demo4 {
     
    /**
     * 多线程通信问题,生产者与消费者问题
     * @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.setNameAndTaste("老干妈小米粥","香辣味");
                }else {
     
                    f.setNameAndTaste("煎饼果子","甜辣味");
                }
            }
        }
    }
   //服务员
    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表示可以生产
        private boolean flag = true;

        public String getName() {
     
            return name;
        }

        public synchronized void setNameAndTaste(String name, String taste) {
     
            //flag为true时候才生产
            if (flag) {
     
                this.name = name;
                try {
     
                    //休眠0.1秒
                    Thread.sleep(100);
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
                this.taste = taste;
                //生产完毕改变标记
                flag = false;
                //唤醒在当前this下睡着的所有线程
                this.notifyAll();
                //厨师线程睡着
                try {
     
                    this.wait();
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
            }
        }

        public synchronized void get() {
     
            //服务员只有在flag为false时才执行因为此时厨师睡着
            if (!flag) {
     
                System.out.println("服务员端走菜的名称是:" + name + ",味道:" + taste);
                  flag = true;
                  //唤醒厨师
                  this.notifyAll();
                  //再让自己睡着
                //这样就可以实现厨师做完唤醒服务员,然后厨师睡,服务员上完再唤醒厨师交替执行
                try {
     
                    this.wait();
                } catch (InterruptedException e) {
     
                    e.printStackTrace();
                }
            }
            
        }

    }
    
}


多线程入门你看这一篇就够了_第27张图片
我们看到此时结果正常,小米粥和煎饼果子交替被服务员端上餐桌。

线程的六种状态

我们通过观察线程的状态更好的了解线程的执行。

  1. 初始状态(NEW)

实现Runnable接口和继承Thread可以得到一个线程类,new一个实例出来,线程就进入了初始状态。

2.1. 就绪状态(RUNNABLE之READY)

就绪状态只是说你资格运行,调度程序没有挑选到你,你就永远是就绪状态。
调用线程的start()方法,此线程进入就绪状态。
当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。
锁池里的线程拿到对象锁后,进入就绪状态。
2.2. 运行中状态(RUNNABLE之RUNNING)

线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一的一种方式。

  1. 阻塞状态(BLOCKED)

阻塞状态是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。

  1. 等待(WAITING)

处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。

  1. 超时等待(TIMED_WAITING)

处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。

  1. 终止状态(TERMINATED)

当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

从创建到运行,在运行过程中中间有可能发生排队执行,排队完又回到运行状态,也有可能在运行的时候进行休眠,休眠完毕继续回到运行状态,也有可能出现指定时间休眠,完毕后回到运行状态。无论线程中间经历多少次排队、休眠、指定时间休眠最终都会走向死亡。

带返回值的线程Callable

主流的创建方式我们一般认为有两种,但其实还有一种比较特殊的。
多线程入门你看这一篇就够了_第28张图片

Runnable 与 Callable

多线程入门你看这一篇就够了_第29张图片

Callable使用步骤

多线程入门你看这一篇就够了_第30张图片

Runnable 与 Callable的相同点

多线程入门你看这一篇就够了_第31张图片

Runnable 与 Callable的不同点

在这里插入图片描述

Callable获取返回值

在这里插入图片描述

线程池概述

多线程入门你看这一篇就够了_第32张图片

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

线程池的好处

·降低资源消耗。

·提高响应速度。

·提高线程的可管理性。

Java中的四种线程池 . ExecutorService

  1. 缓存线程池

多线程入门你看这一篇就够了_第33张图片

2.定长线程池
多线程入门你看这一篇就够了_第34张图片
3.单线程池
多线程入门你看这一篇就够了_第35张图片
4. 周期性任务定长线程池
多线程入门你看这一篇就够了_第36张图片
多线程入门你看这一篇就够了_第37张图片

Lambda表达式

属于函数式编程思想,jdk1.8引进Lambda表达式。

面向对象思想:把解决问题的方法写进对象 创建对象 调用方法
函数式编程思想 : 只关心解决问题的方法 无需再创建对象

lambda是一个匿名函数,我们可以把lambda表达式理解为是一段可以传递的代码。

lambda简明的地将代码或方法作为参数传递进去执行。

“函数式编程”其核心是把函数作为值。

函数式接口 :只有一个 抽象方法的接口 称之为 函数式接口。函数式接口可以使用@FunctionalInterface进行注解。

lambda表达式拆分为两部分

左侧:lambda 表达式的参数列表

右侧:lambda 表达式中所需要执行的功能,即lambda体

@Test
public void test(){
     
    // () -> System.out.println("Hello");
    Runnable a = new Runnable(){
     
     @Override
     public void run(){
     
        System.out.println("Hello")
    }
    };
    //等同于
    Runnable a1 = () -> System.out.println("Hello");
    a1.run();
}


你可能感兴趣的:(java,基础,java,多线程)