【高编 1】 北邮 高级网络程序设计 1.多线程
其中线程池是博主写大创时自学的,考试不考。问题不大
Thread Sum
实现多线程
什么是线程:线程是进程中的单个顺序控制流,是一条执行路径
- 单线程:一个进程如果只有一条执行路径,则称为单线程程序
- 多线程:一个进程如果有多条执行路径,则称为多线程程序
多线程的实现方式:
方式1: 继承Tread类
- 定义一个类MyThread继承Thread类
- 在MyThread类中重写run()方法
- 创建MyThread类的对象
- 启动线程
两个问题:
- 为什么要重写run()方法?
- 因为run()是用来封装被线程执行的代码
- run()方法和start()方法的区别?
- run():封装线程执行代码,直接调用,相当于普通方法的调用
- start():启动线程,然后由JVM调用此线程run()方法
public class MyThreadDemo{public static void main(String[] args){//创建MYThread类对象MyThread my1 = new MyThread();MyThread my2 = new MyThread();my1.run();my2.run();/*my1执行完后my2才执行*/my1.start();//启动线程,调用run方法my2.start();/*my1和my2两个线程同时在跑*/}
}
public class MyThread extends Thread{@Overridepublic void run(){for(int i = 0; i < 100; i++){sout(i);}}}
设置和获取线程的名称
Thread类中设置和获取线程名称的方法
- 设置:
- void setName(String name): 将此线程的名称更改为等于参数name
- 通过带參方法设置名称:在MyThread中通过写无參构造和有參构造方法
super(name)
实现。因为带參方法在Thread()中的Thread(String name)
,要在自己写的类中写有參构造方法用super传值去调用父类(Thread)中的有參构造方法
- 获取:
- String getName(): 返回此线程的名称
- static Thread currentThread():返回对当前正在执行的线程对象的引用```sout(Thread.currentThread().getName());````
没有设置名称直接获取:
- 设置线程时,默认构造方法会给线程默认名称为“Thread-i”(i为第i个线程)
public class MyThreadDemo{public static void main(String[] args){//创建MYThread类对象MyThread my1 = new MyThread();MyThread my2 = new MyThread();my1.setName("高铁");my2.setName("飞机");//Thread(String name)MyThread my1 = new MyThread("飞机");MyThread my2 = new MyThread("高铁");my1.start();//启动线程,调用run方法my2.start();/*my1和my2两个线程同时在跑*///sout(Thread.currentThread().getName());}
}
public class MyThread extends Thread{@Overridepublic void run(){for(int i = 0; i < 100; i++){sout(getName() + " " + i);}}//设置无參构造方法和有參构造方法public MyThread(){}public MyThread(String name){super(name);}}
多线程的实现方式(方法二)
相比继承Thread类,实现Runnable接口的好处
- 避免了Java单继承的局限性
- 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
声明一个实现Runnable接口的类。那个类然后实现了run方法。
- 定义一个类MyRunnable实现Runnable接口
- 在MyRunnable类中重写run()方法。在MyRunnable类中不能直接使用Thread中的getName()/setName()方法,因为它没有继承Thread。要实现Thread中的方法用Thread.currentThread()来引出
- 创建MyRunnable类的对象
- 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
- 启动线程
public class MyRunnableDemo{public static void main(String[] args){//创建MyRunnable类的对象MyRunnable my = new MyRunnable();//创建Thread类的对象,把MyRunnable对象作为构造方法的参数//Thread(Runnable target)Thread t1 = new Thread(my);Thread t2 = new Thread(my);//Thread(Runnable target,String name)Thread t1 = new Thread(my,"飞机");Thread t2 = new Thread(my,"高铁");//启动线程t1.start();t2.start(); }
}
public class MyRunnable implements Runnable{@Overridepublic void run(){for(int i = 0; i < 100;i++){sout(Thread.currentThread.get.Name() + " " + i);}}
线程调度
线程有两种调度模型
- 分时调度模型:所有线程流程使用CPU的使用权,平均分配每个线程占用CPU的时间片
- 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先相同,那么会随机选择一个,优先级高的线程获取CPU时间片相对多一些
Java使用的是抢占式调度模型
多线程程序的执行是随机的
Thread类中设置和获取线程优先级的方法
public final int getPriority()
:返回此线程的优先级public final void setPriority(int newPriority)
:更改此线程的优先级
Priority有范围:
- max:10
- min:1
- 默认值:5
线程优先级高,仅仅表示获取CPU时间片的几率高,而不是 每一次都跑在前面
线程控制
Method’s Name | Statement |
---|---|
static void sleep(long millis) | 使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
void join() | 插队。等待这个线程死亡 |
void setDaemon(boolean on) | 将此线程标记为守护线程,当运行的线程都是守护线程时(主线程执行完毕后),Java模拟机将退出(需要一定时间,不是马上停止) |
static void yeild() | 暂停当前正在执行的线程,并重新竞争一下优先级执行其他线程 |
boolean isAlive() | 测试当前线程是否处于活动状态 |
setPrority(int newPriority) | 更改线程的优先级 |
线程五个状态
线程停止
不建议使用JDK自带的已经过时了的stop
或者destroy
方法
使用一个标志位进行终止变量,在run方法中进行判断:当flag = false
,则线程停止
public class TestStep implements Runnable{private boolean flag = true;@Overridepublic void run(){while(flag){//running code}}//对外提供方法更改标识public void stop(){this.flag = false;}
}
线程中断
interrupt
区分三个方法,
方法 | 描述 | static? |
---|---|---|
public static boolean interrupted |
测试当前线程是否已经中断。调用该方法后,线程的中断状态会被重置。此方法内部调用的是私有的Thread.currentThread().isInterrupted(true); 带true 参数的isInterrupted 方法意思是返回当前线程的中断状态,然后reset当前线程的中断状态
|
Yes |
public boolean isInterrupted() |
测试指定线程是否已经中断。线程的中断状态不受该方法的影响。内部调用的私有的参数为false 的isInterrupted(false) 方法
|
No |
public void interrupt() | 中断线程,将中断状态设为true | No |
线程休眠
Thread.sleep(long millis)
,指定当前线程阻塞的毫秒数
- 异常:存在异常
InterruptedException
- 当达到线程sleep的时间后,线程进入就绪状态
- 可模拟网络延时,倒计时等
- sleep不会释放锁
线程等待与唤醒
如果没有在synchronized
方法内调用wait()
或者nodify()
的话,会抛出**IllegalMonitorStateException
**
线程名.wait()
- 让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)
notify()/notiyfyAll()
- 是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。
wait(long timeout)
让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()
方法或notifyAll()
方法,或者超过指定的时间量”,当前线程被唤醒**(进入“就绪状态”**)。
//main(主线程)
synchronized(t1) {try {t1.start();t1.wait();} catch(exception e){e.printStackTrace();}
}//在 t1 线程中唤醒主线程
synchronized (this) {//这里的 this 为 t1this.notify();
}
注:
synchronized(t1)锁定t1(获得t1的监视器)
synchronized(t1)这里的锁定了t1,那么wait需用t1.wait()(释放掉t1)
因为wait需释放锁,所以必须在
synchronized
中使用(没有锁定则么可以释放?没有锁时使用会抛出异常IllegalMonitorStateException
(正在等待的对象没有锁))notify
也要在synchronized
中使用,应该指定对象,t1. notify()
,通知t1对象的等待池里的线程使一个线程进入锁定池,然后与锁定池中的线程争夺锁。那么为什么要在synchronized使用呢? t1. notify()需要通知一个等待池中的线程,那么这时我们必须得获得t1的监视器(需要使用synchronized),才能对其操作,t1. notify()程序只是知道要对t1操作,但是是否可以操作与是否可以获得t1锁关联的监视器有关。synchronized()
,wait()
,notify()
对象一致性在while循环里而不是if语句下使用wait(防止虚假唤醒spurious wakeup)
不是使调用线程等待,而是当前执行 wait 的线程等待
- 在主线程中调用
t1.wait(3000)
,主线程等待t1通过notify()唤醒 或 notifyAll()唤醒,或超过3000ms延时;然后才被唤醒。
class ThreadA extends Thread{public ThreadA(String name) {super(name);}public void run() {synchronized (this) {try { Thread.sleep(1000); // 使当前线阻塞 1 s,确保主程序的 t1.wait(); 执行之后再执行 notify()} catch (Exception e) {e.printStackTrace();} System.out.println(Thread.currentThread().getName()+" call notify()");// 唤醒当前的wait线程this.notify();}}
}
public class WaitTest {public static void main(String[] args) {ThreadA t1 = new ThreadA("t1");synchronized(t1) {try {// 启动“线程t1”System.out.println(Thread.currentThread().getName()+" start t1");t1.start();// 主线程等待t1通过notify()唤醒。System.out.println(Thread.currentThread().getName()+" wait()");t1.wait(); // 不是使t1线程等待,而是当前执行wait的线程等待System.out.println(Thread.currentThread().getName()+" continue");} catch (InterruptedException e) {e.printStackTrace();}}}
}
运行结果:
main start t1
main wait()
t1 call notify()
main continue
Sleep与Wait对比
sleep()
和wait()
方法都是Java中造成线程阻塞的方法
sleep()和wait()方法的阻塞线程的场景
①sleep()
实现线程阻塞的方法,我们称之为“线程睡眠”,方式是超时等待,怎么理解?就是sleep()通过传入“睡眠时间”作为方法的参数,时间一到就从“睡眠”中“醒来”;
②**wait()
方法实现线程阻塞的方法**,我们称之为“线程等待”,方式有两种:
1)和sleep()方法一样,通过传入“睡眠时间”作为参数,时间到了就“醒了”;
2)不传入时间,进行一次“无限期的等待”,只用通过notify()方法来“唤醒”。
sleep()和wait()的区别
- sleep()和wait()方法的区别之一,就是实现线程阻塞的方式不一样。
- 是否释放同步锁
①sleep()释放CPU执行权,但不释放同步锁;
②wait()释放CPU执行权,也释放同步锁,使得其他线程可以使用同步控制块或者方法。
以上,就是sleep()和wait()方法的两个关键性区别。
线程礼让
Thread.yield()
礼让线程:使当前正在执行的线程暂停,但不阻塞。
将线程从运行状态转为就绪状态
让CPU重新调度,但礼让不一定成功,还得看从就绪状态进入运行状态阶段
线程强制执行
线程名.join()
Join合并线程,让这个线程执行完成后,再执行其他线程。
也就是说,t.join()方法阻塞调用此方法的线程(calling thread)进入 TIMED_WAITING 状态,直到线程t完成,此线程再继续;
通常用于在main()主线程内,等待其它线程完成再结束main()主线程。
public static void main(String[] args) {Thread thread1 = new Thread(new JoinTester01("One"));Thread thread2 = new Thread(new JoinTester01("Two"));thread1.start();thread2.start();try {thread1.join();thread2.join();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}System.out.println("Main thread is finished"); }
会让其他线程(调用者)中等待
tread0
执行完在其他线程中调用
线程名.join()
表示让线程名
该线程插队执行有异常,要捕获或者抛出
InterruptException
Try…Catch包裹
方法名 | 是否要放到Try…Catch内 | Static? |
---|---|---|
sleep | Yes | Yes |
yield | No | Yes |
join | Yes | No |
wait | Yes | No |
观测线程状态
Tread.State
线程状态:线程可以处于以下状态之一:
NEW
尚未启动的线程处于此状态
RUNNABLE
在 Java 虚拟机中执行的线程处于此状态
BLOCKED
被阻塞等待监视器锁定的线程处于此状态
WAITING
正在等待另一个线程执行特点动作的线程处于此状态
TIMED_WAITTING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。例如:调用Sleep后,线程会处于这个状态。
TERMINATED
已退出的线程处于此状态
一个线程可以在给定时间点处于一个状态。这些状态是不反映任何操作系统线程状态的虚拟机状态
观察状态的方法:Thread.State state = Thread.getState();
Thread.State state = Thread.getState();
System.out.println(state);
线程的优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定哪个线程来执行。高优先级线程有更大的概率被分配到执行权。
线程的优先级用数字表示,范围从1~10
- 最低:1
- 最高:10
- 默认:5
- 如果超出 [1,10] 这个范围,会抛出异常
获取优先级的方法:getPriority()
改变优先级的方法::setPriority(int xxx)
守护线程 daemon
线程名.setDaemon(Boolean on)
,默认为false即用户线程
线程分为用户线程和守护线程
只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
Thread thread = new Thread();
thread.setDaemon(true);
thread.start();
主线程执行完后,守护线程也会退出
Java的内存模型JMM以及共享变量的可见性
JMM决定一个线程对共享变量的写入何时对另一个线程可见,JMM定义了线程和主内存之间的抽象关系:共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存保存了被该线程使用到的主内存的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。
对于普通的共享变量来讲,线程A将其修改为某个值发生在线程A的本地内存中,此时还未同步到主内存中去;而线程B已经缓存了该变量的旧值,所以就导致了共享变量值的不一致。解决这种共享变量在多线程模型中的不可见性问题,较粗暴的方式自然就是加锁,但是此处使用synchronized或者Lock这些方式太重量级了,比较合理的方式其实就是volatile。
需要注意的是,JMM是个抽象的内存模型,所以所谓的本地内存,主内存都是抽象概念,并不一定就真实的对应cpu缓存和物理内存
volatile
特性:可将变量变得线程安全
保证可见性,不保证原子性
当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去
这个写会操作会导致其他线程中的volatile变量缓存无效
禁止指令重排
- 执行到volatile变量时,其前面的所有语句都执行完,后面所有语句都未执行。且前面语句的结果对volatile变量及其后面语句可见。
volatile
不适用的场景
不适合复合操作,
inc++
不是一个原子性操作,可以由读取、加、赋值3步组成,所以结果并不能达到30000。public class Test {public volatile inc = 0;public void increase(){inc++;}public static void main(String[] args) {final Test test = new Test();for(int i = 0; i < 10; i++){new Thread(){public void run(){for (int j = 0; j < 1000; j++){test.increase();}}}.start();}while(Thread.activeCount() > 2 )//确保前面线程执行完成,在IDEA中要>2, 其余为>1Thread.yield();System.out.println(test.inc);} }
解决方法:使用
synchronized
public inc = 0;public synchronized void increase(){inc++; }
采用
Lock
public inc = 0; Lock lock = new ReentrantLock();public synchronized void increase(){lock.lock();try{inc++;} finally{lock.unlock;} }
volatile的原理
volatile可以保证线程可见性且提供了一定的有序性,但是无法保证原子性。在JVM底层volatile是采用“内存屏障”来实现的。观察加入volatile关键字和没有加入volatile关键字时所生成的汇编代码发现,加入volatile关键字时,会多出一个lock前缀指令,lock前缀指令实际上相当于一个内存屏障(也成内存栅栏),内存屏障会提供3个功能
- 它确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成;
- 它会强制将对缓存的修改操作立即写入主存;
- 如果是写操作,它会导致其他CPU中对应的缓存行无效。
线程同步
案例:卖票
需求:卖100张票,有3个窗口同时卖票。设计程序模拟
思路:
- 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int ticket = 100;
- 在SellTicket类中重写run()方法实现卖票,代码步骤如下
- 判断票数大于0,就卖票,并告知是哪个窗口卖的
- 卖完之后,总票数减1
- 票卖完后,还会有人来问有没有票,用死循环让程序一直走下去
- 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下:
- 创建SellTicket类对象
- 创建三个Thread类的对象,把SellTicket对象作为构造方法的函数,并给出对应的窗口名称
- 启动线程
public class SellTicketDemo{public static void main(String[] args){//创建一个SellTicket对象SellTicket st = new SellTicket();//创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应窗口的名称Thread t1 = new Thread(st,"窗口1");Thread t2 = new Thread(st,"窗口2");Thread t3 = new Thread(st,"窗口3");//启动线程st1.start();st2.start();st3.start();}
}
public SellTicket implements Runnable{private int ticket = 100;@Overridepublic void run(){//判断票数大于0,就卖票,并告知是哪个窗口卖的//卖完之后,总票数减1//票卖完后,还会有人来问有没有票,用死循环让程序一直走下去while(1){if(ticket > 0){//通过sleep方法来模拟出票时间Thread.sleep(100);sout(Thread.currentThread().getName() + "正在出第" + tickets + "张票");ticket--;}}}}
卖票案例的思考
每次卖票的时候,要有100ms的延迟。用sleep()
实现
线程安全问题
为什么出现问题?(判断多线程程序是否会有数据安全问题的标准)
- 是否是多线程环境
- 是否有共享数据
- 是否有多条语句操作共享数据
卖票程序满足以上三条
如何解决?
- 基本思想:让程序没有安全问题的环境
解决方法:
- 把多条语句操作共享数据锁起来让任一时刻只能有一个线程操作数据
- Java提供同步代码块的方法
保证访问正确性,在访问时加入锁机制 synchronized,当一个线程获得对象的排它锁,独占资源,其他线程必须等待,使用完后释放锁即可,存在以下问题:
- 一个线程持有锁会导致其他所有需要此锁的线程挂起
- 在多线程竞争下,加锁-释放锁会导致比较多的 上下文切换 和 调度延时 引起性能问题
- 如果一个优先级高的线程等待一个低优先级的线程释放锁,会导致优先级倒置,引起性能问题。
同步代码块
锁多条语句操作共享数据,可以使用同步代码块实现
锁的对象一定要会发生变化的对象
- 格式:
synchronized(任意对象){多条语句操作共享数据的代码
}
- synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
public class SellTicket implements Runnable{private Object obj = new Object();//在方法外面定义,确保只有一个锁synchronized(obj){statement...}
}
同步方法
同步方法:把synchronized关键字加到方法上
理解:
- synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行
方法里需要修改的内容的代码部分才需要加锁
格式:
修饰符吗 synchronized 返回值类型 方法名(方法参数){}
同步方法的锁的对象,是
this
对象,相当于synchronized(this){ }同步静态方法:
static synchronized
,相当于synchronized(类名.class)
private synchronized void sellTicket(){statement...
}
线程安全的类 JUC
- StringBuffer:线程安全的可变字符序列(相当于StringBuilder <-运行更快)
- vector:实现了list集合,实现了可扩展的数组(相当于ArrayList <-运行更快)
- HashTable:实现了Map接口,将键映射到值。任何非null对象都可以用作键或者值(建议使用HashMap代替HashTable)
死锁
多线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的清醒。某一个同步块同时拥有”两个以上对象的锁“时,可能会引发死锁。
Lock锁
为了更清晰表达若何加锁和释放锁,设定了Lock。为一个接口。
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
- void lock():获得锁
- void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法:
ReentrantLock():
创建一个ReentrantLock的实例
添加锁和加锁位置:
private Lock lc = new ReentrantLock();public void m(){lc.lock();try{//保证线程安全的代码} finally{lc.unlock();//如果同步代码有异常,要将unlock()写入finally中}
}@Override
public void run(){try{lc.lock();...多线程执行语句...}finally{lc.unlock();}
}
synchronized 和 lock 的对比
Lock是显式锁(手动开启和手动关闭,要记得关闭)
synchronized是隐式锁,出了作用域自动释放
使用优先顺序:
Lock > 同步代码块 > 同步方法
生产者消费者模式
概述:生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻
生产者消费者问题,实际上主要是包含了两类线程:
- 一类是生产者线程,用于生产数据
- 一类是消费者线程,用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
- 生产者生产数据之后直接放在共享数据区中,并不需要关心消费者的行为
- 消费者只需要从共享数据中去获取数据,并不需要关心生产者的行为
等待与唤醒,方法在Object类中Object类的等待和唤醒方法:
Method’s name | Statement |
---|---|
void wait() | 导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法,要用wait()就得synchronized修饰 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
生产者消费者案例
生产者消费者案例中包含的类:
- 奶箱类(Box): 定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作
- 生产者类(Producer): 实现Runnable接口,重写run()方法,调用存储牛奶的操作
- 消费者类(Customer):实现RUnnable接口,重写run()方法,调用获取牛奶的操作
- 测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下
- 创建奶箱对象,这是共享数据区域
- 创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
- 创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛菜的操作
- 创建两个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
- 启动线程
public class BoxDemo{public static void main(String[] args){//创建奶箱对象Box b = new Box();//创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作Producer p = new Producer(b);//创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作Customer c = new Customer(b);//创建两个线程Thread t1 = new Thread(p);Thread t2 = new Thread(c);//启动线程t1.start();t2.start();}
}
public class Box{//定义一个成员变量,表示第x瓶奶private int milk;//定义一个成员变量,表示奶箱的状态private boolean state = false;//提供存储牛奶和获取牛奶的操作public synchronized void put(int milk){//如果有牛奶,等待消费if(state){try{wait();} catch(InterruptedException e){e.printStackTrace();}}//如果没有牛奶,就生产牛奶this.milk = milk;sout("送奶工将第" + this.milk + "瓶奶放入奶箱");//生产完毕之后,修改奶箱状态state = true;//唤醒其他等待的线程notifyAll();}public synchronized void get(){//如果没有牛奶,等待生产if(!state){try{wait();} catch(InterruptedException e){e.printStackTrace();}}//如果有牛奶,就消费牛奶sout("用户拿到第" + this.milk + "瓶奶");//消费完毕之后,修改奶箱状态state = false;//唤醒其他等待的线程notifyAll();}
}
public class Producer implements Runnable{private Box b;public class Producer(Box b){this.b = b;}@Overridepublic void run(){for(int i = 1; i <= 5; i++){b.put(i);//生产者生产了五瓶奶}}
}
public class Customer implements Runnable{private Box b;public Customer(Box b){this.b = b;}@Overridepublic void run(){while(1){b.get();}}
}
线程池
总结
线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池
线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。
创建线程池:
- 创建线程池的一个类:
Executor
- 我们创建时,一般使用它的子类:
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
其中最重要的一个构造方法,这个方法决定了创建出来的线程池的各种属性,下面依靠一张图来更好的理解线程池和这几个参数:
从图中,我们可以看出:
- 线程池中的
corePoolSize
就是线程池中的核心线程数量- 核心线程,在没有用的时候,也不会被回收。
maximumPoolSize
就是线程池中可以容纳的最大线程的数量keepAliveTime
,就是线程池中除了核心线程之外的其他的最长可以保留的时间- 因为在线程池中,除了核心线程即使在无任务的情况下也不能被清除,其余的都是有存活时间的,意思就是非核心线程可以保留的最长的空闲时间,
util
是计算这个时间的一个单位
workQueue
,就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。threadFactory
,就是创建线程的线程工厂handler
,是一种拒绝策略,我们可以在任务满了之后,拒绝执行某些任务。
最大承载 = workQueue.capacity()
+ maximumPoolSize
线程池的执行流程:
由图可见,任务进来时,首先执行判断,判断核心线程是否处于空闲状态,如果不是,核心线程就先就执行任务,如果核心线程已满,则判断任务队列是否有地方存放该任务,若果有,就将任务保存在任务队列中,等待执行,如果满了,在判断最大可容纳的线程数,如果没有超出这个数量,就开创非核心线程执行任务,如果超出了,就调用handler实现拒绝策略。
handler的四种拒绝策略:
AbortPolicy
:默认的抛异常,不出席那个*(不执行新任务,直接抛出异常,提示线程池已满)*DisCardPolicy
:直接扔掉*(不执行新任务,也不抛出异常)*DisCardOldSetPolicy
:作为下一个执行*(消息队列中的第一个任务替换为当前新进来的任务执行)*CallerRunsPolicy
:马上执行*(直接调用execute来执行当前任务)*
四种常见的线程池:
CachedThreadPool
:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。SecudleThreadPool
:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。SingleThreadPool
:只有一条线程来执行任务,适用于有顺序的任务的应用场景FixedThreadPool
:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程
入门
public static void main(String[] args) {ExecutorService threadPool = Executors.newCachedThreadPool();//单一线程Executors.newSingleThreadExecutor();//单一线程Executors.newFixedThreadPool(5);//固定的Executors.newCachedThreadPool();//可变的try{//使用线程池创建线程for(int i = 0; i < 10; i++){threadPool.execute(() ->{System.out.println(Thread.currentThread().getName());});}} catch(Exception e){e.printStackTrace();} finally {//finally保证程序走完才关//程序跑完要关闭线程池threadPool.shutdown();}}
自定义线程池
每一个线程池的创建都调用了ThreadPoolExecutor()
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}
corePoolSize
核心线程池大小maximumPoolSize
就是线程池中可以容纳的最大线程的数量keepAliveTime
超时了没人调用会被回收,指的是非核心线程util
超时的单位workQueue
,就是等待队列,任务可以储存在任务队列中等待被执行,执行的是FIFIO原则(先进先出)。threadFactory
,创建线程的线程工厂,一般不懂handler
,拒绝策略,我们可以在任务满了之后,拒绝执行某些任务。
银行排队例子
两个核心线程
三个非核心线程
等待区3个人
代码:
public static void main(String[] args) {ExecutorService threadPool = new ThreadPoolExecutor(2,5,3,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());try{//最大承载为 3 + 5 = 8个线程//使用线程池创建线程for(int i = 0; i < 10; i++){threadPool.execute(() ->{System.out.println(Thread.currentThread().getName());});}} catch(Exception e){e.printStackTrace();} finally {//finally保证程序走完才关//程序跑完要关闭线程池threadPool.shutdown();}}
Thread 1
Thread 方法总结
主要分清楚是静态方法还是非静态方法
非静态方法
【高编 1】 北邮 高级网络程序设计 1.多线程相关推荐
- 【高编 6】 北邮 高级网络程序设计 6. JSPJavaBean
JSP Java Server Page JSP脚本 三者有很严格的区分 expression: <%... %>:内容会直接放到_jspService()方法里 scriptlet:&l ...
- 计算机网络protocol,北邮高级计算机网络课件1-protocol.pdf
北邮高级计算机网络课件1-protocol,北邮通信原理课件,北邮信号与系统课件,北邮帅天平课件,通信电子电路北邮课件,北邮计算机学院,北邮计算机,北邮计算机学院主页,北邮计算机学院官网,北邮计算机考 ...
- 高级计算机网络技术北邮考试,北邮高级计算机网络技术-英文课件-马严7-IPv6-17.pdf...
北邮高级计算机网络技术-英文课件-马严7-IPv6-17 Agenda n Background IPv6 q Why IPv6? q IPv6 Intro. q IPv6 related stand ...
- 北邮JAVA高级语言程序设计(选修课)设计模式大作业
北邮JAVA高级语言程序设计(选修课)设计模式大作业 题目描述: 设计模式一(单子.工厂.策略模式)练习 1. 假设现在要设计一个贩卖各类书籍的电子商务网站的购物车系统.对所有的教材类图书 实行每本一 ...
- 北邮OJ-269. 网络传输-14网研上机D
算法分析: 分析题目得,应当得到k个结点的dist数组,从这k个数组中可以得知,k个结点中的某个结点A到另一个结点B的路径长度(这条路径中间可能经过k个结点中的第三个结点,我们不去关心他,只抽象为一条 ...
- 2014期同学参观北邮宽带网络监控教研中心
大家参观了Hadoop机房,听张燕申.凌艳.常月.尹劲松同学分别讲解了Hadoop技术在网络流量分析中的应用.CDH的管理软件与HUE环境.HUE环境下的pig应用及自主开发的Hadoop集群管理系统 ...
- 北邮高级语言设计基于java期末_北邮《高级语言程序设计》第三次阶段作业带答案...
一.单项选择题(共20道小题,共100.0分) 1. 下面哪个修饰符修饰的变量是所有同一个类生成的对象共享的?____ A. public B. private C. static D. final知 ...
- 北邮自考《C++程序设计》实践考试,你猜监考小姐姐说了什么?
5月份参加了自考北邮的C++程序设计实践上机考试.做了下实践环节考核指导上的三个程序题, 比较基础,现记录一下. 这三个题,我没有写太多的文字提示,有需要的可以自己加上. 再分享下我的考试,我是5月1 ...
- 2020北邮网安803考研经验
2020北邮网安803考研经验 基本情况介绍 本人本科是医学类专业,本科期间学业成绩也非常一般,属于经常逃课的那类同学.不过大学多多少少接触了一些计算机方面的课程,对计算机方向非常感兴趣,于是考研就打 ...
最新文章
- 2002无法连接mysql阿里云_2002无法登录MySQL服务器
- spring boot项目配置RestTemplate超时时长
- ASP.NET File.Delete只读文件引起的访问被拒绝,设置文件属性为Normal
- 算法的优缺点_朴素贝叶斯算法的优缺点
- 1)⑤爬取搜狗旅游部分新闻
- Jenkins下载war包升级版本
- python计算1的平方减2的平方加3的平方减4的平方怎么算_100的平方减99的平方加98的平方减97的平方怎么算...
- 浅析数据中心存储发展趋势
- 鲸探发布点评:7月7日发售陈孟昕系列绘画数字藏品
- 哔哩哔哩bilibili自动上传视频脚本-配合爬虫营销号狂喜
- 程序猿健身之腹肌~基本版本
- java IO流基础 万字详解(从拷贝文件到模拟上传头像)
- Android 开关控件Switch
- 计算机网络ip地址划分计算机,计算机网络中IP地址大全
- 北京内推 | 字节跳动AML机器学习系统团队招聘机器学习训练框架研发实习生
- SWUST OJ 1168 喝可乐
- GPU大百科全书 前传 看图形与装修的关系
- SLAM中线特征的参数化表示方法/重投影/初始化方法
- 论文分享 | 罗强等:GIS领域知识图谱进展研究
- spark-06:MLlib
热门文章
- 2022-2028全球与中国碳补偿项目市场现状及未来发展趋势
- 执行sudo时避免输入密码 - 脚本用,不使用visudo直接关闭密码
- 综合评价与决策方法04——灰色关联分析法
- Udacity利用深度学习玩赛车小游戏
- 计算机基础图文混合排版作业,《图文表混合排版》说课之我见
- Python的网络编程[3] - BOOTP 协议[0] - BOOTP 的基本理论
- 中国酒店向精品个性化发展
- 使用JDB操作数据库—增删改查(二)
- 安踏用770万个废弃塑料瓶打造环保科技新品
- 【quartz】quartz定时任务service注入失败