Java进阶12 多线程

Java进阶12 多线程

一、进程&线程

1、进程

1.1 定义

进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配的基本单位。通俗来讲,即程序的执行过程

1.2 特点
  • 独立性

    进程是系统中独立存在的实体,它可以拥有自己独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他的进程的地址空间。

  • 动态性

    进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。

  • 并发性

    任何进程都可以同其他进程一起并发执行

    ⭐注意区别并发和并行!!!

    • 并发(在同一时刻,有多个指令在单个CPU上【交替】执行

    • 并行(在同一时刻,有多个指令在多个CPU上【同时】执行

1.3 多进程

对于一个CPU而言,它是在多个进程间轮换执行的,只是轮换很快,我们感知不到。

2、线程

2.1 定义

进程可以同时执行多个任务,每个任务就是线程。线程依赖进程,线程是进程执行的最小单元。一个进程至少应该存在一个线程

2.2 单线程&多线程
  • 单线程:程序只有一条执行路径

  • 多线程:程序有多条执行路径

    Java程序默认就是多线程(主方法一进栈就意味着主线程(main)开启了,并且垃圾回收线程也随之开启,不定时去检测内存中的垃圾并回收)

2.3 多线程的意义
  • 提高执行效率

  • “同时”处理多个任务

二、Java开启线程的方式(3种)

1、继承Thread类

1.1 方法介绍
方法说明
void run()在线程开启后,此方法将被调用执行
void start()使此线程开始执行,Java虚拟机会调用run方法()
1.2 实现步骤
  • 编写一个类,继承Thread类

  • 重写run()方法(将线程任务代码写在run方法中)

  • 创建线程对象

  • 调用start方法开启线程

public class ThreadDemo1 {
    public static void main(String[] args) {
        //4、创建线程对象
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        //5、调用start方法开启线程
        mt1.start();
        mt2.start();
    }
}
//1、编写一个类,继承Thread方法
class MyThread extends Thread{
    //2、重写run方法
    @Override
    public void run() {
        //3、将线程任务代码写在run方法种
        for (int i = 1; i <=500 ; i++) {
            System.out.println("线程任务正在执行"+i);
        }
    }
}

注意statrt方法和run方法的区别

  • 调用start方法,内部会自动调用run方法,只有调用了start方法,才算开启了线程

  • 调用run方法,仅仅是对方法的一次调用而已,并没有开启新的线程

2、实现Runable接口

2.1 Thread构造方法
方法名说明
Thread(Runnable target)分配一个新的Thread对象
Thread(Runnable target,String name)分配一个新的Thread对象
2.2 实现步骤
  • 编写一个类,实现Runnable接口

  • 重写run()方法(将线程任务代码写在run方法中)

  • 创建线程任务对象

  • 创建线程对象,将线程任务传入

  • 使用线程对象调用start方法开启线程

public class ThreadDemo2 {
    public static void main(String[] args) {
        //4、创建线程任务对象
        MyRunnable mr = new MyRunnable();
        //5、创建线程对象,将线程任务传入
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        //6、使用线程对象,调用start方法开启线程
        t1.start();
        t2.start();
    }
}
​
//1、编写一个类,实现Runnable接口
class MyRunnable implements Runnable{
    //2、重写run方法
    @Override
    public void run() {
        //3、将线程任务代码写在run方法中
        for (int i = 1; i <=500 ; i++) {
            System.out.println("线程执行了:"+i);
        }
    }
}

3、实现Callable接口

3.1 方法介绍
方法说明
V call()计算结果,如果无法计算结果,则抛出一个异常
FutureTask(Callable<V> callable)创建一个FutureTask,一旦运行就执行给定的Callable
V get()如有必要,等待计算完成,然后获取其结果
3.2 实现步骤
  • 编写一个类,实现Callable接口

  • 重写call方法(将线程任务代码写在call()方法当中)

  • 创建线程的资源对象

  • 创建线程的任务对象(FutureTask<T>)来封装资源

  • 创建线程对象,传入线程任务

  • 使用线程对象调用start方法,开启线程

  • 获取线程任务结束的返回值

public class ThreadDemo5 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //4、创建线程资源对象
        MyCallable mc = new MyCallable();
        //5、创建线程任务对象,封装资源;
        //  该对象可以作为参数传递给Thread对象,也可以获取线程执行完毕之后的结果
        FutureTask<Integer> task1 = new FutureTask<>(mc);
        FutureTask<Integer> task2 = new FutureTask<>(mc);
        //6、创建线程对象,传入线程任务
        Thread t1 = new Thread(task1);
        Thread t2 = new Thread(task2);
        //7、使用线程对象调用start方法,开启线程
        t1.start();
        t2.start();
        //8、获取线程任务结束的返回值
        Integer r1 = task1.get();
        Integer r2 = task2.get();
​
        System.out.println("task1获取返回值为"+r1);
        System.out.println("task2获取返回值为"+r2);
    }
}
​
//1、编写一个类实现Callable接口
class MyCallable implements Callable<Integer>{
​
    //2、重写call方法
    @Override
    public Integer call() throws Exception {
        //将线程任务代码写在call方法中
        int sum =0;
        for (int i = 1; i <=100 ; i++) {
            sum+=i;
            System.out.println("sum的累加过程"+sum);
        }
        return sum;
    }
}

⭐三种方式的对比

实现Runnable、Callable接口(重点掌握)继承Thread类
好处扩展性强,实现该接口的同时还可以继承其他的类编程比较简单,可以直接使用Thread类中的
缺点编程相对复杂,不能直接使用Thread类中的方法扩展性较差,不能再继承其他的类

推荐使用实现接口的两种方式,扩展性更好。当线程执行任务需要有返回值时,选择实现Callable接口来实现!

三、线程相关方法

1、线程名称相关方法

方法说明
String getName()返回此线程的名称
void setName(String name)设置线程名称(线程的构造方法也可以设置)
Thread currentThread()获取当前正在执行的线程对象的引用
public class MethodDemo1 {
    public static void main(String[] args) {
        //线程的构造方法设置线程名称
        MyThread mt1 = new MyThread("线程A");
        MyThread mt2 = new MyThread("线程B");
​
        /*//线程对象调用setName方法设置线程名
        mt1.setName("线程A:");
        mt1.setName("线程B:");*/
        
        mt1.start();
        mt2.start();
    }
}
​
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 1; i <=500 ; i++) {
            //获取线程名称,此注的super.可以省略
            System.out.println(super.getName()+"线程任务正在执行"+i);
            //获取当前线程名,结果和上一行代码super.getName()相同
            String name = Thread.currentThread().getName();
​
        }
    }
​
    public MyThread() {
    }
​
    public MyThread(String name) {
        super(name);
    }
}

2、线程休眠

方法说明
static void sleep(long millis)使当前正在执行的线程休眠(暂停执行)指定的毫秒数
public class Test2 {
    public static void main(String[] args) {
        MyThread1 mt1 = new MyThread1();
​
        Thread t1 = new Thread(mt1);
​
        t1.start();
    }
}
​
​
class MyThread1 implements Runnable{
​
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-M-d HH:mm:ss");
    @Override
    public void run() {
        try {
            for (int i = 1; i <=10 ; i++) {
                System.out.println(Thread.currentThread().getName()+"  "+formatter.format(LocalDateTime.now()));
                //线程休眠1秒,效果就是每一秒打印一次当前时间
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3、线程优先级

方法说明
final int getPriority()返回此线程的优先级
final void setPriority(int newPriority)设置线程的优先级;线程默认优先级是5;更改范围是1-10
public class MethodDemo2 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <=20 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"线程任务执行"+i);
                }
            }
        },"线程A");
​
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <=30 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"线程任务执行"+i);
                }
            }
        },"线程B");
​
        //设置线程A的优先级为1
        t1.setPriority(1);
        //设置线程B的优先级为10
        t2.setPriority(10);
​
        //获取线程A的优先级
        System.out.println(t1.getPriority());
        //获取线程B的优先级
        System.out.println(t2.getPriority());
​
        t1.start();
        t2.start();
    }
}
​

4、守护线程

方法说明
final void setDeamon(boolean on)设置为守护线程
public class MethodDemo3 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <=20 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"线程任务执行"+i);
                }
            }
        },"线程A");
​
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <=30 ; i++) {
                    System.out.println(Thread.currentThread().getName()+"线程任务执行"+i);
                }
            }
        },"线程B");
​
        //设置t2线程为守护线程,为其他线程“保驾护航”;
        t2.setDaemon(true);
​
        t1.start();
        t2.start();
    }
}

四、线程安全和同步

1、安全问题出现的条件

  • 多线程环境

  • 有数据共享

  • 有多条语句操作共享数据

2、同步技术(解决数据安全问题)

解决数据安全问题的思路:将多条语句操作共享数据的代码给锁起来,让任意时刻只能由一个线程执行即可。

2.1 同步代码块
  • 格式

    synchronized(任意对象) {
        多条语句操作共享数据的代码 
    }

    synchronized(任意对象)就相当于给代码加锁了,任意对象就可以看成是一把锁

  • 好处和弊端

    • 好处:解决了多线程的数据安全问题

    • 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

  • Demo

    public class CinemaTest1 {
        public static void main(String[] args) {
            TicketTask task = new TicketTask();
            new Thread(task,"窗口1:").start();
            new Thread(task,"窗口2:").start();
            new Thread(task,"窗口3:").start();
        }
    }
    ​
    class TicketTask implements Runnable{
    ​
        int tickets = 1000;
    ​
        @Override
        public void run() {
            while (true) {
                //对以下代码加锁(多个线程为了保证共享数据安全需要加同一把锁!!!)
                synchronized (TicketTask.class) {
                    if(tickets == 0){
                        break;
                    }else{
                        System.out.println(Thread.currentThread().getName()+"卖出了第"+tickets+"号票");
                        tickets--;
                    }
                }
                //锁释放
            }
        }
    }
2.2 同步方法
  • 格式

    //非静态同步方法    锁对象为this
    修饰符 synchronized 返回值类型 方法名(方法参数){
        方法体;
    }
    ​
    //静态同步方法   锁对象为 类名.class
    修饰符 static synchronized 返回值类型 方法名(方法参数){
        方法体;
    }
  • 注意事项:①非静态同步方法的锁对象为this;②静态同步方法的锁对象为 类名.class;

2.3 Lock锁(互斥锁)

使用Lock锁,能够更清晰的看到哪里加了锁,哪里释放了锁。Lock是接口,无法直接创建对象,创建的是其实现类对象ReentrantLock(可重入锁)

  • 构造方法

    方法名说明
    public ReentrantLock()创建一个ReentrantLock的实例
  • 加锁解锁方法(Object类的方法)

    方法名说明
    void lock()加锁
    void unlock()释放锁
  • Demo

    public class CinemaTest4 {
        public static void main(String[] args) {
            TicketTask4 task = new TicketTask4();
            new Thread(task,"窗口1:").start();
            new Thread(task,"窗口2:").start();
            new Thread(task,"窗口3:").start();
        }
    }
    ​
    class TicketTask4 implements Runnable{
    ​
        int tickets = 1000;
    ​
        //创建锁对象
        ReentrantLock lock = new ReentrantLock();
    ​
        @Override
        public void run() {
            while (true) {
                try {
                    //该线程获得锁
                    lock.lock();
                    if(tickets == 0){
                        break;
                    }else{
                        System.out.println(Thread.currentThread().getName()+"卖出了第"+tickets+"号票");
                        tickets--;
                    }
                    //不管程序运行何时结束,锁一定要释放,所以使用finally
                } finally {
                    //该线程释放锁
                    lock.unlock();
                }
            }
        }
    }
* 死锁

线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。

五、线程通讯(等待唤醒机制)

1、线程通信

  • 确保线程能够按照预定的顺序执行,并且能够安全地访问共享资源

  • 使多线程更好地进行协同工作

2、等待唤醒机制

成员方法说明
void wait()使当前线程等待
void notify()随机唤醒单个等待地线程
void notifyAll()唤醒所有等待地线程

注意事项:

  • 这些方法都需要使用锁对象调用

  • wait()方法在等待期间会释放锁对象

public class Test6 {
    public static void main(String[] args) {
        Printer p = new Printer();
​
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (Printer.class) {
                        try {
                            p.Print1();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }, "线程1").start();
​
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    synchronized (Printer.class) {
                        try {
                            p.Print2();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }, "线程2").start();
​
    }
}
​
class Printer{
​
    int flag = 1;
​
    public void Print1() throws InterruptedException {
        if (flag != 1) {
            //线程1等待
            Printer.class.wait();
        }
            System.out.print("黑");
            System.out.print("马");
            System.out.print("程");
            System.out.print("序");
            System.out.print("员");
            System.out.println();
​
            flag = 2;
            //唤醒线程2
            Printer.class.notify();
        }
​
    public void Print2() throws InterruptedException {
        if(flag!=2){
            //线程2等待
            Printer.class.wait();
        }
        System.out.print("传");
        System.out.print("智");
        System.out.print("教");
        System.out.print("育");
        System.out.println();
​
        flag = 1;
        //唤醒线程1
        Printer.class.notify();
    }
}

也可以使用ReentrantLock实现同步,并获取Condition监视器对象(案例见3.3)Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁。Condition对象是由Lock对象创建出来的,换句话说,Condition是依赖Lock对象的。Condition在调用方法之前必须获取锁。

成员方法说明
void await()指定线程等待
void signal()指定唤醒单个等待的线程

3、生产者消费者模式

生产者消费者模式是一个十分经典的多线程协作的模式。

3.1 包含线程两类
  • 生产者线程:用于生产数据

  • 消费者线程:用于消费数据

3.2 过程理解
  • 为了解耦生产者和消费者的关系,通常会采用共享的数据区域(缓冲区),就像是一个仓库;

  • 生产者生产数据之后直接放置在共享数据区中,并不需要关信消费者的行为;

  • 消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为;

3.3 案例(吃包子)

public class WareHouse {
    /*
      共享区域的数据,为了方便其他类调用,全都加上public 和 static修饰符
      这样其他类调用就可以直接用类名.进行调用了
    */
    public static boolean mark;
    //选择可重入锁的锁对象,锁机制更灵活
    public static ReentrantLock lock = new ReentrantLock();
    //Condition监视器对象关联锁,Condition对象的创建依赖锁对象
    public static Condition producer = lock.newCondition();
    public static Condition consumer = lock.newCondition();
}
/**
*   生产者类实现Runable接口
*/
public class Producer implements Runnable{
    //重写run方法
    @Override
    public void run() {
        while(true){
            WareHouse.lock.lock();
            if(WareHouse.mark){
                //mark为true,生产者休眠
                try {
                    WareHouse.producer.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                //mark为false,生产者生产,改标记,唤醒消费者
                System.out.println("生产者生产");
                WareHouse.mark= true;
                WareHouse.consumer.signal();
            }
            WareHouse.lock.unlock();
        }
    }
}
/**
*   消费者类实现Runable接口
*/
public class Consumer implements Runnable{
    //重写run()方法
    @Override
    public void run() {
        while(true){
            //消费者线程上锁
            WareHouse.lock.lock();
            if(WareHouse.mark){
                //mark为true说明有包子,吃包子
                System.out.println("消费者消费");
                //改标记
                WareHouse.mark = false;
                //唤醒生产者线程
                WareHouse.producer.signal();
            }else{
                //mark为false说明没有包子,消费者线程等待
                try {
                    WareHouse.consumer.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
         //消费者线程解锁   
        WareHouse.lock.unlock();
        }
    }
}
/**
*    测试类(主类)
*/
public class Test {
    public static void main(String[] args) {
        //开启生产者线程
        new Thread(new Producer()).start();
        //开启消费者线程
        new Thread(new Consumer()).start();
    }
}
  • 51
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值