线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。简单理解为:引用软件中相互独立,可以同时允许的功能
进程是程序的基本执行实体
并发:在同一时刻,有多个指令在单个CPU上交替执行
并行:在同一时刻,有多个指令在多个CPU上同时执行
多线程的实现方式
- 继承Thread类的方式进行实现
- 实现Runnable接口进行实现
- 利用Callable接口和Future接口的方式实现
方式一:
java
//将类声明为Thread类的子类,重写Thread类的run方法。
class MyThread() extends Thread{
public void run(){
//这里书写要执行的代码
System.out.print("hello,xun");
//
System.out.print(getName())//获取当前线程的线程名
}
}
MyThread mythread = new MyThread();
mythread.start();//启动线程
mythread.setName("线程一")//给线程起名字
方式二:
java
//声明实现Runnable接口的类,该类实现run方法。
class MyThread() implements Runnable{
public void run(){
//这里书写要执行的代码
System.out.print("hello,xun");
//获取当前线程对象
Thread t = Thread.currentThread();
t.getName();//获取当前线程的线程名
}
}
//表示多线程要执行的任务
MyThread mythread = new MyThread();
//创建线程对象
Thread t = new Thread(mythread);
//开启线程
t.start();
t.setName("线程一")//给线程起名字
方式三:
java
//利用Callable接口和Future接口的方式实现
//特点:可以获取到多线程运行的结果
class MyCallable implements Callable<Integer>{//该线程返回值的类型
public Integer call() throws Exception{
//求1---100之间的和
int sum=0;
for(int i = 1;i<=100;i++){
sum+=i;
}
return sum;
}
}
//创建MyCallable的对象(表示多线程要执行的任务)
MyCallable mc = new MyCallable();
//创建FutureTask的对象(作用是管理多线程的运行结果)
FutureTask<Integer> ft = new FutureTask<>(mc);
//创建线程的对象
Thread t = new Thread(ft);
t.start();
//获取多线程的运行结果
Integer res = ft.get();
多线程中常用的成员方法
- 如果我们没有给线程设置名字,线程也是有默认名字的,格式:Thread-X(X是序号,从0开始)
- 如果给线程设置名字,有两种方法,1.通过setName设置线程名 2.通过构造函数设置线程名
- 当JVM虚拟机启动后,会自动运行多条线程,其中有一条线程叫main线程,他的作用就是调用main方法,执行里面的代码
- 线程的优先级,最小的是1,最大的10,默认是5;优先级越大,抢到CPU的概率越大
- 当其他的非守护线程执行完毕之后,守护线程会陆续结束(可能不会将自己的程序运行完)
t.join()
//表示将线程t
插入到当前线程之前
线程的声明周期
线程安全问题
线程的执行有随机性,这样会导致线程存在安全问题
解决方法:
同步代码块:把操作共享的代码锁起来
java
synchronized(锁对象){
//操作共享的代码块
}
//特点一:锁默认打开,有一个线程进去了,锁自动关闭
//特点二:里面的代码全部执行完毕,线程出来,锁自动打开
//锁对象一定是唯一的,一般是锁对象的字节码文件对象
synchronized(thread.class);
同步方法:把synchronized关键字加到方法上
java
格式:修饰符 synchronized 返回值类型 方法名(方法参数){...}
//特点一:同步方法是锁住方法里面的所有方法
//特点二:锁对象不能自己指定
非静态方法:this
静态方法:当前类的字节码文件对象
为了更加清晰的表达如何释放锁,获得锁,JDK5之后提供了一个锁对象Lock
java
void lock()//获得锁
void unlock()//释放锁
//手动释放锁,手动获得锁
Lock是接口不能被直接实例化,使用它的实现类ReentrantLock来实例化
死锁
出现了锁的嵌套
产生因素
1、系统拥有的资源数量
2、资源分配策略
3、进程对资源的使用要求
4、并发进程的推荐顺序
必要条件
- 互斥条件:进程互斥使用资源
- 占有和等待条件:进程申请资源得不到时,不会释放已经占有的资源
- 不剥夺条件:一个进程不能抢占其他进程的资源
- 循环等待条件:存在一组进程循环等待资源
死锁的防止
破坏产生死锁的任意一个条件即可
生产者和消费者(等待唤醒机制)
生产者:生产数据
消费者:消费数据
常见方法:
java
public void wait();//当前线程等待,知道被其他线程唤醒
public void notify();//随机唤醒一个线程
public void notifyAll();//唤醒所有线程
线程的状态
java
新建状态(new) -> 创建线程对象
就绪状态(Runnable) -> start方法
阻塞状态(blocked) -> 无法获得锁对象
等待状态(waiting) -> wait方法
计时状态(timed_waiting) -> sleep方法
结束状态(terminated) -> 全部代码运行完毕
线程池
以前使用多线程的弊端:
用到线程时就创建
用完之后线程消失
这样会浪费操作系统的资源
线程池:类似于一个容器,里面有线程,需要用到线程时,直接取即可,用完还回去。
线程池有最大线程数量限制,可以自己设置
bash
1. 创建一个空池子
2. 提交任务时,池子会创建新的线程对象,任务执行完毕,归还线程对象到线程池,下次调用时,直接使用,不需要创建
3. 如果提交任务时,没有空闲线程,也无法创建新的线程,任务就会排队等待
线程池:类似于一个容器,里面有线程,需要用到线程时,直接取即可,用完还回去。
线程池有最大线程数量限制,可以自己设置
bash
1. 创建一个空池子
2. 提交任务时,池子会创建新的线程对象,任务执行完毕,归还线程对象到线程池,下次调用时,直接使用,不需要创建
3. 如果提交任务时,没有空闲线程,也无法创建新的线程,任务就会排队等待