0
点赞
收藏
分享

微信扫一扫

【java学习之路】010.多线程

GG_lyf 2022-01-31 阅读 82

多线程

线程的概念

  • 程序

    是一个指令的集合

  • 进程

    (正在执行中的程序)是一个静态的概念

    • 进程是程序的一次静态执行过程,占用特定的地址空间
    • 每个进程都是独立的,由三部分组成
      • cpu
      • data
      • code
    • 缺点
      • 内存的浪费
      • cpu的负担
  • 线程

    进程中的一个“单一的连续控制流程”

    • 线程又被轻量级进程
    • 多个线程之间可能会相互之间有影响
    • 一个进程可能会拥有多个并行的线程
    • 一个进程中的线程共享一个内存单元,可以访问相同的变量和对象,而且他们从同一堆中分配对象
    • 由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更简便而且信息传递的速度也更快

进程与线程的区别

java中实现多线程

  • 在java中负责线程这个功能的类:Java.lang.Thread
  • 可以通过创建Thread的实例来创建新的线程
  • 每个线程都是通过某个特定Thread对象所对应的方法run()来完成操作,方法run()被称为线程体
  • 通过调用Thread类的start()方法来启动一个线程

创建线程的方式

1.继承Thread

package Demo;
/*
    第一种实现方式
        1.需要继承Thread类
        2.必须重写run(),指的是核心执行的逻辑
        3.线程在启动的时候不要直接调用run(),而是通过start()来进行调用
        4.每次运行相同的代码出来的结果可能不同,原因在于多线程谁先抢占资源无法人为控制
 */
public class ThreadDemo extends Thread{

    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"-----------"+i);
        }
    }

    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        threadDemo.start();
        for(int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+"=========="+i);
        }
    }
}

第一次运行结果

第二次运行结果

2.实现Runnable接口

package Demo;
/*
    第二种实现方式(使用了代理设计模式):
        1.实现Runnable接口
        2.重写run()
        3.创建Thread对象,将刚刚创建好的runnable的子类实现作为thread的构造参数
        4.通过thread.start()启动
 */
public class RunnableDemo implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"-----------"+i);
        }
    }

    public static void main(String[] args) {
        RunnableDemo runnableDemo = new RunnableDemo();
        Thread thread = new Thread(runnableDemo);
        thread.start();
        for(int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+"=========="+i);
        }
        
    }
}

两种事项实现方式的对比

​ 推荐使用第二种方法,java是单继承,将继承关系留给最需要的类

案例思考:卖票程序

package Demo;
/*
    出现的问题:
        1.每次在启动线程对象的时候会创建自己对象的属性值,相当于每个线程操作自己,没有真正意义上实现共享
            解决方法:使用Runnable接口之后不需要再给共享变量添加static关键字,每次创建一个对象,作为共享对象即可
        2.每次访问共享对象的时候,数据不一致了
            解决方法:使用线程同步(在多线程安全问题部分提到)     
 */
    
public class TicketRunnable implements Runnable{
    private int ticket = 5;

    @Override
    public void run() {
        for(int i=0;i<100;i++){
            if(ticket>0){
                System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"张票");
            }
        }
    }

    public static void main(String[] args) {
        TicketRunnable ticketRunnable = new TicketRunnable();
        Thread t1 = new Thread(ticketRunnable);
        Thread t2 = new Thread(ticketRunnable);
        Thread t3 = new Thread(ticketRunnable);
        Thread t4 = new Thread(ticketRunnable);

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

思考:数据错乱的情况该如何解决?

线程的代理设计模式

程序参考《设计模式之禅》

1.接口

package proxy;

public interface KindWoman {
    /*
    抛媚眼
     */
    public void makeEyesWithMen();

    public void playWithMen();
}

2.代理人

package proxy;
/*
    代理人
 */
public class WangPo implements KindWoman{

    private KindWoman kindWoman;

    public WangPo(){
        this.kindWoman = new PanJinLian();
    }

    public WangPo(KindWoman kindWoman) {
        this.kindWoman = kindWoman;
    }

    @Override
    public void makeEyesWithMen() {
        this.kindWoman.makeEyesWithMen();
    }

    @Override
    public void playWithMen() {
        this.kindWoman.playWithMen();
    }
}

3.被代理人

package proxy;

public class PanJinLian implements KindWoman{



    @Override
    public void makeEyesWithMen() {
        System.out.println("潘金莲在抛媚眼");
    }

    @Override
    public void playWithMen() {
        System.out.println("潘金莲.....");
    }
}

4.测试类

package proxy;

public class XiMenQing {

    public static void main(String[] args) {
        WangPo wangPo = new WangPo();
        wangPo.playWithMen();
        wangPo.makeEyesWithMen();
        
    }
}

运行截图

image-20220128001431626

我们可以发现,虽然我们调用的是wangPo的方法,但是实际实现的是潘金莲这个对象的内容

代理模式主要使用了 Java 的多态,干活的是被代理类,代理类主要是接活,你让我干活,好,我交给幕后的类去干,你满意就成,那怎么知道被代理类能不能干呢?同根就成,大家知根知底,你能做啥,我能做啥都清楚的很,同一个接口呗。

线程状态

/*
    线程的生命周期
        1.新生状态
            当创建好当前线程之后,没有启动之前(调用start方法之前)
            ThreadDemo threadDemo = new ThreadDemo();
            RunnableDemo run = new RunnableDemo();
        2.就绪状态
            准备开始执行,但并没有执行,表示调用start()后
            注意:当对应线程创建完成,且调用start方法后,所有的线程会添加到一个就绪队列中,所有的线程同时去抢占cpu的资源
        3.运行状态
            当前进程获取到cppu资源之后,就绪队列中的所有的线程同时去抢占cpu的资源,谁先抢到谁执行,在执行的过程中就是运行状态
            抢占到cpu,代码逻辑开始
        4.死亡状态
            当运行中的线程正常执行完所有的代码逻辑或者因为异常情况导致程序结束
          进入的方式:
            1.程序正常运行结束
            2.人为中断执行,比如stop方法
            3.程序抛出未捕获的异常
        5.阻塞状态
            在程序运行中发生某些异常情况导致当前线程无法再顺利执行下去,此时会进入阻塞状态
            在进入阻塞状态的原因消除之后,所有的阻塞队列会再次进入就绪状态中,随机抢占cpu资源,等待执行
          进入的方式:
            1.sleep方法
            2.等待io资源
            3.join方法
 */

线程操作的相关方法

方法1-5案例

/*
    介绍线程类api方法
 */
public class ThreadApiDemo implements Runnable{
    public static void main(String[] args) {
        //获取当前线程对象
        Thread thread = Thread.currentThread();
        //获取当前线程的名称
        System.out.println(thread.getName());
        //获取线程的id
        System.out.println(thread.getId());
        //获取线程的优先级(在一般系统中范围是0-10,如果没有经过设置,默认为5)
        System.out.println(thread.getPriority());
        //设置线程池的优先级
        /*
            优先级越高就一定优先执行吗?
                不一定,只是优先执行的概率比较大
         */
        thread.setPriority(6);
        System.out.println(thread.getPriority());

        ThreadApiDemo threadApiDemo = new ThreadApiDemo();
        Thread t1 = new Thread(threadApiDemo);
        System.out.println(t1.isAlive());//false
        t1.start();
        System.out.println(t1.isAlive());//true
        System.out.println(t1.getPriority());
        System.out.println(t1.isAlive());//true
    }

    @Override
    public void run() {

    }
}

方法6案例

public class MyRun implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"-------"+i);
        }
    }
}
public class JoinTest {
    public static void main(String[] args) {
        MyRun myRun = new MyRun();
        Thread thread = new Thread(myRun);
        thread.start();

        for(int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+"========"+i);
            if(i==3){
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

方法7案例

public class MyRun implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"-------"+i);
        }
    }
}
public class SleepTest {

    public static void main(String[] args) {
        MyRun myRun = new MyRun();
        Thread thread = new Thread(myRun);
        thread.start();

        for(int i=0;i<5;i++){
            System.out.println(Thread.currentThread().getName()+"========="+i);
            if(i==2){
                try {
                    Thread.sleep(5000);//休眠5s
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

注意:

在多线程的时候,可以实现唤醒和等待的功能,但是唤醒和等待操作对应的不是thread对象,而是我们设置的共享变量

案例思考:双线程输出顺序问题

代码实现

package execcise;

public class Test1 implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"-------"+i);
            try {
                Thread.sleep(1050);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Test1 test1 = new Test1();
        Thread thread = new Thread(test1);
        thread.start();

        for(int i=10;i>0;i--){
            System.out.println(Thread.currentThread().getName()+"======="+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行截图

多线程的安全性问题

引例:案例思考:卖票程序

同步代码块

package Demo;
/*
    解决方法:
        1.同步代码块synchronized(共享资源、共享对象【Object的子类】){具体执行的代码块}

 */
public class TicketRunnable implements Runnable{
    private int ticket = 5;

    @Override
    public void run() {
        for(int i=0;i<100;i++){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (this){
                if(ticket>0){
                    System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"张票");
                }
            }
        }
    }

    public static void main(String[] args) {
        TicketRunnable ticketRunnable = new TicketRunnable();
        Thread t1 = new Thread(ticketRunnable,"A");
        Thread t2 = new Thread(ticketRunnable,"B");
        Thread t3 = new Thread(ticketRunnable,"C");
        Thread t4 = new Thread(ticketRunnable,"D");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

同步方法

package Demo;
/*
    解决方法:
        2.同步方法
            将核心的代码逻辑定义成一个方法,使用synchronized关键字进行修饰,此时不需要指定共享对象

 */
public class TicketRunnable implements Runnable{
    private int ticket = 5;

    @Override
    public void run() {
        for(int i=0;i<100;i++){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sale();
        }
    }

    public synchronized void sale(){
        if(ticket>0){
            System.out.println(Thread.currentThread().getName()+"正在出售第"+(ticket--)+"张票");
        }
    }

    public static void main(String[] args) {
        TicketRunnable ticketRunnable = new TicketRunnable();
        Thread t1 = new Thread(ticketRunnable,"A");
        Thread t2 = new Thread(ticketRunnable,"B");
        Thread t3 = new Thread(ticketRunnable,"C");
        Thread t4 = new Thread(ticketRunnable,"D");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

死锁

  • 同步可以保证资源共享操作的正确性,但是过多同步也会产生死锁
  • 死锁一般情况下表示互相等待,是程序运行时出现的一种问题

线程的生产者和消费者问题

举例

经典代码实现

1.生产者

package pc3;
/*
 *
 * 生产产品,将产房放置到共享空间中
 *
 * */
public class Producer implements Runnable {

    private Goods goods;

    public Producer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i % 2 == 0) {
                goods.set("娃哈哈","矿泉水");
            } else {
                goods.set("旺仔","小馒头");
            }
        }
    }
}

2.消费者

package pc3;
/*
*
* 从共享空间中取走产品
* */
public class Consumer implements Runnable {

    private Goods goods;

    public Consumer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        for(int i = 0;i<10;i++){
            goods.get();
        }
    }
}

3.对象

package pc3;

public class Goods {

    private String brand;
    private String name;
    //默认是不存在商品的,如果值等于true的话,代表有商品
    private boolean flag = false;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    //消费者获取商品
    public synchronized void get(){
        /*
        * 如果flag等于false的话,意味着生产者没有生产商品,此时消费者无法消费,需要让消费者线程进入到阻塞状态,等待生产者生产,当
        * 有商品之后,再开始消费
        * */
        if (!flag){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("消费者取走了"+this.getBrand()+"----"+this.getName());
        flag = false;
        //唤醒生产者去进行生产
        notify();
    }
    //生产者生产商品
    public synchronized void set(String brand,String name){
        //当生产者抢占到cpu资源之后会判断当前对象是否有值,如果有的话,以为着消费者还没有消费,需要提醒消费者消费,同时
        //当前线程进入阻塞状态,等待消费者取走商品之后,再次生产,如果没有的话,不需要等待,不需要进入阻塞状态,直接生产即可
        if(flag){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
       this.setBrand(brand);
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setName(name);
        System.out.println("生产者生产了" + this.getBrand() + "--" + this.getName());
        //如果代码执行到此处,意味着已经生产完成,需要将flag设置为true
        flag = true;
        //唤醒消费者去进行消费
        notify();
    }
}

4.测试类

package pc3;
/*
* 多线程访问的时候出现了数据安全的问题
*   1、生产者没有生产商品,消费者就可以获取
*   2、商品的品牌和名称对应不上
*
* */
public class Test {
    public static void main(String[] args) {

        Goods goods = new Goods();

        Producer producer = new Producer(goods);
        Consumer consumer = new Consumer(goods);

        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(consumer);
        t1.start();
        t2.start();


    }
}

JVC方式(先了解即可)

1.生产者

package pc4;

import java.util.concurrent.BlockingQueue;

public class ProduceQueue implements Runnable{

    private BlockingQueue<Goods> blockingQueue;

    public ProduceQueue(BlockingQueue blockingDeque) {
        this.blockingQueue = blockingDeque;
    }

    @Override
    public void run() {
        for(int i=0;i<10;i++){
            Goods goods = null;
            if(i%2==0){
                goods = new Goods("娃哈哈","矿泉水");
            }else {
                goods = new Goods("旺仔","小馒头");
            }
            System.out.println("生产者开始生产商品"+goods.getBrand()+goods.getName());
            try {
                blockingQueue.put(goods);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

2.消费者

package pc4;

import java.util.concurrent.BlockingQueue;

public class ConsumerQueue implements Runnable{

    private BlockingQueue<Goods> blockingQueue;

    public ConsumerQueue(BlockingQueue blockingDeque) {
        this.blockingQueue = blockingDeque;
    }

    @Override
    public void run() {
        for(int i=0;i<10;i++){
            try {
                Goods goods = blockingQueue.take();
                System.out.println("消费者消费的商品是"+goods.getBrand()+goods.getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

3.对象

package pc4;

public class Goods {

    private String brand;
    private String name;

    public Goods(String brand, String name) {
        this.brand = brand;
        this.name = name;
    }

    //默认是不存在商品的,如果值等于true的话,代表有商品
    private boolean flag = false;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }


}

4.测试类

package pc4;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class Test {

    public static void main(String[] args) {
        BlockingQueue<Goods> blockingQueue = new ArrayBlockingQueue(5);
        ProduceQueue produceQueue = new ProduceQueue(blockingQueue);
        ConsumerQueue consumerQueue = new ConsumerQueue(blockingQueue);
        new Thread(produceQueue).start();
        new Thread(consumerQueue).start();

    }
}
举报

相关推荐

0 条评论