本章概述:
第一部分:线程与进程讲解
第二部分:线程的实现
创建线程的方法
线程的运行规则
第三部分:线程安全问题
线程同步
线程死锁
Lock锁
线程间通讯
第一部分:多线程
1、线程与进程
进程:是一个正在执行的程序,每一个进程都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元
线程:就是进程中的一个独立的控制单元,线程在控制着进程的执行,每个进程至少有一个线程
2、JVM启动时有一个进程java.exe,该进程中至少有一个线程在负责Java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称之为主线程
扩展:其实Java进程中不止有一个线程,还有负责垃圾回收机制的线程
第二部分:线程的实现:
1、java中如何创建线程:
1.1有两种方式:
1.1.1、继承Thread类:
子类继承Thread类,复写Thread类中的run方法,创建子类对象,再调用父类Thread类的start方法
1.1.2、实现Runnable接口:
子类实现Runnable接口,复写run方法,再创建Thread类的对象,再将Runnable的子类对象作为参数传给Thread类的构造函数,调用Thread对象的start方法
1.2、两种方式的区别:
继承Thread:线程run方法存放在Thread的子类中,创建子类对象就可以通过相应方法开启一个线程
实现Runnable接口:子类要作为参数传递到Thread对象的构造方法中开启线程,实现Runnable接口避免了单继承的局限性(建议使用实现方式),线程方法存放在接口run方法中
代码演示创建线程:
/* 需求:演示线程的两种创建方法 */ //封装了主函数的类 class ThreadDemo { //主函数 public static void main(String[] args) { //只使用一次,直接new Threads匿名对象,调用start方法开启新线程,执行run方法中封装的语句 new Threads().start(); //new Thread匿名对象,再将new 的Threades匿名对象作为参数传给Thread对象的构造函数,调用start方法开启新线程 new Thread(new Threades()).start(); } } //用继承Thread类的方法创建一个线程类 class Threads extends Thread { //复写Thread中的run方法(注意Thread类中中run方法访问权限是public,所以要复写访问权限必须是public //run方法内存放的是要在新线程中执行的语句 public void run() { //定义一个for循环让线程打印线程名字100次 for (int i = 0;i<100 ;i++ ) { //打印内容中的Thread.currentThread是获取当前线程方法,getName是获取当前线程名字的方法,返回名字字符串并打印 System.out.println(Thread.currentThread().getName()); } } } //用实现Runnable接口的方法创建一个线程类 //封装的内容跟Threades类的一样 class Threades implements Runnable { public void run() { for (int i = 0;i<100 ;i++ ) { System.out.println(Thread.currentThread().getName()); } } }
2、线程的运行规则:
2.1、CPU的每个核心每次只能执行单个线程,在进行多线程执行时,多个线程一起争夺CPU的执行权,CPU在多个线程间进行随机快速切换
2.2、run方法封装着要在线程中执行的代码,start方法封装开启线程的方法,在开启的线程中运行run方法里面存放的代码
3、线程状态
3.1、线程被创建
3.2、执行状态
线程正在被CPU执行
3.3冻结状态
冻结状态有两种情况:
睡眠状态和等待状态,冻结状态时,线程放弃CPU执行权,当该状态结束后,线程就会进入临时状态
sleep:睡眠状态,由程序控制该线程固定的一段时间放弃执行
wait:等待状态,程序控制该线程进入等待状态,需要被唤醒才能结束冻结状态
notify:唤醒某个线程,使某个睡眠的线程重新参与抢夺CPU执行权
notifyAll:唤醒所有线程
3.4、临时状态,等待执行,线程正在抢夺CPU执行权,一旦取得CPU执行权就进入执行状态
4、操作线程的方法
start 使该线程开始执行
Thread.currentThread 获取当前线程对象
getName 获取线程名称
setName 设置线程名称
stop(已过时)停止线程的方法是run结束,控制住线程中的循环run方法才能结束
join 申请加入执行队列,当A线程执行到B线程的join方法时,A就会等待,直到B线程结束
geiPriority 获取线程的优先级
setPriority 设置线程优先级
yield 暂停当前线程,并执行其他线程
第三部分:线程安全问题
1、发生原因:
当一个线程中存在多条语句操作多个线程的共享数据时,该线程对多条语句只执行了一部分,另一个线程参与进来执行,导致共享数据错误
2、处理方法:
2.1、对多条操作共享数据的语句,只能让一个线程将操作共享数据的多条语句都执行完,再让其它线程参与执行,在对操作共享数据的多条语句执行的过程中,其他线程不可以参与执行
2.2、Java对多线程的安全问题提供了专业的解决方案,同步
2.2.1同步代码块
1、格式:synchronized(对象){}
2、对象如同锁,持有锁的线程可以在同步中执行,没有获得该锁的线程即使获取CPU的执行权,也无法进入同步代码块中执行
2.2.2同步函数
1、格式:修饰符 synchronized 返回值 函数名(){}
2、同步函数中的锁使用的是this
3、静态同步函数中的锁使用的是该方法所属类的字节码文件对象
2.2.3 同步的前提:
1、必须要有两个或以上线程
2、必须是多个线程都使用同一个锁
3、必须保证同步中只有一个线程在运行
2.2.4同步的利弊
好处:解决了多线程的安全问题
弊端:多线程需要判断锁,比较消耗资源
代码演示:
/* 需求:演示线程的同步 */ //封装了主函数的类 class ThreadDemo { //主函数 public static void main(String[] args) { //创建一个Runnable子类对象,用于存储线程运行的代码和激活线程对象 Threads t = new Threads(); //创建两个线程对象,将同一个Runnable的子类对象传给线程的构造函数, //使两线程共享同一资源,调用start方法开启线程 new Thread(t).start(); new Thread(t).start(); } } //用实现Runnable接口的方法创建类 class Threads implements Runnable { //定义一个共享资源num int num = 100; //定义一个Object对象,作为同步代码块的监视器(锁) Object obj = new Object(); //复写run方法 public void run() { while(num>0) { //建立同步代码块,使代码块中同时只有一个线程在执行,保证数据的安全性 synchronized(obj) { //在同步中再判断一下num是否大于0 if (num>0) { //打印当前线程的名字和共享数据 System.out.println(Thread.currentThread().getName()+"::"+num--); } } //让线程睡10毫秒,使多线程的执行效果更明显,sleep方法抛出异常,要处理 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } //分隔同步代码块和同步函数执行的打印 System.out.println("-------------同步函数--------------"); //在循环中打印线程名字和共享资源100次 while(num<=100) { //调用同步函数show show(); //让线程睡10毫秒,使多线程的执行效果更明显 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } //同步函数的锁是this,静态同步函数的锁是该类的字节码文件对象 public synchronized void show () { //在同步中再判断一下num是否<=100 if (num<=100) { //打印当前线程的名字和共享数据 System.out.println(Thread.currentThread().getName()+"::"+num++); } } }
3、线程死锁
3.1、同步中嵌套同步,而锁却不同当两个以上的线程执行时,其中一个线程获取了一个锁,另一个线程又获取了另一个锁,导致两个线程都在等待对方释放锁资源以获得执行下一个同步所需要的锁,从而造成线程都处于等待状态,就是死锁
3.2、死锁没有专业的解决方案,只能尽量避免编写死锁程序
死锁示例:
/* 需求:演示线程的死锁 */ //封装了主函数的类 class ThreadDemo { //主函数 public static void main(String[] args) { //创建Runnable子类对象,在构造函数中传入一个判断标记 Threads t = new Threads(true); //创建两个线程对象,在构造函数传入同一个Runnable子类对象 new Thread(t).start(); new Thread(t).start(); } } //用实现Runnable接口的方法创建类 class Threads implements Runnable { //定义两个同步代码块的锁对象 Object obj1 = new Object(); Object obj2 = new Object(); //定义一个判断标记 private boolean flag; //构造函数接收判断标记 Threads(boolean flag) { this.flag = flag; } public void run() { if(flag) { //有线程进入后,将判断标记更改 flag = !flag; //让线程一直执行 while(true) { //同步中嵌套同步,使用了不同的锁 synchronized(obj1) { //打印语句 System.out.println(Thread.currentThread().getName()+"...if and obj1... "); synchronized(obj2) { System.out.println(Thread.currentThread().getName()+"....if and obj2...."); } } } } else { flag = !flag; while(true) { //同步中嵌套同步,使用了上面相同的两个锁,且锁的位置相反,造成两个线程分别拿了其中的一个锁等待对方释放另一个锁,造成死锁 synchronized(obj2) { System.out.println(Thread.currentThread().getName()+"---else and obj2---"); synchronized(obj1) { System.out.println(Thread.currentThread().getName()+"----else and obj1----"); } } } } } }
4、Lock类
4.1、JDK1.5后,java提供了一个Lock类,用于显示的调用锁机制
4.2、Lock中的方法
lock 添加锁
unlock 释放锁
newCondition 创建一个Condition体系的对象
Condition接口
方法
await 线程等待
signal 唤醒线程
5、线程间通讯
5.1、就是多个线程在操作同一个资源,但是操作的动作不同
等待唤醒机制
方法
wait
notify
notifyAll
这些方法都要对持有监视器(锁)的线程中执行,监视器可以是任意对象,所以定义在Object类中
生产者消费者例子
示例代码:
/* 需求:演示Lock类和线程间通讯 */ import java.util.concurrent.locks.*; //封装了主函数的类 class ThreadDemo { //主函数 public static void main(String[] args) { //创建Runnable子类对象,在构造函数中传入 Data data = new Data(); //创建两个线程对象,在构造函数传入同一个Runnable子类对象 new Thread(new TrueData(data)).start(); new Thread(new FalseData(data)).start(); } } //用实现Runnable接口的方法创建类 class TrueData implements Runnable { //封装一个Data引用 private Data data; //将构造函数接收到的Data对象赋值给类中的引用 TrueData(Data data) { this.data = data; } //run方法调用data对象的on方法操作data中的数据 public void run() { data.on(); } } //用实现Runnable接口的方法创建类 //实现同TrueData相反的操作 class FalseData implements Runnable { private Data data; FalseData(Data data) { this.data = data; } public void run() { data.off(); } } //定义一个数据类 class Data { //封装了两个数据,int型的num和boolean类型的logic private int num = 1; private boolean logic = true; //定义两个同步锁对象 private Lock lock = new ReentrantLock(); private Condition cona = lock.newCondition(); private Condition conb = lock.newCondition(); //on方法将数据改成1和true public void on() { while(true) { //给代码段上同步锁 lock.lock(); //判断数据是否需要修改,否则本方等待 try { if (logic) { cona.await(); } //修改数据 num = 1; logic = true; //打印语句 System.out.println("Logic on..."+"num:"+num+" logic:"+logic); //唤醒对方线程 conb.signal(); } //await方法抛出了异常,在这里捕获处理 catch (InterruptedException e) { e.printStackTrace(); } //一定要释放锁 finally { //释放锁 lock.unlock(); } } } //off方法和on方法操作类似,只是等待和唤醒的线程方不同,修改数据的操作不同 public void off() { while(true) { try { lock.lock(); if(!logic) //本方(conb)等待 conb.await(); num = 0; logic=false; //打印语句 System.out.println("Logic off......"+"num:"+num+" logic:"+logic); //唤醒对方 cona.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } }
6、守护线程
setDaemon
将该线程标记为守护线程或者用户线程(当所有前台线程结束,后台线程自动结束)(该方法必须在线程启动前调用)
7、线程的优先级
优先级:即CPU在线程间切换的时候决定线程优先执行的参数,优先级只有1~10的数值
设置优先级的字段:
MAX_PRIORITY最大优先级,即优先级数值为10
MIN_PRIORITY最小优先级,即优先级数值为1
NORM_PRIORITY 线程优先级为5
应用
用匿名对象new线程
例子:单例模式中懒汉式的同步问题
本章总结:
1、java支持原生的多线程技术,通过多线程技术,程序可以并发的访问程序中的某些资源,提高了程序的使用效率和功能扩展性
2、线程的实现方法可以是创建Thread类的子类对象,复写run方法,用start方法开启新线程运行run方法中的代码,或者子类实现Runnable接口,复写run方法,再将子类作为参数传递给Thread类的构造函数,开启线程用Thread中的Start方法开启线程;
3、多线程会产生资源不同步的安全问题,java提供了同步代码块和同步函数来解决该类问题,或者使用Lock类中的方法实现同步锁功能
4、线程同步又会产生另一个问题,线程死锁,该问题没有专业的解决方法,只能通过人为控制来避免该类问题的产生。