文章目录
1、线程概述
1.1 程序、进程、线程
进程包含了资源分配!
线程就是一个执行机构,进程的执行是交给线程去完成的。
进程是资源分配的独立单位,线程是调度执行的独立单位。
当一个进程分解出多个线程时,执行效率大大提高,因此有了多线程。
★ 下面四个重要方法,获取当前执行的线程的@¥*/@:
例如:
该程序在执行时会产生一个进程,并对应地产生了一个线程,这个线程来完成本次执行。
上面的程序产生了一个主线程,用来完成主函数中的方法体的内容:
1.2 多线程
上述代码全部是由main线程来执行的!
顺序是先完成所有聊天,再开始完成所有上传操作。并没有达到边聊天边上传的那种真实的“QQ”效果。
若想达到多线程的效果,如图所示——
编写一个聊天线程LTThread,继承自Thread类。里面实现/重写了run()方法,该方法的方法体内写好你的这个线程要执行什么东西!
下面的start()方法用来启动线程,即把线程创建出来以后分配资源。
上图代码的效果是:主线程在执行上传时,穿插着执行了聊天线程的聊天。
(它们在交替地使用CPU)
★ 下面再创建出一个上传线程,达到 三个线程 的效果,如图所示:
// 注:这三个线程都是为了很好地完成进程任务。进程有三个业务,一是qq主业务得执行,二三是聊天和下载也要执行。
把原本的“上传”方法—改写为—>“上传”线程:
主函数变更为——
三个线程的效果如图:
2、线程的创建
2.1 继承Thread类创建线程
补充:下面的办法给线程命名更加便捷——
★ 获取当前线程的一些属性——
2.2 实现Runnable接口创建线程
★ 使用方式:
看下面代码:对于同一个实例化对象 t2,同时开两个线程去执行——
2.3 使用Callable和Future创建线程
注意:这两种也都是接口!!!
★ 用法:
该方法可以有返回值!!
上述代码已经可以执行线程,但是如何获取sum的值呢?
“获取返回值”可能出现等待时间过长,因此要求做异常处理,下面这个图用了抛出异常——
2.4 一些特殊创建写法
★ 2.1-2.3 小结
匿名内部类实现、lambda表达式线程创建···
// 先不学了 有用再说。。
3、线程生命周期
3.1 生命周期描述
3.2 Thread常用方法
① start()
② yield() 让步
上述两个方法的实际应用——
t1让步,所以t2的都执行完了以后,才去搞t1,运行截图如下。
③ sleep()
使用示例:
④ join()
4、线程同步
4.1 线程同步-可见性(非线程安全)
★ volatile 关键字 ——
即,当volatile的变量被修改时,会立刻通知使用他的其他线程,重新读取该变量。
4.2 线程同步-原子性
原子性:语句并不是一次性就能执行完的,要分解为很多指令部分!这些个指令部分在执行时,中间是可以被打断的。
原子性问题——T1执行完第一个读取指令后,T2抢先把自己三条语句执行了,T1中断现场保护现场又恢复现场以后,继续执行2、3条指令,结果算出来的结果是错的。。。
上图中:T1在访问变量a时,T2不能去访问a了!!即,变量a不能被两个线程同时访问,否则就会出错!
解决办法:设立临界区,该区同一时间内只允许一个线程进入。
★ 临界区需要开锁和关锁—— synchronized关键词
// synchronized关键词在下一小节细讲
4.3 synchronized实例
上图代码中,测试1在使用f1时,测试2也进来了,同时访问了f1,这样就会出错了!!现在我们要把f1()方法变成临界资源——上锁:
(下图的执行结果显示:起到了“临界资源”的效果!)
上述代码中,执行t1时要去找实例化对象t要锁,t给了t1线程解锁以后,同一时间内就不会再给另一线程t2解锁。t1执行完以后,t2线程才会获得锁。
★ 注意理解下图概念:
静态方法的锁——
上述代码(static静态方法)中,每次执行时已经不再是找类的实例化对象要锁了,而是去找这个类要锁!!!
第三种,对语句块上锁!
4.4 lock接口 & ReentrantLock类
Lock相较于synchronized,需要程序猿人为地去释放锁(在finally语句中)。
// 注意,lock能完成synchronized的所有功能!!
用法——
我进门,把门一关,别人就进不来了。当我(某线程)执行完之后,我再把门打开。
4.5 wait()、notify()、notifyAll()
这三个方法都由Object类提供,用于进程同步。wait()是阻塞方法。notify()唤醒单个线程,notifyAll()唤醒所有线程。
用法——
略。
5、编程例题
第一,掌握使用Thread子类和Runnable接口创建多线程的方法。
第二,掌握线程的执行过程。
5.1 模拟龟兔赛跑
利用多线程技术编写一个模拟龟兔赛跑的程序,要求如下:
(1)乌龟每次跑一个单位,兔子每次跑10个单位;
(2)每个线程运行时,判断是否达到终点,如果到达终点,给出提示信息,未到终点则提示目前距离终点的距离,并判断是否领先;
(3)如果兔子领先,则显示“我跑得快,睡一觉”信息,并睡一会。
package guitu_saipao;
class Race implements Runnable {
private static String winner;
private volatile int rabbitdistance, turtledistance;
public void run() {
for (int i = 0; i <= 100; i++) {
if (Thread.currentThread().getName().equals("兔子")) {
if (gameover(i * 10)) {
break;
}
rabbitdistance = i * 10;
System.out.println(Thread.currentThread().getName() + "距离终点" + (100 - i * 10));
if (rabbitdistance > turtledistance) {
System.out.println("我跑得快,睡一觉");
try {
Thread.sleep(0, 1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if (Thread.currentThread().getName().equals("乌龟")) {
if (gameover(i)) {
break;
}
turtledistance = i;
System.out.println(Thread.currentThread().getName() + "距离终点" + (100 - i));
}
}
}
private boolean gameover(int steps) {
if (winner != null) {
return true;
}
if (steps >= 100) {
winner = Thread.currentThread().getName();
System.out.println(winner + "获胜");
return true;
}
return false;
}
}
public class Main {
public static void main(String[] args) {
Race race = new Race();
new Thread(race, "兔子").start();
new Thread(race, "乌龟").start();
}
}
5.2 模拟多人过独木桥
编写多线程应用程序,模拟多人过独木桥的模拟。独木桥每次只能通过一个人,每个人通过木桥的时间为5秒,随机生成10个人,同时准备过此独木桥,显示一下每次通过独木桥人的姓名。需要用到随机数。
注意:
(1)开始过桥时输出:开始过桥!过完桥后输出:已过桥!
(2)随机选人的时候,每个人都要选到,不能重复选。
package 多人过独木桥;
import java.util.LinkedHashSet;
import java.util.Random;
import java.util.Set;
public class dumuqiao implements Runnable{
private static int deng=0;
public void run() {
deng= deng+5000;
try
{
Thread.sleep(deng);
} catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 独木桥");
}
public static void main(String[] args) {
String ary[] ={"赵","钱","孙","李","周","吴","郑","王","冯","陈"};
dumuqiao gsd = new dumuqiao();
Set<Integer> set=new LinkedHashSet<Integer>();
while(true){
if(set.size() == 10){
break;
}
//乱序排列(随机)
int a=(int) (Math.random()*10);
set.add(a);
}
for(int b:set){
Thread th = new Thread(gsd, ary[b]);
th.start();
}
}
}
5.3 火车站卖票
哈尔滨火车站下面有三个火车票代售点:哈站、哈东站、哈西站,假如哈尔滨到北京的火车票总共是200张,如何用程序来实现三个售票点同时卖票的功能。注意:考虑线程同步问题,避免出现重复卖票问题。需要考虑同步问题。
package 火车站售票;
class Ticket implements Runnable{
private int TicketNum = 10; //100张火车票
private boolean flag = true;
private synchronized void sale()
{
if(TicketNum<=0)
{
flag = false;
return ;
}
TicketNum--;
System.out.println(Thread.currentThread().getName()+"卖了一张票,还剩"+TicketNum+"张票。");
}
public void run() {
while(flag)
{
sale();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
Ticket t = new Ticket();
Thread th1 = new Thread(t,"哈尔滨站");
Thread th2 = new Thread(t,"哈尔滨西站");
Thread th3 = new Thread(t,"香坊站");
th1.start();
th2.start();
th3.start();
}
}