第八章:多线程解析以及线程同步,生产者和消费者

一:进程和线程

进程:就是一个应用程序,JVM也是一个进程,进程和进程的内存是独立不共享的
线程:线程是一个程序的执行单元,一个进程可以有多个线程;堆内存和方法区是线程共享的内存
	  栈内存是独立的,一个线程一个栈!

二:实现线程的三种方式

(1)第一种方法:继承Thread

  1. 一个类继承了Thread线程类,那么他就是一个线程对象,此时我们直接New这个对象就是创建了一个新的线程,它操作的数据是本类的属性,New几个线程实例就是开启了几个单独的线程
  2. 继承线程类重写run方法,因为我们开启线程** start()后即代表开启成功 ,这句开启代码就结束了。启动成功的线程会自动调用Run方法**,run方法结束代表线程结束
  3. 因为单继承的特性,这种方法会破坏原有的类,所以不常用
 class Thread01 extends Thread{
        int count = 1;
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + 
                "------->"+count++);
            }
        }
    }
//创建线程:三条线程,线程类开启的线程数据不共享
Thread01 t = new Thread01();
     t.start();
     t.start();
     t.start();
//匿名内部类:
new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + "------->"+count++);
                }
            }
        }.start();

(2)第二种方法:实现Runnable

  1. 编写一个类继承Runnable接口,重写其run方法
  2. 创建这个对象,将这个对象封装成一个线程对象,Thread t = new Thread(对象)
  3. 创建多个线程,同时操作同一个对象,实现多线程操作同对象
  4. 实现接口的方式还可以继续继承其他类,所以一般情况用这种方式
class runnable01 implements Runnable{
        int count = 1;
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "------->"+count++);
            }
        }
    }
//需要先实现继承Runnable接口的类,再将其封装为线程,多个线程同时操作一个类,数据共享
//2.第二种:实现Runnable接口
        runnable01 tt = new runnable01();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();

public class Test09 {
    public static void main(String[] args){
       Runnable r = new Runnable() {
            int count = 100;
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + "-------->"+ count--);
                }
            }
        };
       new Thread(r).start();
       new Thread(r).start();
       new Thread(r).start();
    }
}

(3)第三种方法:实现Callable

 和Runnable一样的用法,但是这个实现类有返回值

三:关于线程对象的生命周期

线程的五个状态:
新建状态:刚刚New出来的状态
就绪状态:当前线程具备抢夺CPU时间片(执行权)的权利
运行状态:run方法执行标记着进入运行状态,当时间片结束后,重新进入就绪状态
阻塞状态:当遇到阻塞时间线程会放弃当前时间片(执行权),阻塞解除需要重新进入就绪状态
死亡状态:run方法结束,即线程结束

多线程并发即在就绪状态和运行状态来回调度的结果!!!

生命周期

四:线程的几个常用method的用法

(1)sleep()

  1. 线程的休眠,这个是静态方法,它出现在哪个线程就作用在哪个线程。
  2. 线程的休眠时间的单位是毫秒值
  3. 休眠的作用是让当前线程进入阻塞状态,此线程放弃当前执行权,并在阻塞结束后重新进入就绪状态等待再次被挑选到,但是其不会放弃当前拿到的锁
  4. 可以用作定时器的方法(过时)
  5. Sleep会抛出异常,其作用在run的方法内时,只能try…catch,不能Throws,因为子类不能抛出比父类更多的·异常
  6. 休眠可以被打断,即调用线程.interrupt()中断方式。这个机制是利用异常的方法打断的

(2)stop(过时)

  1. 这是停止线程的方法,这个方法直接结束当前线程
  2. 这个方法容易造成数据丢失,因为线程直接被杀死了,这是非正常的结束线程的方法
  3. 这个方法已经过时了

(3)正常的线程终止是让run方法结束

 我们正常的操作是:在线程内部定义一个布尔标记,当我们想结束线程时,
 主动将标记赋值为false即可结束run方法,即结束当前线程

在这里插入图片描述

(4)setDaemon()守护线程

线程分两大类:
一类是:用户线程
一类是:守护线程(后台线程),代表:垃圾回收程序
守护线程的特点:
一般守护线程是死循环,配合定时器起作用,所有用户进程结束,守护线程自动结束

(5)定时器Timer时间类下的schedule()

定时器作用还是特定的时间做特定的事。
三种类型设计定时器:
一:sleep方法设置睡眠时间(不建议使用)
二:定时器类Timer(很少使用),因为有高级框架实现
三:框架

在这里插入图片描述

(6)yieth()线程礼让

线程的礼让是暂停当前线程对象,并执行其他线程
它是对其他线程的让位,并是当前线程重新回到就绪状态

(7)setPriority()设置优先级

优先级的设置:是将当前的线程修改其优先级,只是让其执行的概念更高而已
最高级别:Thread.MAX_PRIORITY = 10
默认优先级:Thread.NORM_PRIORITY = 5;
最低优先级:Thread.MIN_PRIORITY =1;

五:多线程的同步(重点)

(1)什么时候并发会存在安全问题

三个条件:
		条件一:多线程并发下
		条件二:有共享数据
		条件三:共享数据有修改的行为

(2)怎么解决线程安全问题

使用“线程同步机制”解决线程安全问题
在多线程并发环境下有共享数据需要被修改,那么就会出现线程安全问题,此时我们考虑让线程
排队,即不能实现并发。虽然会丢失一些效率,但是安全是第一考虑要素
  1. 异步编程模型
异步编程其实就是多线程并发,各自执行各自的,这种编程模型效率1高,但是容易出现安全问题
  1. 同步编程模型
在有一个线程在执行时,其他线程必须等待这个线程执行结束释放锁才能执行,
这种设计模型效率低,线程需要等待,但是,数据安全

(3)实现同步的方式:加同步锁(synchronized)

synchronized写法有三种:
第一种:同步代码块
第二种:同步方法(实例方法上)
第三种:静态方法上使用

1. 同步代码块

 Runnable t =new Runnable(){
            int tickets =100;
            @Override
            public void run() {
                while (true){
                    synchronized (this) {
                        if (tickets > 0) {
                            try {
                                System.out.println(
                                Thread.currentThread().getName() +
                                 "----窗口卖出第" + tickets-- +"张!");
                                Thread.sleep(100);//一个线程卖出一张票就休眠,释放当前锁
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }else
                            break;
                    }
                }
            }
        };
        //3个线程
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
  1. 同步代码块中的锁可以是任意的对象,只要是所有线程共享的对象就能充当锁
  2. 当有线程拿到锁后,进入代码块中,其他线程过来寻找锁没找到会释放执行权然后在锁池中等待寻找锁。
  3. 当释放锁后,线程进入就绪阶段继续抢夺执行权

2. 同步方法(实例方法)

 class synchronized window {
        private int tickets =100;
        @Override
        public void run() {
            while (tickets >0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                   e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "----窗口卖出第" + tickets-- +"张!");
           }
        }
    }
  1. synchronized出现在实例方法上,后面无需手动添加锁,它的锁就是this
  2. 放在方法体上时,表示整个方法体都需要同步,这样扩大了同步范围,导致程序执行效率降低。不常用

3.静态方法上的synchronized

表示找类锁,这种锁永远只有一个,每个调用静态方法的线程对象都需要拿到这个锁才会运行

4.开发中怎么解决线程安全问题

java中三大变量
1.实例变量:堆中
2.静态变量:方法区中
3.局部变量:栈中
局部变量永远不存在线程安全问题,因为是栈内存,一个栈一个局部变量,不会出现变量修改问题

静态变量和实例变量是线程共享的区域,所以会涉及线程安全问题
  1. synchronized会使程序的执行效率变低,降低用户体验。最好用其他解决方案
  2. 第一种方案:尽量使用局部变量代替“实例变量和静态变量”
  3. 第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了
  4. 第三种:如果不能使用第一第二中才使用synchronize线程同步机制解决

六:生产者与消费者模式

什么是生产者消费者模式
	1.生产者负责生产,消费者负责消费
	2.生产者和消费者线程要求达到均衡
	3.这是一种特殊的业务需求,这种情况需要使用wait和notify

(1)Object下的wait方法和notify方法

1.wait和notify是java对象的方法
2.wait和notify是建立在线程同步的基础上的
3.wait作用:o.wait()让正在o对象上活动的线程t进入到等待阶段,
			并且释放t线程之前占用的o对象的锁
4.notify作用:o.notify()让正在o对象上等待的线程唤醒,
			只是唤醒,不会释放o对象上占有的锁的线程

(2)生产者与消费者代码实现

①:传统wait()、notify()

//生产线程
class Producer1 extends Thread {
    //1.1) 定义共享数据,储存数据的集合
    private Stack stack;

    public Producer1(Stack stack) {
        this.stack = stack;
    }

    @Override
    public void run() {
        Random r = new Random();
        while (true) {
            synchronized (stack) {
                if (stack.size() > 0) {
                    try {
                        stack.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
                    int i = r.nextInt(10);
                    stack.push(i);
                    System.out.println(Thread.currentThread().getName() + "-------->" + i);
                    stack.notify();
            }
        }
    }
}
//消费线程
class Consumer1 extends Thread {
    //1.1) 定义共享数据,储存数据的集合
    private Stack stack;

    public Consumer1(Stack stack) {
        this.stack = stack;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (stack) {
                if (stack.size() == 0) {
                    try {
                        stack.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
                Object pop = stack.pop();
                System.out.println(Thread.currentThread().getName() + "-------->" + pop);
                stack.notify();
            }
        }
    }
}
public class Test02 {
    public static void main(String[] args) {
        //1.1) 创建仓库
        Stack<Integer> stack = new Stack<>();

        //1.2) 创建线程对象
        Producer1 producer1 = new Producer1(stack);
        Consumer1 consumer1 = new Consumer1(stack);

        //1.3) 给线程改名
        producer1.setName("生产者");
        consumer1.setName("消费者");

        //1.4) 启动线程
        producer1.start();
        consumer1.start();

    }
}

②:sleep

//1.生产者
class Producer extends Thread{
    //1.1) 定义共享数据,即仓库
    private Stack stack;

    //1.2) 创建线程将对象传递过来
    public Producer(Stack stack) {
        this.stack = stack;
    }

    //1.3)重写run方法,线程启动其实就是运行这个方法
    @Override
    public void run() {
        //1.4) 定义随机数类
        Random r = new Random();
        //1.5) 循环,无限循环启动模拟生产者行为
        while (true){
            //1.6) 如果栈集合中的元素少于50个,那么就一直生产
            if (stack.size() < 1000){
                //1.7) 生产一个随机数产品
                int i = r.nextInt(100);
//                try {
//                    sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
                //1.8) 压入栈集合顶部
                stack.push(i);
                //1.9) 打印生产信息:
                System.out.println(Thread.currentThread().getName() + "------->"+ i);
            }else {
            //1.9) 当集合中有100个产品后,此生产者线程进入休眠状态,进入阻塞状态,释放执行权
            try {           // 只能try...catch因为子类不能抛出比父类更多的异常
                Thread.sleep(1000*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        }

    }
}
//2.消费者
class Consumer extends Thread{
    //2.1) 定义共享数据,即仓库
    private Stack stack;

    //2.2) 创建线程将对象传递过来
    public Consumer(Stack stack) {
        this.stack = stack;
    }

    //2.3)重写run方法,线程启动其实就是运行这个方法
    @Override
    public void run() {
        //2.5) 循环,无限循环启动模拟消费者行为
        while (true) {
            //2.6) 如果栈集合中的元素大于0个,那么就一直消费
            if (stack.size() > 0) {
                //2.7) 弹出栈集合顶部元素
//                try {
//                    sleep(200);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
                Object pop = stack.pop();
                System.out.println(Thread.currentThread().getName() + "----->" + pop );
            } else {
                //2.8) 当集合中没有产品后,此生产者线程进入休眠状态,进入阻塞状态,释放执行权
                try {           // 只能try...catch因为子类不能抛出比父类更多的异常
                    Thread.sleep(1000 * 5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}
public class Test01 {
    public static void main(String[] args) {
        //1.1) 创建仓库
        Stack<Integer> stack = new Stack<>();

        //1.2) 创建线程对象
        Producer producer = new Producer(stack);
        Consumer consumer = new Consumer(stack);

        //1.3) 给线程改名
        producer.setName("生产者");
        consumer.setName("消费者");

        //1.4) 启动线程
        producer.start();
        consumer.start();

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值