十一:多线程

本节内容是跟着宋红康老师学的,相应的视频地址如下所:

教程来源:

java基础到高级_零基础自学Java–尚硅谷–宋红康
教程视频地址:

 java基础到高级_零基础自学Java--尚硅谷--宋红康_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1ny4y1Y7CW

目录

1.线程的生命周期

2.理解线程的安全问题

3.线程安全问题的举例和解决措施

4.同步代码块处理实现Runnnable的线程安全问题

5.同步代码块处理继承Thread类的线程安全问题

6.同步方法处理实现Runnnable的线程安全问题

7.同步方法处理继承Thread类的线程安全问题

8.线程安全的单例模式之懒汉式

9.死锁问题

10.Lock锁方式解决线程安全问题

11.同步机制的课后练习

12.线程通信的例题

13.sleep()和wait()的异同

14.线程通信:生产者、消费者例题

15.创建线程的方式三:实现Callable接口

16.使用线程池的好处​

17.创建多线程的方式四:使用线程池

18.每天一考


1.线程的生命周期

  • 查找类的快捷键是ctrl + shift + alt + N
  • strl+F12是搜索当前页面的类

2.理解线程的安全问题

  • 举一个例子,你取2000块钱,你的同学取你的同一张卡2000块钱,但是若是出现两个人都取出2000块钱。------这就是取钱的不安全问题。

3.线程安全问题的举例和解决措施

  • alt + enter是进行一个try-catch的操作
  • 理想状态

  • 极端状态

* 1.问题:卖票的过程之中,出现了重票、错票。---出现了线程的安全问题。
* 2.问题的出现原因:当某个线程操作车票的过程之中,尚未完成操作的时候,其他线程也参与进来,导致   
    错误的出现。
* 3.如何解决:当一个线程在操作票的时候,其他线程不能参与进来,直到线程A操作完成之后,其他线程才 
    可以才操作票数,这种情况,即使是线程A出现了阻塞,也是不能欧改变。

4.同步代码块处理实现Runnnable的线程安全问题

  • 必须是一把锁,不能是两把或者多把。
package com.atguigu.java;

/**
 * 例子:创建三个窗口卖票,总票数是100,使用Runnable接口的方式
 * 1.问题:卖票的过程之中,出现了重票、错票。---出现了线程的安全问题。
 * 2.问题的出现原因:当某个线程操作车票的过程之中,尚未完成操作的时候,其他线程也参与进来,导致错误的出现。
 * 3.如何解决:当一个线程在操作票的时候,其他线程不能参与进来,直到线程A操作完成之后,其他线程才可以才操作票数,这种情况,即使是线程A出现了阻塞,也是不能欧改变。
 * 4.在Java之中,我们通过同步机制,来解决线程的安全问题。
 * 方式一:同步代码块
 * synchronized(同步监视器)
 * {
 *    // 需要被同步的代码
 * }
 * 说明:1.操作共享数据的代码,即为需要被同步的代码
 *      2.共享数据:多个线程共同操作的变量.比如:ticket就是共享数据
 *      3.同步监视器:俗称是锁。任何一个类的对象都是可以充当锁。
 *        要求:多个线程必须共用同一把锁。------火车上的厕所,灯是绿的可以进(面向厕所的编程)【唯一的】
 * 方式二:同步方法
 * 5.同步的方式,解决了线程的安全问题,但是也是有一些缺点---变慢了
 *   操作同步代码的时候,只有一个线程参与,其他的线程进行等待,相当于是只有一个线程的过程,效率变低了。即便这样我们也是需要进行这个同步。
 * @anthor shkstart
 * @create 2021-11-0216:48
 */
class Window1 implements Runnable
{
    public  int ticket = 100;
    Object obj = new Object();//对随便的一个对象,必须是一把锁
    Dog dog = new Dog();
    @Override
    public void run() {
        while (true)
        {
            synchronized(dog) //同步监视器
            {
                if (ticket > 0)
                {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖出的票是" + ticket);
                    ticket--;
                }
                else
                    {
                    break;
                    }
            }
        }
    }
}
public class WindowsTest1 {
    public static void main(String[] args) {
        Window1 mThread1 = new Window1();//共用100张票
        Thread t1 = new Thread(mThread1);
        Thread t2 = new Thread(mThread1);
        Thread t3 = new Thread(mThread1);
        t1.setName("线程1");
        t2.setName("线程2");
        t3.setName("线程3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Dog
{

}

5.同步代码块处理继承Thread类的线程安全问题

  •  这个灯是绿色的,谁抢到了灯就变成了红色的了,等下一个再抢。就像是一群人在抢厕所的过程。
  • 下面的代码使用上一个代码的方式进行解决会导致出现错误。因为对象不是一个。这里我们的解决方式是声明一个private static Object obj = new Object();即可继续进行票数的分配。
  • 下面是方式一进行多线程的代码
package com.atguigu.java;

/**
 * 例子:创建三个C窗口卖票,总票数是100张
 * 存在线程安全问题,有待解决。
 * 说明:在继承Thread类创建多线程的方式之中,慎用this充当同步监视器。可以考虑使用当前类充当同步监视器。
 * 使用同步代码块解决继承Thread类的方式,解决线程的安全问题。
 * @anthor shkstart
 * @create 2021-11-0216:01
 */
class Window2 extends Thread
{
    private static int ticket = 100;//共享同一个静态变量,暂时先忽略三个100
    private static Object obj = new Object();
    @Override
    public void run() {
        while(true)
        {
              //正确的
           // synchronized(obj)
             synchronized(Windows2.class)//注意类也是对象       
                    //错误的
                   // synchronized(this) 这里的this代表的是t1t2t3
            {
                if (ticket > 0) {
                    try {
                        sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖号,票号是:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
public class WindowsTest2 {
    public static void main(String[] args) {
        Window2 t1 = new Window2();
        Window2 t2 = new Window2();
        Window2 t3 = new Window2();
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();

    }
}
  • 千万要注意:类也是一个对象。Windows2.class
  • 下面是进行方式二多线程的代码----用当前对象直接去当一把锁。
package com.atguigu.java;

/**
 * 例子:创建三个窗口卖票,总票数是100,使用Runnable接口的方式
 * 1.问题:卖票的过程之中,出现了重票、错票。---出现了线程的安全问题。
 * 2.问题的出现原因:当某个线程操作车票的过程之中,尚未完成操作的时候,其他线程也参与进来,导致错误的出现。
 * 3.如何解决:当一个线程在操作票的时候,其他线程不能参与进来,直到线程A操作完成之后,其他线程才可以才操作票数,这种情况,即使是线程A出现了阻塞,也是不能欧改变。
 * 4.在Java之中,我们通过同步机制,来解决线程的安全问题。
 * 方式一:同步代码块
 * synchronized(同步监视器)
 * {
 *    // 需要被同步的代码
 * }
 * 说明:1.操作共享数据的代码,即为需要被同步的代码。 --->不能包含代码多了,也不能够包含代码少了。
 *       2.共享数据:多个线程共同操作的变量.比如:ticket就是共享数据
 *       3.同步监视器:俗称是锁。任何一个类的对象都是可以充当锁。
 *        要求:多个线程必须共用同一把锁。------火车上的厕所,灯是绿的可以进(面向厕所的编程)【唯一的】
          补充:在实现Runnable接口创建多线程的方式之中,我们可以考虑使用this充当同步监视器。
 * 方式二:同步方法
 * 5.同步的方式,解决了线程的安全问题,但是也是有一些缺点---变慢了
 *   操作同步代码的时候,只有一个线程参与,其他的线程进行等待,相当于是只有一个线程的过程,效率变低了。即便这样我们也是需要进行这个同步。
 * @anthor shkstart
 * @create 2021-11-0216:48
 */
class Window1 implements Runnable
{
    public  int ticket = 100;
   // Object obj = new Object();//对随便的一个对象,必须是一把锁
   // Dog dog = new Dog();
    @Override
    public void run() {
        while (true)
        {
            synchronized(this) //同步监视器//此时的this:唯一的windows1的对象
            {
                if (ticket > 0)
                {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖出的票是" + ticket);
                    ticket--;
                }
                else
                    {
                    break;
                    }
            }
        }
    }
}
public class WindowsTest1 {
    public static void main(String[] args) {
        Window1 mThread1 = new Window1();//共用100张票
        Thread t1 = new Thread(mThread1);
        Thread t2 = new Thread(mThread1);
        Thread t3 = new Thread(mThread1);
        t1.setName("线程1");
        t2.setName("线程2");
        t3.setName("线程3");
        t1.start();
        t2.start();
        t3.start();
    }
}
class Dog
{

}

6.同步方法处理实现Runnnable的线程安全问题

package com.atguigu.java;
/**
 * 例子:创建三个窗口卖票,总票数是100,使用Runnable接口的方式
 * 1.问题:卖票的过程之中,出现了重票、错票。---出现了线程的安全问题。
 * 2.问题的出现原因:当某个线程操作车票的过程之中,尚未完成操作的时候,其他线程也参与进来,导致错误的出现。
 * 3.如何解决:当一个线程在操作票的时候,其他线程不能参与进来,直到线程A操作完成之后,其他线程才可以才操作票数,这种情况,即使是线程A出现了阻塞,也是不能欧改变。
 * 4.在Java之中,我们通过同步机制,来解决线程的安全问题。
 * 方式一:同步代码块
 * synchronized(同步监视器)
 * {
 *    // 需要被同步的代码
 * }
 * 说明:1.操作共享数据的代码,即为需要被同步的代码
 *      2.共享数据:多个线程共同操作的变量.比如:ticket就是共享数据
 *      3.同步监视器:俗称是锁。任何一个类的对象都是可以充当锁。
 *        要求:多个线程必须共用同一把锁。------火车上的厕所,灯是绿的可以进(面向厕所的编程)【唯一的】
 * 方式二:同步方法
 *      如果操作共享数据的代码完整的声明在一个方法之中,我们不妨将此方法同步声明。
 * 5.同步的方式,解决了线程的安全问题,但是也是有一些缺点---变慢了
 *   操作同步代码的时候,只有一个线程参与,其他的线程进行等待,相当于是只有一个线程的过程,效率变低了。即便这样我们也是需要进行这个同步。
 * @anthor shkstart
 * @create 2021-11-0216:48
 */
class Window3 implements Runnable
{
    public  int ticket = 100;

    @Override
    public void run() {
        while (true)
        {
                show();
        }
    }
    public synchronized void show()//操作共享数据 这里的同步监视器是:this
    {
        //synchronized (this) {
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "卖出的票是" + ticket);
                ticket--;
            }
       // }
    }
}
public class WindowsTest3 {
    public static void main(String[] args) {
        Window3 mThread1 = new Window3();//共用100张票
        Thread t1 = new Thread(mThread1);
        Thread t2 = new Thread(mThread1);
        Thread t3 = new Thread(mThread1);
        t1.setName("线程1");
        t2.setName("线程2");
        t3.setName("线程3");
        t1.start();
        t2.start();
        t3.start();
    }
}

7.同步方法处理继承Thread类的线程安全问题

  • 继承------同步代码块
  • 实现Runnable------同步方法
package com.atguigu.java;
/**
 * 使用同步方法解决实现Runnable接口的线程安全问题
 * 关于同步方法的总结
 * 1.同步方法仍然涉及到同步监视器,只是不需要我们进行显式声明。
 * 2.非静态的同步方法的监视器是this
 *   静态的同步方法的同步监视器是:当前类本身。
 * @anthor shkstart
 * @create 2021-11-0216:01
 */
class Window4 extends Thread
{
    private static int ticket = 100;//共享同一个静态变量,暂时先忽略三个100

    @Override
    public void run() {
        while(true)
        {
              show();
        }
    }
    private static synchronized void show()//锁的问题:这里的同步监视器是windows4.class
    //private synchronized void show()//此种方法是错误的
    {
        if (ticket > 0) {
            try {
                sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":卖号,票号是:" + ticket);
            ticket--;
        }
    }
}
public class WindowsTest4 {
    public static void main(String[] args) {
        Window4 t1 = new Window4();
        Window4 t2 = new Window4();
        Window4 t3 = new Window4();
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");
        t1.start();
        t2.start();
        t3.start();

    }
}

8.线程安全的单例模式之懒汉式

package com.atguigu.java.java;

/**
 * 适用同步机制将单例模式中的懒汉式改写为线程安全
 * @anthor shkstart
 * @create 2021-11-0310:58
 */
public class BankTest {
}
class Bank
{
    private Bank(){}
    private static Bank instance = null;
  /*方式一
   public static synchronized Bank getInstance()//这里的锁是Bank.class,类本身也是充当是一个对象.
    {
        if(instance == null)//首次调用,显然instance是一个null
        {
            instance = new Bank();//这里的instance相当于是一个共享数据.
        }
        return instance;
    }
    */
  //方式二
    /*
    public static Bank getInstance()
    {//这种改法的效率是比较低的
        synchronized (Bnak.class)
        {
            if(instance == null)//这里还需要注意的,举个例子,当时苹果的5s的土豪金--石家庄火车站被偷了可还行,买完之后就直接散了就行了,但是他不立牌子呀
            {
                instance = new Bank();
            }
            return instance;
        }
    }
    */
    //再次改写
    //效率是比较高的
    if(instance == null)
    {
        synchronized (Bank.class)
        {
            if(instance == null)
            {
                instance =new Bank();
            }
        }
    }
    return instance;
}

9.死锁问题

  • 两个人都是有爱慕之心,但是都是不表达,导致最后两个人都是等待着,这就是相应的死锁问题。
package com.atguigu.java.java;

/**
 * 演示线程的死锁
 * 1.死锁的理解:不同的线程分别占用对方的需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
 * 2.说明:
 * (1)出现死锁之后是不会抛出异常,只是无法继续
 * (2)我们使用同步的时候,要避免进行死锁
 * 
 * @anthor shkstart
 * @create 2021-11-0311:23
 */
public class ThreadTest {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();//这里的StringBuffer我们可以将他理解为是一个特殊的字符串
        StringBuffer s2 = new StringBuffer();
        new Thread()
        {
            @Override
            public void run() {
                super.run();
                synchronized(s1)//让s1作为一个锁
                {
                    s1.append("a");
                    s2.append("1");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized(s2)
                    {
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();
        new Thread(new Runnable() {
            @Override
            public void run() {

                synchronized(s2)//让s1作为一个锁
                {
                    s1.append("c");
                    s2.append("3");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized(s1)
                    {
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}
  • 康老师上课举例的那个代码已经懵了呀。----脑子转不过来了(死锁问题),想一些办法去规避死锁问题。

10.Lock锁方式解决线程安全问题

  •  显示参数是ctrl + p,将光标放到两个括号里面
  • 下面的这个是lock进行的一道题目,上课的时候老师讲的
package com.atguigu.java.java;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 解决线程安全问题的方式三:Lock锁 --- JDK5.0新增
 *
 * @anthor shkstart
 * @create 2021-11-0311:52
 */
class Window implements Runnable
{
    private int ticket = 100;
    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock(true);//IDEA是ctrl + p,注意这里的的fair代表的含义是先进先出
    @Override
    public void run() {
        while(true)
        {
            try
            {
                //2.调用锁定lock方法,----这里就像是之前的获取同步监视器
                lock.lock();//保证下面是单线程的,防止出现异常
                if(ticket > 0 )
                {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "售票的票号是"+ ticket);
                    ticket --;
                }
                else
                {
                    break;
                }
            }finally
            {
                //3.调用解锁的方法:lock.unclock();
                lock.unlock();
            }
        }
    }
}
public class LockTest {
    public static void main(String[] args) {
        Window w = new Window();
        Thread thread1 = new Thread(w);
        Thread thread2 = new Thread(w);
        Thread thread3 = new Thread(w);
        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");
        thread1.start();
        thread2.start();
        thread2.start();


    }
}
  • 面试题1:synchronized与Lock的异同?

        同:二者都可以解决线程的安全问题。

        不同:synchronized的机制是自己执行完相应的代码逻辑以后,自动的进行一个同步监视器的释放。Lock需要手动的去启动同步(Lock()),同时,结束同步也需要手动的去实现(UNclock())。

  • 在实际的开发的过程之中,我们一般是直接使用synchronized,但是后面的lock是新增的,我们建议使用,因为手动的上锁与解锁是比较灵活的,因此,首先建议使用lock。
  • 面试题2:如何解决线程的安全问题?有几种方式?

        synchronized与两种方式,lock有一种方式。

11.同步机制的课后练习

  • 在IDEA之中使用ALT+insert是调用相应的构造器
package com.atguigu.java.java;

/**
 * 银行有一个账户。
 * 有两个储户分别向同一个账户存3000块钱,每次存1000块钱,存3次。每次存完之后打印账户余额。
 * 分析:
 * 1.是否是多线程的问题?是,两个储户线程
 * 2.是否有共享数据?有,账户(或者账户余额)
 * 3.是否有线程安全问题?有
 * 4.如何解决线程安全问题?同步机制:有三种方式
 * @anthor shkstart
 * @create 2021-11-0315:18
 */
class Account
{
    private double balance;

    public Account(double balance) {
        this.balance = balance;
    }
    //存钱的方法
    public synchronized void deposit(double amt)//这里我们使用synchronized
    {

        if(amt > 0)
        {
            balance += amt;
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "存钱成功,当前的余额为" + balance);
        }
    }

}
class Customer extends Thread
{
    private Account acct;
    public Customer(Account acct) {
        this.acct = acct;
    }
    @Override
    public void run() {
        super.run();
        for(int i=0;i<3;i++)
        {

            acct.deposit(1000);
        }
    }
}
public class AccountTest {
    public static void main(String[] args) {
        Account acct = new Account(0);
        Customer c1 = new Customer(acct);
        Customer c2 = new Customer(acct);//这里是先让两个Customer共用同一个账户
        c1.setName("甲");
        c2.setName("乙");
        c1.start();
        c2.start();
    }
}
  •  上面是共用一个Account。

12.线程通信的例题

  • ctrl + alt + t是进行将代码块的快速调取相应的函数,比如说try-catch,synchronized
  • 下面是代码的是实现,并没有实现代码的交替。
package com.atguigu.java.java2;

/**
 * 线程通信的例子:使用两个线程打印1-100.线程1,线程2交替打印
 * @anthor shkstart
 * @create 2021-11-0315:50
 */
class Number implements Runnable
{
    private int number = 1;
    @Override
    public void run() {
        while(true)
        {
            synchronized (this) {//这里需要一个锁
                if(number <= 100)
                {
                    //加入sleep是将其暴露问题的可能性变大
                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;
                }
                else
                {
                    break;
                }
            }
        }
    }
}
public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);
        t1.setName("线程甲");
        t2.setName("线程乙");
        t1.start();
        t2.start();

    }
}
  • 下面是将代码进行交替输出的一个过程。
  • 这里应当注意notify()是唤醒一个,notifyAll()是唤醒所有的---唤醒别人,沉睡自己,辅助带着坩埚来了
  • sleep()不会释放锁,但是wait()是会释放锁的,具体的代码如下所示:
package com.atguigu.java.java2;

/**
 * 线程通信的例子:使用两个线程打印1-100.线程1,线程2交替打印
 涉及到了三个方法:
 wait():一旦被执行,当前线程就进入了阻塞状态,并释放同步监视器
 notify():一旦被执行,就会唤醒wait()的一个线程。如果有多个线程被wait,就会唤醒优先级较高的那一个
 notifyAll():一旦被执行就会被唤醒所有的wait()的线程。

 注意点:
 1.wait(),notify()以及notifyAll()必须使用在同步代码块或者同步方法之中。
 2.这三个方法的调用者必须是同步代码块或同步方法之中的同步监视器。因此,就是不能够出现this.notify();
   否则的话就会出现IllegalMonitorStateException的情况。
 3.wait(),notify()以及notifyAll()这三个方法是定义在Object类之中的。
 * @anthor shkstart
 * @create 2021-11-0315:50
 */
class Number implements Runnable
{
    private int number = 1;
    private Object obj = new Object();
    @Override
    public void run() {
        while(true)
        {
            synchronized (obj) {//这里需要一个锁
              obj.notifyAll();//这里应当注意notify()是唤醒一个,notifyAll()是唤醒所有的
                if(number <= 100)
                {
                    //加入sleep是将其暴露问题的可能性变大
                    System.out.println(Thread.currentThread().getName() + ":" + number);
                    number++;
                    //我们使用wait()进行这个交替输出
                    try {//1.使得调用下属方法的线程,进入阻塞状态。可以知道是只用wait()是不可以的
                        obj.wait();//wait会释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                else
                {
                    break;
                }
            }
        }
    }
}
public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number);
        Thread t2 = new Thread(number);
        t1.setName("线程甲");
        t2.setName("线程乙");
        t1.start();
        t2.start();

    }
}

13.sleep()和wait()的异同

  • 上述的标题是一个面试题:sleep()与wait()的异同

        1.相同点:一旦执行方法,都可以使得当前线程进入阻塞状态

        2.不同点:

(1)两个方法的声明位置是不同的:Thread类中声明sleep(),Object类中声明wait()

(2)调用的时候的范围和要求是不一样的,sleep()的调用是在任何需要的地方调用,wait()必须在同步代码块或者同步方法区里面调用。

(3)关于是否释放监视器的问题:如果两个方法都使用在同步代码块或者同步方法区内,sleep()不会释放锁,wait()会释放锁。

 

14.线程通信:生产者、消费者例题

package com.atguigu.java.java2;

/**
 * 线程通信的一个应用:经典的生产者/消费者问题
 * 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处
 * 取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图
 * 生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通
 * 知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如
 * 果店中有产品了再通知消费者来取走产品。
 * 分析:
 * 1.是否是多线程问题?是,生产者的线程,消费者的线程
 * 2.是否有线程安全问题?是,店员(或者产品)
 * 3.如何解决线程的安全问题?同步机制,有三种方法
 * 4.是否涉及到线程的通信?是,
 * @anthor shkstart
 * @create 2021-11-0519:29
 */
class Clerk
{
private int productCount = 0;
    public synchronized void ProduceProduct() {//生产产品
    if(productCount < 20)
    {   productCount++;
        System.out.println(Thread.currentThread().getName() + "开始生产第" + productCount + "个产品");
        notify();//通知消费者,我可以了
    }
    else
    {
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    }
    public synchronized void ConsumeProduct() {//消费产品
        if(productCount > 0)
        {
            System.out.println(Thread.currentThread().getName() + "开始消费第" + productCount + "个产品");
            productCount--;
        }
        else
        {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class  Producer extends Thread//生产者
{
    private Clerk clerk;
    public Producer(Clerk clerk)
    {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":开始生产产品");
        while(true)
        {
            try {
                Thread.sleep(10);
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
            clerk.ProduceProduct();//alt+enter进行方法的创建
        }
    }
}
class Consumer extends Thread//消费者
{
    private Clerk clerk;
    public Consumer(Clerk clerk)
    {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ":开始消费产品");
        while(true)
        {
            try {
                Thread.sleep(40);
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
            clerk.ConsumeProduct();//alt+enter进行方法的创建
        }
    }
}
public class ProductTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer p1 = new Producer(clerk);
        p1.setName("生产者1");
        Consumer p3 = new Consumer(clerk);
        Consumer p2 = new Consumer(clerk);
        p2.setName("消费者1");
        p3.setName("消费者2");
        p1.start();
        p2.start();
        p3.start();

    }
}

15.创建线程的方式三:实现Callable接口

package com.atguigu.java.java2;

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

/**
 * 创建线程的方式三:实现Callable接口。---JDK5.0新增的
 *
 * 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程强大?
 * 1.call()是可以有返回值的
 * 2.call()是可以抛出一个异常,被外面的操作捕获,获取异常信息
 * 3.Callable支持泛型的
 * @anthor shkstart
 * @create 2021-11-0520:15
 */
//创建一个实现Callable的实现类
class NumberThread implements Callable<Integer> //标出红线,使用alt + enter
{
    //2.实现call方法,将此线程需要执行的操作声明在call()方法之中。
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for(int i = 1;i<= 100;i++)
        {
            if(i%2==0)
            {
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}
public class ThreadNew {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        NumberThread numberThread = new NumberThread();
        //4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器之中,创建FuturnTask的对象
        FutureTask<Integer> futureTask = new FutureTask<Integer>(numberThread);
        //5.将FuturnTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并且调用start()
        new Thread(futureTask).start();
        try {
            //6.获取Callable中的call方法的返回值。------这一步骤是可以不要的。
            //get()返回值即是FutureTask构造器参数Callable实现类重写的call()的返回值。
           Object sum =  futureTask.get();
            System.out.println("zonghe" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

16.使用线程池的好处

17.创建多线程的方式四:使用线程池

package com.atguigu.java;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 创建线程池的方法四:使用线程池
 * 真正在开发之中用到的都是线程池,因为他有许多的好处
 * 如下所示:
 * 1.提高响应速度(减少了创建新线程的时间)
 * 2.降低资源的消耗(重复利用线程池,不需要每次都是进行一个创建的过程)
 * 3.便于线程的管理
 *
 * @anthor shkstart
 * @create 2021-11-0718:22
 */
class NumberThread implements Runnable
{
    @Override
    public void run() {
        for(int i = 0; i <= 100;i++)
        {
            if(i%2==0)
            {
                System.out.println(Thread.currentThread().getName()+i);

            }
        }
    }
}
class NumberThread1 implements Runnable
{
    @Override
    public void run() {
        for(int i = 0; i <= 100;i++)
        {
            if(i%2!=0)
            {
                System.out.println(Thread.currentThread().getName()+i);
            }
        }
    }
}
public class ThreadPool {
    public static void main(String[] args) {
        //1.提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);//ExecutorService是一个接口,通过接口的形式去实现类。
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //设置线程池的属性
        System.out.println(service.getClass());//class java.util.concurrent.ThreadPoolExecutor
        service1.setCorePoolSize(15);
        //service1.setKeepAliveTime(12);
        //2.执行指定的线程的操作。需要提供一个实现Runnable接口或者Callable接口实现类的对象
        service.execute(new NumberThread());//适合使用Runnable
        service.execute(new NumberThread1());//适合使用Runnable
       // service.submit();//适合适用于Callbable
        //3.关闭线程池
        service.shutdown();
    }
}
  • 面试题:创建多线程是有四种方式

18.每天一考

  • 画图说明线程的生命周期,以及各状态切换使用到的方法等。(状态,方法)

        什么暂停的状态,阻塞等,方法的调用是关于从一种状态转换为另一种状态的过程---回调方法。方法调用导致状态的变化。

  • 同步代码块中涉及到同步监视器共享数据,谈谈你对同步监视器和共享数据的理解,以及注意点。

        synchronized(同步监视器){

       //对于非静态的同步方法,同步监视器是this,对于静态而言,同步监视器是当前类本身

        //操作共享数据的代码 (不能包括多了,也不能包括少了)

        }

  • sleep()和wait()的区别

1.位置不同,sleep()在Tread里面,第二个是在Object里面。

2.wait只能在同步代码块或者同步方法之中。

3.sleep()需要声明一个时间,wait()需要一个notify();

  •  写一个线程安全的懒汉式

前面已经写了(看一下就行)

  • 创建多线程有哪几种方式:4种

继承Thread类

实现Runnable接口

实现Callable接口

线程池(上面写的三点:响应速度提高了,提高了资源的重用率,便于管理)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值