网页左边,向下滑有目录索引,可以根据标题跳转到你想看的内容 |
---|
一、线程与进程,线程的实现方式
什么是线程? |
---|
聊线程我们需要先知道什么是进程(进程包含线程,一个进程可以有多个线程,进程结束线程一定结束,线程结束,进程不一定结束)
进程:是当前正在执行的一个程序,比如我们同时运行了qq和微信,这两个程序就是两个独立的进程
- 进程是静态的执行过程
- 占用特定的内存空间
- 不同进程间相互独立,由cpu、内存地址code 和 内存空间data组成
- 一个单核cpu同一时刻只能处理一个进程,如果进程过多,cpu负担会很重,所以现在有很多4核,8核cpu来同时处理多个进程
cpu如何处理进程
- 同一时刻单核cpu只能处理一个进程,简单来说,一个进程有3个状态(只是简单来说),就绪-运行-等待(阻塞)
- 进程准备好了除cpu外所有资源,进入就绪状态
- 这时当cpu空闲就会处理这个进程,此时进程为运行状态
- 当此进程突然需要某些资源,比如io设备(这个设备很慢,和cpu速度完全不是一个量级),那么此时cpu不会等待这个进程获取它要的资源,转而处理其它就绪进程
- 此时此进程进入等待状态,当它获取到了它需要的资源,这时又只差cpu了,就重新进入就绪状态
线程:线程是单一的可连续处理的可控流程,可以称为是轻量级进程,一个进程可以有多个线程
- 一个进程可以有多个并行的线程
- 相同进程的所有线程共享相同的内存单元、内存地址空间、可以共享资源(变量和对象)
- 相同进程的所有线程从同一个堆(不懂请百度’堆栈’的概念)中分配对象和通信,还可以数据交换,同步操作
- 不需要额外通信机制,通信简便,数据传输快。(因为某一进程中的所有线程通信是在同一地址空间进行的)
这也是为什么现在出现4核8线程cpu,8核16线程cpu的原因
线程执行状态或者称为生命周期(注意是java的线程,不是进程,以下两张图片取自马士兵教育
的课件)
1、实现线程的方式1
package com.company;
/**
* 实现线程的第一种方法,通过类实现
* 1、继承Thread类
* 2、实现run()方法
* 3、创建类对象,调用线程方法,比如start()启动线程
*/
public class ThreadTest extends Thread{ //1、继承Thread类
//2、实现run方法
@Override
public void run() {
for (int i = 0;i <= 10;i++){
Thread.currentThread().setName("ThreadTest类的线程");//设置当前线程的名字,可以不设置,有默认值Thread-0
System.out.println(Thread.currentThread().getName()+""+i);//获取当前线程名字
}
}
}
package com.company;
/**
* 实现线程的第一种方法,通过类实现
* 1、继承Thread类
* 2、实现run()方法
* 3、创建类对象,调用线程方法,比如start()启动线程
*/
public class Main extends Thread{ //1、继承Thread类
//2、实现run方法
@Override
public void run() {
for (int i = 0;i <= 10;i++){
Thread.currentThread().setName("Main类的线程");//设置当前线程的名字,可以不设置,有默认值Thread-0
System.out.println(Thread.currentThread().getName()+""+i);//获取当前线程名字
}
}
public static void main(String[] args) {
//3、创建类对象,可省略,因为这里main方法就在这个类,其它情况不能省略
ThreadTest threadTest = new ThreadTest();//创建第一个线程类对象
Main main = new Main();//创建第二个
//4、调用Thread类继承来的方法,启动线程
threadTest.start();//启动第一个线程
main.start();//启动第二个线程
}
}
2、实现线程的方式2
package com.company;
/**
* 实现线程的第二种方法,通过继承Runnable接口(因为Thread类就是实现Runnable接口实现的)
* 此方法适用于需要使用线程,还需要继承其它类的时候(因为一个类同时只能继承一个类,但可以同时实现多个接口)
* 1、实现Runnable接口(与方法1的不同点)
* 2、实现run()方法
* 3、创建类对象,然后创建Thread类对象,将刚实现了Runnable接口的类放在构造方法中,实现线程(与方法1的不同点),最后通过Thread类对象调用start()方法启动线程
*/
public class ThreadTest implements Runnable{ //1、实现Runnable接口
//2、实现run方法
@Override
public void run() {
for (int i = 0;i <= 10;i++){
Thread.currentThread().setName("ThreadTest类的线程");//设置当前线程的名字,可以不设置,有默认值Thread-0
System.out.println(Thread.currentThread().getName()+""+i);//获取当前线程名字
}
}
}
package com.company;
/**
* 实现线程的第二种方法,通过继承Runnable接口(因为Thread类就是实现Runnable接口实现的)
* 此方法适用于需要使用线程,还需要继承其它类的时候(因为一个类同时只能继承一个类,但可以同时实现多个接口)
* 1、实现Runnable接口(与方法1的不同点)
* 2、实现run()方法
* 3、创建类对象,然后创建Thread类对象,将刚实现了Runnable接口的类放在构造方法中,实现线程(与方法1的不同点),最后通过Thread类对象调用start()方法启动线程
*/
public class Main extends Thread{ //1、继承Thread类
//2、实现run方法
@Override
public void run() {
for (int i = 0;i <= 10;i++){
Thread.currentThread().setName("Main类的线程");//设置当前线程的名字,可以不设置,有默认值Thread-0
System.out.println(Thread.currentThread().getName()+""+i);//获取当前线程名字
}
}
public static void main(String[] args) {
//3、创建类对象,可省略,因为这里main方法就在这个类,其它情况不能省略
Main main = new Main();//创建第二个
//4、调用Thread类继承来的方法,启动线程
main.start();//启动线程
/**方法2不同的地方*/
//创建刚刚实现了Runnable接口的类
ThreadTest threadTest = new ThreadTest();
//创建Thread类对象,将刚实现了Runnable接口的类放在构造方法中,实现线程
Thread thread = new Thread(threadTest);
//通过Thread类对象调用start()方法
thread.start();
}
}
3、经典卖票问题
首先,使用继承Thread类的方式实现线程会出现共享资源不同步问题,当然可以添加static关键字解决
继承Runnable接口实现不会出现资源不同步的问题
可以看到,上面的结果依旧不是我们想要的,这里就涉及到正真的线程同步解决方案了,学习之前我们需要先学习代理设计模式 |
---|
4、代理设计模式
因为篇幅原因,我将这部分内容放在别的博客中了。请参考https://blog.csdn.net/grd_java/article/details/109690730 |
---|
5、线程操作相关方法
从Object类继承来的方法 |
---|
wait():暂停当前线程,进入阻塞。这只是片面的解释。因为wait方法是所有类共享的方法,所以一般我们暂停的是当前共享资源(共享对象),导致线程无法强制资源,被迫进入阻塞 |
notify():唤醒暂停的线程,和wait一样,唤醒的其实是某个共享对象 |
package com.company;
/**
* 调用动态代理
*/
public class Main{
public static void main(String[] args) {//首先主函数就是一个线程,我们可以直接在main方法中测试方法
Thread thread = Thread.currentThread();//获取当前线程对象
String threadName = thread.getName();//获取当前线程名称
System.out.println(threadName);//打印结果为main,因为main函数就是一个线程
System.out.println(thread.getId());//获取线程id
/*
线程优先级,值越高,获取优先的概率越高,但是不一定就是它优先,仅仅概率更高一点罢了
对比优先级低的线程,高优先线程有高一点的几率抢到cpu资源,但根据情况不同,也会抢不过低优先线程
*/
thread.setPriority(10);//设置线程的优先级为10,一般大多数的系统中优先级是0-10,默认都会取中间值5,而有些系统是0-100
System.out.println(thread.getPriority());//获取线程优先级
System.out.println(thread.isAlive()?"线程存活":"线程死亡");//thread.isAlive()方法用来返回线程的状态,还在活动就返回True,否则false
try {
thread.sleep(1000);//休眠线程1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.isAlive()?"线程存活":"线程死亡");
thread.yield();//暂停线程,让其他线程先执行,此时此线程重新回到就绪状态,开始抢cpu
System.out.println(thread.isAlive()?"线程存活":"线程死亡");
try {
thread.join();//強行阻塞线程,一般此方法用在其他线程中,当调用这个方法的线程执行完了,才重新就绪这个被阻塞的线程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.isAlive()?"线程存活":"线程死亡");
thread.stop();//强行停止线程,已淘汰,不推荐使用,推荐使用yield
}
}
二、线程同步,线程死锁
死锁:就是我让张三先交货,张三让我先给钱,我不肯先给钱,张三也不肯先给货,我俩各拿一份资源,谁的不肯松手,这就叫死锁。就是线程a拿一份资源,线程b拿一份资源,a想要b现在的资源,b也想要a现在的资源,两个都不肯让,就叫死锁。
1、线程安全,线程同步
上面我们卖票,出现了几个线程同时访问资源的问题,这时就需要用到线程同步处理方案 |
---|
同步的条件 |
---|
至少有两个线程 |
必须多个线程使用同一个资源 |
必须保证同步中只能有一个线程运行(需要同步的代码中,只能同时有一个线程,不能两个线程同时工作) |
1、同步代码块
synchronized (共享对象,必须是object的子类){代码} |
---|
package com.company;
import org.apache.commons.io.filefilter.TrueFileFilter;
public class ThreadTest implements Runnable{
private int ticket = 10;
@Override
public void run() {
boolean b = true;
synchronized (this){//同步代码块
while(b){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"正在卖第--"+(ticket--)+"--张票");
}else{
b = false;
}
}
}
}
}
2、同步方法(同步方法可以有效解决同步安全问题,并且不需要知道共享对象,通常将当前对象作为同步对象
)
下面这张截图中忘了删掉同步代码块了,但是运行结果依然正常,详细代码请看我贴出来的代码
package com.company;
import org.apache.commons.io.filefilter.TrueFileFilter;
public class ThreadTest implements Runnable{
private int ticket = 10;
@Override
public void run() {
boolean b = true;
while(b){
sale(b);//调用同步方法
}
}
public synchronized void sale(boolean b){//将需要同步的代码放在使用synchronized修饰的方法中
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"正在卖第--"+(ticket--)+"--张票");
}else{
b = false;
}
}
}
3、解决线程独占资源的状况
仔细观察上面讲的同步的代码结果,你就会发现,所有的票都是同一个线程卖的,怎么解决呢? |
---|
2、线程的生产者与消费者问题
生产者不断生产,消费者不断取走生产者生产的产品(生产者不断生产东西到某个区域,消费者不断从此区域取走产品)
这里要重新提到从Object类继承来的方法,拿过来让大家注意一下 |
---|
wait():暂停当前线程,进入阻塞。这只是片面的解释。因为wait方法是所有类共享的方法,所以一般我们暂停的是当前共享资源(共享对象),导致线程无法强制资源,被迫进入阻塞 |
notify():唤醒暂停的线程,和wait一样,唤醒的其实是某个共享对象 |
package com.company;
/**
* 商品实体类
*/
public class Goods {
private String brand;//品牌
private String name;//商品名
private boolean flag = false;//判断仓库中是否有商品,有就是true,没有就是false
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 线程安全同步,生产者生产的方法
* @param brand
* @param name
*/
public synchronized void set(String brand,String name){
//如果flag为true,说明有商品,让生产者线程等待商品被取走,并通知消费者来取
if(flag){
System.out.println("生产者查看了仓库,发现商品还未被取走,==========通知消费者来取");
try {
wait();//等待当前共享资源goods,相当于阻塞当前线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);//睡眠1s
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBrand(brand);//设置品牌
this.setName(name);//设置名称
System.out.println("生产者生产了"+this.getBrand()+"----"+this.getName());
/**
* 生产者生产完商品,让flag设置为true表示仓库有商品了,并且使用notify重新唤醒生产者线程
*/
flag = true;
notify();//唤醒当前共享资源,让当前线程可以争取当前共享资源,就是唤醒线程的效果
}
public synchronized void get(){
//如果flag为false,就是没有商品,让消费者赶紧生产,并且暂停当前线程
if(!flag){
System.out.println("消费者来取东西发现没有商品==========通知生产者生产");
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.getBrand();
this.getName();
System.out.println("消费者取走了"+this.getBrand()+"----"+this.getName());
//取走商品后,flag设置为false,表示仓库没有资源了,然后唤醒线程
flag = false;
notify();
}
}
三、线程池
为什么需要线程池
- 在实际使用中,线程非常占用系统资源,管理不当会导致一系列系统问题,许多并发框架中都会使用线程池来管理线程
- 线程池,就是预先创建好一堆线程放在池里,有人需要就拿走,用完还回来,给其它人继续使用
- 使用线程池可以重复利用已有线程继续执行任务,避免线程重复创建销毁,造成系统资源浪费,提高系统响应速度
- 线程池可以合理管理线程,会根据系统承受能力调整可运行线程的数量和大小等
以下概念性图片均来自马士兵教育的课件ppt
1、CachedThreadPool,高速缓存线程池
package com.company;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main{
public static void main(String[] args) {
/**
* Executors:是所有线程池的父类,它提供了execute方法,用来向线程池添加线程
*/
ExecutorService executorService = Executors.newCachedThreadPool();//创建高速缓存线程池
for (int i = 0;i < 10;i++){
executorService.execute(new ThreadTest());//添加线程
}
executorService.shutdown();//关闭线程池
}
}
2、FixedThreadPool,固定数量线程池
package com.company;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main{
public static void main(String[] args) {
/**
* Executors:是所有线程池的父类,它提供了execute方法,用来向线程池添加线程
*/
ExecutorService executorService = Executors.newFixedThreadPool(5);//创建固定线程池,就是线程有多少个是谁都固定的
for (int i = 0;i < 10;i++){
executorService.execute(new ThreadTest());//添加线程
}
executorService.shutdown();//关闭线程池
}
}
3、SingleThreadExecutor,单例线程
package com.company;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main{
public static void main(String[] args) {
/**
* Executors:是所有线程池的父类,它提供了execute方法,用来向线程池添加线程
*/
ExecutorService executorService = Executors.newSingleThreadExecutor();//创建单例线程池,就是只有一个线程
for (int i = 0;i < 10;i++){
executorService.execute(new ThreadTest());//添加线程
}
executorService.shutdown();//关闭线程池
}
}
4、ScheduledThreadPool,可调度预定线程池
package com.company;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Main{
public static void main(String[] args) {
/**
* Executors:是所有线程池的父类,它提供了execute方法,用来向线程池添加线程
*/
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);//创建最大容量为5的可调度线程池
/**
* schedule(Runnable接口,时间(比如5),单位(秒,分钟还是小时))
*/
scheduledExecutorService.schedule(new Runnable() {//添加一个匿名函数线程,延迟3s执行
@Override
public void run() {
System.out.println("延迟3秒");
}
},3, TimeUnit.SECONDS);
scheduledExecutorService.schedule(new ThreadTest(),3,TimeUnit.SECONDS);//添加ThreadTest线程类延迟3秒
/**
* scheduleAtFixedRate(Runnable接口,延迟时间,每次执行间隔时间,单位)
*/
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("延迟4秒执行,之后每1s执行一次"+"========="+(Thread.currentThread().getName()));
}
},4,1,TimeUnit.SECONDS);
// scheduledExecutorService.shutdown();//注意,想要每秒执行就不能关闭线程池
}
}
另外的单例模式,除了多个线程对象变成一个,其它没有变化,就不多介绍了