线程概念
线程的生命周期
新建状态:使用关键字new和Thread类或其子类建立一个线程对象后,该线程对象就处于新建状态。
就绪状态:线程独享调用start()方法之后,就会进入就绪状态。该线程处于就绪队列中,等待JVM里线程调度器的调度。
运行状态:就绪状态的线程获取CPU资源,执行run(),此时线程处于运行状态。
阻塞状态:如果一个线程执行了sleep,suspend,失去所占资源后,该线程就从运行状态进入阻塞状态。
1、等待阻塞:运行状态中的线程执行wait()方法
2、同步阻塞:线程获取synchronized同步锁失败(因为被其他线程占用)
3、其他阻塞:通过调用线程的sleep()或join()发出IO请求,线程就会进入阻塞状态。
死亡状态:线程完成任务或者其他终止命令
线程优先级
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
线程默认优先级是NORM_PRIORITY(5)
创建线程
1、实现Runnable接口
1)创建Runnable接口的实现类RunnableDemo
2)重写run方法
3)运行
①创建Thread实例,将实现类RunnableDemo作为构造方法的参数传入,调用Thread实例的start方法
②通过匿名内部类,创建Thread实例并运行
new Thread(new Runnable(){
@Override
public void run(){
for (int i=0;i<3;i++){
System.out.println("name: " + Thread.currentThread().getName() + ",id: " + Thread.currentThread().getId());
}
}
}, "线程04").start();
③Runable本身为函数式接口,可以使用lambad将其作为入参传入
2、继承Thread类
1)创建一个类,继承Thread类。继承类必须重写run()方法,该方法是新线程的入口点。
2)然后创建该类的实例。实例必须调用start()方法才能执行。该方法本质上也是实现了Runnable接口的一个实例。
3、通过Callable和Future创建线程
1)创建Callable接口的实现类,并实现call()方法,并作为线程执行体,有返回值
2)创建Callable实现类的实例,并用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值
3)使用FutureTask对象作为Thread对象的target创建并启动新线程
4)调用FutureTask对象的get()方法来获取子线程执行结束后的返回值
三种方式对比
1、Runnable、Callable接口的方式创建多线程时,线程类只是实现了Runnable接口或Callable接口,还可以继承其他类
2、继承Thread类的方式创建多线程时,可以使用this.获取当前线程
线程同步
1、使用synchronized关键字修饰的方法。Java每个对象都有一个内置锁,使用此关键字时,内置锁会保护整个方法,调用时必须获取内置锁,否则就处于阻塞状态。synchronized关键字也可以修饰静态方法,此时调用该静态方法,将会锁住整个类。
2、同步代码块。同步是一种高开销的操作,因此需要减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块即可。
3、volatile修饰变量。它的原理是每次要线程要访问volatile修饰的变量时都是从内存中读取,而不是存缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。
4、ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。ReenreantLock类的常用方法有:ReentrantLock() : 创建一个ReentrantLock实例lock() : 获得锁 unlock() : 释放锁
注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用
线程通信
1、共享内存
2、消息通知