多线程
1.认识多线程
了解并发/并行
并发:指两个或多个事件在同一个时间段内发生(交替执行)
并行:指两个或多个事件在同一时刻发生(同时执行)
进程:
进程:指一个内存中运行的应用程序,每个进程都有一个独立的空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;
系统运行一个程序即是一个进程从创建 运行 到消亡的过程
线程:
线程是进程中的一个执行单元,负责当前进程的执行,一个进程中至少有一个线程,一个进程中是可以有多个线程的。这个应用程序也可以称为多线程程序
简而言之:一个程序运行后至少一个进程,一个进程中可以包含多个线程(线程是进程中的一个执行单元,负责程序中的执行)
多线程的好处:1.效率高 2.多个线程之间互不影响
线程的调度:
1.分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间
2.抢占式调度
优先让优先级高的线程使用CPU ,如果线程的优先级相同,那么会随机选择一个(线程随机性) Java使用的为抢占式调度
主线程: 执行主(main)方法的线程
Jvm执行main方法,main方法进入到栈内存中,Jvm会找操作系统开辟一条main方法通向CPU的执行路径 CPU就可以通过这个路径来执行main方法,这个路径就是main(主)线程
单线程:java程序中只有一个线程 执行从main方法开始,从上往下依次执行
多线程原理
随机性打印结果原因
多线程内存图解
Java.lang.Thread类 常用方法
构造方法:
public Thread();分配一个新的线程对象
public Thread(String name); 分配一个指定名字的线程对象
public Thread(Runnable traget,String name);
分配一个带有指定目标新的线程对象并指定名字
常用方法:
1.获取当前线程的名称:
方法1:使用Thread类中的方法 getName()
1.public String getName() 返回当前线程名称
获取线程的名字 方法1
String name = getName();
System.out.println(name);
方法2::可以先获取到当前正在执行的线程,使用线程中的方法getName() 获取当前正在执行的线程的信息
2.public static Thread currentThread();返回当前正在执行的线程对象的引用
线程的名字:主线程:main 新线程:Thread-0,Thread-1,Thread-2
Thread name = Thread.currentThread();
System.out.println(name);//Thread[Thread-0,5,main] 5:线程的优先级
String string = name.getName();
System.out.println(string)
等价于:(链式编程)System.out.println(Thread.currentThread().getName());
2.设置线程的名称(了解)
1.public void setName(String name) 将此线程的名称更改为等于参数 name
2.创建一个带参数的构造方法,参数传递线程的名称,调用父类的带参构造方法,把线程名称传递给父类,让父类Thread 给子线程起一个名字
Thread(String name) 分配一个新的 Thread对象。
3.线程的休眠
public static void sleep(long millis) 使当前正在执行的线程以指定的毫秒暂停
毫秒数结束以后,线程继续执行
创建线程类
java使用java.lang.Thread 类代表线程 所有的线程对象都必须是Thread类或子类的实例
创建多线程的第一种方式 :
创建Thread类的子类
java.lang.Thread 是描述线程的类 ,我们想要实现多线程程序,就必须继承Thread类
实现步骤:
1.创建一个Thread类的子类,
2.在Thread类的子类中的run方法 设置线程任务(开启线程做什么)
3.创建一个Thread类的子类对象
4.调用Thread类中的方法 start方法 开启新的线程 执行run方法
ps:void start() 使该线程开始执行,Java虚拟机调用线程的run方法
结果是两个线程并发的运行:当前线程(main线程)和另一个线程(创建的新线程,执行其run方法)。
多次启动一个线程是非法的,特别是当前线程已经结束执行后,不能再重新启动
注意:java程序属于抢占式调度,那个线程的优先级高,那个线程优先执行;同一优先级随机选择一个线程执行
public class MyThread extends Thread{
//1.创建一个Thread类的子类
//2.在Thread类的子类中的run方法 设置线程任务(开启线程做什么)
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("run"+i);
}
}
}
public class Demo03MuitCode {
public static void main(String[] args) {
//3.创建一个Thread类的子类对象
MyThread p1 = new MyThread();
//4.调用Thread类中的方法 start方法 开启新的线程 执行run方法
p1.start();
for (int i = 0; i < 20; i++) {
System.out.println("main"+i);
}
}
}
创建多线程的第二种方式:
实现java.lang.Runnable接口 也是非常常见的一种创建线程的方式。我们只需要重写run方法即可
Runnable接口应由那些打算通过某一线程执行其 实例的类来实现。 该类必须定义一个无参数的方法,称为run 。
java.lang.Thread的构造方法:
Thread(Runnable target) 分配一个新的 Thread对象。
Thread(Runnable target, String name) 分配一个新的 Thread对象。
实现步骤:
1.创建一个Runable接口的实现类
2.在实现类中重写Runable接口的run方法。设置线程任务
3.创建一个Runable接口的实现类对象
4.创建Thread类对象,构造方法中传递Runable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程执行run方法
public class Runableimpl implements Runnable{
//1.创建一个Runable接口的实现类
@Override
public void run() {
//2.在实现类中重写Runable接口的run方法。设置线程任务
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
public class Demo06 {
public static void main(String[] args) {
//3.创建一个Runable接口的实现类对象
Runableimpl impl = new Runableimpl();
//4.创建Thread类对象,构造方法中传递Runable接口的实现类对象
Thread thread = new Thread(impl);
//5.调用Thread类中的start方法,开启新的线程执行run方法
thread.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+i);
}
}
}
使用Runable接口创建多线程程序的好处
如果一个类继承Thread ,则不适合资源共享,但是如果实现了Runable接口的话 就很容易实现资源共享
实现Runable接口比继承Thread类所有的优势
1.适合多个相同的程序代码的线程去共享同一个资源
2.可以避免java中的单继承的局限性
一个类只能继承一个类(一个人只能由一个亲爹) 类继承了Thread类就不能继承其他类,而实现了Runable接口,还可以继承其他的类,实现其他的接口
3.增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立
实现Runable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
4.线程池只能放入实现Runable或者Callable类线程。不能直接放入继承Thread的类
扩展
在Java中,每次程序运行至少启动2个线程,一个是main线程,一个是垃圾回收线程,视为每当使用java命令执行一个类的时候,实际上都会启动一个jvm每个jvm其实就是在操作系统中启动了一个进程
匿名内部类的方式实现线程的创建
回顾
匿名:没有名字
内部类:写在其他类内部的类
匿名内部类的作用:简化代码
实现原理
把子类继承父类,重写父类的方法,创建子类对象合成一步完成
把实现类实现接口,重写接口中的方法,创建实现类对象合成一步完成
匿名内部类的最终产物:子类/实现类对象,而这个类没有名字
格式:
new 父类/接口(){
重写父类/接口中的方法;
}
//1.线程的父类Thread方法
//1.线程的父类Thread方法
new Thread() {
//2.重写run方法
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"->"+"线程1");
}
}
}.start();
//2.线程接口的方式
Runnable runnable = new Runnable() { //采用匿名的方法实现的接口 赋值给一个Runable
@Override
//重写run方法,设置线程任务
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"->"+"线程2");
}
}
};
new Thread(runnable).start();
//简化线程接口的方法
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"->"+“线程3”);
}
}
}).start();
多线程安全问题
买票问题 多个线程同时买一张票的问题
public class Runableimpl implements Runnable{
//定义一个多个线程共享的票源
private int ticker = 100;
@Override
//设置线程任务,卖票
public void run() {
//使用死循环,让卖票重复操作
while(true) {
if (ticker > 0) {
//提高安全问题出现的概率,让城乡睡眠一下
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第:"+ticker+"张票");
ticker--;
}else {
break;
}
}
}
}
解决线程安全问题: 线程同步机制
1.同步代码块
格式
sychranized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
1.通过代码块中的锁对象,可以使用任意的对象
2.但是必须保证多个线程使用的锁对象是同一个
3.锁对象作用:
把同步代码块锁住,只让线程在同步代码块中执行
同步技术的原理
使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象监视器
同步中的线程,没有执行完毕不会释放当前锁。同步外的线程没有锁进不去同步
同步保证了只能有一个线程在同步中执行共享数据,保证了安全,但程序会频繁的获取锁,释放锁,程序的效率会有所降低
2.同步方法
格式:
修饰符 synchronized 返回值类型 方法名(参数列表){
//访问了共享数据的代码
}
实现步骤:
1.把访问了共享数据的代码抽取出来,放到一个方法中
2.在方法上添加synchronized 修饰符
同步方法锁住的就是实现类的对象 new Runable(); 也就是this
静态的同步方法:(了解)
静态方法的参数只能调用静态的
静态的同步方法的锁对象是?
不能是this,this是创建对象之后产生的,静态方法优先于对象,静态方法的锁对象是本类的class属性 -》class文件对象(反射)
public static synchronized void staticmethod() {
if(ticker >0) { //静态方法的参数只能调用静态的
System.out.println(Thread.currentThread().getName()+"正在卖第:"+ticker+"张票");
ticker--;
}
}
3.锁机制
Lock锁
Jdk1.5后提供了 Java.util.concurrent.locks.Lock 提供了比 synchronized 代码块和 synchronized 方法更广泛的锁定操作
同步代码块/同步方法 具有的功能Lock都有,除此之外更强大,更体现面向对象
lock接口中的两个方法:
1.void lock() 获得锁。
2.void unlock() 释放锁。
java.util.concurrent.locks.ReentrantLock 实现了lock接口
使用步骤:
1.在成员位置创建一个ReentrantLock对象
2.在可能会出现线程安全问题 前 调用Lock接口中的方法 lock获取锁
3.在可能会出现线程安全问题 后 调用Lock接口中的方法 unlock释放锁
我们可以把释放锁放在 finally代码块中 无论程序是否是否异常,都会把锁释放。提高程序的效率
public class Runableimpl3 implements Runnable{
//定义一个多个线程共享的票源
private int ticker = 100;
//创建一个Lock锁实现对象
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true) {
lock.lock();
try {
//在可能发生线程安全问题前 调用Lock接口中的方法 lock获取锁
if (ticker > 0) {
System.out.println(Thread.currentThread().getName()+"正在卖第:"+ticker+"张票");
ticker--;
}else {
break;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
//在可能发生线程安全问题后 调用Lock接口中的方法 unlock释放锁
lock.unlock(); //无论程序是否是否异常,都会把锁释放。提高程序的效率
}
}
}
}
线程的状态
线程的状态,线程可以处以下列状态之一
NEW 尚未启动的线程处于此状态。
RUNNABLE 在Java虚拟机中执行的线程处于此状态。
BLOCKED 被阻塞等待监视器锁定的线程处于此状态。
WAITING 正在等待另一个线程执行特定动作的线程处于此状态。
TIMED_WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TERMINATED 已退出的线程处于此状态
进入到TIMED_WAITING(记时等待) 有两种方式:
1.使用sleeep(long time)方法 在毫秒值之后。线程睡醒进入到Runnable/Blocked状态
2.使用wait(long time)方法,如果在毫秒值之后,还没有被notify唤醒,就会自动醒来,进入到Runnable/Blocked状态
唤醒的方法:
void notify() 唤醒监视器上的单个线程
void notifyAll() 唤醒监视器上的所有线程
public class Demo04 {
public static void main(String[] args) {
//创建一个锁对象,保证锁对象唯一
Object obj = new Object();
//创建一个顾客线程(消费者)
new Thread() {
@Override
public void run() {
while(true) {
//保证等待和唤醒,只能由一个在执行,需要使用同步技术
synchronized (obj) {
System.out.println("顾客1告知老板要的包子的种类和数量");
try {
obj.wait(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("包子拿到了,顾客1开吃");
}
}
}
}.start();
//创建一个顾客2线程(消费者)
new Thread() {
@Override
public void run() {
while(true) {
//保证等待和唤醒,只能由一个在执行,需要使用同步技术
synchronized (obj) {
System.out.println("顾客二告知老板要的包子的种类和数量");
try {
obj.wait(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("包子拿到了,顾客2开吃");
}
}
}
}.start();
//创建一个老板线程(生成者):
new Thread() {
@Override
public void run() {
while(true) {
//花5秒钟做包子
try {
sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//保证等待和唤醒,只能由一个在执行,需要使用同步技术
synchronized (obj) {
System.out.println("包子做好了,请慢用");
obj.notifyAll();
}
}
}
}.start();
}
}
线程之间的通信
等待唤醒机制
多个线程在处理同一个资源,但是处理的动作(线程和任务)却不同
多个线程在处理统一资源,并且任务不同,多个线程在操作同一份数据时,避免对统一共享变量的争夺,我们需要通过一定的手段使各个线程能够有效的利用资源
等待唤醒机制: 多个线程间的一种 协作 机制
等待唤醒案例
创建一个顾客线程(消费者):告知老板要的包子的种类和数量。调用wait方法,放弃cpu的执行,进入WAITING状态(无限等待状态)
创建一个老板线程(生成者):花了5S做这个包子,做好包子后,调用notify方法,唤醒顾客吃包子
注意:
顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒,只能由一个在执行
同步使用的锁对象必须保证唯一
//创建一个锁对象,保证锁对象唯一
Object obj = new Object();
只有锁状态才能调用wait和notify方法
Object类中的方法:
void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
void notify() 唤醒正在等待对象监视器的单个线程。 会继续执行wait方法之后的方法
public class ThreadState {
public static void main(String[] args) {
//创建一个锁对象,保证锁对象唯一
Object obj = new Object();
//创建一个顾客线程(消费者)
new Thread() {
@Override
public void run() {
while(true) {
//保证等待和唤醒,只能由一个在执行,需要使用同步技术
synchronized (obj) {
System.out.println("告知老板要的包子的种类和数量");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//唤醒之后执行的代码
System.out.println("包子拿到了,开吃");
}
}
}
}.start();
//创建一个老板线程(生成者):
new Thread() {
@Override
public void run() {
while(true) {
//花5秒钟做包子
try {
sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//保证等待和唤醒,只能由一个在执行,需要使用同步技术
synchronized (obj) {
System.out.println("包子做好了,请慢用");
obj.notify();
}
}
}
}.start();
}
}
线程池的概念及原理
线程池:容器 -》 集合(ArrayList,HashSet 一般采用LinkedList , HashMap)
**线程池:**其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源
1.当程序第一次启动的时候,创建多个线程,保存到一个集合中
2.当我们想要使用线程的时候,就可以从集合中取出来线程使用
3. 如果是List集合 我们可以使用
Thread t = list.remove(0) 返回被移出的元素(线程只需要被一个任务使用)
如果是linked集合 我们可以使用
Thread 他= lisked.removedFirst();
4.当我们使用完毕线程,需要把线程归还给线程池
list.add(t);
linked.addLast(t):
在Jdk1.5 以后 就内置了线程池 我们可以直接使用
java.util.concurrent.Executors :线程池的工厂类,用来生成线程池
Executors 类中的静态方法
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数量的线程池
参数:int nThreads : 创建线程池中,包含的线程数量
返回值:ExecutorService 返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
java.util.concurrent.ExecutorService:线程池接口
用来从线程池中获取线程,调用start方法,执行线程任务
submit(Runnable task) 提交一个Runnable 任务用于执行
(了解)关闭/销毁线程池的方法
void shutdown() 启动有序关闭,其中先前提交的任务将被执行,
线程池的使用步骤:
1.使用线程池的工厂类Executors里面提供的静态方法:newFixedThreadPool(int nThreads)生产一个值得线程数量的线程池
2.创建一个类,实现Runnable接口 ,重写run方法 设置线程任务
3.调用ExecutorService中的方法 submit 传递线程任务(实现类) 开启线程,执行run方法
4.调用ExecutorService中的方法 shutdown 销毁线程池(不建议执行)
public class ThreadPool {
public static void main(String[] args) {
//1.使用线程池的工厂类Executors里面提供的静态方法:newFixedThreadPool(int nThreads)生产一个值得线程数量的线程池
//获取一个线程池
ExecutorService service = Executors.newFixedThreadPool(2);
//3.调用ExecutorService中的方法 submit 传递线程任务(实现类) 开启线程,执行run方法
service.submit(new Runableimpl4());//pool-1-thread-1创建了一个新的线程
//线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
service.submit(new Runableimpl4());//pool-1-thread-1创建了一个新的线程
service.submit(new Runableimpl4());//pool-1-thread-2创建了一个新的线程
//4.调用ExecutorService中的方法 shutdown 销毁线程池(不建议执行)
service.shutdown(); //关闭线程池
}
}
//2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
public class Runableimpl4 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"创建了一个新的线程");
}
}