进程与线程
进程:进程是程序的基本执行实体,比如你现在正在看的CSDN就是一个进程。
线程:线程是操作系统可以进行运算调度的的最小单位,被包含在进程之中,是进程中的实际运算单位。其实就是一个进程中相互独立,可以同时运行的功能。
多线程
见名知意,多线程就是让一个进程有多个线程。
作用:多线程可以让一个程序同时做多件事情,进而提高效率
多线程的实现方式
第一种:
1.自己定义一个类继承Thread类(线程)
——Thread类就表示线程类
2.重写run方法(方法里面写要执行的代码)
3.创建子类对象,并启动线程(对象.start)
——可以给线程命名:线程.setName();
——这样要用的时候,就可以用getName获取线程名字了
第二种:
1.自己定义一个类实现Runnable接口
2.重写run方法
3.创建自己类(就是实现类)的对象
4.创建Thread类的对象,把自己类的对象传入构建方法中的小括号中
5.用Thread类的对象.start(),也可以给Thread对象起名字来区分
——但是这样怎么才能获取到线程名字呢?
——要在run方法里先获取当前线程:
Thread.currentThread();//这是Thread类里面的静态方法,返回当前执行这个run方法的线程对象
——之后我们可以用这个对象再调用getName方法获取名字
第三种:
1.创建一个类实现Callable接口,这个接口有个泛型,表示返回值的类型
2.重写call方法,这个方法是有返回值的,返回值是Callable接口的泛型,表示多线程运行的结果,方法体写要执行的代码
3.在main类里面创建这个实现类的对象1,表示多线程要执行的任务
4.创建Future实现类FutureTask类的对象2,泛型也写返回值的结果,管理多线程运行的结果,把对象1放进这个类的构造方法中,进而管理运行的结果
5.创建Thread类对象,把对象2放进构造方法,启动start,这样就启动了线程
6.对象2.get();返回运行的结果
三种方式区别
——第一种和第二种无返回值,第三种有返回值
——第一种扩展性差,不能再继承别的类;第二种第三种扩展性强
Thread类常用方法
String getName()
返回当前线程的名字。如果我们没给线程设置名字,那也是有默认名字的(是在构造方法中自己默认的),格式为Thread—X(X是序号,从0开始)
void setName(String name)
设置线程名字,构造方法也可以设置名字。我们想设置名字,可以用这个方法,也可以用构造方法(Thread类的继承类要重写父类Thread的构造方法,这个构造方法的参数是String name)
static Thread currentThread()
获取当前线程的对象。当JVM虚拟机启动,会有一个叫做main的线程执行main方法
static void sleep(long time)
让线程休眠指定时间,单位为毫秒。哪条线程执行到这个方法,哪条就会停留对应的时间;方法的参数表示休眠的时间,单位是毫秒;当时间到了,线程醒来,继续执行下面代码;有异常,父类中方法不能抛的,子类也不可以抛出,Thread类中run方法没有抛出异常,那么作为重写方法,自然也不能抛出
线程的优先级
先了解调度:
——抢占式调度:多个线程在抢夺CPU执行权,体现了随机性
——非抢占式调度:多个线程有顺序的掌控CPU执行权,时间也差不多
在Java中,线程采取的是抢占式调度。
优先级越大,抢占到CPU执行权的概率越大,共有十档,最小是1,最大是10,不设置优先级的话,默认为5。
守护线程
当其他非守护线程结束的时候,守护线程也会陆续结束 。
出让/礼让线程
让线程把CPU执行权出让,但也有可能自己再次抢到
插入/插队线程
作用是可以等插入线程执行完毕,在执行之后的线程
假设在main方法中,有一个线程t,调用t.join();
//会先把t线程执行完,再执行main线程;
//表示把t这个线程插入到当前线程前面
线程安全
原因:线程在执行的时候,具有随机性。
当我们的线程在运行的时候,有些操作不是原子操作,会出现在运行的时候被其他线程打断,这样有时就会造成数据错误的问题,也就是线程安全的问题
那么怎么处理这些影响线程安全的代码呢?
1.同步代码块:
把操作共享数据的代码锁起来;
——格式:
synchronized(锁){
操作共享数据的代码;
}
——特点:
1.锁默认打开,有一个线程进去,锁自动关闭
2.里面的代码执行完毕,线程出来,锁自动打开
3.锁一定是唯一的,共同的;我们经常用类.class(一个类的字节码文件)当锁,因为一个包中一个类的字节码文件一定是唯一的。
——线程的一般写法:
1.循环
2.同步代码块
3.判断共享数据到没到末尾
4.如果没到末尾,执行逻辑
5.如果到了末尾,退出循环
2.同步方法:
把整个方法内所有代码都锁起来;
就是把synchronized关键字加到方法上
——格式:
修饰符 synchronized 返回值 方法名(){}
——特点:
1.锁住所有代码
2.锁对象不能自己指定,是Java规定好的
——非静态:this
——静态:当前类的字节码文件对象
不知道怎么写?可以先写同步代码块,然后选中ctrl+alt+m变成方法。
3.Lock锁:
用实例对象.lock()为上锁;用实例对象.unlock()为手动释放锁;
而且这个实例对象也要是唯一的。
小细节:
try{}catch(){}finally{}
//可以把实例对象.unlock()放到finally里面,因为这个异常处理体系中,finally无论如何都会被执行
生产消费模型/等待唤醒机制
生产者:生产数据
消费者:消费数据
核心思想:而且要有第三方来控制线程执行
下面的方法都是锁对象调用的:
自己实现
public class Desk {
//控制线程的执行
//1有,0没有
public static int food = 0;
//数据最多的个数
public static int count=10;
//锁对象
public static Object lock = new Object();
}
public class Cook extends Thread{
@Override
public void run() {
/*
1.循环
2.同步代码块
3.判断共享数据到没到末尾
4.如果没到末尾,执行逻辑
5.如果到了末尾,退出循环
*/
while (true){
synchronized(Desk.lock){
if (Desk.count==0){
break;
}else {
if(Desk.food==0){
System.out.println("厨师正在做包子");
Desk.food=1;
Desk.lock.notify();
}else {
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
}
}
public class People extends Thread{
public void run(){
/*
1.循环
2.同步代码块
3.判断共享数据到没到末尾
4.如果没到末尾,执行逻辑
5.如果到了末尾,退出循环
*/
while (true){
synchronized(Desk.lock){
if (Desk.count==0){
break;
}else {
if(Desk.food==0){
try {
Desk.lock.wait(); //让当前线程跟锁对象产生联系,
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
Desk.count--;
System.out.println("人正在吃包子,还能吃"+(Desk.count)+"个包子");
//唤醒厨师
Desk.lock.notify();
Desk.food=0;
}
}
}
}
}
}
测试类:
public class Test {
public static void main(String[] args) {
Cook cook=new Cook();
People people=new People();
cook.setName("厨师");
people.setName("顾客");
cook.start();
people.start();
}
}