Java学习笔记day22-多线程

多线程

​ CPU在多个软件间作高速切换

并发和并行
  • 并行:在同一时刻,有多个指令在多个CPU上同时执行。
  • 并发:在同一时刻,有多个指令在单个CPU上交替执行。
进程和线程

进程:是正在运行的软件

  • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
  • 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的;
  • 并发性:任何进程都可以同其他进程一起并发执行;

线程:是进程中的单个顺序控制流,是一条执行路径。

  • 单线程:一个进程如果只有一条执行路径,则称为单线程程序;
  • 多线程:一个进程如果有多条执行路径,则称为多线程程序;
多线程的实现方案
  • 继承Thread类的方式进行实现
public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程开始了"+i);
        }
    }
}

测试类:
public class ThreadTest {
    public static void main(String[] args) {
        //创建一个线程对象
        MyThread t1 = new MyThread();
        //创建一个线程对象
        MyThread t2 = new MyThread();
        //启动线程
        t1.start();
        //启动另一条线程
        t2.start();
    }
}

补充:

  1. 重写run()方法原因:因为run()是用来封装被线程执行的代码
  2. run()和start()的区别:
    • run():封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程;
    • start():启动线程,然后由JVM调用此线程的run()方法;
  • 实现Runnable接口的方式进行实现
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("线程开启了"+i);
        }
    }
}

public class RunnableTest {
    public static void main(String[] args) {
        //创建了一个参数对象
        MyRunnable mr = new MyRunnable();
        //创建了一个线程对象,并把参数传递给线程
        //线程启动后,执行的就是参数里的run()方法
        Thread t1 = new Thread(mr);
        //开启线程
        t1.start();
    }
}
  • 利用Callable和Future接口方式实现
import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 1; i <= 100; i++) {
            System.out.println("第"+i+"次浇100g水");
        }
        return "种树!!!";
    }
}

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

public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //线程开启之后执行里面的call方法
        MyCallable mc = new MyCallable();
        //可以获取线程执行完毕之后的结果,也可以作为参数传递给Thread对象
        FutureTask<String> ft = new FutureTask<>(mc);
        //创建线程对象
        Thread t1 = new Thread(ft);
        //开启线程
        t1.start();
        //获取线程结束之后的结果
        String s = ft.get();
        System.out.println(s);
    }
}
三种实现方式的对比
优点缺点
实现Runnable、Callable接口扩展性强,实现该接口的同时,还可以继承其他的类编程相对复杂,不能直接使用Thread类中的方法
继承Thread类编程比较简单,可以直接使用Thread类中的方法可扩展性较差,不能再继承其他的类
获取和设置线程名称

获取线程的名字

  • String getName():返回此线程的名称

Thread类中设置线程的名字

  • void setName(String name):将此线程的名称更改为等于参数name
  • 通过构造方法也可以设置线程名称
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(getName() + "@@@" + i);
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //线程是有默认名字的,格式:Thread-编号
        MyThread mt1 = new MyThread("阿花");
        MyThread mt2 = new MyThread("阿岚");
        //mt1.setName("阿花");
        //mt2.setName("阿岚");
        mt1.start();
        mt2.start();
    }
}
获得当前线程对象
  • public static Thread currentThread():返回当前正在执行的线程对象的引用;
线程休眠
  • public static void sleep(long time):让线程休眠指定的时间,单位为毫秒。
线程优先级
  • public final void setPriority(int new Priority) 设置线程的优先级
  • public final int getPriority() 获取线程的优先级
import java.util.concurrent.Callable;

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
        return "线程执行完毕了";
    }
}

import java.util.concurrent.FutureTask;

public class CallableTest {
    public static void main(String[] args) {
        MyCallable mc1 = new MyCallable();
        FutureTask<String> ft1 = new FutureTask<>(mc1);
        Thread t1 = new Thread(ft1);
        t1.setName("兔子");
        //设置优先级(1~10)
        t1.setPriority(10);
        //获取优先级
        System.out.println(t1.getPriority());
        //t1.start();

        MyCallable mc2 = new MyCallable();
        FutureTask<String> ft2 = new FutureTask<>(mc2);
        Thread t2 = new Thread(ft2);
        t2.setName("乌龟");
        //设置优先级(1~10)
        t2.setPriority(5);
        //获取优先级
        System.out.println(t2.getPriority());
        //t2.start();
    }
}
线程安全问题
同步代码块

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

  • 格式:

    synchronized(任意对象){

    ​ 多条语句操作共享数据的代码

    }

  • 默认情况是打开的,只要有一个线程进去执行代码了,锁就会关闭

  • 当线程执行完出来了,所才会自动打开

同步的好处和弊端

  • 好处:解决了多线程的数据安全问题
  • 弊端:耗费资源,降低程序运行效率
案例:买票

​ 某电影院有三个售票窗口,共有100张票,设计一个程序模拟售票;

public class Ticket implements Runnable {
    //票的数量
    private int ticket = 100;
    private Object obj = new Object();

    @Override
    public void run() {
        while (true){
            synchronized (obj){ //多个线程必须使用同一把锁
                if(ticket<=0){
                    //卖完了
                    break;
                }else {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    ticket--;
                    System.out.println(Thread.currentThread().getName()+"正在买票,还剩"+ticket+"张票");
                }
            }
        }
    }
}

public class TicketTest {
    public static void main(String[] args) {
        Ticket t = new Ticket();
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");
        t1.start();
        t2.start();
        t3.start();
    }
}
同步方法

​ 同步方法就是把synchronized关键字加到方法上

  • 格式:

    修饰符 synchronized 返回值类型 方法名(方法参数){}

​ 同步代码块和同步方法的区别

  • 同步代码块可以锁住指定的代码,同步方法是锁住方法中所有的代码
  • 同步代码块可以指定锁对象,同步方法不能指定锁对象

​ 同步方法的锁对象:this

示例

public class MyRunnable implements Runnable {
    private int ticketCount = 100;
    @Override
    public void run() {
        while (true){
            if("窗口一".equals(Thread.currentThread().getName())){
                //同步方法
                boolean result = synchronizedMethod();
                if (result == true){
                    break;
                }
            }
            if("窗口二".equals((Thread.currentThread().getName()))){
                //同步代码块
                synchronized (this){
                    if(ticketCount == 0){
                        break;
                    }else{
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        ticketCount--;
                        System.out.println(Thread.currentThread().getName()+"正在买票,还剩"+ticketCount+"张票");
                    }
                }
            }
        }
    }

    private synchronized boolean synchronizedMethod() {
        if(ticketCount == 0){
            return true;
        }else{
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticketCount--;
            System.out.println(Thread.currentThread().getName()+"正在买票,还剩"+ticketCount+"张票");
            return false;
        }
    }
}

public class RunnableTest {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);
        t1.setName("窗口一");
        t2.setName("窗口二");
        t1.start();
        t2.start();
    }
}
Lock锁
  • void lock():获得锁
  • void unlock():释放锁

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

  • ReentrantLock():创建一个ReentrantLock的实例
死锁

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

等待和唤醒方法
方法名说明
void wait()导致当前线程等待,直到另一个线程调用该对象notify()方法或notifyAll()方法
void notify()唤醒正在等待对象监视器的单个线程
void notifyAll()唤醒正在等待对象监视器的所有线程
生产者和消费者

交易场所:

public class Desk {

    //定义一个标记
    //ture 就表示桌子上有汉堡包,允许吃货执行
    //false 表示桌子上没有汉堡包,允许厨师执行
    //public static boolean flag = false;
    private boolean flag;
    //汉堡包的总数量
    //public static int count = 10;
    private int count;
    //锁对象
    //public static final Object lock = new Object();
    private final Object lock = new Object();

    public Desk() {
        this(false,10);
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public Object getLock() {
        return lock;
    }

    public Desk(boolean flag, int count) {
        this.flag = flag;
        this.count = count;
    }

    @Override
    public String toString() {
        return "Desk{" +
                "flag=" + flag +
                ", count=" + count +
                ", lock=" + lock +
                '}';
    }
}

生产者:

public class Cook extends Thread {
    Desk desk;
    public Cook(Desk desk) {
        this.desk = desk;
    }

    @Override
    public void run() {
        while (true){
            synchronized (desk.getLock()){
                if (desk.getCount() == 0){
                    break;
                }else{
                    if (!desk.isFlag()){
                        //生产
                        System.out.println("给干饭王做汉堡");
                        desk.setFlag(true);
                        desk.getLock().notifyAll();
                    }else {
                        try {
                            desk.getLock().wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

消费者:

public class Foodie extends Thread {
    Desk desk;
    public Foodie(Desk desk) {
        this.desk = desk;
    }

    @Override
    public void run() {
        //套路:
        //while(true)死循环
        //synchronized锁,锁对象是唯一
        //判断共享数据是否结束,结束
        //判断共享数据是否结束,没有结束
        while (true){
            synchronized (desk.getLock()){
                if(desk.getCount() == 0){
                    break;
                }else {
                    if (desk.isFlag()){
                        //有
                        System.out.println("开始干饭!!!");
                        desk.setFlag(false);
                        desk.getLock().notifyAll();
                        desk.setCount(desk.getCount()-1);
                    }else{
                        //没有就等待
                        //使用什么对象当锁,就必须使用这个对象去调用等待和唤醒的方法
                        try {
                            desk.getLock().wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        /*消费者步骤:
        * 1.判断桌子上是否有汉堡
        * 2.如果没有就等待
        * 3.如果有就开吃
        * 4.吃完后,桌子上的汉堡就没了,唤醒等待的生产者继续生产
        * 汉堡的总数量减一*/

        /*生产者步骤:
        * 1.判断桌子上是否有汉堡
        * 2.如果有就等待,如果没有才生产
        * 3.把汉堡放在桌子上
        * 4.叫醒等待的消费者开吃*/
        Desk desk = new Desk();
        Foodie f = new Foodie(desk);
        Cook c = new Cook(desk);
        f.start();
        c.start();
    }
}
阻塞队列
阻塞队列继承结构

image-20220225012010666

阻塞队列实现等待唤醒机制

BlockingQueue的核心方法:

  • put(anObject):将参数放入队列,如果放不进去会阻塞;

  • take():取出第一个数据,取不到会阻塞;

干饭王:
import java.util.concurrent.ArrayBlockingQueue;

public class Foodie extends Thread{
    private ArrayBlockingQueue<String> list;
    public Foodie(ArrayBlockingQueue<String> list) {
        this.list = list;
    }

    @Override
    public void run() {
        while (true){
            try {
                String take = list.take();
                System.out.println("干饭王从队列中获取了"+take);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
厨师:
import java.util.concurrent.ArrayBlockingQueue;

public class Cook extends Thread {
    private ArrayBlockingQueue<String> list;
    public Cook(ArrayBlockingQueue<String> list) {
        this.list = list;
    }

    @Override
    public void run() {
        while (true) {
            try {
                list.put("汉堡包");
                System.out.println("厨师放了一个汉堡包");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
测试类:
import java.util.concurrent.ArrayBlockingQueue;

public class Zuse {
    public static void main(String[] args) throws InterruptedException {
        //阻塞队列的基本用法
        //创建阻塞队列的对象,容量为1
        ArrayBlockingQueue<String> list = new ArrayBlockingQueue<>(1);
       //创建线程
        Foodie f = new Foodie(list);
        Cook c = new Cook(list);
        f.start();
        c.start();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值