多线程
文章目录
- 多线程
- 前言
- 一、多线程
- 1、多线程的概念
- 2、多线程的好处
- 二、主线程
- 1、Thread类
- 2、主线程
- 三、线程的创建和启动
- 1、创建线程的两种方式
- 2、使用线程的步骤
- 四、继承Thread类创建线程
- 五、实现Runnable接口创建线程
- 六、比较两种创建线程的方式
- 1、继承Thread类
- 2、实现Runnable接口
- 七、线程的状态
- 八、线程的调度
- 九、线程优先级
- 十、线程休眠
- 十一、线程的强制执行
- 十二、线程的礼让
- 十三、多线程共享数据引发的问题
- 十四、同步方法解决多线程共享数据引发的问题
- 1、同步方法
- 2、同步代码块
- 十五、线程安全的类型
前言
理解线程的概念,掌握线程的创建和启动,了解线程的状态,掌握线程调度的常用方法,掌握线程的同步,理解线程安全的类型
一、多线程
1、多线程的概念
1)如果一个进程中同时运行了多个线程,用来完成不同的工作,则称为“多线程”
2)多个线程交替占用CPU资源,而非真正的并行执行
2、多线程的好处
1)充分利用CPU的资源
2)简化编程模型
3)带来良好的用户体验
二、主线程
1、Thread类
Java提供了java.lang.Thread类支持多线程编程
2、主线程
main()方法即为主线程入口
产生其它子线程的线程
必须最后完成执行,因为它执行各种关闭动作
public class ThreadDemo01 {
public static void main(String[] args) {
// 获取当前运行的线程
// static Thread currentThread()返回对当前正在执行的线程对象的引用
Thread thread = Thread.currentThread();
/*
因为Thread类中重写了Object类中的toString()方法
所以在这里直接输出对象thread的时候,调用的是重写后的toString()方法,输出的是当前线程thread的一些内容信息
*/
System.out.println("thread : " + thread);
// 获取当前运行线程的名称
// String getName()返回该线程的名称
String threadName = thread.getName();
System.out.println("当前线程名称 :" + threadName);
// 修改当前线程名称
// void setName(String name)改变线程名称,使之与参数 name 相同
thread.setName("发发发");
System.out.println("修改之后线程名 :" + thread.getName());
// 获取当前运行线程的优先级
// int getPriority()返回线程优先级
System.out.println("当前线程优先级 :" + thread.getPriority());
// 获取线程的最大优先级、最小优先级、默认优先级
System.out.println("线程最大优先级 :" + Thread.MAX_PRIORITY); // 10
System.out.println("线程最小优先级 :" + Thread.MIN_PRIORITY); // 1
System.out.println("线程默认优先级 :" + Thread.NORM_PRIORITY); // 5
// 修改当前线程优先级
// void setPriority(int newPriority)更改线程的优先级
thread.setPriority(8);
System.out.println("修改后线程优先级 :" + thread.getPriority()); // 8
}
}
/*
thread : Thread[main,5,main]
当前线程名称 :main
修改之后线程名 :发发发
当前线程优先级 :5
线程最大优先级 :10
线程最小优先级 :1
线程默认优先级 :5
修改后线程优先级 :8
*/
三、线程的创建和启动
1、创建线程的两种方式
1)继承java.lang.Thread类
2)实现java.lang.Runnable接口
2、使用线程的步骤
1)定义线程
2)创建线程对象
3)启动线程
4)终止线程
四、继承Thread类创建线程
1)定义MyThread类继承Thread类
2)重写run()方法,编写线程执行体
3)创建线程对象,调用start()方法启动线程
多个线程交替执行,不是真正的“并行”
线程每次执行时长由分配的CPU时间片长度决定
public class MyThread extends Thread {
@Override
public void run() {
// 循环输出1-20之间所有的整数
for (int i = 1; i <= 20; i++) {
System.out.println(Thread.currentThread().getName() + "正在运行:" + i + "次");
}
}
}
// ------ ------ ------
public class Test {
public static void main(String[] args) {
// 创建自定义线程类MyThread对象
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
// 修改线程名
myThread1.setName("线程A");
myThread2.setName("线程B");
// 启动线程
// 通过线程对象直接调用run()方法,是由main来执行
// myThread1.run(); // main正在运行:1-20次
// 通过线程调用start()方法来启动线程,是由子线程来执行
myThread1.start();
myThread2.start(); // 多线程交替占用CPU的过程
}
}
直接调用run()方法和start()方法的区别
直接调用run()方法只有主线程一条执行路径
调用start()方法多条执行路径,主线程和子线程并行交替执行
五、实现Runnable接口创建线程
1)定义MyRunnable类实现Runnable接口
2)实现run()方法,编写线程执行体
3)创建线程对象,调用start()方法启动线程
public class MyRunnable implements Runnable {
public void run() {
// 循环输出1-20之间所有的整数
for (int i = 1; i <= 20 ; i++) {
System.out.println(Thread.currentThread().getName() + "正在运行:" + i + "次");
}
}
}
package javaapidemo0221.demo03;
public class Test {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
// 启动线程
// myRunnable.run(); // main正在运行:1-20次
// myRunnable.start(); // 报错
/*
myRunnable类中没有start()方法
它的父类Object类中也没有start()方法,它实现的接口中也没有start()方法,因为start()方法是Thread类中
*/
// Thread(Runnable target)分配新的 Thread 对象
// Thread(Runnable target, String name)分配新的 Thread 对象
// Thread thread1 = new Thread(myRunnable);
// Thread thread2 = new Thread(myRunnable);
Thread thread1 = new Thread(myRunnable, "线程A"); // 同时修改线程名称
Thread thread2 = new Thread(myRunnable, "线程B");
// 设置优先级,优先级高的线程比优先级低的线程获取CPU资源的概率更高一些
// thread1.setPriority(1);
// thread2.setPriority(10);
thread1.start();
thread2.start();
}
}
六、比较两种创建线程的方式
1、继承Thread类
编写简单,可直接操作线程
适用于单继承
2、实现Runnable接口
避免单继承局限性
便于共享资源
七、线程的状态
1、创建状态
2、就绪状态
3、运行状态
4、阻塞状态
5、死亡状态
/*
新建(NEW):线程对象被创建后,尚未调用start()方法时的状态。
运行(RUNNABLE):线程对象调用start()方法后进入的状态,表示线程在逻辑上是可执行的,可能正在CPU上运行,也可能处于就绪状态等待调度。
阻塞(BLOCKED):线程阻塞于监视器锁,通常是等待获取一个对象的同步锁。
等待(WAITING):线程需要等待其他线程执行特定动作(如通知)才能继续执行的状态。
超时等待(TIMED_WAITING):与WAITING类似,但可以在指定时间后自动返回。
终止(TERMINATED):线程执行完毕或因异常而终止的状态。
*/
八、线程的调度
线程调度指按照特定机制为多个线程分配CPU的使用权
九、线程优先级
1)线程优先级由1-10表示,1最低,默认优先级为5
2)优先级高的线程获得CPU资源的概率较大
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
System.out.println(Thread.currentThread().getName() + "正在运行:" + i + "次");
}
}
}
package javaapidemo0221.demo04;
public class Test {
public static void main(String[] args) {
// 创建MyRunnable类对象
MyRunnable myRunnable = new MyRunnable();
Thread thread1 = new Thread(myRunnable, "线程A");
Thread thread2 = new Thread(myRunnable, "线程B");
// 获取两个线程对象的优先级
System.out.println("thread1线程优先级:" + thread1.getPriority()); // thread1线程优先级:5
System.out.println("thread2线程优先级:" + thread2.getPriority()); // thread2线程优先级:5
// 设置两个线程的优先级
// 优先级只能反映线程获取CPU资源概率问题,优先级高的线程比优先级低的线程获取CPU资源的概率大,不能说优先级高的线程一定一优先级低的线程先获取CPU资源
thread1.setPriority(Thread.MIN_PRIORITY);
thread2.setPriority(Thread.MAX_PRIORITY);
// 启动线程
thread1.start();
thread2.start();
}
}
十、线程休眠
1)让线程暂时睡眠指定时长,线程进入阻塞状态
2)睡眠时间过后线程会再进入可运行状态
public static void sleep(long millis);
// millis为休眠时长,以毫秒为单位
// 调用sleep()方法需处理InterruptedException异常
package javaapidemo0221.demo05;
public class ThreadSleepDemo01 {
public static void main(String[] args) throws InterruptedException {
System.out.println("线程开始执行");
// 线程休眠5秒钟
Thread.sleep(5000);
System.out.println("线程结束执行"); // 执行程序等待5s后输出线程结束执行
}
}
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 100; i <= 5000; i++) {
// public static Thread currentThread()返回对当前正在执行的线程对象的引用
System.out.println(Thread.currentThread().getName() + "正在运行:" + i);
}
}
}
package javaapidemo0221.demo06;
public class Wait {
// 定义一个静态方法实现线程休眠指定的秒数
public static void waitBySecond(int second) {
for (int i = 1; i <= second; i++) {
System.out.println("正在休眠..." + i + "秒");
// 处理异常
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
package javaapidemo0221.demo06;
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
System.out.println(Thread.currentThread().getName() + "正在运行:" + i + "次");
if (i == 25) {
Wait.waitBySecond(5);
}
}
}
}
package javaapidemo0221.demo06;
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread = new Thread(new MyRunnable());
// 启动两个线程
myThread.start();
thread.start();
}
}
十一、线程的强制执行
使当前线程暂停执行,等待其它线程结束后再继续执行本线程
public final void join()
public final void join(long mills)
public final void join(long mills,int nanos)
// millis:以毫秒为单位的等待时长
// nanos:要等待的附加纳秒时长
需处理InterruptedException异常
package javaapidemo0221.demo07;
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 1; i <= 20; i++) {
System.out.println(Thread.currentThread().getName() + "正在运行:" + i + "次");
}
}
}
package javaapidemo0221.demo07;
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
// thread.setPriority(10);
thread.start();
for (int i = 10; i <= 100; i += 10) {
System.out.println(Thread.currentThread().getName() + "正在运行:" + i);
if (i == 50) {
try {
// 强制执行线程,会暂停当前正在运行的线程main,当强制执行的线程执行完了,暂停执行的当前线程会继续执行
thread.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
十二、线程的礼让
暂停当前线程,允许其它具有相同优先级的线程获得运行机会
该线程处于就绪状态,不转为阻塞状态
public static void yield()
// 只是提供一种可能,但是不保证一定会实现礼让
package javaapidemo0221.demo08;
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 10 ; i++) {
System.out.println(Thread.currentThread().getName() + "正在运行:" + i);
if (i == 5) {
System.out.println("线程礼让");
// static void yield()暂停当前正在执行的线程对象,与其它线程一起抢占CPU资源。
Thread.yield();
}
}
}
}
package javaapidemo0221.demo08;
public class Test {
public static void main(String[] args) {
Thread thread1 = new Thread(new MyRunnable(), "线程A");
Thread thread2 = new Thread(new MyRunnable(), "线程B");
// 启动线程
thread1.start();
thread2.start();
}
}
十三、多线程共享数据引发的问题
public class Site implements Runnable {
// 定义两个属性来统计票的数目和买到第几张票
// 票剩余的数量
private int num = 10;
// 购买到时是第几张票
private int count = 0;
@Override
public void run() {
while (true) {
// 票卖完即结束循环
if (num <= 0) {
break;
}
num--;
count++;
// 模拟网络延迟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 输出卖票信息,看是哪个线程(用户)购买了票
System.out.println(Thread.currentThread().getName() + "购买了第" + count + "张票,剩余" + num + "张票");
}
}
}
package javaapidemo0221.demo09;
public class Test {
public static void main(String[] args) {
Site site = new Site();
Thread thread1 = new Thread(site,"张三");
Thread thread2 = new Thread(site,"李四");
Thread thread3 = new Thread(site,"王五");
// 启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
/*
李四购买了第3张票,剩余7张票
王五购买了第3张票,剩余7张票
张三购买了第3张票,剩余7张票
李四购买了第6张票,剩余4张票
王五购买了第6张票,剩余4张票
张三购买了第6张票,剩余4张票
王五购买了第9张票,剩余1张票
张三购买了第9张票,剩余1张票
李四购买了第9张票,剩余1张票
王五购买了第10张票,剩余0张票
*/
/*
发现问题,没有从第一张票开始,存在多人抢到一张票的情况,有些票号没有被抢到
这是因为在某线程抢占到CPU资源时,因为网络延迟,CPU资源被其它线程抢占,无法输出语句
*/
十四、同步方法解决多线程共享数据引发的问题
1、同步方法
// 使用synchronized修饰的方法控制对类成员变量的访问
访问修饰符 synchronized 返回类型 方法名(参数列表){……}
// 或
synchronized 访问修饰符 返回类型 方法名(参数列表){……}
// synchronized就是为当前的线程声明一把锁
package javaapidemo0221.demo10;
public class Site implements Runnable {
private int num = 10;
private int count = 0;
@Override
public void run() {
while (true) {
if (!sale()) {
break;
}
}
}
//定义一个同步方法,实现卖票
public synchronized boolean sale() {
if (num <= 0) {
return false;
}
num--;
count++;
try {
Thread.sleep(300);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "购买了第" + count + "张票,剩余" + num + "张票");
return true;
}
}
package javaapidemo0221.demo10;
public class Test {
public static void main(String[] args) {
Site site = new Site();
Thread thread1 = new Thread(site, "张三");
Thread thread2 = new Thread(site, "李四");
Thread thread3 = new Thread(site, "王五");
//启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
/*
张三购买了第1张票,剩余9张票
张三购买了第2张票,剩余8张票
张三购买了第3张票,剩余7张票
张三购买了第4张票,剩余6张票
张三购买了第5张票,剩余5张票
张三购买了第6张票,剩余4张票
张三购买了第7张票,剩余3张票
张三购买了第8张票,剩余2张票
张三购买了第9张票,剩余1张票
张三购买了第10张票,剩余0张票
*/
2、同步代码块
// 使用synchronized关键字修饰的代码块
synchronized(syncObject){
// 需要同步的代码
}
// syncObject为需同步的对象,通常为this
// 效果与同步方法相同
public void run() {
while (true) {
synchronized (this) { //同步代码块
// 省略修改数据的代码......
// 省略显示信息的代码......
}}}
多个并发线程访问同一资源的同步代码块时
1)同一时刻只能有一个线程进入synchronized(this)同步代码块
2)当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
3)当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码
public class Site implements Runnable {
private int num = 10;
private int count = 0;
@Override
public void run() {
while (true) {
synchronized (this) {
//票卖完了就可以结束这个循序
if (num <= 0) {
break;
}
num--;
count++;
//模拟网络延迟
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//输出一下卖票信息,看是哪一个线程(用户)购买了票
System.out.println(Thread.currentThread().getName() + "购买了第" + count + "张票,剩余" + num + "张票");
}
}
}
}
package javaapidemo0221.demo11;
public class Test {
public static void main(String[] args) {
Site site = new Site();
Thread thread1 = new Thread(site, "张三");
Thread thread2 = new Thread(site, "李四");
Thread thread3 = new Thread(site, "王五");
//启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
十五、线程安全的类型
常见类型比较
1)Hashtable && HashMap
Hashtable
继承关系:实现了Map接口,Hashtable继承Dictionary类
线程安全,效率较低
键和值都不允许为null
HashMap
继承关系:实现了Map接口,继承AbstractMap类
非线程安全,效率较高
键和值都允许为null
2)StringBuffer && StringBuilder
前者线程安全,后者非线程安全