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.start();//启动线程,调用run方法 my2.start(); } }
public class MyThread extends Thread{ @Override public 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(); //sout(Thread.currentThread().getName()); } }
public class MyThread extends Thread{ @Override public 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{ @Override public 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时间片的几率高,而不是 每一次都跑在前面
线程控制不建议使用JDK自带的已经过时了的stop或者destroy方法
使用一个标志位进行终止变量,在run方法中进行判断:当flag = false,则线程停止
public class TestStep implements Runnable{ private boolean flag = true; @Override public void run(){ while(flag){ //running code } } //对外提供方法更改标识 public void stop(){ this.flag = false; } }线程中断
interrupt
区分三个方法,
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 为 t1 this.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 continueSleep与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 block e.printStackTrace(); } System.out.println("Main thread is finished"); }
会让其他线程(调用者)中等待tread0执行完
-
在其他线程中调用线程名.join()表示让线程名该线程插队执行
-
有异常,要捕获或者抛出InterruptException
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变量时,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, 其余为>1 Thread.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; @Override public 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类的等待和唤醒方法:
生产者消费者案例中包含的类:
- 奶箱类(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; } @Override public 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; } @Override public void run(){ while(1){ b.get(); } } }线程池 总结
线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池
线程池的好处,就是可以方便的管理线程,也可以减少内存的消耗。
创建线程池:
- 创建线程池的一个类:Executor
- 我们创建时,一般使用它的子类:ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueworkQueue, 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, BlockingQueueworkQueue, 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 方法总结
主要分清楚是静态方法还是非静态方法
非静态方法
静态方法
4个概念:Atomicity、Visibility、Order of execution、Critical Code
两种创建多线程的方法:
继承Thread
实现Runnable
Thread类的两种构造器
- 无参
- 带参(Runnable)
要求:
- 理解Thread是什么
- 理解Threads用来干嘛
- 学习如何创建Threads
- 学习用Java *** 作Thread
- sleep()
- Interrupting threads
Java中线程的状态分为6种。
Process & JVM
初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。阻塞(BLOCKED):表示线程阻塞于锁。
等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。
终止(TERMINATED):表示该线程已经执行完毕。
是什么?
- You can download a file (takes a few minutes)
- You can use a word processor at same time.
- (it only need to listen for your keystrokes)
JVM (Java Virtual Machine) is a process
- 默认只会用一个Process来运行
- Java中的多线程是伪并行处理或异步行为行为Within a Java application you work with several threads to achieve pseudo parallel processing or asynchronous behaviour. 伪并行处理或异步行为
Java程序的运行都是从Main方法中开始的
Process 和 Thread的区别process:
- Process和其他的process是isolated的
- process之间不能直接的共享数据
- process所利用的资源,例如memory和CPU time,都是在 *** 作系统中的
thread:a thread is a “lightweight process”.
- Each has its own call stack
- Can access shared data of other threads in the same process.
- Every thread has its own memory cache.
- If a thread reads shared data, it stores this data in its own memory cache. A
thread can re‐read the shared data
定义:不能被打断的 *** 作,具有原子性(An operation is said atomic when it cannot be interrupted.)
性质:一旦开始必须执行完
- 例如声明变量。int i = 5
This occurs when a thread must watch the actions of another thread
例如:
- 终结一个线程
- 设置一个值
正常程序:按照代码顺序执行
并发编码(concurrent programming):execution的顺序是不保证的
Critical codeA part of code that must only be executed by a single thread at one time
例如:
- Writing to a file
是什么:可执行类。The Thread class is responsible for executing your stuff in a thread.
- 要执行的代码放到run()
- 管理需要运行的代码
两种给Thread设置run()的方法
- Passing your class (with a run() method) into a new Thread object
- Extending the Thread class
通过方法:start()
两种创建多线程的方法 实现Runnable接口implement Runnable
一定要重写实现run()方法,否则会报错
报错为:Class 'RunnableError' must either be declared abstract or implement abstract method 'run()' in 'Runnable'
Main方法中使用
Thread t = new Thread(RunnableClass);继承Thread类
步骤:
- 继承Thread类 extends Thread
- 重写run()方法,在run()方法中加入代码
- 在main方法中创建实例
- start()
Main方法中使用
MyThread t = new MyThread();两种方法的比较
第一种方法要比第二种方法好
- 继承Thread后不能继承其他类
- 继承Runnable接口后还可以继承其他类
-
无参构造器
new Thread();
-
带Runnable的构造器
r = new MyRunnable(); new Thread(r);
要求:
- 线程的sleep()
- 让sleep的线程苏醒
目的:
- 让代码在合适的时间执行
- 管理资源
要使用try...catch...代码包裹,报错为:InterruptedException
Thread.sleep(long millis);
try{ Thread.sleep(1000); }catch(InterruptedException e){ e.printStackTrace(); }
参数:milliseconds,一秒为1000
使用对象:Thread
注意:只能使当前的线程休眠,不能选择某一个线程休眠
作用:
- free up CPU time
转让CPU的使用权:yield()
- 可以让其他线程获的CPU使用的优先权
- 正在运行的线程暂停
为什么线程会被Interrupt?
- 当其他线程需要资源时
- 当调用interrupt()时
详解:Sleep()和interrupt()如果那个线程B在执行一个低级可中断阻塞方法,例如Thread.sleep()、 Thread.join() 或 Object.wait(),那么它将取消阻塞并抛出InterruptedException。否则, interrupt()只是设置线程B的中断状态。
线程A正在使用sleep()暂停着: Thread.sleep(100000);
如果要取消他的等待状态,可以在正在执行的线程里(比如这里是B)调用a.interrupt();
令线程A放弃睡眠 *** 作(这里a是线程A对应到的Thread实例)执行interrupt()时,并不需要获取Thread实例的锁定。
任何线程在任何时刻,都可以调用其他线程interrupt()。
当sleep中的线程被调用interrupt()时,就会放弃暂停的状态.并抛InterruptedException.丢出异常的,是A线程.
两种情况:
- 先睡眠后打断,则直接打断睡眠,并且清除停止状态值,使之变成false,抛出InterruptedException
- 先打断后睡眠,则直接不睡眠,抛出InterruptedException
抛出时候:在线程被interrupted时抛出,并执行了sleep()
作用:可捕获该异常并进行响应的处理
Interrupt Flagpublic static boolean interrupted
每一个线程都有一个boolean值变量表示线程是否被中断(interrupt)
Interrupt Method每个线程都有一个与线程是否已中断的相关联的 Boolean 属性,用于表示线程的中断状态(interrupted status)。中断状态初始时为 false,中断后变为true
public void interrupt()
中断线程,将中断状态(boolean interrupted)设为true,执行完后会抛出InterruptedException
注意:
-
线程的阻塞方法(Blocking Methods,如Thread.sleep()):在执行前会先检测该线程是否为interrupted的状态。如果是interrupted,则会先返回
-
当对一个阻塞的线程使用该方法时,不会有任何效果。
- 线程执行太久,如线程被锁
- 打断睡眠,注意不是唤醒所以不推荐该方法
- 先睡眠后打断,则直接打断睡眠,并且清除停止状态值,使之变成false:
- 先打断后睡眠,直接不睡眠
- 抛出InterruptedException
-
打断wait状态
-
抛出InterruptedException
- 线程正常执行
- flag 变成 true
区分三个方法,很tm的像
使当前(executing)线程进入等待状态,直到有其他线程中调用notify()或者notifyAll()方法对其进行唤醒。
- 方法应该被拥有这个对象的监视器的线程调用
- 如果当前对象不拥有Monitor,则会报错IllegalMonitorStateException
- 被Interrupt,会报错InterruptedException
wait(), notify(), notifyAll()只能在有Lock的对象或者在同步方法中使用
- 如果在其他地方使用,会抛出IllegalMonitorStateException
- sleep()
- wait()
- join()
使用interrupt()可以将线程从blocked状态拉出
join 插队作用:等待另一个线程死亡,使用该方法的线程会停止直到另一个线程死亡
join()方法的作用,是等待这个线程结束;
也就是说,t.join()方法阻塞调用此方法的线程(calling thread)进入 TIMED_WAITING 状态,直到线程t完成,此线程再继续;
通常用于在main()主线程内,等待其它线程完成再结束main()主线程。
**异常:**当调用此方法的线程被interrupt时,会报InterruptedException
Thread 3学习目标:
- 理解interrupts如何实现
- 理解当使用interrupt时为什么以及什么时候抛出异常
- 理解如何terminate一个线程
- 理解线程安全问题,并学会如何避免
总结已经学的方法
Java Virtual Machine 是一个进程Process
- 一个Java程序默认只会用一个线程运行
- Java 中的多线程是pseudo parallel processing 或者 asynchronous behaviour
非静态方法
**是什么:**中断是对线程的指示,它应该停止正在做的事情并做其他事情。
- 可以通过编程决定中断后去做什么
- 通过线程名.interrupt()使名为线程名的线程对象被打断
- 不会使线程进入终结状态
异常何时抛出:
如果线程处于**sleep,join,waiting另一个线程**,对其使用interrupt()会抛出异常InterruptedException
- 随后,interrupted status会被清除
- 可以让被interrupt的线程接着完成其任务
Mechanism:
- 使用内部的标志去标识是否处于interrupt状态,称为interrupt status
- 调用interrupt()方法实际是改变了interrupt status的状态为true
- Thread.interrupted可以让线程自己检查是否处于interrupt状态
- 该方法是static的
- 使用该方法后,interrupt的状态会自动清除
- 查询方法:isInterrupt(),非静态
Thread.interrupted()&线程名.isInterrupt()比较
要处理异常而不是什么都不做
-
接住后再抛出
try { ……} catch (FileNotFoundException e) { System.err.println("FileNotFoundException: " + e.getMessage()); throw new SampleException(e); }
-
直接进行处理
catch (IOException e) { System.err.println("Caught IOException: " + e.getMessage()); }
接住异常后再执行一次myThread.interrupt()确保其运行正常
public void run(){ while(!Thread.currentThread().isInterrupted(){ // Doing some heavy operations try { Thread.sleep(5000); } catch(InterruptedException e){ Thread.currentThread().interrupt(); } } }线程是否存活
非静态方法
myThread.isAlive()
- true表示线程还在运行
- 只要运行了还没有死亡都算Alive
用法:
if(myThread.isAlive()){ // Check something //myThread.interrupt(); }
不常用,主要用join()
结束线程有三种方法使线程结束
- 当run()中代码运行完
- 守护线程:如果创建其的线程灭亡,那对应的守护线程也将死亡
- 运行时接受到interrupt signal
- 不执行Run方法里面内容直接退出
- 退出sleep、wait状态接着工作
没有命令使Thread直接死亡
自然死亡可以添加pleaseFinish的Boolean型变量。
- 添加判断,如果判断到其为true,这跳过Run中全部的代码
class MyThread extends Thread{ private volatile boolean pleaseFinish = false; public void run(){ //Periodically check for pleaseFinish to be // set to true } public void finishThreadPlease(){ pleaseFinish = true; } }
注意:
- 如果线程没有抢到执行权,设定停止Flag将无任何影响
- 处于sleep状态
- wait状态
- 被锁
- 使用interrupt()方法使其提早结束sleep或者wait状态
- 记得使用volatile
myThread.setDaemon(false)
- 当父线程运行完后,myThread线程仍继续运行
myThread.setDaemon(true)
- 当父线程运行完后,myThread线程也被迫结束
- 可以使用interrupt来终结一个线程
- 必须捕获异常InterruptedException,再结束
- thread.interrupt()不会自动终结一个线程,需要在代码中自己终结
- 不适用于在有wait或者sleep的run中
public void run(){ for (int i = 0; i < importantInfo.length; i++){ try { Thread.sleep(4000); } catch (InterruptedException e){ return; } System.out.println(importantInfo[i]); } }
重申异常抛出时机:
- 只在sleep()和wait()中会自动抛出
- 应该常态化将检查中断作为程序检查点已到达
设定判断使中断变成结束点
if (Thread.currentThread.isInterrupted()){ // cleanup and stop execution return; }
例子:
public class StopThread { public static void main(String[] args) throws InterruptedException { Thread thread = new DemoThread(); thread.start(); Thread.sleep(1000); thread.interrupt(); } private static class DemoThread extends Thread{ @Override public void run() { for (int i = 0; i < 1000000; i++) { if (Thread.currentThread().isInterrupted()) return; System.out.println("i is " + i); } } }Please Stop
使用于有wait和sleep的方法中
例子:
public class StoppableTask extends Thread{ private volatile boolean pleaseStop; public void run(){ while(!pleaseStop){ ... } } public void tellMeStop(){ pleaseStop = true; } }
csdn例子:
public class StopThread { private static volatile boolean isStopped = false; public static void main(String[] args) throws InterruptedException { Thread thread = new DemoThread(); thread.start(); Thread.sleep(1000); // thread.interrupt(); isStopped = true; } private static class DemoThread extends Thread{ @Override public void run() { for (int i = 0; i < 1000000; i++) { if (isStopped) return; System.out.println("i is " + i); } } } }再讲Interrupt
为了使得线程安全,可以在catch中再次interrupt来运行
public void run() { while(!Thread.currentThread().isInterrupted() { // Doing some heavy operations try { Thread.sleep(5000); } catch(InterruptedException e) { Thread.currentThread().interrupt(); } } }Interrupt状态的恢复
抛出InterruptedException的方法,例如sleep会中断当前的运行并且立马返回
sleep方法会在被interrupt后清除掉interrupt的状态
线程安全为什么会有线程安全问题?
看另一个笔记
Volatile保持变量的安全
public volatile int counter = 0
原理:一个变量一次只会发给一个线程使用,使用其具备原子性的 *** 作,例如load, store, read, write
坑点:
对于数组来说:如果声明了volatile int[] arr
- 其中arr[5]不安全
复合 *** 作不能使用
- 例如i++
应用举例:使线程停下
public class StoppableTask extends Thread { private volatile boolean pleaseStop; public void run() { while (!pleaseStop) { // do some stuff... } } public void tellMeToStop() { pleaseStop = true; } }
不必使用Volatile的时刻
- 已经被final修饰
- 当只有一个线程
- 复合运算
- 有同步方法了
被多个线程使用的access的代码称为:critical sections
Monitor支持两个用处:
- 同步方法,锁
- wait()和notify()
属于Object类下,任何一个对象都可以作为一个Monitor
每一个对象都有着两个方法
- 每一个对象都有锁。锁确保一次只能有一个线程执行使用或执行该对象。因此每一个对象都有 wait set
因为每一个对象都有两个方法、锁、wait set,所以称之为monitor
wait和notify都要放在synchronized方法中执行
“entry” and “wait” set一堆正在等待被notify的Thread的集合
- 处于该状态的集合,需要等待nodify来唤醒
图示:每一次只能有一个线程被唤醒
entry set拥有相关Monitor的线程会首先进入entry set,如果没有其他的线程在等待或者持有Monitor,当前线程则会拿取Monitor继续执行Monitor的代码
拥有该Monitor对象释放条件:当当前线程已经执行完
wait set当一个拥有Monitor的线程在Monitor内部执行了wait(),则释放Monitor并去到Wait Set
此时如果有notify()唤醒,则该线程会继续持有Monitor并执行代码直至再次释放,释放的两种可能
- 执行完代码
- 再次wait()
注:被唤醒的线程需要再次抢夺Monitor而非直接拥有,
对象一致性wait, notify, synchronized 需确保其对象一样
Notify entry set还是wait set?- 如果拥有Monitor的线程在执行完代码前没有notify,则唤醒的是在entry set中的线程
- 如果执行了 notify,则entry set 的线程要与wait set中的线程竞争Monitor
如果一个线程当前拥有Monitor可以执行,它只能执行wait()方法
生产者消费者模型中间变量为数组的
- 标记最后的位置,使用单独变量private int last;
- 判断已满方法:public boolean isFull() { return(last == buf.length); }
- 判断为空方法:public boolean isEmpty() { return (last == 0); }
- 取出元素后去掉数组内数据方法:System.arraycopy(buf, 1, buf, 0, --last);
public class Buffer { private char[] buf; // buffer storage private int last; // last occupied position public Buffer(int sz) { buf = new char[sz]; last = 0; } public boolean isFull() { return(last == buf.length); } public boolean isEmpty() { return (last == 0); } public synchronized void put(char c) { while (isFull()) { try { wait(); } catch(InterruptedException e) { } } buf[last++] = c; notify(); //to the buffer } public synchronized char get(){ while (isEmpty()) { try { wait(); } catch(InterruptedException e) { } } char c = buf[0]; System.arraycopy(buf, 1, buf, 0, --last); notify(); return c; } }
public class Consumer implements Runnable { private Buffer buffer; public Consumer(Buffer b) { buffer = b; } public void run() { for (int i=0; i<250; i++) { System.out.println(buffer.get()); } } }
public class Producer implements Runnable { private Buffer buffer; public Producer(Buffer b) { buffer = b; } public void run() { for (int i=0; i<250; i++) { buffer.put((char)('A' + (i%26))); } } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)