Java多线程机制
线程概念
什么是进程?
进程是一个具有一定独立功能的程序,是系统进行资源分配和调度运行的基本单位;
什么是线程?
线程是程序执行流的最小单元;
什么的多线程并发执行?
多线程分时地占用CPU的时间片的情况称为线程的并发执行(个人理解:现代计算机的处理器性能可以达到每秒执行上亿条指令,所以cpu并不是一个线程执行到底,在当前线程阻塞、等待的情况下可以切换到其他线程继续执行,我们可以理解成CPU一秒的工作时间被分割成100个时间片,在每个时间片里选择等待处理的线程进行处理,即多线程的分时占用时间片)
线程的作用
多线程是程序并发执行的基础,同时使得java面向对象编程凸显优势
线程与进程的区别
- 线程是进程内的一个执行单元,进程至少有一个线程,多线程共享进程的地址空间,而进程有自己独立的地址空间
- 操作系统以进程为单位分配资源,同一个进程内的线程共享进程的资源;
- 线程是处理器调度的基本单位,但进程不是,进程是系统调度的基本单位;
线程创建的方法
java中常用的创建线程的方法有三种,分别是集成Thread类,实现Runnable接口,可以用Callable接口和FutureTask类实现
此处有很多免费课程的视频有讲,但我个人就听了好几个版本了,最后还是翻了下书确认的,大家学习的时候最好注意下资源的正确性。
1.扩展Thread类
创建一个继承于java.lang.Thread类的线程类,并重写该类的run方法,是最简单直接的创建线程的方法。run方法是一个线程的核心,一个运行的线程实际上是该线程的run()方法呗调用。
/*
* 线程类
*/
public class ExtThread extends Thread{
public ExtThread(String name){
super(name);
}
@Override
public void run(){
//do something
for(int i=0;i<50;i++){
System.out.println(this.getName()+":"+i);
}
}
}
eg:线程调用类 eg:调用测试类
public class ThreadTest {
public static void main(String[] args){
//1.利用thread创建线程测试
Thread t1 = new ExtThread("A");
Thread t2 = new ExtThread("B");
t1.start();
t2.start();
}
}
2.实现Runnable接口
由于java不支持多继承,在已有一个父类的情况下不能再对Thread扩展,这种情况下可以通过实现接口Runnable来创建线程类。 eg:线程类
public class ImpRunnable implements Runnable{
private String name;
public ImpRunnable(String name){
this.name = name;
}
@Override
public void run() {
for(int i=0;i<50;i++){
if(i%5 == 0){
try {
Thread.sleep(1000);
System.err.println(this.name+"线程休眠");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//Thread.yield();
System.out.println(this.name+":"+i);
}
}
}
eg:调用测试类
public class ThreadTest {
public static void main(String[] args){
//2.利用Runnable创建线程测试
ImpRunnable ds1 = new ImpRunnable("A");
ImpRunnable ds2 = new ImpRunnable("B");
Thread t1 = new Thread(ds1);
Thread t2 = new Thread(ds2);
t1.start();
t2.start();
}
}
- 备注:要启动线程必须调用线程Thread中的方法start()。所以,即使用Runnable接口实现线程,也必须有Thread类的对象,并且该对象的run方法由实现Runnable接口的类的对象提供。
3.用Callable和FutureTask定义线程
第三种方法通常使用于需要返回值的情况 步骤详解
- 创建Callable接口的实现类,并实现call方法,该call方法为线程执行体。
- 创建Callable实现类的实例,使用FutureTask类来包装Callable对象。
- 使用FutureTask对象作为Thread对象的target创建并启动新线程。
- 调用FutureTask对象的get方法来获得子线程执行结束后的返回值。
eg:线程类
public class ExtFutureTask implements Callable<Integer> {
private String name;
public ExtFutureTask(String name){
this.name = name;
}
@Override
public Integer call() throws Exception {
int i = 1;
for(;i<=50;i++){
if(i%5==0){
System.err.println(this.name+"重新选择线程");
Thread.yield();
}
System.out.println(this.name+"(Priority:"+Thread.currentThread().getPriority()+")" + ":执行" + i);
}
return i;
}
}
eg:调用测试类
public class ThreadTest {
public static void main(String[] args){
//3.利用Callablze创建线程测试
ExtFutureTask task1 = new ExtFutureTask("A");
ExtFutureTask task2 = new ExtFutureTask("B");
FutureTask<Integer> f1 = new FutureTask<>(task1);
FutureTask<Integer> f2 = new FutureTask<>(task2);
Thread t1 = new Thread(f1);
Thread t2 = new Thread(f2);
t1.start();
t2.start();
try {
System.out.println("Task1 result:"+f1.get());
System.out.println("Task2 result:"+f2.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
线程的状态及转换
线程的状态
线程在生命周期中可能经历5中状态,新建(new)、就绪(Ready or Runnable)、运行(Runnging)、阻塞(Blocked)、死亡(Dead).
- 新建状态:新创建了一个线程对象,但该对象不能占用CPU,不能运行。
- 就绪状态:线程对象创建后调用了start()方法后,该线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
- 运行状态:线程调度器使某个处于就绪状态的线程获得CPU使用权,执行线程体代码的run方法。
- 阻塞状态:阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。知道满足某个触发条件又使线程进入就绪状态,才有机会转到运行状态。
- 死亡状态:线程执行完了或者因异常突出了run方法,该线程结束声明周期。
线程状态转换
阻塞的四种情况:
- 等待阻塞:运行的线程执行wait方法,JVM会把该线程放入等待池中。需要等待到notify方法通知才解除阻塞状态。
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中,等待当前持锁线程执行完解锁后才结束阻塞阻塞状态。
- IO阻塞:JDK1.4开始,IO分为非阻塞和阻塞两种模式。阻塞IO模式下,线程若从网络流中读取不到制定大小的数据量时,IO就在哪里等待着,只有等到数据IO完成才解除阻塞。
- 其他阻塞:sleep,join等方法造成的阻塞,需要对应条件执行完成后才能解除阻塞。
线程调度
概念
Java线程调度是抢占式调度策略,在可运行池中的就绪状态具有不同优先级,优先级高的线程将优先得到比优先级低的线程更多的CPU执行时间,相同则随机选择线程,一个时刻(单核)CPU只有一个线程在运行。抢占的意义在于获得更多的时间而不是独占。
设置线程优先级
通过setPriority(int)方法设置线程优先级,参数可取1-10,数值越大代表优先级越高。
线程常用方法
currentThread
返回对当前正在执行的线程对象的引用
/**
* Returns a reference to the currently executing thread object.
*
* @return the currently executing thread.
*/
public static native Thread currentThread();
getName
返回该线程的名称
getPriority
返回该线程的优先级
interrupt
使一个阻塞状态的线程中断执行
isAlive
测试线程是否处于活动状态
join
等待该线程终止
setPriority
设置线程优先级
setName
设置线程名称
static void sleep(long millis)
在指定的毫秒数内让当前正在执行的线程休眠
start
使该线程可运行
static void yield
暂停当前正在执行的线程对象,并执行其他线程
线程同步与锁
synchronized关键字
用synchronized修饰方法或代码块即可实现同步锁。
synchronized关键字修饰的方法是不能继承的,父类方法的synchronized方法在子类中并不自动成为synchronized方法,而是变成了非同步方法,子类可以显示地指定他的某个方法为synchronized方法
volatile关键字
用于多线程同步变量。volatile告诉JVM,它所修饰的变量不使用拷贝,直接访问主内存中的变量。volatile一般情况下不能代替synchronized,因为volatile不能保证操作的原子性,只能保证不同线程之间操作的变量是在同一块内存中。
Lock
Java5提供了同步代码块的另一种机制,基于Lock接口及其实现类。
和synchronized关键字相比,锁机制的好处包括以下几点:
- 提供了更多的功能,例如tryLock;
- 允许分离读和写操作,允许多个读线程和单一写线程;
- 具有更好的性能;