一.基础概念
程序:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
进程:执行程序的一次执行过程,它是一个动态的概念,是系统资源分配的单位。通常一个进程中包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义,线程是CPU调度和执行的单位。进程就是运行起来的程序,操作系统会给进程分配空间。
线程:线程是进程创建的,是进程的一个实体,比如用迅雷下载文件,每开启一个下载任务,就是开启了一个进程。
单线程:同一个时刻,只允许执行一个线程。
多线程:同一个时刻,可以执行多个线程。
并发:计算机怎么做到并发有许多不同的形式,比如对于一个单核处理器,计算机可以通过分配时间片的方式,让一个任务执行一段时间,再切换到另一个任务再运行一段时间,不同的任务会交替往复的执行下去,这个过程也成为进程或线程的上下文切换,单核CPU实现的多任务就是并发。比如单核CPU的电脑同时打开qq和迅雷,看似同时执行,其实是cpu在来回切换。
并行:在不同的核心上真正并行的执行任务,而不用通过分配时间片的方式运行。多核cpu执行多个任务。一个cpu或者一核单独处理一个线程。
多线程不一定是并发也不一定是并行,需要看有多个线程的这个进程被分配到什么样的CPU,当分配到单核CPU就只能并发,而分配到核心个数大于线程个数的CPU就可以并行
并发是同时出发,并行是同时执行,并发是两个队交替去使用咖啡机,并行是两个队去使用两个咖啡机,并发是多个线程被多个CPU执行,并行是多个线程被一个CPU轮流执行,串行是一个任务没做完,下面的任务只能等着,也就是两个线程执行完一个才能去执行另一个
注意:很多多线程是模拟出来的,真正的多线程是指多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。
线程的创建和使用
方式1:继承Thread类
继承Thread类,重写run()方法,调用start()开启线程后,会由CPU调度运行run()方法里的代码
//1.继承Thread类 public class Demo extends Thread { //2.重写run()方法 public void run(){ for(int i = 0;i<20;i++) System.out.println("我是子线程"+i); } public static void main(String[] args) { //3.创建线程对象,调用start()方法开启线程,调用run()方法并不是开启线程,只是调用方法 //开启线程后,两个线程输出的内容可能是交替的,体现出多个线程同时执行,线程不一定立即执行,由CPU调度 Demo demothread = new Demo(); demothread.start(); //demothread.run(); for(int i = 0;i<20;i++) System.out.println("我是主线程"+i); } }
代码简述:通过继承Thread开启多线程,该类就可以当作线程使用,通过线程对象直接start()启动线程,不建议这种方法,为了避免OOP单继承局限性 ,因为类继承了Thread后就不能继承其他类了。
原理解析-线程问题
当运行程序后,就开启了一个进程。
执行main方法,就开启了一个main线程。
创建线程类,启动后,在main线程中又创建了一个子线程。
当主线程启动子线程后,主线程不会阻塞,会继续执行。
当所有的线程执行完后,也就是执行时间最长的线程结束后,进程也就结束了。
原理解析-run和start
run方法是一个普通的方法,如果调用run方法,不开启子线程,会先执行完run方法,再执行main方法,不能同时执行,会导致main方法阻塞。
start方法才能真正启动线程。
start方法调用start0方法后,该线程并不一定立马执行,只是将线程变成了可运行状态,具体什么时候执行,取决于CPU,由CPU统一调度。所以start0方法才是真正实现多线程的方法。
方式2:实现Runnable接口
如果一个类继承了其它类,就不能继承Thread类了,如果还想实现多线程,就需要借助Runnable接口
//1.创建一个类实现Runnable接口
public class Demo2 implements Runnable{
//2.重写run()方法
public void run(){
for(int i = 0;i<20;i++){
System.out.println("我是子线程"+i);
}
}
public static void main(String[] args) {
//3.创建实现类对象
Demo2 demoThread2 = new Demo2();
//4.创建线程对象,通过线程对象开启线程,也就是说还是离不开Thread类,因为start()方法只在
//Thread里,通过其他类重写的run()方法需要传进Thread对象里
Thread thread = new Thread(demoThread2);
thread.start();
//new Thread(demoThread2).start();,可以将两行上面两行代码简写
for(int i = 0;i<20;i++)
System.out.println("我是主线程"+i);
}
}
代码简述:实现Runnable接口实现多线程,通过传入Thread类目标对象+start()方法开启线程,避免了单继承的局限性,灵活方便,通过向Thread传入不同的实现类对象,即该线程可以执行不同的任务。也可以创建一个实现类对象,这一个实现类对象可以传入多个new的Thread类对象里,即一个对象被多个线程使用。
小结一下
继承Thread和实现Runnable接口本质都是得靠Thread方法才能实现多线程
介绍以下Thread类中的方法
int getPriority(); 返回线程优先级
boolean isAlive(); 确定线程是否在运行
static void sleep(ms); 使线程暂停执行
void setName(); 给线程设置名称
String getName(); 返回线程名称
void start(); 通过调用run()方法启动线程执行
static Thread currentThread(); 返回当前正在执行的线程对象的引用
public final void join(); 允许线程等待直到调用该方法的线程终止为止
void interrupt(); 用于使线程执行中断
static boolean interrupted(); 用于确定当前线程是否被其他线程中断
Alive()方法的使用情景
由于主线程与其它所以线程并行运行,不能确定哪个线程最后执行完,有时需要确保主线程最后一个执行结束,同时想知道哪个子线程是否执行结束(主线程即将结束时判断子线程是否结束),就需要借助Alive()和join()
public class Demo8 {
public static void main(String[] args) {
//创建对象即调用构造方法,所以直接开启了线程
NewThreadClass obj = new NewThreadClass();
//打破传统的印象,引用类型不一定都是对象,这里展示了引用类型当作属性时的调用情况
System.out.println(obj.t+"is alive ? :"+obj.t.isAlive());
try {
for(int i = 1;i<=5;i++) {
System.out.println("Main Thread loop:" + i);
Thread.sleep(200);
}
} catch (InterruptedException e) {
System.out.println("Main thread is interrupted");
}
System.out.println(obj.t.getName() + "is alive ?:"+obj.t.isAlive());
System.out.println("Main Thread is exiting");
}
}
class NewThreadClass implements Runnable{
Thread t;
NewThreadClass(){
t = new Thread(this,"ChildThread");//将Runnable接口实现类对象传入
System.out.println("Thread created:" + t.getName());
t.start();
}
@Override
public void run() {
try {
for(int i = 1;i<=5;i++){
System.out.println(t.getName() + "loop:"+i);
Thread.sleep(100);
}
}
catch (InterruptedException obj) {
System.out.println("Thread:"+t.getName()+"interrupted");
}
}
}
Thread created:ChildThread
ChildThreadloop:1
Thread[ChildThread,5,main]is alive ? :true
Main Thread loop:1
ChildThreadloop:2
Main Thread loop:2
ChildThreadloop:3
ChildThreadloop:4
Main Thread loop:3
ChildThreadloop:5
Main Thread loop:4
Main Thread loop:5
ChildThreadis alive ?:false
Main Thread is exiting
join()方法的使用情景
join方法让主线程等待,直到子线程调用完成才继续执行,也可以在join()方法中指定等待的最长时间,指定时间过后,主线程不再等待,将继续执行。
当子线程被中断,join方法将抛出InterruptedException异常,一个线程的执行可以被另一个线程的interrupt()方法中断,所以要确定当前线程是否被另一个线程中断。
public class Demo9 {
public static void main(String[] args) {
ChildThread obj = new ChildThread();
System.out.println(obj.t.getName()+"is alive?:"+obj.t.isAlive());
try{
System.out.println("Main thread waiting for child thread to finish");
obj.t.join();//暂时理解为让t的线程优先执行
for(int i = 1;i<=5;i++){
System.out.println("Main thread loop:"+i);
}
}catch (InterruptedException e){
System.out.println("Main thread is interrupted");
}
System.out.println(obj.t.getName()+"is alive?:"+obj.t.isAlive());
System.out.println("Main Thread is exiting");
}
}
class ChildThread implements Runnable{
Thread t;
ChildThread(){
t = new Thread(this,"ChildThread");
System.out.println("Thread created:"+t.getName());
t.start();
}
@Override
public void run() {
try{
for(int i = 1 ;i<=5;i++){
System.out.println(t.getName()+"loop:"+i);
Thread.sleep(500);
}
}catch (InterruptedException obj){
System.out.println("Thread:"+t.getName()+"interrupted");
}
}
}
Thread created:ChildThread
ChildThreadloop:1
ChildThreadis alive?:true
Main thread waiting for child thread to finish
ChildThreadloop:2
ChildThreadloop:3
ChildThreadloop:4
ChildThreadloop:5
Main thread loop:1
Main thread loop:2
Main thread loop:3
Main thread loop:4
Main thread loop:5
ChildThreadis alive?:false
Main Thread is exiting
方式3:实现Callable接口
1.实现Callable接口,需要返回值类型
2.重写call方法,需要抛出异常
3.创建目标对象
4.创建执行服务,ExecutorService ser = Executors.newFixedThreadPool(1);
5.提交执行:Future<Boolean> result1 = ser.submit(t1);
6.获取结果:boolean r1 = result1.get();
7.关闭服务:ser.shuutdownNow();
好处:可以有返回值,可以抛出异常
public class DemoThread5 implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
DemoThread5 d1 = new DemoThread5();
DemoThread5 d2 = new DemoThread5();
DemoThread5 d3 = new DemoThread5();
//创建执行服务,需要三个线程
ExecutorService ser = Executors.newFixedThreadPool(3);
//提交执行,跟run一样
Future<Boolean> r1 = ser.submit(d1);
Future<Boolean> r2 = ser.submit(d2);
Future<Boolean> r3 = ser.submit(d3);
//获取结果
boolean rs1 = r1.get();
boolean rs2 = r1.get();
boolean rs3 = r1.get();
//关闭服务
ser.shutdown();
}
}
代码简述:创建三个实现类对象,创建一个线程池,将三个实现类对象放入线程池,执行call方法再用get获取结果,最后关闭池子
线程的生命周期
新建状态-New
new了线程之后
就绪状态-Runnable
调用start()之后,此时Java虚拟机为当前线程创建了线程栈和程序计数器,new的时候没有
运行状态-Runnable
经过CPU调度后会处于一个运行状态,当运行的线程被抢走了线程就会变成Runnable中的就绪状态
阻塞状态
因为某些原因,暂时放弃CPU的使用权,即刻让出CPU的时间片,停止运行这个线程,直到线程再次进入到一个可运行状态,再次获取CPU的时间片,再去运行
等待阻塞
执行了wait()方法,JVM会把该线程放到等待队列中,直到其它线程调用Object类的notify()或notifyAll()方法后。
同步阻塞-Block
运行的线程在获取对象的同步锁(synchronized)时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池(Lock pool)中,这个阻塞状态只针对被synchronized修饰时
其他阻塞
运行的线程执行Thread.sleep()或join()方法,或者发出了IO请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时或join()等待线程终止或超时或或者IO处理完毕时,线程重新转入可运行状态。在主线程中调用子线程的join()方法,可让主线程等待子线程执行完毕,然后进入可运行状态
死亡状态
当run()方法中的语句全部执行完毕,线程就进入死亡状态,将线程对象设为null值也会让其进入死亡状态,调用stop()方法也会杀死线程,线程一旦死亡,就彻底终止,不能够重写启动。Thread类中的isAlive()方法用于判断线程是否或者。
六种状态
New
Runnable:对应着就绪状态和运行状态
Blocked:等待排他锁,要执行某个方法被锁在外面
Waiting:只能等待唤醒信号才能被唤醒
Timed Waiting:有时间参数,超过规定时间会被自动唤醒,或者提前接收到了等待信号
Terminated
解释一下阻塞状态
一般习惯而言,把Blocked(被阻塞),Watting(等待),Timed_Watting(计时等待)都称为阻塞状态
//展示线程的New,Runnable,Terminated状态,即使正在运行,也是Runnable状态,而不是Running
public class DemoThread implements Runnable{
@Override
public void run() {
for(int i = 0;i<1000;i++){
System.out.println(i);
}
}
public static void main(String[] args) {
Thread thread = new Thread(new DemoThread());
System.out.println(thread.getState());//打印出New状态
thread.start();
System.out.println(thread.getState());//打印出Runnable状态
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程能够休眠肯定已经开始运行了,打印出Runnable,即使是正常运行也是Runnable,而不是Running
System.out.println(thread.getState());
//当sleep时间长点,就能打印出TERMINATED
}
}
代码简述:展示状态NEW,RUNNABLE,TERMINATED
//展示Blocked,Waiting,TimedWaiting
public class DemoThread6 implements Runnable{
@Override
public void run() {
syn();
}
private synchronized void syn(){
try {
Thread.sleep(1000);
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
DemoThread6 d = new DemoThread6();
Thread t1 = new Thread(d);
t1.start();
Thread t2 = new Thread(d);
t2.start();
System.out.println("11");
System.out.println("11");
//此时t1线程执行sleep方法会进入TimedWaiting状态
//t2拿不到锁,是Blocked状态
System.out.println(t1.getState());
System.out.println(t2.getState());
try {
Thread.sleep(1300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印出Watting状态,因为执行了wait方法而且没有被唤醒
System.out.println(t1.getState());
}
}
代码简述:展示BLOCKED,WATTING,TIMED_WATTING 三个状态
线程的优先级
由于CPU一个时间只能执行一个线程,每个线程都有一个优先级,执行就绪的各个线程将按照优先级排队,有较高优先级的线程会被先调用
定义线程优先级
线程优先级被定义为1-10之间的整数,CPU以指定顺序执行多个线程的过程成为调度。
如果处理器遇到优先级更高的其它线程,那么当前线程执行完成后,将执行具有较高优先级的线程
如果较高优先级的线程停止或变为不可运行或正在等待IO操作,那么下一个较低优先级的线程开始执行
设置线程优先级
通过Thread类的setPriority(int newPriority)
MAX_PRIORITY是10,MIN_PRIORITY是0,NORM_PRIORITY是5
如果参数值不在1-10中,则抛出IllegalArgumentException异常。
线程优先级高不代表一直执行这个线程,当优先级高的线程在执行时,其它线程已经排好队,所以会按照队列执行。优先级高的线程会先执行完
public class Demo8 {
public static void main(String[] args) {
NewThreadClass obj1 = new NewThreadClass(Thread.NORM_PRIORITY - 2);
NewThreadClass obj2 = new NewThreadClass(Thread.NORM_PRIORITY + 2);
NewThreadClass obj3 = new NewThreadClass(Thread.NORM_PRIORITY + 3);
obj1.t.start();
obj2.t.start();
obj3.t.start();
try {
System.out.println("Main thread waiting foro child thread to finish");
obj1.t.join();
obj2.t.join();
obj3.t.join();
} catch (InterruptedException e) {
System.out.println("Main thread is interrupted");
}
System.out.println(obj1.t + "is alive?:" + obj1.t.isAlive());
System.out.println(obj2.t + "is alive?:" + obj2.t.isAlive());
System.out.println(obj3.t + "is alive?:" + obj3.t.isAlive());
System.out.println("Main Thread is exiting");
}
}
class NewThreadClass implements Runnable{
Thread t;
NewThreadClass(int p){
t = new Thread(this,"ChildThread");
t.setPriority(p);
System.out.println("Thread created:" + t);
}
@Override
public void run() {
try {
for(int i = 1;i<=5;i++){
System.out.println(t + "loop:"+i);
Thread.sleep(500);
}
}
catch (InterruptedException obj) {
System.out.println("Thread:"+t+"interrupted");
}
}
}
线程同步
线程的同步确保当两个或多个线程需要访问共享资源时,一次仅一个线程使用该资源
同步方法
当线程在同步方法内时,所有试图在同一时间调用方法的其它线程必须等待,在同步方法的执行过程中,它所属的对象被锁定,因此无法调用任何其它同步方法。当方法完成其执行时会自动释放监视程序,当线程正在休眠或等待时,它临时释放它所持有的锁。
同步语句
如果只是对方法内的一小段关键代码进行同步,也可以使用同步语句,而不是同步整个方法
线程通信
多用于生产者-消费者的场景,thead1将生产的数据放到容器中,thread2来使用
在线程通信中展示notify方法和wait()和notifyAll()使用
wait():通知当前线程离开其对监视程序的控制并等待,直到遇到其它线程调用notify()方法为止
notify():唤醒正等待对象的监视程序得到执行的单个线程。如果多个线程正在等待,则随机选择一个
notifyAll():唤醒正等待对象的监视程序的所有线程。
机制1
轮询,让消费者thread2重复检查容器中是否有数据,有就立即使用,如果没有就等待一段时间再次检查,直到容器中有了数据。
多线程在共享数据时,为了避免数据不一致,必须给数据加锁,某一个时间段只能有一个线程访问容器,那么轮询中的无效检查就对CPU时间产生了浪费。
机制2
消费者thread2去访问容器,当它发现数据没有准备好,就进入等待状态,并释放容器的锁,使得其它线程可以使用容器。
某个时刻生产者thread1访问容器并放入数据,在它释放锁之前,通知正在等待状态的消费者thread2去使用
对比
机制2中thread2在条件不满足时暂时等待,通知其它线程先使用,thread1使用后通知正在等待thread2,线程之间通过交流,减少了对共享容器的无效占用时间。
机制1中thread2一直在访问容器,其它线程无法使用容器,thread1优先级高,当thread1要使用容器时,thread2会让出。