Day_16 【Java基础】多线程、线程通信、线程安全和线程池【附源码】

一.什么是程序、进程、线程?

程序:就是用编程语言编写的一段静态的代码。
进程:一个正在运行的应用程序
线程:一个程序内部的一条执行路径
线程、进程根本区别
① 进程是资源分配的基本单位,而线程是处理器任务调度和执行的基本单位;
② 一个进程可以由多个进程组成,但是一个线程只能属于一个进程;
③ 每个线程拥有自己独立的栈、程序计数器(pc),多个线程共享一个进程的结构和资源:堆、方法区
并行:多个cpu同时执行多个任务
并发:一个cpu(采用时间片)同时执行多个任务,如:秒杀

二.线程的创建方式

1.继承Thread类,重run()方法
① 创建子类继承Thread类
② 子类重写run()方法(线程需要实现的逻辑功能)
③ 子类创建对象
④ 使用对象调用start()方法(启动当前线程、调用当前线程的run()方法)
注意:
启动一个线程,必须调用start()方法,不要使用对象调用重写后的run()方法,此时就相当于在main线程中使用“对象.方法”调用方法,无法体现多线程的功能。

public class ThreadTest {
    public static void main(String[] args) {
        //Thread.currentThread().getName():获取当前运行的线程名
        System.out.println(Thread.currentThread().getName());
        //3.创建子类对象
        MyThread myThread = new MyThread();
        //4.调用start()方法
        myThread.start();
//        myThread.start();   //java.lang.IllegalThreadStateException
    }
}
//1.创建子类继承Thread类
class MyThread extends Thread{
    @Override
    //2.重写run()方法
    public void run() {
        for(int i=0;i<100;i++){
            if(i%2!=0){
                System.out.println(Thread.currentThread().getName()+"-i :" +i);
            }

        }
    }
}

说明:
① Thread中常用的方法
在这里插入图片描述

/**
 * @author wds
 * @date 2021-11-15-17:30
 */
public class ThreadTest01 {
    public static void main(String[] args) {
        Mythread01 mythread01 = new Mythread01();
        Mythread01 mythread02 = new Mythread01();
        mythread01.run();                    //1.run():调用当前线程的run()方法
        System.out.println("main函数所在的线程是:"+Thread.currentThread().getName());
        mythread01.setName("线程1");           //2.setName(String name):给线程命名
        mythread02.setName("线程2");
        mythread01.start();
        mythread02.start();
        System.out.println("mythread01线程是否存活:"+mythread01.isAlive());
    }
}
class Mythread01 extends Thread{

    @Override
    public void run() {                     //3.run():声明线程需要实现的功能
        for(int i=0;i<5;i++){
            if(i%2==0){
               try {
                    sleep(100);     //4.sleep(long mill)使线程修眠,暂时放弃对cpu的控制
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }                           //5.currentThread():获取当前代码的线程
                yield();                    //6.yield():释放当前的cpu执行权
                                            //7.getName():当前的线程名
                System.out.println("run所在线程名"+Thread.currentThread().getName()+" i:"+i);
            }
        }

    }
}

② 线程的调度:
策略:时间片策略、抢占式调度
Java调度策略:同优先级先来先服务时间片策略,高优先级抢占cpu;
MAX_PRIORITY:10
MIN_PROIORITY:1
NORM_PRIORITY:5(默认优先级)
getPriority():获取线程的优先级
setPriority(int p):设置线程的优先级
说明:高优先级的线程会抢占低优先级的线程cpu的执行权,并不是一定会执行高优先级的线程。

public class ThreadTest01 {
    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        Thread t1 = new Thread(thread1,"线程1");
        Thread t2 = new Thread(thread1,"线程2");
        t1.setPriority(8);		//设置线程t1的优先级为8
        t1.start();
        t2.start();
    }
}
class Thread1 implements Runnable{
    public static int number = 1;
    @Override
    public void run() {
        while(true){
            synchronized (this) {
                if (number < 100) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if(number%2==0){
                        System.out.println(Thread.currentThread().getName() + "输出:" + number
                                + ",优先级是" + Thread.currentThread().getPriority());
                    }
                    number++;
                }
            }
        }
    }
}

③ 线程的分类
根据JVM何时离开,分为:
守护线程:为用户进程服务,使用start()调用。如:Java垃圾回收
用户线程
若JVM都是守护进程,JVM将推出。
④ 用户进程—>守护进程:thread.setDaemon(true)

2.实现Runnable接口,重写run()方法
① 创建一个类实现Runnable接口
② 重写run()方法
③ 创建实现类对象
④ 将实现类对象作为参数传入Thread构造器中,创建构造器的对象
⑤ 通过Thread对象调用start()

public class ThreadTest02 {
    public static void main(String[] args) {
        //3.创建实现类对象
        MyThread02 myThread02 = new MyThread02();
        //4.将实现类对象作为参数传给Thread构造器,创建Thread对象
        Thread thread1 = new Thread(myThread02,"线程1");
        //5.通过Thread对象调用start()
        thread1.start();
        Thread thread2 = new Thread(myThread02, "线程2");
        thread2.start();
    }
}
//1.创建类实现Runnable接口
class MyThread02 implements Runnable{
    @Override
    //2.重写run()方法
    public void run() {
        int i = 0;
        while(i++<3){
            System.out.println(Thread.currentThread().getName()+"的run方法在运行...");
        }
    }
}

3.实现Callable接口,重写call()方法
① 创建类实现Callable接口
② 重写实现call()方法
③ 创建实现类的对象
④ 将实现类的对象传递给FutureTask构造器,创建FutureTask的对象
⑤ 将FutureTask的实现类作为参数传递给Thread创建Thread类的对象,调用start()方法
⑥ 获取Callable接口的返回值

public class ThreadTest01 {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        Thread2 thread2 = new Thread2();
        //4.将实现类的对象传递给FutureTask构造器,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(thread2);
        //5.将FutureTask的实现类作为参数传递给Thread创建Thread类的对象,调用start()方法
        new Thread(futureTask).start();
        try {
            //6.获取Callable接口的返回值
            //get()返回call()方法重写后的返回值
            Object sum = futureTask.get();
            System.out.println("1-100求和为:"+sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
//1.定义一个类实现Callable接口
class Thread2 implements Callable{
    int sum = 0;
    @Override
    //2.实现call()方法,将此线程实现的操作声明在方法中
    public Object call() throws Exception {
        for(int i = 1;i<=100;i++){
            sum += i;
            System.out.println(sum);
        }
        return sum;
    }
}

实现Runnable、Callable接口异同:

① Runnable接口重写run()方法,没有返回值,无法抛出异常;
② Callable接口重写call()方法,可以有返回值,可以抛出异常;
③ 推荐使用Callable接口方式、线程池的方式实现多线程。

4.使用线程池
① 定义一个接口的实现类,实现Runnable接口或Callable接口
② 提供指定数量的线程池
③ 创建接口的实现类对象
④ 将实现类对象提交到线程池进行管理
⑤ 关闭线程池

//1.定义一个接口的实现类,实现Runnable接口或Callable接口
class Thread11 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) {
        //2.提供指定数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        //管理线程池强制类型转换为ThreadPoolExecutor,利用service01管理线程池
        ThreadPoolExecutor service01 = (ThreadPoolExecutor)service;
        System.out.println(service.getClass());
        //3.创建接口的实现类对象
        Thread11 thread1 = new Thread11();
        Thread11 thread2 = new Thread11();
        //4.将实现类对象提交到线程池进行管理
        //service.submit();           //Callable接口实现
        service.execute(thread1);     //Runnable接口实现
        service.execute(thread2);
        //5.关闭线程池
        service.shutdown();
    }
}

线程池的好处
① 提高响应速度(减少了创建新线程的时间)
② 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
③便于线程管理

三.线程的生命周期

线程的五大状态:创建、就绪、运行、阻塞、死亡。
在这里插入图片描述

四.线程通信

线程通信的三个方法
① wait():线程进入阻塞,释放同步监视器
② notify():唤醒被wait()的一个线程,当有多个被wait()时,唤醒优先级高的。
③ nitifyAll():唤醒被wait()的多个线程
注意:
这三个方法必须使用在同步方法或同步代码块中,调用者必须是同步代码块或同步方法的监视器。

线程通信例子:两个线程交替打印输出1-20的数字

/**
 * 线程通信例子:两个线程交替打印输出1-20的数字
 * @author wds
 * @date 2021-11-16-16:03
 */
public class ThreadTest {
    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        Thread a = new Thread(thread1, "线程a");
        Thread b = new Thread(thread1, "线程b");
        a.start();
        b.start();
    }
}
class Thread1 implements Runnable {
    private static int number = 1;
    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (number <= 20) {
                    notify();
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "打印:" + number);
                    number++;
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        }
    }
}

五.线程安全问题

线程安全问题的处理
例题模拟三个窗口共同售100张票(附源码)
1.同步代码块:
语法格式:
synchronized(监视器){
//需要同步的代码
}

监视器又叫锁,任何一个对象都可以作为锁,多个线程共享的锁唯一即可
① 实现Runnable接口的方式中,可以使用this关键字或对象名,也可以使用“类名.class”
② 继承Thread方式中,慎用this,可以使用“类名.class”。
好处:解决了同步问题,实现了线程安全;
不足:操作同步代码块内只有一个线程执行,相当于单线程,其他等待,效率低。

2.同步方法
语法格式
[修饰符] synchronized 返回值类型 方法名(参数列表){
//需要同步的代码
}

同步方法说明
① 同步方法仍然设计到同步监视器,只是不需要我们显式声明;
② 非静态的同步方法,同步监视器是this;静态的同步方法,监视器是类本身;

3.同步锁lock
在这里插入图片描述
优先使用顺序:
Lock—>同步代码块(已经进入了方法体,分配了相应资源)—>同步方法(在方法体之外)

六.死锁问题

所谓死锁,是指不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的向步资源,就形成了线程的死锁。
死锁面试题

public class ThreadTest {
    public static void main(String[] args) {
        StringBuffer str1 = new StringBuffer();
        StringBuffer str2 = new StringBuffer();
        new Thread(){           //继承Thread类方式的匿名对象
            @Override
            public void run() {
                synchronized (str1){
                    str1.append(1);
                    str2.append("a");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (str2){
                        str1.append("b");
                        str2.append(2);
                        System.out.println(str1);
                        System.out.println(str2);
                    }
                }
            }
        }.start();
        new Thread(new Runnable() {     //实现Runnable接口方式的匿名对象
            @Override
            public void run() {
                synchronized (str2){
                    str1.append(3);
                    str2.append("a");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (str1){
                        str1.append("b");
                        str2.append(4);
                        System.out.println(str1);
                        System.out.println(str2);
                    }
                }
            }
        }).start();
    }
}

死锁线程不报错,不终止,必须避免死锁
在这里插入图片描述
面试题:
1.Lock和synchronized有什么区别?
相同:二者都可以解决线程安全问题
不同:
Lock是显式锁(手动开启和关闭锁);synchronized是隐式锁,出了作用域会自动释放
Lock只有代码块锁,synchronized有代码块锁和同步方法锁
2.处理线程安全有几种方式?
三种:同步方法,同步代码块、lock同步锁
3.sleep()和wait()方法的异同?
相同:一旦调用方法,都会使得线程进入阻塞状态
不同:
① 声明位置不同。sleep()声明在Thread中,wait()声明在Object中;
② sleep()在任何场景下都可以调用,wait()在同步代码块或同步方法中;
③ 若都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

智商三岁半i

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

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

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

打赏作者

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

抵扣说明:

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

余额充值