目录
1 多线程
1.1 并发与并⾏
1.2 线程与进程
1.3 创建线程类
2 多线程详解
2.1 多线程原理
2.2 Thread类
2.3 创建线程⽅式⼆
2.4 Thread 和 Runnable 的区别
2.5 匿名内部类⽅式实现线程的创建
3 线程安全
3.1 线程安全
3.2 线程同步
3.3 同步代码块
3.4 同步⽅法
3.5 Lock锁
4 线程状态
4.1 线程状态概述
4.2 Timed Waiting(计时等待)
4.3 BLOCKED(锁阻塞)
4.4 Waiting(⽆限等待)
4.5 练习
5 小结
注意:单核处理器的计算机肯定是不能并⾏的处理多个任务的,只能是多个任务在单个 CPU 上并发运 ⾏。同理 , 线程也是⼀样的,从宏观⻆度上理解线程是并⾏运⾏的,但是从微观⻆度上分析却是串⾏ 运⾏的,即⼀个线程⼀个线程的去运⾏,当系统只有⼀个 CPU 时,线程会以某种顺序执⾏多个线程, 我们把这种情况称之为线程调度。
所有线程轮流使⽤ CPU 的使⽤权,平均分配每个线程占⽤ CPU 的时间。
优先让优先级⾼的线程使⽤ CPU,如果线程的优先级相同,那么会随机选择⼀个(线程随机性), Java使⽤的为抢占式调度。
2.抢占式调度详解
public class Demo01 {
public static void main(String[] args) {
// 创建⾃定义线程对象
MyThread mt = new MyThread("新的线程!");
// 开启新线程
mt.start();
// 在主⽅法中执⾏for循环
for (int i = 0; i < 10; i++) {
System.out.println("main线程!" + i);
}
}
}
public class MyThread extends Thread {
// 定义指定线程名称的构造⽅法
public MyThread(String name) {
// 调⽤⽗类的String参数的构造⽅法,指定线程的名称
super(name);
}
/**
* 重写run⽅法,完成该线程执⾏的逻辑
*/
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + ":正在执⾏!" + i);
}
}
}
public class MyThread extends Thread {
/*
* 利⽤继承中的特点
* 将线程名称传递 进⾏设置
*/
public MyThread(String name) {
super(name);
}
/*
* 重写run⽅法
* 定义线程要执⾏的代码
*/
public void run() {
for (int i = 0; i < 20; i++) {
//getName()⽅法 来⾃⽗亲
System.out.println(getName() + i);
}
}
}
public class Demo {
public static void main(String[] args) {
System.out.println("这⾥是main线程");
MyThread mt = new MyThread("⼩强");
mt.start(); // 开启了⼀个新的线程
for (int i = 0; i < 20; i++) {
System.out.println("旺财:" + i);
}
}
}
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class Demo {
public static void main(String[] args) {
// 创建⾃定义类对象 线程任务对象
MyRunnable mr = new MyRunnable();
// 创建线程对象
Thread t = new Thread(mr, "⼩强");
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("旺财 " + i);
}
}
}
Tips : Runnable 对象仅仅作为 Thread 对象的 target , Runnable 实现类⾥包含的 run() ⽅法仅作为线程 执⾏体。⽽实际的线程对象依然是 Thread 实例,只是该 Thread 线程负责执⾏其 target 的 run() ⽅法。
扩充:在 java 中,每次程序运⾏⾄少启动 2 个线程。⼀个是 main 线程,⼀个是垃圾收集(GC)线程。因为 每当使⽤ java 命令执⾏⼀个类的时候,实际上都会启动⼀个 JVM ,每⼀个 JVM 其实在就是在操作系 统中启动了⼀个进程。
public class NoNameInnerClassThread {
public static void main(String[] args) {
// new Runnable() {
// public void run() {
// for (int i = 0; i < 20; i++) {
// System.out.println("张宇:" + i);
// }
// }
// }; //---这个整体 相当于new MyRunnable()
Runnable r = new Runnable() {
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("张宇:" + i);
}
}
};
new Thread(r).start();
for (int i = 0; i < 20; i++) {
System.out.println("费⽟清:" + i);
}
}
}
public class Ticket implements Runnable {
private int ticket = 100;
/*
* 执⾏卖票操作
*/
@Override
public void run() {
// 每个窗⼝卖票的操作
// 窗⼝ 永远开启
while (true) {
if (ticket > 0) {// 有票 可以卖
// 出票操作
// 使⽤sleep模拟⼀下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket--);
}
}
}
}
public class Demo {
public static void main(String[] args) {
// 创建线程任务对象
Ticket ticket = new Ticket();
// 创建三个窗⼝对象
Thread t1 = new Thread(ticket, "窗⼝1");
Thread t2 = new Thread(ticket, "窗⼝2");
Thread t3 = new Thread(ticket, "窗⼝3");
// 同时卖票
t1.start();
t2.start();
t3.start();
}
}
结果中有⼀部分这样现象:
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,⽽⽆写操作,⼀般来说,这个全局变量是线程安全的;若有多个线程同时执⾏写操作,⼀般都需要考 虑线程同步,否则的话就可能影响线程安全。
窗⼝1线程进⼊操作的时候,窗⼝2和窗⼝3线程只能在外等着,窗⼝1操作结束,窗⼝1和窗⼝2和窗⼝3才有机会进⼊代码去执⾏。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
synchronized(同步锁) {
需要同步操作的代码
}
注意:在任何时候,最多允许⼀个线程拥有同步锁,谁拿到锁就进⼊代码块,其他的线程只能在外等 着( BLOCKED )。
public class Ticket implements Runnable {
private int ticket = 100;
Object lock = new Object();
/*
* 执⾏卖票操作
*/
@Override
public void run() {
// 每个窗⼝卖票的操作
// 窗⼝ 永远开启
while (true) {
synchronized (lock) {
if (ticket > 0) { // 有票 可以卖
// 出票操作
// 使⽤sleep模拟⼀下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖: " + ticket--);
}
}
}
}
}
public synchronized void method() {
可能会产⽣线程安全问题的代码
}
同步锁是谁?对于⾮ static ⽅法,同步锁就是 this 。对于 static ⽅法,我们使⽤当前⽅法所在类的字节码对象(类名 .class )。
使⽤同步⽅法代码如下:
public class Ticket implements Runnable {
private int ticket = 100;
/*
* 执⾏卖票操作
*/
@Override
public void run() {
// 每个窗⼝卖票的操作
// 窗⼝ 永远开启
while (true) {
sellTicket();
}
}
/*
* 锁对象 是 谁调⽤这个⽅法 就是谁
* 隐含 锁对象 就是 this
*/
public synchronized void sellTicket() {
if (ticket > 0) { // 有票 可以卖
// 出票操作
// 使⽤sleep模拟⼀下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket--);
}
}
}
public class Ticket implements Runnable {
private int ticket = 100;
Lock lock = new ReentrantLock();
/*
* 执⾏卖票操作
*/
@Override
public void run() {
// 每个窗⼝卖票的操作
// 窗⼝ 永远开启
while (true) {
lock.lock();
if (ticket > 0) { // 有票 可以卖
// 出票操作
// 使⽤sleep模拟⼀下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket--);
}
lock.unlock();
}
}
}
public class MyThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
if ((i) % 10 == 0) {
System.out.println("-------" + i);
}
System.out.print(i);
try {
Thread.sleep(1000);
System.out.print(" 线程睡眠1秒!\n");
} catch (InterruptedException e) {
e.printStackTrace(); }
}
}
public static void main(String[] args) {
new MyThread().start();
}
}
⼩提示: sleep() 中指定的时间是线程不会运⾏的最短时间。因此, sleep() ⽅法不能保证该线程睡眠到期后就开始⽴刻执⾏。
Timed Waiting 线程状态图:
public class WaitingTest {
public static Object obj = new Object();
public static void main(String[] args) {
// 演示waiting
new Thread(new Runnable() {
@Override
public void run() {
while (true) {
synchronized (obj) {
try {
System.out.println(Thread.currentThread().getName() + "=== 获取到锁对
象,调⽤wait⽅法,进⼊waiting状态,释放锁对象");
obj.wait(); // ⽆限等待
// obj.wait(5000); // 计时等待, 5秒 时间到,⾃动醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "=== 从waiting状态
醒来,获取到锁对象,继续执⾏了");
}
}
}
}, "等待线程").start();
new Thread(new Runnable() {
@Override
public void run() {
// while (true) { // 每隔3秒 唤醒⼀次
try {
System.out.println(Thread.currentThread().getName() + "----- 等待3秒 钟");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "----- 获取到锁对
象,调⽤notify⽅法,释放锁对象");
obj.notify();
}
}
// }
}, "唤醒线程").start();
}
}
⼀条有意思的 Tips :我们在翻阅 API 的时候会发现 Timed Waiting (计时等待)与 Waiting (⽆限等待)状态联系还是很紧密的,⽐如 Waiting (⽆限等待)状态中 wait ⽅法是空参的,⽽ Timed Waiting (计时等待)中 wait ⽅ 法是带参的。这种带参的⽅法,其实是⼀种倒计时操作,相当于我们⽣活中的⼩闹钟,我们设定好时 间,到时通知,可是如果提前得到(唤醒)通知,那么设定好时间再通知也就显得多此⼀举了,那么 这种设计⽅案其实是⼀举两得。如果没有得到(唤醒)通知,那么线程就处于 Timed Waiting 状态, 直到倒计时完毕⾃动醒来;如果在倒计时期间得到(唤醒)通知,那么线程从 Timed Waiting 状态⽴刻唤醒。
线程通信:练习1
需求:两个线程
线程1:图片的加载 1%~100%
线程2:图片的显示 显示
同时开启线程
线程2 等待 线程1 结束后在执行,
在线程2中使用 线程1.join() -> 线程2 进入阻塞状态
采用匿名内部类的方式
public class ThreadDemo3 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(){
public void run(){
System.out.println("开始加载...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 1; i <= 100; i++) {
System.out.println("已加载" + i + "%");
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("结束加载");
}
};
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("等待加载中。。。");
System.out.println("显示图片");
}
});
t1.start();
t2.start();
}
}
线程通信:练习2
需求: 两个线程
线程1: 图片的加载 1% ~ 100% (等待图片显示) 图片的下载 1%~100%
线程2: 图片的显示
同时开启线程: 显示之前 需要等待 加载完成
显示后 才能开始下载
wait(long): 等待指定的时间毫秒值
wait(): 无限等待, 可以被唤醒 notify() notifyAll()
public class TreadDemo4 {
public static void main(String[] args) {
Object obj = new Object();//这是用来做加锁的工具的, 此案例中什么意义都没有
LoadThread load = new LoadThread(obj);
ShowThread showT = new ShowThread(obj);
Thread show = new Thread(showT);
load.start();
show.start();
}
}
class LoadThread extends Thread {
private Object obj;
public LoadThread(Object obj) {
this.obj = obj;
}
public void run() {
System.out.println("开始加载...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 1; i <= 100; i++) {
System.out.println("已加载" + i + "%");
try {
Thread.sleep((long) (Math.random() * 300));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("结束加载");
synchronized (obj){
obj.notify();
}
synchronized (obj){
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("开始下载...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 1; i <= 100; i++) {
System.out.println("已下载" + i + "%");
try {
Thread.sleep((long) (Math.random() * 300));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("结束下载");
}
}
class ShowThread implements Runnable {
private Object obj;
public ShowThread(Object obj) {
this.obj = obj;
}
@Override
public void run() {
// try {
// t1.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
System.out.println("等待加载中。。。");
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("显示图片");
synchronized (obj){
obj.notify();
}
}
}
多线程: 父类 Thread
程序: 软件, 工程
进程: 正在运行的程序
线程: 进程中的任务单位CPU 可以"同时"处理多个线程
并行: 同一时刻, 同时运行, 通常需要多核处理器
并发: 多线程, 交替执行(交替速度足够快, 看起来是同时)实现多线程:
main -> 一个线程
执行多线程: 随机性方式一: 只能继承一个类, 功能性单一
1.自定义类, 继承Thread
2.重写run方法
3.在主程序中创建线程对象
4.开启线程 start()方式二: 实现接口
1.自定义类, 实现Runnable接口
2.实现run方法
3.创建线程对象 ※ 使用Runnable对象来构造
4.开启线程 start方式三: 匿名内部类
Thread 基础的API:
String getName(): Thread 属性 name
static Thread currentThread(): 获得当前线程对象
static void sleep(long time): 当前线程的阻塞时间线程的状态:
见图线程安全: 多个线程共享资源
解决安全: 实现线程同步
加锁: 同步锁 synchronized, 需要借助一个对象
Lock锁 接口 实现类 ReentrantLock()
上锁 lock() 解锁 unlock()线程其他属性和方法:
setPriority(1-10越来越大): 设置优先级, 提升了这个线程的执行概率
setDaemon(true): 设置守护线程, 所有的"前置"线程结束, 守护线程也将自动结束
GC -> 垃圾回收(守护线程)
System.gc() -> 手动清理