Java基础复习——DAY13(实现多线程,线程同步;生产者消费者)

多线程

一、 实现多线程

1. 进程和线程

进程概述:正在运行的程序

  • 系统进行资源调度和分配的独立单位
  • 每个进程有自己独立的内存空间和系统资源

线程概述:是进程中单个顺序控制流,是一条执行路径
单(多)线程:一个进程有单个(多个)执行路径

2. 多线程的实现方式1——Thread

方式1:继承Thread类

  • 定义一个类MyThread继承Thread类
  • 在MyThread类中重写run()方法
  • 创建MyThread类的对象
  • 启动线程

两个小问题:

  • 为什么要重写run()方法?
    因为run()是用来封装被线程执行的代码的
  • run()和start()的区别?
    run():封装线程执行的代码,直接调用的,相当于普通方法的调用
    start():启动线程;然后由JVM调动此线程的run()
package file;

public class myThread extends Thread{
    @Override
    public void run() {
        for(int i =0 ; i<100 ; i++){
            System.out.println(i);
        }
    }
}

package file;

import java.io.IOException;

public class Demo {
    public static void main(String[] args) throws IOException {
        myThread my1 = new myThread();
        myThread my2 = new myThread();

        my1.start();
        my2.start();
    }
}

多线程执行的效果:
在这里插入图片描述

3. 设置和获取线程名称

Thread类中设置和获取线程名称的方法:

  • void setName(Stringname):将此线程的名称更改为等于参数name
  • String getName():返回此线程的名称
  • 通过构造方法也可以设置线程名称

如何获取main()方法所在的进程?

  • currentThread(): 返回当前正在执行的线程对象的引用
package file;

public class myThread extends Thread {
    public myThread() {
    }

    public myThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(i);
        }
    }
}

package file;

import java.io.IOException;

public class Demo {
    public static void main(String[] args) throws IOException {
        myThread my1 = new myThread("高铁");//使用带参构造方法构造需要在myThread里面自己定义
        myThread my2 = new myThread("飞机");
        myThread my3 = new myThread();

        my3.setName("大炮");

        my1.start();
        my2.start();
        my3.start();

        System.out.println(my1.getName());
        System.out.println(Thread.currentThread().getName());
    }
}

4. 线程调度

线程有两种调度模型:

  • 分时调度模型: 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
  • 抢占式调度模型: 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些
    多线程执行的时候有随机性,因为谁抢到cpu的使用权不一定

Thread类中设置和获取线程优先级的方法:

  • public final int getPriority():返回此线程的优先级
  • public final void setPriority(int newPriority):更改此线程的优先级
    线程默认优先级是5,优先级范围:1~10
    优先级高仅仅代表线程获取CPU的时间片的概率变高。
int num = my1.getPriority();
        my1.setPriority(10);//1-10,优先级递增
        my2.setPriority(5);
        my3.setPriority(1);

5. 线程的控制

在这里插入图片描述

  • sleep()
package file;

public class myThread extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + "," + i);
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
package file;

import java.io.IOException;

public class Demo {
    public static void main(String[] args) throws IOException {
        myThread my1 = new myThread();
        myThread my2 = new myThread();
        myThread my3 = new myThread();
        my1.setName("关羽");
        my2.setName("张飞");
        my3.setName("刘备");
        my1.start();
        my2.start();
        my3.start();
    }
}

在这里插入图片描述

  • join()
 my1.start();
        my1.join();//等待这个进程死亡之后,下面的进程才开始执行
        my2.start();
        my3.start();
  • setDaemon()
package file;

public class Demo {
    public static void main(String[] args) throws InterruptedException {
        myThread my1 = new myThread();
        myThread my2 = new myThread();
        my1.setName("关羽");
        my2.setName("张飞");

        //设置主线程为刘备
        Thread.currentThread().setName("刘备");

        //设置守护线程
        my1.setDaemon(true);
        my2.setDaemon(true);
        //效果是:在主线程结束后,守护线程也慢慢停止

        my1.start();
        my2.start();

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "," + i);
        }
    }
}

6. 线程的生命周期

计组有手就行
在这里插入图片描述

7. 多线程的实现方式2——Runnable

实现Runnable接口:

  • 定义一个类MyRunnable实现Runnable接口
  • 在MyRunnable类中重写run)方法
  • 创建MyRunnable类的对象
  • 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
  • 启动线程

相比继承Thread类,实现Runnable接口的好处:

  • 避免了Java单继承的局限性
  • 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
package file;

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "," + i);
            //实现Runnable接口不能直接用get.name()方法
        }
    }
}

package file;

public class MyRunnableDemo {
    public static void main(String[] args) {
        //创建MyRunnable类的对象
        MyRunnable mr = new MyRunnable();
        //创建Thread类的对象,把MyRunnable类的对象当作构造参数
        Thread tr1 = new Thread(mr, "A");
        Thread tr2 = new Thread(mr, "B");
        //启动线程
        tr1.start();
        tr2.start();
    }
}

二、线程的同步

1. 同步代码块(锁)——synchronized

锁多条语句操作共享数据,可以使用同步代码块实现

  • 格式:
    synchronized(任意对象){
    多条语句操作共享数据的代码
    }
  • synchronized(任意对象):就相当于给代码加了,任意对象就可以看成是一把锁

同步的利弊

  • 好处:解决多线程的数据安全问题
  • 弊端:线程很多时,因为每个线程都要判断同步上的锁,耗资源,降效率
案例:买票

存在的问题:线程随机性导致很多问题
为什么会有问题?(也是判断多线程程序是否有数据安全问题的保准)

  • 是否是多线程环境
  • 是否有共享数据
  • 是否存在多条语句操作共享数据

如何解决?

  • 让程序没有安全问题的环境(破坏上述三个)

如何实现?

  • 同步代码块(锁多条语句操作共享数据)
package file;

public class MyRunnable implements Runnable {
    private int ticket = 100;//100张票
    private Object obj = new Object();//任意对象

    @Override
    public void run() {
        while (true) {
            //假设tr1抢到cpu执行权,往下执行,在输出是tr2抢到了执行权,但是tr2看到锁,只能等tr1全部执行完毕后才可以执行
            synchronized (obj) {//上锁
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);//出票时间
                    } catch (InterruptedException e) {

                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
                    ticket--;

                }
            }
        }
    }
}
package file;

public class MyRunnableDemo {
    public static void main(String[] args) {
        //创建MyRunnable类的对象
        MyRunnable mr = new MyRunnable();
        //创建Thread类的对象,把MyRunnable类的对象当作构造参数
        Thread tr1 = new Thread(mr, "窗口A");
        Thread tr2 = new Thread(mr, "窗口B");
        Thread tr3 = new Thread(mr, "窗口C");
        //启动线程
        tr1.start();
        tr2.start();
        tr3.start();
    }
}

结果一下一下跳出来(Thread.sleep()的作用)
在这里插入图片描述

2. 同步方法

同步方法:就是把synchronized关键字加在方法上

  • 格式:
    修饰符synchronized返回类型 方法名
  • 同步方法的锁的对象是
    this
  • 同步静态方法:把synchronized加在静态方法上
    static synchronized 方法名
  • 同步静态方法的锁的对象是
    类名.class

本类方法锁:

package file;

public class MyRunnable implements Runnable {
    private int ticket = 100;//100张票
    private Object obj = new Object();
    private int x = 0;

    @Override
    public void run() {
        while (true) {
            if (x % 2 == 0) {
                synchronized (this) {//this
                    if (ticket > 0) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {

                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
                        ticket--;
                    }
                }
            } else {
                sellTicket();
                x++;
            }
        }
    }

    private synchronized void sellTicket() {
        if (ticket > 0) {
            try {
                Thread.sleep(10);//
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
            ticket--;
        }
    }
}

同步静态方法锁: (this的地方改,还有相关的static)

package file;

public class MyRunnable implements Runnable {
    private static int ticket = 100;//100张票
    private Object obj = new Object();
    private int x = 0;

    @Override
    public void run() {
        while (true) {
            if (x % 2 == 0) {
                synchronized (MyRunnable.class) {//this
                    if (ticket > 0) {
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {

                        }
                        System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
                        ticket--;
                    }
                }
            } else {
                sellTicket();
                x++;
            }
        }
    }

    private static synchronized void sellTicket() {
        if (ticket > 0) {
            try {
                Thread.sleep(10);//
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
            ticket--;
        }
    }
}

说实话没听懂

3. Lock锁(接口)

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作Lock中提供了获得锁和释放锁的方法:

  • void lock():获得锁
  • void unlock():释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

package file;

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

public class MyRunnable implements Runnable {
    private static int ticket = 100;//100张票
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            try {
                lock.lock();//获得锁
                if (ticket > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + ticket + "张票");
                    ticket--;
                }
            } finally {
                lock.unlock();//释放锁 
                //try finally 的作用,,防止try里面出现问题,最后释放不了锁
            }
        }
    }
}

三、 生产者和消费者

1. 模式描述

包含两类线程:生产数据和消费数据.
在这里插入图片描述
存在等待和唤醒
在这里插入图片描述

2. 案例: 奶箱,送奶工,用户

package file;

public class Box {
    //等一成员变量,表示第几瓶奶
    private int milk;
    private boolean statue = false; //奶箱状态,没奶

    //存牛奶
    public synchronized void put(int milk) {
        //如果有牛奶,等待消费
        if (statue) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } //没有牛奶,就生产
        this.milk = milk;
        System.out.println("送牛奶将" + this.milk + "瓶牛奶放入奶箱");
        statue = true;

        notifyAll();
    }

    //取牛奶
    public synchronized void get() {
        //没牛奶,生产奶
        if (!statue) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("用户拿第" + this.milk + "瓶牛奶");
        statue = false;
        notifyAll();
    }
}
package file;

public class Consumer implements Runnable {
    private Box b;

    public Consumer(Box b) {
        this.b = b;
    }

    @Override
    public void run() {
        while (true) {
            b.get();
        }
    }
}
package file;

public class Producter implements Runnable {
    private Box b;

    public Producter(Box b) {
        this.b = b;
    }

    @Override
    public void run() {
        for (int i = 1; i <= 5; i++) {
            b.put(i);
        }
    }
}
package file;

public class BoxDemo {
    public static void main(String[] args){
        //创建奶箱对象,共享数据
        Box b = new Box();

        //创建生产者和消费者对象,把奶箱对象作为构造参数传递,因为这个类要调用存入牛奶和取出牛奶的数据
        Consumer consumer = new Consumer(b);
        Producter producter = new Producter(b);

        //创建两个进程,存奶和取奶
        Thread t1 = new Thread(consumer);
        Thread t2 = new Thread(producter);

        //启动线程
        t1.start();
        t2.start();

    }
}

很难理解但是,仔细理解

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值