// 多线程
// 进程 是系统进行资源分配和调度的基本单位
// 线程 系统独立调度和分派CPU的基本单位指令运行时的程序的调度单位
// 多线程: 因计算机的硬件支持能够在同一时间执行多个线程的开发技术
// 实现多线程的两个方法
// 1. 继承Thread类 重写run方法 创建实现类的对象 开启线程
// 2. 实现Runnable接口 重写run方法 创建实现类对象 分配线程并开启
// 1. 继承Thread类 重写run方法 创建实现类的对象 开启线程
// 比如三个人同时说三句祝福语
// 继承Thread方法
public class Demo02 extends Thread{
// 线程名
public Demo02(String nString) {
super(nString);
}
// 重写run方法
// 需要执行的代码
@Override
public void run() {
// 获取当前线程名
String ctName = Thread.currentThread().getName();
// 三遍祝福语
for (int i = 0; i < 3; i++) {
System.out.println(ctName +": 生日快乐");
}
}
}
public class Demo01 {
public static void main(String[] args) {
// 创建实现类的对象
Demo02 e1 = new Demo02("小明");
Demo02 e2 = new Demo02("小红");
Demo02 e3 = new Demo02("小白");
// 实现类对象 开辟线程
e1.start();
e2.start();
e3.start();
}
}
2.卖票的例子
// 2. 实现Runnable接口
// 有三个窗口进行卖票 三个窗口对票进行操作 java类作为同一资源时
// 将此类情况的java类定义为Runnable接口的实现类
// 以供多个线程共同特有
// 实现Runnable接口
public class Demo01 implements Runnable{
// 票的数量
private int ticketNum;
// 固定总票的数量
int ticketSum = 0;
// 外界控制票数 开放对外访问的接口
public Demo01() {
this.ticketNum = 20;
ticketSum = getTicketNum();
}
public int getTicketNum() {
return ticketNum;
}
public void setTicketNum(int ticketNum) {
this.ticketNum = ticketNum;
}
// 重写run方法
@Override
public void run() {
sellTicket();
}
// 卖票的方法
private void sellTicket() {
while (true) {
synchronized (this) {
if (ticketNum > 0) {
// 获取当前线程名
String ctName = Thread.currentThread().getName();
// 抢到一张票
int count = ticketSum - ticketNum + 1;
System.out.println(ctName + ",抢到了第" + count + "张票");
// 卖一张少一张
ticketNum--;
System.out.println(ctName + "卖出一张,还剩余" + ticketNum + "张票");
}
}
if (ticketNum <= 0) {
break;
}
Thread.yield();
}
}
}
3.使用线程管理做家务
衣服类
public class Clothes implements Runnable{
// 衣服干净度
// 1. 内外都需要访问--public static
// 2. 多个线程需要访问 -- volatile
// volatile: 在多个线程访问一个资源时 多个线程中的任意一个线程修改了
// 资源值,会讲资源修改信号和修改值马上同步给其他所有访问线程
public static volatile boolean isClean;
public static volatile boolean isOver;
@Override
public void run() {
// 1. 获取线程名
String ctName = Thread.currentThread().getName();
// 2. 根据线程名决定执行的逻辑
// 洗衣线程 - 洗衣
if (ctName.equals("洗衣线程")) {
xy();
}
// 晾衣线程 - 晾衣
if (ctName.equals("晾衣线程")) {
ly();
}
}
private void xy() {
System.out.println("洗衣机开始洗衣,请稍等");
// 洗衣5s: 依次洗衣中 间隔1s打印一次
for (int i = 0; i < 5; i++) {
// 等待一秒要做的事情
long time = System.currentTimeMillis();
while (System.currentTimeMillis() - time <1000) {}
System.out.println("洗衣中...");
}
System.out.println("洗衣机洗衣完成");
// 将衣服状态置为"干净"
isClean = true;
}
private void ly() {
System.out.println("保姆开始晒衣,请稍等");
// 晒衣2s: 依次晒衣中 间隔1s打印一次
for (int i = 0; i < 2; i++) {
// 等待一秒要做的事情
long time = System.currentTimeMillis();
while (System.currentTimeMillis() - time <1000) {}
System.out.println("晾衣中...");
}
System.out.println("保姆晾衣完成");
isOver = true;
}
}
扫地类
// 扫地类
public class SD extends Thread{
// 扫地是否完事
public static volatile boolean isOver;
// 构造器--线程名
public SD(String threadName) {
super(threadName);
}
@Override
public void run() {
System.out.println("保姆开始扫地,请稍等");
// 扫地3s: 依次扫地中 间隔1s打印一次
for (int i = 0; i < 10; i++) {
// 等待一秒要做的事情
long time = System.currentTimeMillis();
while (System.currentTimeMillis() - time <1000) {}
System.out.println("扫地中");
}
System.out.println("保姆扫地完成");
isOver = true;
}
}
保姆类
public class BM extends Thread{
@Override
public void run() {
// 创建共有的衣服资源
Clothes clothes = new Clothes();
// 为衣服资源分配线程 - 洗衣线程 晾衣线程
Thread xyThread = new Thread(clothes, "洗衣线程");
Thread lyThread = new Thread(clothes, "晾衣线程");
System.out.println("保姆来到洗衣间");
xyThread.start();
SD sd = new SD("扫地线程");
System.out.println("保姆来到客厅");
sd.start();
// 等待衣服洗完 及 扫地完事
// 刚开始为false 取非为true 在走循环 变为true时 取非 false不走循环 执行下面的语句
while (!Clothes.isClean || !SD.isOver) {}
System.out.println("保姆来到晾衣间");
lyThread.start();
while (!Clothes.isOver) {}
System.out.println("家务处理完毕");
}
}
测试类
// 做家务 Runnable & Thread
public class Thread05 {
// 主人为主线程
public static void main(String[] args) {
System.out.println("主人发现屋子中有家务待做");
System.out.println("于是吩咐保姆开始做家务");
BM bm = new BM();
System.out.println("保姆开始做家务了>>>>");
bm.start();
System.out.println("主人开始玩耍");
}
}
总结
总结同步锁synchronized
synchronized 同步锁
1. synchronized作为方法的关键词
1) 该方法为同步方法:
目的: 方法同一时刻只能被一个线程锁调用
应用场景: 方法功能单一,且方法整体需要做同步处理
2. synchronized同步代码块
1) 代码块为同步代码块
目的: 代码块同一时刻只能被一个线程锁使用
应用场景: 方法功能逻辑较多,方法需局部做同步处理
3. synchronized同步静态代码块 synchronized (mutex) 互斥锁
参数 mutex: 唯一的任意对象
通常互斥锁标识:
static方法中 当前类.class 的class对象
成员方法: this
this不唯一时 : static final Object
例如: private static final Object mutex = new Object();
线程管理总结
jion 比如: t.join();使调用线程t在此之前执行完毕。 t.join(1000);等待t线程,等待时间是1000毫秒
sleep 当前正在执行的线程休眠(暂停执行
System.currentTimeMills的方法应用 延迟打印 (等待一秒要做的事情)
4.优先权最高问题
C优先级最高 最先打印(A B 需等待)
C打印完毕后,A B挣CPU执行权 逐一打印
public class ThreadABC implements Runnable{
// C优先级最高 最先打印(A B 需等待)
// C打印完毕后,A B挣CPU执行权 逐一打印
@Override
public void run() {
String ctName = Thread.currentThread().getName();
锁池操作
synchronized (锁标识): 创建锁池区域 会拥有对应的等待队列
wait: 在所属的锁池区域(在同步代码块中),通过锁标识可进入 等待队列
notify: 在所属的锁池区域(在同步代码块中),通过标识去等待队列唤醒一个等待线程(无法唤醒不指定的线程)
notifyAll:在所属锁池区域(在同步代码块中) 通过标识去等待队列唤醒所有线程
synchronized (this) {
if (!ctName.equals("线程C")) {
try {
this.wait(); // 进入等待池
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
}
System.out.println("我是 " + ctName);
}
this.notify(); // 去等待池唤醒下一个等待的线程
}
}
}
测试类
public class TestABC {
public static void main(String[] args) {
ThreadABC abc = new ThreadABC();
Thread a = new Thread(abc, "线程A");
Thread b = new Thread(abc, "线程B");
Thread c = new Thread(abc, "线程C");
a.start();
b.start();
c.start();
}
}
总结
synchronized (锁标识): 创建锁池区域 会拥有对应的等待队列
wait: 在所属的锁池区域(在同步代码块中),通过锁标识可进入 等待队列
notify: 在所属的锁池区域(在同步代码块中),通过标识去等待队列唤醒一个 等待线程(无法唤醒不指定的线程)
notifyAll:在所属锁池区域(在同步代码块中) 通过标识去等待队列唤醒所有线程
5.计数应用
public class CouutErr extends Thread{
// 计数
public int count;
// 暂停
public static volatile boolean pause;
@Override
public void run() {
/*
* 线程中断检查
* 1. Thread.interrupted(): 检查当前线程的中断状态 如果发现线程
* 中断后(中断状态为true),该操作会清除中断状态
* 2. this.isInterrupted() 检查某线程的中断状态 如果发现线程
* 中断后(中断状态为true),该操作不会清除中断状态
* interrupted相关操作 与 sleep join操作
*
* 对于同一线程操作而言 不建议interrupt操作与sleep join操作结合使用
* 原因: 某线程发生interrupt操作时 会被该线程的sleep join操作捕获InterruptedException异常
* 且当前现在的中断状态也会被清除
* 注: A线程interrupt操作 不会影响到B线程的sleep join操作
*/
while (!isInterrupted()) {
if (!pause) {
// 200ms计数一次
// 线程自身不能使用sleep操作
// try {
// Thread.sleep(200);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
long time = System.currentTimeMillis();
while (System.currentTimeMillis() - time < 200) {}
System.err.println(count++);
}
}
}
}
public class CountOut extends Thread{
private int count;
// 暂停
public static volatile boolean pause;
// 结束
public static volatile boolean over;
// 线程要执行的语句
@Override
public void run() {
// 不结束一直计数
while (!over) {
// 暂停停止计数 否则继续计数
if (!pause) {
// 200ms计数一次
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 计数
System.out.println(++count);
}
}
}
}
// 线程管理类(外部) 控制 管理的线程(内部) 开启及结束
public class Thread06 {
private static void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
}
}
public static void main(String[] args) {
// out计数器 开始 暂停 结束
//fn1();
// err计数器 开始 暂停
fn2();
}
// err计数器
private static void fn2() {
CouutErr err = new CouutErr();
// 开启计数器
err.start();
// 2s后中断err计数器
sleep(2000);
CouutErr.pause = true;
// 0.9s重新开启out计数
sleep(900);
CouutErr.pause = false;
// 在一秒计数
sleep(1000);
err.interrupt();
}
// out计数器 开始 暂停 结束
private static void fn1() {
// 生成out计数器并开启线程
CountOut cOut = new CountOut();
cOut.start();
sleep(2000);
CountOut.pause = true;
// 0.5秒重新开启计数
sleep(900);
CountOut.pause = false;
// 在1秒后结束out计数
sleep(1000);
CountOut.over = true;
}
}
总结
线程中断检查
1. Thread.interrupted(): 检查当前线程的中断状态 如果发现线程
中断后(中断状态为true),该操作会清除中断状态
2. this.isInterrupted() 检查某线程的中断状态 如果发现线程
中断后(中断状态为true),该操作不会清除中断状态
interrupted相关操作 与 sleep join操作
对于同一线程操作而言 不建议interrupt操作与sleep join操作结合使用
原因: 某线程发生interrupt操作时 会被该线程的sleep join操作捕获InterruptedException异常
且当前现在的中断状态也会被清除
注: A线程interrupt操作 不会影响到B线程的sleep