java高级--------多线程的学习

java多线程是java高级阶段的知识点,也是java中比较难学的一部分,今天我们来初步学习一下java的多线程知识。说在前面,只是简单的入门,更高深的讲解可以参考其它大牛博文。
一、首先对于学习多线程有必要先了解一些概念:
1、什么是并行?什么是并发?
并行是指真正意义上的在同一时刻同时执行多个任务,比如多处理机的电脑就可以在同一时刻处理多个任务。
并发是虚拟上的同时执行,比如我们平时玩电脑,同时听歌,玩游戏,看电视,貌似是同时进行的,但是其实没有,只是cpu工作切换的时间很快,我们根本无法感受到,导致我们认为是同时进行的。并发的意义就在于充分利用cpu资源。对于java中的多线程就是并发程序。在某一刻只能有一个任务执行,cpu调度任务执行。
2、进程、线程、程序的区别?
首先说程序,它是静态的,它只是一连串的字符组成的代码块
进程:进程是操作系统的资源分配的基本空间,操作系统会为其分配对应的内存空间,而且进程有自己的代码和数据空间。不存在没有线程的进程
线程:线程是cpu调度的基本单位,没有自己的内存空间,线程可以理解为轻量级的进程,一个进程中可以有多个线程。
二、多线程实现的两者方式:
继承Thread类和实现Runnable接口

package com.yxc.thread;

/**
 * 多线程的学习
 * 创建多线程的两种方式
 * 第一继承Thread类
 * 第二种实现Runable接口
 */
public class MyThread extends Thread{
    private String name;
    //通过构造方法进行初始化工作
   public MyThread(String name){
       this.name=name;
   }
   //继承了Thread类,可以重写里面的run方法,这个方法是多线程的核心方法
   public void run(){
       //这个线程的工作根简单,就是从1打印到19
       System.out.println(name+"开始执行");
       for(int i=0;i<20;i++){
           System.out.println(i);
           try {
               Thread.sleep(20);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
       System.out.println(name+"结束执行");
   }
}

package com.yxc.thread;

/**
 * 通过实现接口实现多线程
 */
public class MyThread2 implements Runnable{
    private String name;
    public MyThread2(String name){
        this.name=name;
    }
    @Override
    public void run() {
        System.out.println(name+"开始执行");
        for(int i=0;i<10;i++){
            System.out.println("java多线程的学习 使用runnable实现");
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(name+"结束执行");
    }
}

package com.yxc.thread;

/**
 * 大部分情况下,我们都使用第二种方式实现多线程
 * 因为在java中继承是有限制的,那就是单继承,如果继承了这个类以后,如果这个
 * 类还想继承其它的类,那么就不可以,但是实现接口可以实现多个。
 */
public class TestThread {
    public static void main(String[] args) {
        //下面是实现两者多线程方式的测试代码
        MyThread myThread = new MyThread("打印线程");
        //对于实现接口来说。最终还是要通过Thread类来创建执行多线程的对象
        MyThread2 myThread2=new MyThread2("输出线程");
        Thread thread=new Thread(myThread2);
        //开启线程一定是调用start()方法,而不是通过对象调用run方法
        myThread.start();
        thread.start();
        //最后的执行效果的肯定是没有规律的,因为多线程的执行情况我们根本无法预知,是操作系统随机调度的
        //后面的学习中还会涉及到这个概念
    }
}

这里面涉及到几个常用的多线程中的方法:
start():开启线程,注意开启线程不一定就开始执行了,只是进入就绪态,要想真正执行需要cpu的调度
Sleep():休眠,传入一个毫秒数,表示当前线程休眠一定时间后又进入就绪态

三、线程的五种状态
在这里插入图片描述
下面通过一段案例来了解这几种状态的具体情况:

package com.yxc.thread;

/**
 * 线程的五种状态的了解
 */
public class ThreadStatusDemo {
    public static void main(String[] args) {
        PrintStar printStar=new PrintStar();
        System.out.println("线程新建状态");
        Thread thread=new Thread(printStar,"数星星线程");
        thread.start();
        System.out.println("线程进入就绪转状态");
        //这个时候还不一定指定,要等待cpu调用才进入真正的运行态
    }
}
//一个数星星的线程
class PrintStar implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程进入运行态");
        for(int i=1;i<100;i++){
            System.out.println("第"+i+"颗星星");
            try {
                Thread.sleep(10);
                System.out.println(Thread.currentThread().getName()+"线程进入阻塞态");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程阻塞结束进入就绪态");
        }
        System.out.println(Thread.currentThread().getName()+"线程结束");
    }
}

4、几种方法的学习以及对比:
sleep():让当前线程休眠指定的时间,线程进入阻塞态,时间到以后又进入就绪态,开始抢占cpu资源
yield ():这个方法比较有意思,表面上是让出,让其它线程执行,但是它让出以后又马上进入就绪态,同其它的线程抢占资源,可以理解为给你们个机会,公平竞争。
join(); 等待其它的线程执行完以后再执行,前面的两个方法都是属于类方法,这个是实例方法,要通过对象来调用的,这个方法是用在其它线程里面的,通过单词join(加入)也可以理解,强行加入到某一个线程中去执行
wait():实例方法,调用这个方法以后线程进入阻塞状态,而且需要等待其它线程的唤起才能进入就绪态,而且调用它,资源会被释放

package com.yxc.thread;

public class MthodDemo {
    public static void main(String[] args) {
        Thread thread=new Thread(new MyThread1());
        thread.start();
        for(int i=100;i<120;i++){
            if(i==105) {
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(i);
        }
    }
}
class MyThread1 implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<20;i++){
            System.out.println(i);
        }
    }
}

在代码中join()方法加入到了main线程中去,当main线程中的执行到105的时候将不再执行,线程将强行加入到main线程中执行。

5、经典的生产者与消费者模式:
下面通过代码实现多线程最经典的生产者与消费者模式,通过两个线程之前的通知等待来实现,也学习一下wait方法和notify()、notifyAll()三个方法的使用。我们写一个简单的,一个机器负责生产和消费,然后两个线程去执行,话不多说,直接上代码。

package com.yxc.producerAndConsumer;

import java.util.ArrayDeque;
import java.util.Queue;

/**
 * 这个类是生产和消费冰淇淋的机器类
 */
public class IceMachine {
    //定义一个机器中最大的容量
    private static final int MAX_BUFFER=20;

    //定义一个队列来存放冰淇淋
    Queue<Integer> IceQueue=new ArrayDeque<>();
    /**生产者生产冰淇淋的方法*/
    public synchronized void produceIce(){
        //如果机器中的数量达到了上线,那么通知生产者暂时不要生产
        //直接调用wait()方法就可以
        while(IceQueue.size()==MAX_BUFFER){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        int count=0;
        //如果机器中数量没有达到限定,那么就不断的生产
        while(IceQueue.size()<MAX_BUFFER){
            //随机产生一个1~100整数
            int i = (int)(Math.random()*100+1);
            //将编号为I的冰淇淋添加到队列中,等待消费者的消费
            IceQueue.offer(i);
            count++;
        }
        System.out.println("入库了"+count);
        //通知消费者可以消费了
        notify();
    }

    /**消费者消费方法*/
    public synchronized int ConsumeIce(){
        //如果队列中的产品数量为零,那么通知消费者等待
        while(IceQueue.size()==0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        Integer i = IceQueue.poll();
        System.out.println(i+"号产品被消费");
        return i;
    }
}

package com.yxc.producerAndConsumer;

/**
 * 消费者线程
 */
public class Consumer implements  Runnable{
    //将生产机器传进来
   private  IceMachine iceMachine=null;
    //通过构造方法初始化
    public Consumer(IceMachine iceMachine){
        this.iceMachine=iceMachine;
    }
    @Override
    public void run() {
        System.out.println("消费者开始消费产品");
        while(true){
            try {
                iceMachine.ConsumeIce();
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

package com.yxc.producerAndConsumer;

/**
 * 生产者线程
 */
public class Producer implements Runnable{
    //将生产机器传进来
    private IceMachine iceMachine=null;
    //通过构造方法初始化
    public Producer(IceMachine iceMachine){
        this.iceMachine=iceMachine;
    }
    @Override
    public void run() {
        System.out.println("生产者开始生产产品");
        while(true){
            try {
                iceMachine.produceIce();
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

package com.yxc.producerAndConsumer;

/**
 * 测试类
 */
public class TestMain {
    public static void main(String[] args) {
        IceMachine iceMachine=new IceMachine();
        Producer produce=new Producer(iceMachine);
        Consumer consumer=new Consumer(iceMachine);
        new Thread(produce).start();
        new Thread(consumer).start();
    }
}

部分实现的结果图:
在这里插入图片描述
6、线程池
*概念:*池顾名思义就是由很多线程组成的一个集合,系统启动时系统就创建了很多空的线程保存在线程池中,程序将一个任务传给线程池时,系统就会在线程池中寻找空闲的线程来执行这个任务。
优势:大量的创建线程、释放线程需要花费很多的系统资源,有了线程池以后,有效的提高了性能。减少了资源的消耗
线程池工作原理:任务直接提交给线程池,又线程池寻找空闲线程执行任务,执行完以后,线程又返回空的状态,返回线程池中,等待下一个任务的执行。一个任务只能有分配一个线程,但是可以同时向线程池发送多个任务。
下面通过代码实现集中常见的创建线程池的方法
为了方便,我将四种常见的创建方法以静态方法的形式实现,然后在主方法中直接调用四种方式,每一种都写了注释。

package com.yxc.threadPool;

import java.util.concurrent.*;

/**
 * 线程池的学习使用
 */

public class ThreadPoolDemo {
    public static void main(String[] args) {
        test1();
        test2();
        test3();
        test4();
    }
    /**创建指定个数的线程池*/
    public static void test1(){
        //创建一个容量为3的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for(int i=0;i<10;i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"线程被执行");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
    /**执行线程池的个数,而且有定时效果的 */
    public static void test2(){
        //创建一个这样的线程池,可以定时以及周期性执行任务
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("延迟一秒以后每两秒执行一次");
            }
        },1,2,TimeUnit.SECONDS);//这里两个参数 延时时间和周期时间
    }
    /**创建一个单例线程,线程池中只有一个线程可以执行任务,这样符合先来先执行的规则*/
     public static void test3(){
         ExecutorService executorService = Executors.newSingleThreadExecutor();
         for(int i=0;i<10;i++){
             int index=i;
             executorService.execute(new Runnable() {
                 @Override
                 public void run() {
                     System.out.println(index);
                     try {
                         Thread.sleep(20);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                 }
             });
         }
     }
     /**带缓存的线程池,如果这个线程被用过,直接分配,如果没有被用过,那么就生成一个新的线程分配给任务,然后将线程添加到线程池中*/
     public static void test4(){
         ExecutorService executorService = Executors.newCachedThreadPool();
         for(int i=0;i<10;i++){
             try {
                 Thread.sleep(100);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             executorService.execute(new Runnable() {
                 @Override
                 public void run() {
                     System.out.println(Thread.currentThread().getName()+"在被执行");
                 }
             });
         }
     }
}

代码执行结果:
test1:
在这里插入图片描述
test3:
在这里插入图片描述
test4:
在这里插入图片描述
对于测试2,代码延迟一秒执行,然后之后每两秒执行一次,无限循环下去
当然对于多线程这一章,代码一样,很可能执行的结果是不一样的,总结它是一个不靠谱的东西。这一章还有一些方法,没有说明,比如设置权限,设置权限只能优先级高一点,但是不能保证一定被执行。还有设置守护线程等等。

发布了33 篇原创文章 · 获赞 37 · 访问量 4419

猜你喜欢

转载自blog.csdn.net/weixin_42142899/article/details/101447408