进程和线程的区别

根本区别:进程是操作系统分配资源的最小单位,线程是任务调动和执行的最小单位。

在开销方面:每个进程都有独立的代码和数据空间,程序切换会有较大的开销。线程之间共享代码和数据,每个线程都有自己独立的栈和调度器,线程之间的切换的开销较小。

所处环境:一个操作系统中可以运行多个进程,一个进程中有多个线程同时执行。

内存分配方面:系统在运行时会为每个进程分配内存,系统不会单独为每个线程分配内存。

包含关系:创建进程时系统会自动创建一个主线程由主线程完成,进程中有多线程时,由多线程共同执行完成。

多线程的创建和常见方法

1、多线程的创建

1、通过创建类继承Thread来调用

1
2
3
4
5
6
7
8
9
10
11
12
//1、通过类直接创建
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();
//break;
}
}
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中嵌套synchronized
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); //true
}

懒汉模式:

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>{
//通过Runnable中的run方法,来描述具体执行的类容
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);
//加锁wait()就是死等到notify()方法在执行
//wait(time)中time时间到了就会去执行
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 {
//用Task来描述这段逻辑
static class Task implements Comparable<Task>{
//通过Runnable中的run方法,来描述具体执行的类容
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{
// 每个 Worker 线程都需要从任务队列中取任务.
// 需要能够获取到任务队列的实例
private BlockingQueue<Runnable> queue=null;

public Worker(BlockingQueue<Runnable> queue) {
this.queue = queue;
}

@Override
public void run() {
try {
//只要线程收到异常就会结束run;
while (!Thread.currentThread().isInterrupted()){
Runnable command=queue.take();
command.run();
}
} catch (InterruptedException e) {
//e.printStackTrace();
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
//定义一个10来约束workers中size的个数
//添加
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;
// 每个 Worker 线程都需要从任务队列中取任务.
// 需要能够获取到任务队列的实例
private BlockingQueue<Runnable> queue=null;

public Worker(BlockingQueue<Runnable> queue,int id) {
this.queue = queue;
this.id=id;
}

@Override
public void run() {
try {
//只要线程收到异常就会结束run;
while (!Thread.currentThread().isInterrupted()){
Runnable command=queue.take();
System.out.println(id + "running、、、");
command.run();
}
} catch (InterruptedException e) {
//e.printStackTrace();
System.out.println("线程被终止了");
}
}
}

static class MyThreadPoll{
private BlockingQueue<Runnable> queue=new LinkedBlockingQueue<>();

private List<Worker> workers = new ArrayList<>();

//定义一个10来约束workers中size的个数
//添加
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个,随着线程的终止,线程一个一个的被销毁(在执行任务的过过程中使用哪个线程是不知道的)。


说明:本文是收集参考网络文档,以方便查看(侵删)


信息链接:

  1. 进程与线程详解
  2. Java中的进程与线程详解-CSDN博客

=================我是分割线=================

欢迎到公众号来唠嗑: