进程和线程的区别
根本区别:进程是操作系统分配资源的最小单位,线程是任务调动和执行的最小单位。
在开销方面:每个进程都有独立的代码和数据空间,程序切换会有较大的开销。线程之间共享代码和数据,每个线程都有自己独立的栈和调度器,线程之间的切换的开销较小。
所处环境:一个操作系统中可以运行多个进程,一个进程中有多个线程同时执行。
内存分配方面:系统在运行时会为每个进程分配内存,系统不会单独为每个线程分配内存。
包含关系:创建进程时系统会自动创建一个主线程由主线程完成,进程中有多线程时,由多线程共同执行完成。
多线程的创建和常见方法
1、多线程的创建
1、通过创建类继承Thread来调用
1 2 3 4 5 6 7 8 9 10 11 12
| static class MyThread extends Thread{ @Override public void run() { System.out.println("qewrwerwerw"); } }
public static void main(String[] args) { Thread t=new MyThread(); t.start(); }
|
2、通过Thread匿名内部类进行调用
1 2 3 4 5 6 7 8 9
| public static void main(String[] args) { Thread t=new Thread(){ @Override public void run() { System.out.println("wefwefwef"); } }; t.start(); }
|
3、通过创建类实现Runnable接口实现
1 2 3 4 5 6 7 8 9 10 11
| static class MyThread implements Runnable{ @Override public void run() { System.out.println("awetqeragearga"); } }
public static void main(String[] args) { Thread t=new Thread(new MyThread()); t.start(); }
|
4、通过Runnable实现匿名内部类调用实现
1 2 3 4 5 6 7 8 9 10
| public static void main(String[] args) { Runnable runnable=new Runnable() { @Override public void run() { System.out.println("w3rqetqwetqw"); } }; Thread r=new Thread(runnable); r.start(); }
|
5、通过lambad表达式调用
1 2 3 4
| public static void main(String[] args) { Thread t=new Thread(()->System.out.println("asdasfasfasfas")); t.start(); }
|
2、多线程的常用方法
2.1、多线程的构造方法
| 方法 |
解释 |
| Thread() |
创建线程对象 |
| Thread(Runnable target) |
使用Runnable类型创建对象 |
| Thread(String name) |
创建线程对象并命名 |
| Thread(Runnable target,String name) |
使用Runnable类型创建对象并命名 |
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public static void main(String[] args) { Thread t=new Thread("我是你爸爸"){ @Override public void run() { System.out.println("你好"); while (true){
} } }; t.start(); while(true){
} }
|
2.2、线程的常见属性
| 属性 |
获取方法 |
| ID |
getId() |
| 名称 |
getName() |
| 状态 |
getState() |
| 优先级 |
getPriority() |
| 是否后台线程 |
isDaemon() |
| 是否存活 |
isAlive() |
| 是否被中断 |
isInterrupted() |
2.3、中断程序
1)让线程run执行(比较温和)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| private static boolean cread=false;
public static void main(String[] args) throws InterruptedException { Thread t=new Thread(){ @Override public void run() { while(!cread){ System.out.println("转账中"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } } }; t.start();
Thread.sleep(2000); System.out.println("有内鬼终止交易"); cread=true; }
|
2)调用线程的interrupt方法来(比较激烈)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public static void main(String[] args) throws InterruptedException { Thread t=new Thread(){ @Override public void run() { while(!Thread.currentThread().isInterrupted()){ System.out.println("转账中"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("转账被终止"); } };
t.start(); Thread.sleep(2000); System.out.println("有内鬼终止交易"); t.interrupt(); }
|
Thread.interrupted()判断当前线程中断标志被设置–清除中断
Thread.currentThread().isInterrupted()判断指定线程的中断标志–不清除中断标志
2.4、线程的等待和休眠
线程之间是并发执行的
线程的等待:
我们可以通过join方法来阻塞线程,join为了控制线程结束的先后顺序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public static void main(String[] args) throws InterruptedException { Thread t1=new Thread(){ @Override public void run() { for(int i=0;i<3;i++){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我在执行t1"); } } };
Thread t2=new Thread(){ @Override public void run() { for(int i=0;i<3;i++){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("我在执行t2"); } } };
t2.start(); t1.start(); t2.join(); t1.join(); }
|
当两个线程执行完成之后,主线程才会继续向下执行,我们可以通过这种方法来获取时间。
线程的休眠 :
在内核态中线程分为就绪队列和阻塞队列,当正常运行时,线程在就绪队伍排队执行,当出现sleep、join、wait、锁时线程会放入阻塞队伍,当通过恢复条件时会在进入就绪队伍
2.5、线程状态
| NEW |
创建对象,没有PCB,布置了任务但是还没开始执行 |
| RUNNABLE |
就绪状态当前线程在CPU上执行或者准备好随时上CPU,通过就绪队列来维护 |
| BLOCKED |
阻塞状态,锁 |
| WAITING |
阻塞状态,wait |
| TIMED_WAITING |
阻塞状态,sleep |
| TERMINATED |
内核中的线程结束了,但是代码中的Thread对象还在 |
isAlive线程存活,除了NEW和TERMINATED之外,其他状态都表示线程存活。
线程的安全问题
线程不安全:多线程在并发执行某个代码时,产生了逻辑上的错误,就是线程不安全。
线程安全:多线程在并发执行过程中,没有产生逻辑上的错误,就是线程安全。
1、线程不安全的原因是什么?
1)线程是抢占式执行的
线程之间的调度完全由内核负责,线程之间的执行过程用户也不能干预。
2)自增操作不是原子性的
我们把自增操作分为三部分
load 把内存中的数据读取到CPU
incr 在CPU中把数据加1
save 把计算完成的结果放入内存中
当线程一和线程二并行执行的时候增加了两次但是结果返回1,就出现了线程不安全的情况。
3)多个线程尝试修改同一个变量
如果一个线程修改一个变量,线程安全
如果多个线程读取一个变量,线程安全
如果多个线程尝试修改不同变量,线程安全
4)内存可见性导致的线程安全问题
5)指令重排序
java在编译代码时,会针对指令进行优化、调整指令的输出顺序,在原有逻辑不变的情况下,提高算法的运行速度。
2如何解决线程不安全问题
如果是抢占式执行解决不了
对于自增操作我们可以采用加锁的方式来让线程变得安全
多线程同时修改同一个变量,这个想要看具体要求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class TreadDome { static class Test{ public int count=0;
public void add(){ count++; } } public static void main(String[] args) throws InterruptedException { Test t=new Test(); Thread t1=new Thread(){ @Override public void run() { for(int i=0;i<5000;i++){ t.add(); } } }; t1.start(); Thread t2=new Thread(){ @Override public void run() { for(int i=0;i<5000;i++){ t.add(); } } }; t2.start();
t1.join(); t2.join(); System.out.println(t.count); } }
|
注意:前置++ 后置++ 前置– 后置– += -= *= /=····等操作都不是原子性的。
直接赋值操作= 如果是针对内置类来说,一般是原子的,如果是针对引用类型来说不一定。
锁–synchronized 关键字
锁的特点:锁是具有互斥性,同线程只能有一个获取到锁。其他线程如果想要获取锁就会发生阻塞,直到获取锁的线程结束其他线程才能继续竞争锁。
在上述代码中我们加入:
1 2 3
| synchronized public void add(){ count++; }
|
我们就会发现代码运行正常了,线程安全了。
这个时候的synchronized就是在针对t这个对象来加锁,进入add方法内部,就把加锁状态设为true,退出方法时把状态设置为false。
我们加上锁之后就会把自增操作变成原子性的,这样就实现了线程的安全。
synchronized的几种常用方法:
1、加到普通方法前,表示锁this
2、加到静态方法前,表示锁当前类的类对象
3、加到某个代码块之前,显示指定给某个对象加锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| public static void main(String[] args) throws InterruptedException { Object object=new Object(); Thread t1=new Thread(){ @Override public void run() { Scanner scanner=new Scanner(System.in); synchronized (object){ System.out.println("请输入一个数"); int num=scanner.nextInt(); System.out.println("num="+num); } } }; Thread t2=new Thread(){ @Override public void run() { while(true){ synchronized (object){ System.out.println("线程2"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } }; t1.start(); t2.start();
}
|
线程就会停止,发生阻塞。
1 2 3
| synchronized (object01.getClass()){}
synchronized (object02.getClass()){}
|
getClass()方法调用的是这个类的类对象,所以也会发生互斥。
扩展:
如果有一个线程一直占用锁的情况发生就会出现锁死的情况,一旦锁死之后解不开,需要进行重启。
如果对锁死感兴趣可以去了解一下哲学家就餐问题。
| stringBuffer |
Vector |
HashTable |
内部加上了锁线程安全 |
| StringBuilder |
ArrayList |
HashMap |
在单线程中可以使用,效率更高 |
volatile 关键字的作用和用法
当我们尝试使用两个线程,通过其中一个线程去进行读取操作,一个线程进行写入操作时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class TreadDome01 { static class Counter{ public int flag=0; } public static void main(String[] args) { Counter counter=new Counter(); Thread t1=new Thread(){ @Override public void run() { while(counter.flag==0){
} } }; t1.start();
Thread t2=new Thread(){ @Override public void run() { Scanner scanner=new Scanner(System.in); System.out.println("请输入一个整数:"); counter.flag=scanner.nextInt(); } }; t2.start(); } }
|
我们会发现代码运行的结果和我们想的不太一样。
这是因为在进行比较的时候while(counter.flag==0),我们先从内存中读取到flag的值到cpu中,在cpu中比较这个值和0的相等关系,由于读取cpu的速度远大于读取内存的速度,在同一个线程中flag的值没有改变,编译器就会优化这个代码,后续的判断都在cpu上进行,而不是从内存中读取flag这个值。
1 2 3
| static class Counter{ public volatile int flag=0; }
|
当我们加入volatile时,读取时我们就会在内存上读取到cpu中进行比较,
volatile的作用是:保持内存的可见性。
synchronized和volatile的区别
|
区别 |
| synchronized |
一般是两个线程都进行写入操作 |
| volatile |
一般是两个线程,一个线程写入,一个线程读出 |
对象的等待集
为什么会有对象的等待集,这是因为抢占式执行会出现问题,当一个线程重复进行抢占时,会对其他的线程产生影响,这时候我们就需要用到等待集来帮助我们来手动干涉抢占式执行。
对象等待集中会有wait方法、notify方法;
wait方法:当操作条件不成熟就等待;
notify方法:当条件成熟时,通知指定的线程来工作;
1、wait方法
wait方法的工作流程:
1、释放锁
2、等待通知(过程可能会很久)
3、当收到通知后,尝试重新获取锁,继续往下执行
注意:wait方法需要在锁中执行
1 2 3 4 5 6 7 8
| public static void main(String[] args) throws InterruptedException { Object object = new Object(); synchronized (object) { System.out.println("等待前"); object.wait(); System.out.println("等待后"); } }
|
2、notify方法
notify方法就是使停止的线程继续运行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public static void main(String[] args) { Object object=new Object(); Thread t1=new Thread(){ @Override public void run() { synchronized(object){ while (true) { System.out.println("等待中"); try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("等待结束"); } } } }; t1.start(); Thread t2=new Thread(){ @Override public void run() { Scanner scanner=new Scanner(System.in); System.out.println("请输入一个数:"); int ma=scanner.nextInt(); synchronized (object){ System.out.println("notify开始"); object.notify(); System.out.println("notify结束"); } } }; t2.start(); }
|
注意:
1、在synchronized中嵌套synchronized
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public static void main(String[] args) { Object object=new Object(); Thread t=new Thread(){ @Override public void run() { synchronized (object){ Scanner scanner=new Scanner(System.in); System.out.println("输入一个数:"); int a=scanner.nextInt(); System.out.println(a); synchronized (object){ System.out.println("输入一个数:"); int b=scanner.nextInt(); System.out.println(b); } } } }; t.start(); }
|
这样看起来像是一个死锁,但是代码不会锁死,sychronized在内部针对这样的情况进行了特殊处理。调用的是操作系统提供的mutex(互斥量)。
加锁操作是调用pthread_mutex_lock函数。
解锁操作调用的是pthread_mutex_unlock函数。
像这种情况,不会真正的执行lock函数,而是仅仅维护一个“引用计数”,第一次进行调用才会调用lock函数,当引用计数减到0时调用unlock函数。
2、竞态条件问题
通过这个图我们引发了以下问题,当wait释放锁之后还没有等待通知时,第二个线程发送了通知,第一个线程没有接受到,这是就会出现竞态条件问题,但是java中释放锁和等待通知是具有原子性,两个同时进行,就避免了这种情况的发生。
多线程案例
1、单例模式
单例模式是一种常见的设计模式,应用于代码中有些概念不应该存在多个实例,在这种场景中我们需要用单例模式来解决问题
单例模式分为饿汉模式和懒汉模式两种
饿汉模式:
1 2 3 4 5 6 7 8 9 10 11 12 13
| static class Singleton{ private Singleton(){} private static Singleton instance=new Singleton(); public static Singleton getInstance(){ return instance; } }
public static void main(String[] args) { Singleton s1=Singleton.getInstance(); Singleton s2=Singleton.getInstance(); System.out.println(s1==s2); }
|
懒汉模式:
1 2 3 4 5 6 7 8 9 10
| static class Singleton{ private Singleton(){} private static Singleton instance=null; public static Singleton getInstance(){ if(instance==null){ instance=new Singleton(); } return instance; } }
|
注意: 懒汉模式和饿汉模式的区别是饿汉模式只要被加载就会创建一个类,而懒汉模式在第一次调用getInstance方法的时候才会被调用。 在这里注意懒汉模式的线程是不安全的,当多个线程同时修改一个变量的时候就会出现问题,饿汉模式的线程是安全的,因为饿汉模式多个线程是在读取一个变量。
由于上图所示,懒汉模式不安全,我们需要对懒汉模式进行优化。
最终优化结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| static class Singleton{ private Singleton(){} public volatile static Singleton instance=null; public static Singleton getInstance(){ if(instance==null){ synchronized (Singleton.class){ if (instance==null){ instance=new Singleton(); } } } return instance; } }
|
锁粒度要最小(锁粒度就是锁中包含的代码量)。同时因为多线程操作时,第一个线程写入,其他的线程读出,因此我们需要在变量前加入volatile来修改,避免因为内存可见性而引起的线程不安全
为了保证线程安全最重要的三个点:
1、加锁 保证进程的安全
2、双重if保证效率
3、volatile避免内存可见性带来的问题
2、阻塞式队列
阻塞队列是一种并发编程的方式,是一种生产者消费者模型。
它是一个先进先出的队列:
入队列时发现队列满了,就会阻塞,等待有线程出队列有空位了,才能继续入队列。
出队列时发现队列为空,就会阻塞,等待有线程入队列之后,才能继续出队列。
入队列:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public void put(int value) throws InterruptedException { synchronized (this) { if(size==array.length){ wait(); } array[tail]=value; tail++; if(tail==array.length){ tail=0; } size++;
notify(); } }
|
出队列:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public int get() throws InterruptedException { int temp=-1; synchronized (this) { if(size==0){ wait(); } temp = array[head]; head++; if(head==array.length){ head=0; } size--; notify(); } return temp; }
|
我们通过入队列的wait用出队列中的notify来控制,通过出队列的wait用出队列中的notify来控制。
注意:
当三个线程时(两个线程入队列,一个线程出队列):
如果队列已经满了、两个入队列线程都堵塞
如果出队列被唤醒,其中一个入队列就会被唤醒,继续插入元素。
如果队列已经空了,出队列就会被阻塞,直到两个入队列任何一个插入成功
当我们使用notifyAll时,我们需要用while来搭配使用。
1 2 3
| while (size==array.length){ wait(); }
|
代码解析:
我们通过数组来实现阻塞队列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
| public class ThreadDome02 { public static class BlockingQueue{ private int[] array=new int[1000]; private int head=0; private int tail=0; private int size=0; public void put(int value) throws InterruptedException { synchronized (this) { if(size==array.length){ wait(); } array[tail]=value; tail++; if(tail==array.length){ tail=0; } size++; notify(); } } public int get() throws InterruptedException { int temp=-1; synchronized (this) { if(size==0){ wait(); } temp = array[head]; head++; if(head==array.length){ head=0; } size--; notify(); } return temp; } } public static void main(String[] args) { BlockingQueue queue=new BlockingQueue(); Thread t1=new Thread(){ @Override public void run() { for(int i=0;i<10000;i++){ try { queue.put(i); System.out.println("生产"+i); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } }; Thread t2=new Thread(){ @Override public void run() { while (true) { try { int temp=queue.get(); System.out.println("消费"+temp); } catch (InterruptedException e) { e.printStackTrace(); } } } }; t1.start(); t2.start(); } }
|
3、定时器
定时器是是多线程编程的重要组件,当编写好定时器之后,会等到延迟之后的时间进行执行,就像闹钟定时一样。
定时器的构成:
1、使用一个Task类来描述一段逻辑,同时要对执行的时间来进行记录。
2、使用柱塞优先级队列来组织若干跟Task。
3、使用一个扫描进程来进行不断的扫描检测,若需要执行就执行这个任务。
Task类的创建:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| static class Task implements Comparable<Task>{ private Runnable command; private long time; public Task(Runnable command, long time) { this.command = command; this.time =System.currentTimeMillis()+time; } public void run(){ command.run(); } @Override public int compareTo(Task o) { return (int) (this.time-o.time); } }
|
阻塞优先级队列:
1
| private PriorityBlockingQueue<Task> queue=new PriorityBlockingQueue<>();
|
对于扫描类的创建和使用:
1 2 3 4
| public Timer(){ Work work=new Work(queue); work.start(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| static class Work extends Thread{ private PriorityBlockingQueue<Task> queue=null; public Work(PriorityBlockingQueue<Task> queue) { this.queue = queue; } @Override public void run() { while (true){ try { Task task=queue.take(); long curTime=System.currentTimeMillis(); if(task.time>curTime){ queue.put(task); }else{ task.run(); } } catch (InterruptedException e) { e.printStackTrace(); } } } }
|
创建一个方法来安排:
1 2 3 4
| public void schenule(Runnable command,long after){ Task task=new Task(command,after); queue.put(task); }
|
注意:这样编写会出现忙等的情况,当输入的时间和现在的时间间隔过长时,就会一直扫描,就出现了忙等的情况。
对于这种情况我们使用wait和notify来解决。
1 2 3 4 5 6 7 8 9 10
| if(task.time>curTime){ queue.put(task); synchronized (mailBox){ mailBox.wait(task.time-curTime); } }else{ task.run(); }
|
1 2 3 4 5 6 7
| public void schenule(Runnable command,long after){ Task task=new Task(command,after); queue.put(task); synchronized (mailBox){ mailBox.notify(); } }
|
源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| public class ThreadDome04 { static class Task implements Comparable<Task>{ private Runnable command; private long time; public Task(Runnable command, long time) { this.command = command; this.time =System.currentTimeMillis()+time; } public void run(){ command.run(); } @Override public int compareTo(Task o) { return (int) (this.time-o.time); } } static class Work extends Thread{ private PriorityBlockingQueue<Task> queue=null; private Object mailBox=null; public Work(PriorityBlockingQueue<Task> queue,Object mailBox) { this.queue = queue; this.mailBox=mailBox; } @Override public void run() { while (true){ try { Task task=queue.take(); long curTime=System.currentTimeMillis(); if(task.time>curTime){ queue.put(task); synchronized (mailBox){ mailBox.wait(task.time-curTime); } }else{ task.run(); } } catch (InterruptedException e) { e.printStackTrace(); } } } } static class Timer{ private PriorityBlockingQueue<Task> queue=new PriorityBlockingQueue<>(); private Object mailBox=new Object(); public Timer(){ Work work=new Work(queue,mailBox); work.start(); } public void schenule(Runnable command,long after){ Task task=new Task(command,after); queue.put(task); synchronized (mailBox){ mailBox.notify(); } } } public static void main(String[] args) { Timer timer=new Timer(); timer.schenule(new Runnable() { @Override public void run() { System.out.println("hehe"); } },2000); } }
|
4、线程池
线程池会包含一些线程,可以让我们直接使用,线程池可以避免频繁创建和销毁线程的开销。
线程池的组成部分:
1、需要有一个类来描述具体线程要做的工作。
2、用一个数据结果来组织任务(队列)。
3、通过一个类来描述工作进程。
4、需要有一个数据结构来组织若干个进程。
1、用一个类来描述工作进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| static class Worker extends Thread{ private BlockingQueue<Runnable> queue=null; public Worker(BlockingQueue<Runnable> queue) { this.queue = queue; } @Override public void run() { try { while (!Thread.currentThread().isInterrupted()){ Runnable command=queue.take(); command.run(); } } catch (InterruptedException e) { System.out.println("线程被终止了"); } } }
|
2、阻塞队列
1 2
| private BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>();
|
3、用List中放入线程
1
| private List<Worker> workers = new ArrayList<>();
|
4、编写execute(将一个任务加入到线程池中)和shutdown(销毁线程中的所有线程)方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| private static final int maxWorkerCount=10; public void execute(Runnable command) throws InterruptedException { if(workers.size()<maxWorkerCount){ Worker worker=new Worker(queue); worker.start(); workers.add(worker); } queue.put(command); } public void shutdown() throws InterruptedException { for(Worker worker:workers){ worker.interrupt(); } for(Worker worker:workers){ worker.join(); } }
|
这样基本上所有的基础的东西就写完了,下面是完整代码展示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| package Thread; import java.util.ArrayList; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; public class ThreadDome01 { static class Worker extends Thread{ private int id=0; private BlockingQueue<Runnable> queue=null; public Worker(BlockingQueue<Runnable> queue,int id) { this.queue = queue; this.id=id; } @Override public void run() { try { while (!Thread.currentThread().isInterrupted()){ Runnable command=queue.take(); System.out.println(id + "running、、、"); command.run(); } } catch (InterruptedException e) { System.out.println("线程被终止了"); } } } static class MyThreadPoll{ private BlockingQueue<Runnable> queue=new LinkedBlockingQueue<>(); private List<Worker> workers = new ArrayList<>(); private static final int maxWorkerCount=10; public void execute(Runnable command) throws InterruptedException { if(workers.size()<maxWorkerCount){ Worker worker=new Worker(queue,workers.size()); worker.start(); workers.add(worker); } queue.put(command); } public void shutdown() throws InterruptedException { for(Worker worker:workers){ worker.interrupt(); } for(Worker worker:workers){ worker.join(); } } static class Command implements Runnable{ private int num; public Command(int num) { this.num = num; } @Override public void run() { System.out.println("正在执行任务"+num); } } public static void main(String[] args) throws InterruptedException { MyThreadPoll pol=new MyThreadPoll(); for(int i=0;i<1000;i++){ pol.execute(new Command(i)); } Thread.sleep(2000); pol.shutdown(); System.out.println("被销毁了"); } } }
|
通过结果我们可以看出随着代码的执行,线程开始创建,线程最终被限制在10个,随着线程的终止,线程一个一个的被销毁(在执行任务的过过程中使用哪个线程是不知道的)。
说明:本文是收集参考网络文档,以方便查看(侵删)
信息链接:
- 进程与线程详解
- Java中的进程与线程详解-CSDN博客
=================我是分割线=================
欢迎到公众号来唠嗑: