Java多线程
前言:
在上一节中,我们已经对Java的多线程进行了初步的了解;
在这一节中,我们将继续深入地探讨Java多线程的更多知识点!
线程堵塞,守护线程,线程组,经典线程同步问题:消费者与生产者问题
还有是很难的线程池,那么请读者跟随笔者的脚步,进一步剖析多线程
的更多知识!
线程堵塞:
Thread类中关于控制线程堵塞的相关方法:
守护线程:
线程组的使用
介绍:
在Java中,我们可以通过java.lang.ThreadGroup对线程进行组操作;每一个线程都归属于某个线程组管理的一员,如在main()工作流程
产生的一个都线程,则产生的线程属于main这个线程组管理的一员;在创建Thread实例时,如果没有指定线程组参数,则默认隶属于创建者
线程所隶属的线程组;这种隶属关系在创建新线程时指定,在线程的整个生命周期里都不能够改变
作用:
简化对多个线程的管理,对若干线程同时操作,比如:调用线程组的方法设置所有线程的优先级,调用线程组的方法启动或堵塞组中所有的
线程等;其实线程组最重要的意义是安全,Java默认创建的线程都是属于系统线程组,而处于同一线程组的线程时可以相互修改对方数据的;
当如果在不同的线程组中,那么就不能"跨线程组"修改数据,从一定程度上保证了数据的安全
线程组提供的操作:
①集合管理方法,用于管理包含在线程组中的线程与子线程组
②组操作方法:设置或者获取线程对象的属性
③组中所有线程的操作方法,针对组中所有线程与子线程执行某一操作,eg:线程启动,恢复
④访问限制方法基于线程组的成员关系
代码使用实例:
- package com.jay.example;
- /*
- * 该代码演示的是线程组的集合管理,一些对线程组中线程与子线程组的
- * 一些基本操作,这里故意弄了一个自定义线程增加效果;
- * 输出结果每次都不一样,这个是因为多线程执行的随机性哦!
- *
- * */
- class MyThread extends Thread
- {
- MyThread()
- {
- super("呵呵");
- }
- @Override
- public void run() {
- for(int i = 0;i < 10;i++)
- {
- System.out.println(i);
- }
- }
- }
- public class ThreadGroupDemo {
- public static void main(String[] args) {
- //故意添加一个线程演示效果
- MyThread mt = new MyThread();
- mt.start();
- //①获得当前线程的线程组对象
- ThreadGroup tg = Thread.currentThread().getThreadGroup();
- //②打印线程组的名称
- System.out.println(tg.getName());
- //③获得线程组中活动线程的数目
- int count = tg.activeCount();
- System.out.println("当前线程组中有"+count+"个活动线程!");
- //④将当前所有活动线程放到一个线程数组中
- Thread[] th = new Thread[count];
- tg.enumerate(th);
- //⑤输出线程数组中所有线程的名字:
- for(int i = 0;i < count;i++)
- {
- System.out.print("线程#"+(i + 1) + "=");
- if(th[i] != null)
- System.out.println(th[i].getName());
- else System.out.println("线程组中木有线程!");
- }
- }
- }
运行截图:
线程组操作示例:
- package com.jay.example;
- /*
- * 该代码演示的是通过线程组来管理线程组的优先级
- * 出了下面的方法还有:
- * getParent():返回本线程组的父线程组
- * parentOf(ThreadGroup g):判断本线程组是否指定线程组的上层线程组
- * isDaemon():设置或判断线程组是否为一个守护线程组
- * */
- public class ThreadGroupDemo2 {
- public static void main(String[] args) {
- //①新建一个线程组对象
- ThreadGroup tg = new ThreadGroup("自定义线程组");
- //②定义两个线程对象放到线程组中
- Thread t1 = new Thread(tg,"线程一");
- Thread t2 = new Thread(tg,"线程二");
- //③获得线程中初始最大优先级,所有线程不能高过这个级别
- //除了设置之前已经存在的线程以外
- System.out.println("线程组的出事最大优先级:" + tg.getMaxPriority());
- //获得两个线程的优先级
- System.out.println(t1.getName()+"的初始优先级"+t1.getPriority());
- System.out.println(t2.getName()+"的初始优先级"+t1.getPriority());
- //将一号线程优先级设置为9
- t1.setPriority(9);
- //④设置线程组的最高优先级为8
- tg.setMaxPriority(8);
- //输出设置后的线程组最高优先级,同时输出一号线程的优先级
- System.out.println("线程组的新优先级:" + tg.getMaxPriority());
- System.out.println("一号线程的新优先级:" + t1.getPriority());
- //⑤我们将线程组优先级该成了8,如果我们此时为2号线程优先级设置为10,结果如何?
- t2.setPriority(10);
- System.out.println("二号线程的新优先级:" + t2.getPriority());
- //返回本线程组的字符串描述
- System.out.println(tg.toString());
- }
- }
运行截图:
生产者与消费者的问题:
问题解析:
代码解决生产者消费者问题的流程:
代码示例:
产品类:Producer.java
- package com.jay.example;
- public class Products {
- private int productID = 0;
- public Products(int productID) {
- super();
- this.productID = productID;
- }
- public int getProductID() {
- return productID;
- }
- public void setProductID(int productID) {
- this.productID = productID;
- }
- @Override
- public String toString() {
- //通过""连接将其转化为字符串
- return "" + productID;
- }
- }
仓库类:WareHouse.java
- package com.jay.example;
- public class WareHouse {
- private int base = 0;
- private int top = 0;
- //设置仓库的容量为10
- private Products[] products = new Products[10];
- //定义生产产品的方法,因为线程同步的问题,需要将方法设置为同步方法,即
- //为其设置同步锁
- public synchronized void produce(Products product)
- {
- //判断仓库是否已经满了,如果满了的话,先让生产者歇一歇
- //之所以把notify写到外面是为了唤醒消费线程;
- //因为开始生产了,那么消费者也可以开始消费了
- notify();
- while(top == products.length)
- {
- try
- {
- System.out.println("仓库已经满了,等待消费者消费...");
- wait();
- }catch(InterruptedException e){e.printStackTrace();}
- }
- //如果仓库没满的话,那么继续将生产好的商品添加到仓库中,仓库产品数量+1
- products[top] = product;
- top++;
- }
- //定义消费产品的方法,同理,需要为其设置同步锁
- public synchronized Products consume()
- {
- Products product = null;
- //判断仓库是否为空,空的话,消费者等一等,把生产者叫醒
- while(top == base)
- {
- notify();
- try{
- System.out.println("仓库中木有商品了,等待生产者生产...");
- wait();
- }catch(InterruptedException ex){ex.printStackTrace();}
- }
- //仓库没空,继续消费,消费一个产品,仓库产品数量-1
- top--;
- product = products[top];
- products[top] = null;
- return product;
- }
- }
生产者线程:Producer.java
- package com.jay.example;
- public class Producer implements Runnable{
- private String produceName;
- private WareHouse wareHouse;
- public Producer(String produceName, WareHouse wareHouse) {
- super();
- this.produceName = produceName;
- this.wareHouse = wareHouse;
- }
- public String getProduceName() {
- return produceName;
- }
- public void setProduceName(String produceName) {
- this.produceName = produceName;
- }
- @Override
- public void run() {
- int i = 0;
- int j = 0;
- while(j <= 100)
- {
- j++;
- i++;
- Products products = new Products(i);
- wareHouse.produce(products);
- System.out.println(getProduceName() + " 生产了 " + products);
- try
- {
- Thread.sleep(200);
- }catch(InterruptedException ex){ex.printStackTrace();}
- }
- }
- }
消费者进程:Consumer.java
- package com.jay.example;
- public class Consumer implements Runnable{
- private String consumerName = null;
- private WareHouse wareHouse = null;
- public Consumer(String consumerName, WareHouse wareHouse) {
- super();
- this.consumerName = consumerName;
- this.wareHouse = wareHouse;
- }
- public String getConsumerName() {
- return consumerName;
- }
- public void setConsumerName(String consumerName) {
- this.consumerName = consumerName;
- }
- @Override
- public void run() {
- int j = 0;
- while(j < 100)
- {
- j++;
- System.out.println(getConsumerName() + " 消费了 " + wareHouse.consume());
- try{
- Thread.sleep(300);
- }catch(InterruptedException ex){ex.printStackTrace();}
- }
- }
- }
测试类:ThreadTest.java
- package com.jay.example;
- public class ThreadTest {
- public static void main(String[] args) {
- WareHouse wh = new WareHouse();
- Producer pd = new Producer("生产者", wh);
- Consumer cs = new Consumer("消费者", wh);
- Thread t1 = new Thread(pd);
- Thread t2 = new Thread(cs);
- t1.start();
- t2.start();
- }
- }
部分运行截图:因为太长,读者可以自己把代码运行一次,就知道了
ps:如果是多个生产者和消费者就稍微复杂点,这个时候可能就需要用notifyAll方法了!
线程池:
线程池的相关概念:
使用Executors生成简单的线程池
代码实例:生成四中线程池的简单例子:
自定义的一个线程类:MyThread.java
- package com.jay.example;
- public class MyThread extends Thread
- {
- private String name;
- public MyThread(String name) {
- super(name);
- this.name = name;
- }
- @Override
- public void run() {
- System.out.println(Thread.currentThread().getName() + "\t" + name +"开始执行...");
- System.out.println(name + "结束运行!");
- }
- }
实现前面的三个生成线程池方法:
- package com.jay.example;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- /*
- * 本程序演示的是使用Executors生成简单的线程池
- * 调用它的三个方法进行演示
- * newSingleThreadExecutor:生成单线程池
- * newFixedThreadPool:生成固定大小的线程池
- * newCachedThreadPool:生成可缓存的线程池
- *
- * 只需要把注释依次撤销就可以看到效果了!
- * */
- public class SingleThreadExecutorTest {
- public static void main(String[] args) {
- ExecutorService pool = Executors.newSingleThreadExecutor();
- // ExecutorService pool = Executors.newFixedThreadPool(3);
- // ExecutorService pool = Executors.newCachedThreadPool();
- MyThread mt1 = new MyThread("线程一");
- MyThread mt2 = new MyThread("线程二");
- MyThread mt3 = new MyThread("线程三");
- MyThread mt4 = new MyThread("线程四");
- MyThread mt5 = new MyThread("线程五");
- //将线程放到线程池中
- pool.execute(mt1);
- pool.execute(mt2);
- pool.execute(mt3);
- pool.execute(mt4);
- pool.execute(mt5);
- //关闭线程池
- pool.shutdown();
- }
- }
运行截图:
第四个生成线程池方法的演示:
ScheduledThreadThreadPool.java
- package com.jay.example;
- import java.util.concurrent.Executors;
- import java.util.concurrent.ScheduledExecutorService;
- import java.util.concurrent.TimeUnit;
- //该代码演示的是第四个静态方法:newScheduledThreadPool();生成大小无限
- //可执行定时周期性操作的线程池,代码中设置了该线程池容量为3,一,四号线程
- //延迟10ms再
- public class ScheduledThreadThreadPool {
- public static void main(String[] args) {
- ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
- MyThread mt1 = new MyThread("线程一");
- MyThread mt2 = new MyThread("线程二");
- MyThread mt3 = new MyThread("线程三");
- MyThread mt4 = new MyThread("线程四");
- MyThread mt5 = new MyThread("线程五");
- pool.execute(mt3);
- pool.execute(mt2);
- pool.execute(mt5);
- //使用延时执行的方法,让后面两线程10ms以后才执行
- pool.schedule(mt1, 10, TimeUnit.MILLISECONDS);
- pool.schedule(mt4, 10, TimeUnit.MILLISECONDS);
- }
- }
运行截图:
自定义一个简单的线程池流程
最简单的自定义线程池演示:
三个文件:线程池类 + 线程 + 测试类
MyThread.java
- package com.jay.example;
- public class MyThread implements Runnable{
- //设置哨岗值
- private boolean flag;
- private String str;
- private String index;
- //在构造方法中完成初始化
- public MyThread(String index) {
- flag = false;
- this.index = index;
- System.out.println(index + "开始启动...");
- }
- //因为线程池中所有的线程都是一个线程类,需要对run方法加锁
- @Override
- public synchronized void run() {
- while(true)
- {
- if(!isFlag())
- {
- try{
- wait();
- }catch(InterruptedException ex){ex.printStackTrace();}
- }
- else
- {
- System.out.println(index + "正在运行" + getStr());
- try {
- Thread.sleep(3000);
- } catch (InterruptedException e) {e.printStackTrace();}
- }
- }
- }
- //通过改变flag的值,唤醒线程
- public synchronized void setFlag(boolean flag)
- {
- this.flag = flag;
- if(flag)this.notify();
- }
- public boolean isFlag()
- {
- return flag;
- }
- public String getStr() {
- return str;
- }
- public void setStr(String str) {
- this.str = str;
- }
- }
线程池类: ThreadPoolManager,java
- package com.jay.example;
- import java.util.ArrayList;
- public class ThreadPoolManager {
- private int num;
- //定义一个集合用于存放线程池中的线程对象
- private ArrayList<MyThread> alist;
- //初始化线程池集合,同时启动线程
- public ThreadPoolManager(int num) {
- this.num = num;
- alist = new ArrayList<MyThread>(num);
- //通过for循环初始化集合中的线程
- for(int i = 0; i < num;i++)
- {
- MyThread mt = new MyThread("线程" + (i+1));
- alist.add(mt);
- Thread t = new Thread(mt);
- try
- {
- Thread.sleep(1000);
- }catch(InterruptedException ex){ex.printStackTrace();}
- t.start();
- }
- }
- //如果改变了条件则唤醒线程
- public void notifyThread(String str)
- {
- int i;
- for(i = 1; i < alist.size() + 1;i++)
- {
- MyThread mt = alist.get(i);
- if(!mt.isFlag())
- {
- System.out.println("线程" + (i + 1) + "正在唤醒" + str);
- mt.setFlag(true);
- mt.setStr(str);
- return;
- }
- }
- if(i == alist.size() + 1)System.out.println("线程池中的所有线程已经启动...");
- }
- }
测试类:ThreadPoolTest.java
- package com.jay.example;
- import java.io.BufferedReader;
- import java.io.InputStreamReader;
- /*
- * 因为线程池已经启动了所有线程,我故意通过一个输入事件
- * 将所有的线程处于堵塞状态,然后没输入一次按回车
- * 将会随机的激活线程池中的一个线程
- *
- * */
- public class ThreadPoolTest {
- public static void main(String[] args) {
- ThreadPoolManager tpm = new ThreadPoolManager(10);
- BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
- try
- {
- String str = "";
- while((str = br.readLine())!= null)
- {
- System.out.println("===========================");
- tpm.notifyThread(str);
- }
- }catch(Exception e){e.printStackTrace();}
- }
- }
运行截图:
具体的,读者把代码复制下运行就可以知道大概效果了!
总结:
多线程第二节足足花了我一天多的时间去深入理解,这一节的有点难理解,
进程的堵塞倒没什么,是一些方法的了解而已,守护线程也是;
线程组是线程池的雏形;
经典线程同步问题:消费者与生产者问题,这是多线程的一个难点,需要花比较多的时间理解
还有线程池,这个真心纠结,查阅大量资料,才可以说基本掌握(ps:网上的都很乱..),线程池在web方面用处比较大;
这个在这里能够大概能掌握基本的使用就可以了,在后面的web博文中会再深入讲解线程池这个东东!
ps:这两天看线程看到相死的心都有了,最纠结的莫过于线程池,不过现在已经有个大概的轮廓了;
等到深入web部分的时候在深入研究这个东东吧,线程池对性能的优化很明显的说!
好了,这一节就到这里,如果有什么错误,纰漏,疑问,或者好的建议,欢迎读者指出
由衷感激!O(∩_∩)O谢谢~