目录
示例2:使用继承Thread类的方式创建线程,在线程中输出1~20的整数
示例3:使用Runnable接口的方式擦行间线程,在线程中输出1~20的整数
(1)同步方法:使用synchronized修饰的方法控制对类成员变量的访问
(2)同步代码块:使用synchronized关键字修饰的代码块
示例7-3: 使用synchronized关键字修饰的代码块
01、认识线程
进程:应用程序的执行实例,有独立的内存空间和系统资源
线程:CPU调度和分派的基本单位、进程中执行运算的最小单位,可完成一个独立的顺序控制流程
多线程:如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程”,多线程交替占用CPU资源,而非真正的并发执行。
多线程的好处:
(1)充分利用CPU的资源
(2)简化编程模型
(3)带来良好的用户体验
02、编写线程类
每个程序至少自动拥有一个线程,称为主线程。当程序加载到内存时启动主线程。Java程序中的public static void main()方法是主线程的入口,会先执行这个方法。
定义一个线程类 通常有两种方法,分别是:
(1)继承java.lang.Thread类
(2)实现java.lang.Runnable接口
1、使用Thread类创建线程
方法 | 说明 |
currentThread() | 获取当前线程对象 |
getName() | 获取当前线程的名称 |
setName() | 设置线程的名称 |
getPriority() | 获取当前线程的优先级 |
setPriority() | 设置线程优先级 |
start() | 启动线程 |
void run() | 执行任务操作的方法 |
字段摘要 | 说明 |
MAX_PRIORITY | 线程优先级最高值 |
MIN_PRIORITY | 线程优先级最低值 |
NORM_PRIORITY | 线程优先级默认值 |
创建线程时继承Thread类并重写Thread类的run()方法。Thread类的run()方法是线程要执行操作任务的方法,所以线程要执行的操作代码都需要写在run()方法中,并通过调用start()方法来启动线程。
示例1:Thread类常用方法(部分)
package cn.bdqn.demo01;
public class ThreadDemo01 {
public static void main(String[] args) {
//main()方法是程序的主入口,是一个线程
//currentThread():获取当前线程对象
Thread thread = Thread.currentThread();
//getName():获取当前线程的名称
String name = thread.getName();
System.out.println(name);
//setName():设置线程的名称
thread.setName("恒哥");
System.out.println("设置后的线程名称为:"+thread.getName());
//getPriority():获取当前线程的优先级
int priority = thread.getPriority();
System.out.println("当前线程的优先级:"+priority);
//setPriority():设置线程的优先级
thread.setPriority(8);
System.out.println("设置后的线程优先级:"+thread.getPriority());
System.out.println("线程优先级最高值"+Thread.MAX_PRIORITY);
System.out.println("线程优先级最低值"+Thread.MIN_PRIORITY);
System.out.println("线程优先级默认值"+Thread.NORM_PRIORITY);
}
}
输出结果:
示例2:使用继承Thread类的方式创建线程,在线程中输出1~20的整数
解题思路:
(1)定义MyThread类继承Thread类,重写run()方法,在run()方法中实现数据输出
(2)创建线程对象
(3)调用start()方法启动线程
MyThread类
package cn.bdqn.demo02;
//一个类要定义成线程类,可以通过继承Thread类来实现,然后重写Thread类里的run()方法
public class MyThread extends Thread {
public MyThread(){
}
public MyThread(String name){
super(name);
}
@Override
public void run() {
//在重写的run()方法中编写你要执行的代码:使用循环输出1-20
for (int i = 1; i <= 20; i++) {
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
Test测试类
package cn.bdqn.demo02;
public class Text {
public static void main(String[] args) {
//创建线程类对象
MyThread mt1 = new MyThread("饲养员");
MyThread mt2 = new MyThread("小猪");
// mt1.run():只有主线程一条执行路径
// mt1.start():多条执行路径,主线程和子线程并行交替执行
//start()方法是启动线程的方法
mt1.start();
mt2.start();
//注:当同时启动两个线程以后,会出现两个线程交替占用CPU执行代码的结果
}
}
输出结果:
2、使用Runnable接口创建线程
(1)定义MyThread类实现Runnable接口
(2)实现run()方法,编写线程执行体
(3)创建线程对象,调用start()方法启动线程
注:start()方法是thread类中的方法,而我们需要通过start()方法来调用run()方法,不能直接调用run()方法,但是Runnable接口中只有一个抽象方法run()方法,那么实现Runnable接口的类不能调用start()方法。
解决方法:将实现Runnable接口的类对象作为参数传递给Thread构造方法,然后通过Thread类对象调用start()方法。
MyThread mt = new MyThread();
Thread thread3 = new Thread(mt, "恒哥")
示例3:使用Runnable接口的方式擦行间线程,在线程中输出1~20的整数
解题思路:
(1) 定义一个MyThread类实现java.lang.Runnable接口,并实现Runnable接口的run()方法,在run()方法中输出数据
(2)创建线程对象。
(3)调用start()方法启动线程。
MyThread类
package cn.bdqn.demo03;
public class MyThread implements Runnable {
@Override
public void run() {
//在重写的run()方法中编写你要执行的代码,使用循环输出1-20
for (int i = 1; i <=20; i++) {
//使用currentThread()方法获取当前的线程对象,并使用getName()方法获取当前线程的名称
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
Test测试类
package cn.bdqn.demo03;
public class Test {
public static void main(String[] args) {
//创建线程类对象
MyThread mt1 = new MyThread();
//创建Thread类对象
Thread thread1 = new Thread(mt1,"千里眼");
Thread thread2 = new Thread(mt1,"顺风耳");
/*
* start()方法是Thread类中的方法,而我们需要通过start()方法来调用run()方法,不能直接调用run()方法,
* 但是Runnable接口中只有一个抽象方法run()方法,那么实现Runnable接口的类不能调用start()方法
*
* 解决方法:
* 将实现Runnable接口的类对象作为参数传递给Thread构造方法,然后通过Thread类对象调用start()方法
*
*
*
* */
//start():启动线程
thread1.start();
thread2.start();
}
}
3、以上两种创建线程的方式比较
继承Thread类:
(1)编写简单,可直接操作线程
(2)适用于单继承
实现Runnable接口
(1)避免单继承局限性
(2)便于共享资源
03、线程的状态
(1)新生状态(NEW Thread)
(2)就绪状态
(3)运行状态(Runnable)
(4)死亡状态(Daed)
(5)阻塞状态(Blocked)
04、线程调度
方法 | 说明 |
void setPriority(int newPriority) | 更改线程的优先级 |
static void sleep(long millis) | (线程休眠)在指定的毫秒数内让当前正在执行的线程休眠 |
void join | (强制执行)等待该线程终止 |
static void yield | 暂停当前正在执行的线程对象,并执行其他线程 |
void interrupt() | 中断线程 |
boolean isAlive() | 测试线程是否处于活动状态 |
1、线程优先级
(1)线程优先级有1~10表示,1最低,10最高,默认优先级为5
(2)优先级高的线程获得CPU资源的概率较大
System.out.println("线程优先级最高值:"+Thread.MAX_PRIORITY);//10
System.out.println("线程优先级最低值:"+Thread.MIN_PRIORITY);//1
System.out.println("线程优先级默认值:"+Thread.NORM_PRIORITY);//5
问题:什么是线程优先级?它在线程调度中的作用是什么?
答:
①线程优先级是反映线程获得CPU资源概率的问题。
②线程优先级在线程调度中的作用:提高获取CPU资源在线程调度中的概率
2、线程休眠( sleeep()方法 )
(1)让线程暂时睡眠指定时长,线程进入阻塞状态
(2)睡眠时间过后线程会再进入可运行状态
sleep()方法的语法格式:public static void sleep(millis)
示例4:sleep()方法
下述代码可以看出:在执行主线程以后首先输出了“main线程开始运行...”,然后主线程等待5,秒后继续执行。
关键代码:Wait类
package cn.bdqn.demo01;
public class Wait {
/*
* 线程休眠
* (1)让线程暂时睡眠指定时长,线程进入阻塞状态
* (2)睡眠时间过后线程会再进入可运行状态
*
* */
public static void bySec(long s){ //s表示你想要休眠的秒数
for(int i=0;i<s;i++){
System.out.println(i+1+"秒");
try {
Thread.sleep(1000);//线程休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
测试类Test类
package cn.bdqn.demo01;
public class Test {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"线程开始运行...");
Wait.bySec(5);
System.out.println(Thread.currentThread().getName()+"程序结束运行...");
}
}
输出结果
3、线程的强制运行( join()方法 )
(1)使当前线程暂停执行,等待其他线程结束后再继续执行本线程
join()方法有3种重载形式:
(1)public final void join()
(2)public final void join(long millis)
(3)public final void join(long millis,int nanos)
①millis:以毫秒为单位的等待时长
②nanos:要等待的附加纳秒时长
③需处理InterruptedException异常
示例5: join方法
解析:主线程main运行5次后,使用join()方法使主线程main进入阻塞状态,开始执行子线程MyThread,直到子线程MyThread执行结束后再执行主线程main
关键代码:MyThread线程类实现Runnable接口
package cn.bdqn.demo02;
public class MyThread implements Runnable {
@Override
public void run() {
for(int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"运行第"+(i+1)+"次");
}
}
}
测试类:Test类
package cn.bdqn.demo02;
public class Test {
public static void main(String[] args) {
/*
* join():使当前线程暂停执行,等待其他线程结束后再继续执行本线程
*
*
* */
MyThread mt = new MyThread();
Thread t = new Thread(mt, "尼古拉斯");
t.start();
for(int i=0;i<10;i++){
/*主线程main运行5次后,使用join()方法使主线程main进入阻塞状态,开始执行子线程MyThread,
* 直到子线程MyThread执行结束后再执行主线程main
* */
if(i==5){
try {
t.join();//阻塞主线程,子线程强制执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"运行第"+(i+1)+"次");
}
}
}
输出结果:
4、线程的礼让( yield()方法 )
(1)暂停当前线程,允许其他具有相同优先级的线程获得运行机会
(2)该线程处于就绪状态,不转为阻塞状态
注:只是提供一种可能,但是不能保证一定会实现礼让。
yield()方法的语法格式:public static void yield()
示例6: yield()方法
关键代码:MyThread线程类实现Runnable接口
package cn.bdqn.demo03;
public class MyThread implements Runnable {
/*
* 线程的礼让:
* (1)暂停当前线程,允许其他具有相同或更高优先级的线程获得运行机会,若无相同或更高优先级的线程,则该线程继续执行
* (2)该线程处于就绪状态,不转为阻塞状态
*/
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + "正在运行" + i);
if (i == 3) {
System.out.print("线程礼让:");
Thread.yield();// 线程礼让,只是提供一种让其他线程获得CPU资源的机会,自己也会再次去抢占CPU资源
}
}
}
}
测试类:Test类
package cn.bdqn.demo03;
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread t1 = new Thread(mt, "线程A");
Thread t2 = new Thread(mt, "线程B");
t1.start();
t2.start();
}
}
输出结果:
5、sleep()方法与yield()方法的区别
sleep()方法--线程休眠 | yield()方法--线程礼让 |
(1)让线程暂时睡眠指定时长,线程进入阻塞状态 (2)睡眠时间过后线程会再进入可运行状态 | (1)暂停当前线程,允许其他具有相同优先级的线程获得运行机会 (2)该线程处于就绪状态,不转为阻塞状态 |
即使没有其他等待运行的线程,当前线程也会等待指定的时间 | 如果没有其他等待执行的线程,当前线程会马上恢复执行 |
练习03:模拟多人爬山2-1
练习04:线程的优先级
练习05:模拟叫号看病
05、线程同步
1、线程同步的必要性
前面介绍的线程都是独立的。而且异步执行,也就是说每个线程都包含了运行时所需要的数据或方法,而不需要外部资源或方法,也不必关心其他线程的状态或行为。但是经常有一些同时运行的线程需要共享数据,此时就需要考虑其他线程的状态和行为,否则就不能保证程序运行结果的正确性。:
2、多线程共享数据引发的问题
以下案例发现的问题:
(1)不是从第1张票开始
(2)存在多人抢到一张票的情况
(3)有些票号没有被抢到
(4).............
示例7-1:网络购票(多线程共享数据出现问题案例)
需求说明:
多用户实现网络购票,用户提交购票信息后
(1)第一步:网站修改网站车票数据
(2)显示出票反馈给用户
解析:
开始num=10,count=0,有一个线程,首先张三开始买票,执行run()方法,执行到if语句判断num<=0不满足,执行num--,count++ (此时num=9,count=1),(1)非常理想的状态是程序从头到尾执行到System.out.println()这里输出购票信息(输出此时的信息为:张三抢到了第1张票,还剩下9张票),但是当张三在执行到休眠的时候,失去了CPU资源,这个时候李四来了,执行run()方法,执行到if语句判断num<=0不满足,执行num--,count++ (此时num=8,count=2),(2)非常理想的状态是程序从头到尾执行到System.out.println()这里输出购票信息(输出此时的信息为:李四抢到了第2张票,还剩下8张票),但是李四在执行到休眠的时候,有可能被张三抢去了票,也有可能被王五抢到了票,当王五开始买票,执行run()方法,执行到if语句判断num<=0不满足,执行num--,count++ (此时num=7,count=3),但是这个时候张三或者李四休眠结束了,则抢占CPU资源,比如张三休眠结束了,将会进行执行System.out.println(),此时的num=7,count=3,则会进行输出张三抢到了第3张票,还剩下7张票,依次类推,这样就会实现一张票卖给多人的情况,也会出现有些票号没有被抢到,也会出现不是从第一张票开始买的情况,这个情况就是多线程操作共享数据造成的,在操作过程中CPU资源可能被其他线程抢去了。
(1)通过Piao线程类实现Runnable接口
package cn.bdqn.demo06;
public class Piao implements Runnable{
//声明一个变量用来表示票库中票的数据
public int num=10;
//声明一个变量用来表示用户抢到了第几张票
public int count=0;
public void run(){
//实现抢票行为:修改票库里的票的数目和显示用户抢到了第几张票
while(true){
if(num<=0){
break;
}
num--;
count++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了第"+count+"张票,还剩下"+num+"张票");
}
}
}
(2)测试类Test类
package cn.bdqn.demo06;
public class Test {
public static void main(String[] args) {
Piao p = new Piao();
Thread t1 = new Thread(p, "张三");
Thread t2 = new Thread(p, "李四");
Thread t3 = new Thread(p, "王五");
t1.start();
t2.start();
t3.start();
}
}
输出结果:
3、实现线程同步(解决多线程共享数据出现的问题)
线程同步的两种方法:
(1)方法一:同步方法
(2)方法二:同步代码块
(1)同步方法:使用synchronized修饰的方法控制对类成员变量的访问
同步方法的语法格式:
(1)访问修饰符 synchronized 返回类型 方法名(参数列表){}
例如:public synchronized void sale()
(2)synchronized 访问修饰符 返回类型 方法名(参数列表){}
synchronized是同步关键字
访问修饰符是指:public、privatr等
示例7-2:使用同步方法的网络购票
(1)通过Piao线程类实现Runnable接口
package cn.bdqn.demo07;
public class Piao implements Runnable {
//声明一个变量用来表示库票中票的数据
public int num=10;
//声明一个变量用来表示用户抢到了第几张票
public int count=0;
public boolean flag = false;//声明一个标志位
@Override
public void run() {
while(!flag){//当条件为真时,执行循环操作
sale(); //调用方法
}
}
public synchronized void sale(){
//当票库里的票小于0的时候,不在进行循环
if(num<=0){
flag = true;//此时flag=true,将其值返回,此时结束while循环
return;
}
num--;
count++;
try {
Thread.sleep(1000); //线程休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了第"+count+"张票,还剩下"+num+"张票");
}
}
(2)测试类Test类
package cn.bdqn.demo07;
public class Test {
public static void main(String[] args) {
Piao p =new Piao();
Thread t1 = new Thread(p, "张三");
Thread t2 = new Thread(p, "李四");
Thread t3 = new Thread(p, "王五");
t1.start();
t2.start();
t3.start();
}
}
输出结果:
(2)同步代码块:使用synchronized关键字修饰的代码块
同步代码快的语法格式:
(1)synchronized(syncObject){
//需要同步的代码
}
①syncObject为需要同步到对象,通常为this
②效果与同步方法相同
多个并发线程访问同一资源的同步代码块时:
(1)同一时刻只能有一个线程进入synchronized(this)同步代码块
(2)当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定。
(3)当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码。
示例7-3: 使用synchronized关键字修饰的代码块
(1)通过Piao线程类实现Runnable接口
package cn.bdqn.demo08;
public class Piao implements Runnable {
// 声明一个变量用来表示库存中票的数据
public int num = 10;
// 声明一个变量用来表示用户抢到了第几张票
public int count = 0;
public boolean flag = false;
@Override
public void run() {
while (!flag) {
synchronized (this) {
// 当库票里的票<0的时候,不在进行循环
if (num <= 0) {
flag = true;
break;
}
num--;
count++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢到了第"
+ count + "张票,还剩下" + num + "张票");
}
}
}
}
(2)测试类Test类
package cn.bdqn.demo08;
public class Test {
public static void main(String[] args) {
Piao p = new Piao();
Thread t1 = new Thread(p, "张三");
Thread t2 = new Thread(p, "李四");
Thread t3 = new Thread(p, "王五");
t1.start();
t2.start();
t3.start();
}
}
输出结果:
06、线程安全的类型
1、线程安全与非线程安全的比较
(1)ArrayList为非线程安全类型
ArrayList类的add()方法为非同步方法
当多个线程向同一个ArrayList对象添加数据时,可能出现数据不一致问题
方法是否同步 | 效率比较 | 适合场景 | |
线程安全 | 是 | 低 | 多线程并发共享资源 |
非线程安全 | 否 | 高 | 单线程 |
2、常见类型比较
Hashtable && HashMap
(1)Hashtable
继承关系
实现了Map接口,Hashtable继承Dictionary类
线程安全,效率较低
键和值都不允许为null
(2)HashMap
继承关系
实现了Map接口,继承AbstractMap类
非线程安全,效率较高
键和值都允许为null
StringBuffer && StringBuilder
前者线程安全,后者非线程安全