1. 概念
2. 线程运行状态图 – 重点
2.1 线程终止 - 代码测试
简单示例 --线程终止 → ( 标志符,外部干涉 )
class Run4 implements Runnable {
// 1. 当前线程停止的标志位
boolean isStop = false;
@Override
public void run() {
while(!isStop) {
System.out.println(Thread.currentThread().getName() + ": 正在执行");
}
}
// 2. 让线程停止
public void stop() {
isStop = true;
}
}
// 测试代码
public static void main(String[] args) throws Exception {
Run4 run4 = new Run4();
Thread thread = new Thread(run4);
thread.start();
Thread.sleep(1000);
int i = 0;
while(true) {
i++;
if(i == 100) {
run4.stop();
System.out.println(thread.getName() + " :停止");
break;
}
}
}
运行结果
2.2 线程阻塞join() - 代码测试
简单示例 - Thread.join()
class Thread1 extends Thread {
@Override
public void run() {
for(int i = 0; i<10; i++) {
System.out.println(Thread.currentThread().getName() + ".....正在运行");
}
}
}
// 代码测试
public static void main(String[] args) throws Exception {
// 1. 开始main线程 开始创建 t1对象
Thread t1 = new Thread1();
// 2. main线程将 t1 线程放入就绪状态 -- 让 t1、main本身线程进行抢CPU
t1.start();
// 3. 当main线程抢到CPU时,main线程告诉CPU,把CPU让给t1线程进行使用,直到执行完毕为止,其他线程阻塞状态
t1.join();
// 4. 当t1线程执行完毕,该行的main线程的代码才执行
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "...正在运行");
}
}
2.3 当前线程抢到CPU,但强制其重新跟其他线程进行此次的竞争 - 代码测试 - yield()
代码示例 - Thread.yield()
class Thread2 extends Thread {
@Override
public void run() {
for(int i = 0; i<1000; i++) {
System.out.println(Thread.currentThread().getName() + ".....正在运行");
}
}
}
// 测试代码
public static void main(String[] args) throws Exception {
Thread t1 = new Thread2();
t1.start();
for(int i = 0; i<1000; i++) {
if(i%5 == 0) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + i);
}
}
2.4 线程等待, 不释放锁 – sleep()
简单示例 - Thread.sleep( long ) - 打印时间
public static void main(String[] args) throws Exception {
// 1. 获取运行时那刻的时间
Date date = new Date();
long Millisecond = date.getTime();
// 2. 打印时间
while(true) {
// 3. 获取下一秒的时间
date = new Date();
// 4. 20秒后自动停止main进程
if(Millisecond + 20000 < date.getTime()) {
break;
}
System.out.println(date);
Thread.sleep(1000);
}
}
2.5 线程等待, 释放锁 – wait()
3. Thread类、Runnable接口
1. 线程对象直接执行run()方法是不可以执行的,只能调用start(),由CPU调用run方法
2. Thread类:已经实现Runnable接口
3. Thread是静态代理
4. 静态代理特点:、
- 4.1 真实角色、代理角色
- 4.2 两者实现相同的接口
- 4.3 代理角色持有真实角色的引用
3.1 构造线程的形式
方法1 - 继承Thread
1. 线程实现 - 1
缺点: 不可以在继承其他类
class ThreadDemo1 extends Thread {
@Overrride
public void run() {
代码
}
}
Thread t1 = new ThreadDemo1();
方法2 - 实现Runnable
2. 线程实现 - 2(推荐这种)
优点
- 1. 只实现接口,所以可以继承器其他类
- 2. 方便共享资源,多个代理访问
- 3. 静态代理
class RunDemo1 implements Runnable {
@Overrride
public void run() {
代码
}
}
Thread t1 = new Thread( new RunDemo1() );
模拟三个人( 代理角色 ) 抢 食物( 真实角色 )
class Foods implements Runnable {
private int breadCount = 50;
@Override
public void run() {
while (true) {
if (breadCount < 0) {
break;
}
System.out.println(Thread.currentThread().getName() + " 抢到了第 " + breadCount + " 面包");
--breadCount;
}
}
}
//测试代码
public static void main(String[] args) {
// 1. 真实角色
Foods foods = new Foods();
// 2. 三个代理角色,并且都持有同一个真实角色foods的引用
Thread student1 = new Thread(foods, "student1");
Thread student2 = new Thread(foods, "student2");
Thread student3 = new Thread(foods, "student3");
// 3. 三个学生开始抢面包
student1.start();
student2.start();
student3.start();
}
方法3 - 实现Callable
优点:
- 1. 可返回值、可声明异常
步骤:
class Thr3 implements Callable<String> {
int id;
public Thr3(int id) {
this.id = id;
}
@Override
public String call() throws Exception {
return "Callable"+ id +"实现 --" + Thread.currentThread().getName();
}
}
// 测试代码
public static void main(String[] args) throws Exception {
// 1. 创建Callable对象
Callable<String> thread1 = new Thr3(1);
Callable<String> thread2 = new Thr3(2);
// 2. 创建线程池
ExecutorService es = Executors.newFixedThreadPool( 3 );
// 3. Callable对象的方法执行由线程池的某个线程来执行-- 返回执行完线程后的结果对象
Future<String> result = es.submit(thread1);
Future<String> result2 = es.submit(thread2);
// 4. 得到3步骤 获取线程执行完的结果
String res = result.get();
System.out.println(res);
res = result2.get();
System.out.println(res);
// 5. 停止线程池中所有正在执行、等待的线程
es.shutdownNow();
}
运行结果
3.2 同步(锁)
3.2.1 同步成员方法
当前线程抢到真实角色调用方法的使用,其他线程不可以使用同一真实角色的同步方法,直到当线程的同步方法运行完毕,其他线程才可以去抢同一真实角色的同步方法调用
class Foods2 implements Runnable {
int count = 50;
boolean isStop = false;
@Override
public void run() {
while (!isStop) {
// 1. 线程同步 -- 当前真实角色上锁 - 同一真实角色同一同步方法只能由一个线程进行调用
gain1();
// 2. 线程不同步 -- 当前真实角色不上锁 -- 同一真实角色非同步方法可由多个线程同时调用
// gain2();
}
}
private synchronized void gain1() {
// 1. 进来先查看面包是否还有,没有则立即停止所有线程的运行
if (count < 1) {
isStop = true;
return;
}
// 2. 打印当前线程抢到的面包数
System.out.println(Thread.currentThread().getName() + " : 获取第 " + count-- + " 个面包");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void gain2() {
if (count < 1) {
isStop = true;
return;
}
System.out.println(Thread.currentThread().getName() + " : 获取第 " + count-- + " 个面包");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//---------------------------
// 测试代码
// 1. 创建一个真实角色 -- 一份资源
Runnable foods = new Foods2();
// 2. 创建三个线程 -- 共同争抢 同一个真实角色
Thread student1 = new Thread(foods, "学生1");
Thread student2 = new Thread(foods, "学生2");
Thread student3 = new Thread(foods, "学生3");
// 3. 线程启动
student1.start();
student2.start();
student3.start();
运行测试
3.2.1 同步代码块 – 方法内 – 锁对象
如果是 synchronized(this){ } 跟 同步成员方法 是一样的 – 都是锁当前对象
简单示例 - synchronized( this ) - 锁当前的真实对象
class Foods3 implements Runnable {
int count = 50;
boolean isStop = false;
@Override
public void run() {
while (!isStop) {
gain1();
}
}
private void gain1() {
synchronized (this) {
// 1. 进来先查看面包是否还有,没有则立即停止所有线程的运行
if (count < 1) {
isStop = true;
return;
}
// 2. 打印当前线程抢到的面包数
System.out.println(Thread.currentThread().getName() + " : 获取第 " + count-- + " 个面包");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
简单示例 - synchronized( this ) - 锁当前的真实对象
3.3 死锁 - 过多的同步
Thread3线程类 – 完成执行完run方法需要先抢到o2对象,然后在抢到o1对象才能执行完
class Thread3 extends Thread {
Object o1 ;
Object o2;
public Thread3(Object o1, Object o2) {
super();
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized(o1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(o2) {
System.out.println(Thread.currentThread().getName() + ":执行完成");
}
}
}
}
Thread4线程类 – 完成执行完run方法需要先抢到o1对象,然后在抢到o2对象才能执行完
class Thread3 extends Thread {
Object o1 ;
Object o2;
public Thread3(Object o1, Object o2) {
super();
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized(o1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(o2) {
System.out.println(Thread.currentThread().getName() + ":执行完成");
}
}
}
}
利用上述两个线程类,进行死锁情况的制作
Object o1 = new Object();
Object o2 = new Object();
Thread thread1 = new Thread3(o1, o2);
Thread thread2 = new Thread4(o1, o2);
thread1.start();
thread2.start();
运行结果 – 没死锁多运行几遍
3.4 生产者、消费者 - 减少多锁导致死锁的机率
Producer-consumer problem 或者 Bounded-Buffer problem
思想:
- 固定的大小缓冲区 — 线程共享
- 生产者往缓冲区添加数据,消费者往缓冲区取走数据
- 缓冲区满时,生产者休眠 – 等待下次消费者取走数据时才唤醒
- 缓冲区空时,消费者休眠 – 等待下次生产者添加数据时才唤醒
3.4.0 模拟生产者、消费者遇到的问题
即消费者明知缓冲区空依然还在取数据
共享缓冲区类
// 消费者、生产者共同分享访问的资源
class Movices {
// 1. 生产者、消费者共同的缓存区
String[] movies = new String[10];
// 2. movies的位置
int index = 0;
// 3. 生产者生产的第几个片
static int addCount = 1;
// 4. 消费者已经看了第几个片
static int watchCount = 1;
public synchronized void play(String pic) {
// 超出数组缓冲区,直接结束方法
if(index >= 10) {
System.out.println("生产者别存片了,我已经放不下啦");
return;
}
movies[index] = pic;
System.out.println(Thread.currentThread().getName() + ": 增加" + movies[index] + " - 第" + addCount++ + "部");
++index;
}
public synchronized void watch() {
// 数组一旦存满index=10,防止数组超出数组长度
if(index >= 10) {
index = 9;
}
System.out.println(Thread.currentThread().getName() + ": 观看" + movies[index] + " - 第" + watchCount++ + "部");
if(index <= 0) {
System.out.println("消费者你都没片看了,你到底在看什么啊!--------------------------------");
return;
}
--index;
}
}
生产者 – 将电影放入播放数组中
class Player implements Runnable {
Movices mov;
Player(Movices mov) {
this.mov = mov;
}
@Override
public void run() {
// 一共生产15张电影
for(int i = 1; i <= 15; i++) {
mov.play("电影");
}
}
}
消费者 – 观看电影的人
class Watcher implements Runnable {
Movices mov;
Watcher(Movices mov) { this.mov = mov; }
@Override
public void run() {
// 消费者即将观看15张电影
for(int i = 1; i<= 15; i++) {
mov.watch();
}
}
}
测试代码
Movices mov = new Movices();
new Thread( new Player(mov), "生产者").start();
new Thread( new Watcher(mov), "消费者").start();
运行结果
synchronizedwaitnotifyAll_689">3.4.1 信号灯法 - 标志位 - synchronized、wait()释放锁、notifyAll()唤醒线程
修改一下共享缓冲区域的代码
class Movices {
/*
* 1. true:生产者生产,消费者等待
* 2. false: 生产者等待,消费者消费
*/
boolean flag = true;
// 1. 生产者、消费者共同的缓存区
String[] movies = new String[10];
// 2. movies的位置
int index = -1;
// 3. 生产者生产的第几个片
static int addCount = 1;
// 4. 消费者已经看了第几个片
static int watchCount = 1;
public synchronized void play(String pic) {
// flag=false则让生产线程此时此刻等待不运行,释放锁,让消费线程使用释放的锁
if(!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 生产一部电影
++index;
movies[index] = pic;
System.out.println(Thread.currentThread().getName() + ": 增加" + movies[index] + " - 第" + addCount++ + "部");
// 变为false,提醒消费者进行消费
flag = false;
this.notifyAll();
}
public synchronized void watch() {
// flag=true则让消费线程此时此刻等待不运行,释放锁,让生产线程使用释放的锁
if(flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消费一部电影
System.out.println(Thread.currentThread().getName() + ": 观看" + movies[index] + " - 第" + watchCount++ + "部");
if(index < 0) {
System.out.println("消费者你都没片看了,你到底在看什么啊!--------------------------------");
return;
}
--index;
// 提醒生产线程进行生产
flag = true;
this.notifyAll();
}
}
运行结果
TimerTimerTask___764">3. Timer工具类、TimerTask抽象类 – 线程执行次数
Timer_770">Timer的使用
代码示例 – Timer.schedule( TimeTask, Date )
class Task extends TimerTask {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "响了!: 该做作业了");
}
}
//测试代码
public static void main(String[] args) throws Exception {
// 1. 声明闹钟 -- 并设置TimerTask线程的名字
Timer timer = new Timer("闹钟");
// 2.声明闹钟响后所做的事情
Task task = new Task();
// 3. 设置2秒后的时间点
Date date = new Date( new Date().getTime() + 1000);
// 4. 运行闹钟
timer.schedule(task, date, 2);
}
运行结果