学习大纲
- 线程简介
- 线程实现(重点)
- 线程状态
- 线程同步(重点)
- 线程通信问题
- 高级主题(线程池)
一、线程简介
1. 多任务
在生活中,我们一般会边吃饭边玩手机,或者边走路边玩手机,这样看起来我们在同时做着多个事情,但是其实我们大脑在同一时间只能处理一件事情,
2.多线程
例子:原来一条路,车太多了,开始堵塞,为了提高效率,我们多增加个车道,从此,妈妈在也不用担心道路阻塞了。玩王者荣耀也是如此,你和队友组队,大家都是多线程执行。

3.进程
在操作系统的运行的程序就是进程。一个进程可以有多个线程。
- 程序是指令和数据的有序集合,是静态的。
- 进程是程序的一次执行过程,是动态的,是系统资源分配的单位。
- 一个进程可以包含多个线程,但至少有一个线程,线程是cpu执行和调度的基本单位。
Java中的多线程是模拟出来的,真的的多线程是指有多个CPU,即 多核,比如 服务器。而模拟出来的cpu,在同一个时间点,cpu只能执行一个代码,因为cpu调度,不同线程切换很快,所以就有同时执行的错觉。
总结来说,就是宏观上并行,微观上串行。

二、线程实现(Thread,Runnable、Callable)
1、三种方式:
- Thread class ------> 继承 Thread 类 (重点)
- Runnable 接口 -----> 实现Runnable 接口(重点)
- Callable 接口 ------> 实现Callable 接口
2.实现代码学习
(1)创建线程方式一:继承Thread 类,重写 run() 方法,调用start()方法开启线程(注意,线程开启不一定执行,由cpu进行调度)
package thread;
/**
* @Author Janson
* @Date 2022/2/24 9:24
* @Version 1.0
*/
// 注意,线程开启不一定执行,由cpu进行调度
//创建线程方式一:继承Thread 类,重写 run() 方法,调用start()方法开启线程
public class TestThread extends Thread {
@Override
//重写 run() 方法
public void run(){
for (int i = 0; i < 200; i++) {
System.out.println("我在看代码-----"+i);
}
}
public static void main(String[] args) {
//main 线程,主线程
TestThread testThread = new TestThread();
//testThread.run();
testThread.start();
for (int i = 0; i < 20; i++) {
System.out.println("我在学习多线程:" + i );
System.out.println();
}
}
}
网图下载例子:
下载图片的类 借助 commons io 包中的 FileUtils 类的 copyURLToFile方法进行 下载 图片
package thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* @Author Janson
* @Date 2022/2/24 12:32
* @Version 1.0
*/
public class TestThreadDownlPic extends Thread {
private String name;
private String url;
//构造方法,调用的时候用于传递初始参数
public TestThreadDownlPic(String url,String name) {
this.name = name;
this.url = url;
}
//重写run方法
@Override
public void run(){
//下载图片的线程的执行体
WebDownload webDownload = new WebDownload();
webDownload.downloader(url,name);
System.out.println("下载了文件名为:" + name);
}
//主线程 main 方法
public static void main(String[] args) {
//创建 线程 对象
TestThreadDownlPic testThreadDownlPic1 = new TestThreadDownlPic(
"https://www.todesk.com/image/download/windows_pic.png",
//C:\Users\25393\Desktop\img\ 该地址为路径,1.jpg为文件名
"C:\\Users\\25393\\Desktop\\img\\1.jpg");
//如果不写路径,则默认在项目根路径下
TestThreadDownlPic testThreadDownlPic2 = new TestThreadDownlPic(
"https://www.todesk.com/image/logo-text.png",
"2.jpg");
TestThreadDownlPic testThreadDownlPic3 = new TestThreadDownlPic(
"https://www.todesk.com/image/news/1.jpg",
"3.jpg");
//启动线程
testThreadDownlPic1.start();
testThreadDownlPic2.start();
testThreadDownlPic3.start();
}
}
//下载图片的类
class WebDownload{
public void downloader(String url,String name){
try {
//借助 commons io 包中的 FileUtils 类的 copyURLToFile方法进行 下载 图片
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("执行webdownload类,出现io异常。");
}
}
}
(2)创建线程方式2 ,实现Runnable接口,重写run() 方法
实现Runnable 接口 实现线程时,需要用Thread 类 的代理进行启动线程,即 将Runnable 接口的实现类的对象,作为Thread 类的参数,创建对象,用该对象启动线程
package thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* @Author Janson
* @Date 2022/2/24 14:48
* @Version 1.0
*/
public class TestThreadRunnable implements Runnable {
private String url;
private String name;
public TestThreadRunnable(String url,String name){
this.name = name;
this.url = url;
}
@Override
public void run() {
//下载图片的线程的执行体
WebDownload webDownload = new WebDownload();
webDownload.downloader(url,name);
System.out.println("下载了文件名为:" + name);
}
public static void main(String[] args) {
//创建 线程 对象
TestThreadRunnable testThreadRunnable1= new TestThreadRunnable(
"https://www.todesk.com/image/download/windows_pic.png",
//C:\Users\25393\Desktop\img\ 该地址为路径,1.jpg为文件名
"C:\\Users\\25393\\Desktop\\img\\1.jpg");
//如果不写路径,则默认在项目根路径下
TestThreadRunnable testThreadRunnable2= new TestThreadRunnable(
"https://www.todesk.com/image/logo-text.png",
"2.jpg");
TestThreadRunnable testThreadRunnable3= new TestThreadRunnable(
"https://www.todesk.com/image/news/1.jpg",
"3.jpg");
//启动线程, 继承 thread 实现线程时,直接用实现类的对象 调用start方法启动线程
// 实现Runnable 接口 实现线程时,需要用Thread 类 的代理进行启动线程,
// 即 将Runnable 接口的实现类的对象,作为Thread 类的参数,创建对象,用该对象启动线程
new Thread(testThreadRunnable1).start();
new Thread(testThreadRunnable2).start();
new Thread(testThreadRunnable3).start();
}
}
class WebDownload2{
public void downloader(String url,String name){
try {
//借助 commons io 包中的 FileUtils 类的 copyURLToFile方法进行 下载 图片
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("执行webdownload类,出现io异常。");
}
}
}
3、小结

4. 龟兔赛跑
package thread;
/**
* 龟兔赛跑
* @Author Janson
* @Date 2022/2/24 15:57
* @Version 1.0
*/
public class Race implements Runnable {
private String winner;
@Override
public void run() {
for (int i=1;i<=100;i++){
//让兔子线程 sleep
if (Thread.currentThread().getName()=="兔子" && i%10==0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("线程 sleep 异常");
}
}
System.out.println(Thread.currentThread().getName() + "跑了"+ i +"步");
//程序终止条件
boolean flag = gameOver(i);
if (flag){
break;
}
}
}
//判断是否产生了赢家,产生了,程序终止
private boolean gameOver(int steps){
//必须要判断winner 是否为空,不判断的话,程序会将另一个线程执行结束才终止
if (winner != null){
return true;
}else if (steps>=100){
winner = Thread.currentThread().getName();
System.out.println("最后的赢家:"+ winner);
return true;
}
return false;
}
//main方法 主线程
public static void main(String[] args) {
Race race = new Race();
new Thread(race, "乌龟").start();
Thread rabbit = new Thread(race, "兔子");
//设置线程的优先级
//rabbit.setPriority(Thread.MAX_PRIORITY);
rabbit.start();
}
}
5、实现Callable 接口
- 重写call方法
- 创建线程池对象,用submit 方法启动线程,有返回值
package thread;
import java.util.concurrent.*;
/**
* @Author Janson
* @Date 2022/2/24 19:56
* @Version 1.0
*/
public class TestCallable implements Callable {
@Override
public Object call() throws Exception {
System.out.println("执行多线程了:");
return 1;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable testCallable = new TestCallable();
ExecutorService executorService = Executors.newCachedThreadPool();
Future submit = executorService.submit(testCallable);
System.out.println(submit.get());
executorService.shutdownNow();
}
}
6.静态代理模式
- 真实对象和代理对象都要实现同一个接口
- 代理对象要代理真实对象,即 在代理对象中调用真实对象
package thread;
/**
* @Author Janson
* @Date 2022/2/24 21:16
* @Version 1.0
*/
// 静态代理模式总结:
//真实对象和代理对象都要实现同一个接口
//代理对象要代理真实对象,即 在代理对象中调用真实对象
public class StaticProxy {
public static void main(String[] args) {
//You you = new You();
//WeddingCompany weddingCompany = new WeddingCompany(you);
//weddingCompany.happyMarry();
//下边一行 ,和上边三行功能一样
new WeddingCompany(new You()).happyMarry();
//new WeddingCompany(()-> System.out.println("我爱你")).happyMarry();
}
}
// 结婚接口
interface Marry{
void happyMarry();
}
//结婚对象 You
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("janson要娶媳妇了。");
}
}
//代理公司,代理你办理一切事宜
class WeddingCompany implements Marry{
private Marry target;
public WeddingCompany(Marry marry) {
this.target = marry;
}
@Override
public void happyMarry() {
before();
target.happyMarry(); //执行真实的对象
after();
}
private void after() {
System.out.println("结婚后,睡媳妇");
}
private void before() {
System.out.println("结婚前,接媳妇");
}
}
7、Lambda表达式
(1)简介


-
函数式接口:
*任何接口,如果只包含一个抽象方法,那么它就是一个函数式接口。
*对于函数式接口,我们可以通过 lambda 表达式来创建该接口的对象,简化编程 -
匿名内部类与lambda表达式存在相似之处:
* = 前边直接用 接口 创建对象,在 = 后边,不一样,
*匿名内部类时 new 接口{ 重写接口中的方法};
*lambda 表达式 是 ()->{需要执行的语句};
*注意:两个类的 } 后边都由 ; ,不要忘记。
(2)代码示例
package thread;
//为什么要使用 lambda 表达式
//避免匿名内部类定义过多
//可以让代码看起来更简洁
//去掉了一堆没有意义的代码,只留下核心的代码
/**
* @Author Janson
* @Date 2022/2/25 10:26
* @Version 1.0
*/
/**
*函数式接口:
*任何接口,如果只包含一个抽象方法,那么它就是一个函数式接口。
*对于函数式接口,我们可以通过 lambda 表达式来创建该接口的对象,简化编程
*/
public class TestLambda {
public static void main(String[] args) {
//外部类实现接口
Like1 like1 = new Like1();
like1.lambda();
//内部类实现接口
Like2 like2 = new Like2();
like2.lambda();
//3.局部内部类
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda3");
}
}
Like3 like3 = new Like3();
like3.lambda();
/**
* 匿名内部类与lambda表达式存在相似之处:
* = 前边直接用 接口 创建对象,在 = 后边,不一样,
*匿名内部类时 new 接口{ 重写接口中的方法};
*lambda 表达式 是 ()->{需要执行的语句};
*注意:两个类的 } 后边都由 ; ,不要忘记。
*/
//4.匿名内部类,没有类名称,用接口进行创建
ILike like4 = new ILike() {
@Override
public void lambda() {
System.out.println("I like lambda4");
}
};
like4.lambda();
//5.用lambda 表达式
ILike like5 = () -> {
System.out.println("I like lambda5");
};
like5.lambda();
}
//2.内部类
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda2");
}
}
}
//定义一个函数式接口
interface ILike{
void lambda();
}
//1.实现类 外部类
class Like1 implements ILike{
@Override
public void lambda() {
System.out.println("I like Lambda1");
}
}
三、线程状态(五个状态)
-
创建
-
就绪
-
运行
-
阻塞
-
死亡
(1)状态讲解


(2)线程方法

(3)线程停止 -
1.建议线程正常停止 ——————> 利用次数,不建议死循环
-
2.建议使用标志位------> 设置一个标志位
-
3.不要使用stop 或者 destory 等过时 或者不建议使用的方法

代码实现:
package thread;
/**
* @Author Janson
* @Date 2022/2/25 12:26
* @Version 1.0
*/
//测试 stop
// 1.建议线程正常停止 ——————> 利用次数,不建议死循环
// 2.建议使用标志位------> 设置一个标志位
// 3.不要使用stop 或者 destory 等过时 或者不建议使用的方法
public class ThreadStop implements Runnable{
private boolean flag = true;
public static void main(String[] args) {
ThreadStop threadStop = new ThreadStop();
new Thread(threadStop).start();
for (int i = 0; i <1000 ; i++) {
System.out.println("main线程" + i);
if (i==900){
threadStop.stop();
System.out.println("线程该停止了");
}
}
}
//写一个公开的方法停止线程
public void stop() {
this.flag = false;
}
@Override
public void run() {
int i = 0;
while (flag){
System.out.println("run ...... Thread..." + i++);
}
}
}
(4)线程 休眠 sleep
- 作用:模拟网络延时, 放大问题的发生性
- sleep(时间)指定当前线程阻塞的毫秒数;
- sleep存在异常InterruptedException;
- sleep时间达到后线程进行就绪状态
- sleep可以模拟网络延时,倒计时等;
- 每一个对象都有一个锁,sleep不会释放锁;
代码:倒计时例子
package thread;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @Author Janson
* @Date 2022/2/25 15:23
* @Version 1.0
*/
//模拟网络延时, 放大问题的发生性
public class TestThreadSleep implements Runnable{
@Override
public void run() {
while (true){
int i = 10;
for (; i >0; i--) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
if (i == 0)
break;;
}
}
public static void main(String[] args) throws InterruptedException {
//TestThreadSleep testThreadSleep = new TestThreadSleep();
//new Thread(testThreadSleep).start();
Date date = new Date(System.currentTimeMillis());
while (true){
//ms 级别
Thread.sleep(1000);
int i = 0;
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
i++;
date = new Date(System.currentTimeMillis());
if (i == 10)
break;
}
}
}
(5)线程礼让 yield
- 礼让线程,让当前正在执行的线程暂停,但不阻塞;
- 将线程从运行状态转为就绪状态;
- 让cpu重写调度,礼让不一定成功,看cpu心情。
代码
package thread;
/**
* @Author Janson
* @Date 2022/2/27 12:49
* @Version 1.0
*/
public class TestThreadYield implements Runnable{
@Override
public void run() {
System.out.println("副线程执行前----");
System.out.println("副线程执行后----");
}
public static void main(String[] args) {
TestThreadYield testThreadYield = new TestThreadYield();
new Thread(testThreadYield).start();
System.out.println("main 线程执行前----");
//主线程先执行,遇到该命令,主线程就会转为 就绪 状态,与 副线程 重写开始竞争cpu
//谁竞争到,谁就先执行
Thread.yield();
System.out.println("main 线程执行后----");
}
}
(6)线程强制执行 join
- Join 合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞
- 类似于插队
- 抢占方式,一旦该线程启动,就会抢占cpu,直到该线程执行结束
代码实现:
package thread;
/**
* join() 抢占方式,一旦该线程启动,就会抢占cpu,直到该线程执行结束
* @Author Janson
* @Date 2022/2/27 12:42
* @Version 1.0
*/
public class TestThreadJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i <200 ; i++) {
System.out.println("测试线程执行------");
}
}
public static void main(String[] args) throws InterruptedException {
TestThreadJoin testThreadJoin = new TestThreadJoin();
new Thread(testThreadJoin).start();
//抢占cpu,接下来 cpu 只执行该线程,而不执行主线程,直至该线程执行结束
new Thread(testThreadJoin).join();
for (int i = 0; i <1000 ; i++) {
System.out.println("main线程执行------");
}
}
}
(6)线程状态(Thread.State())

(7)线程优先级
- Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度按个线程来执行。
- 线程优先级用数字表示,范围1-10;
- Thread.MIN_PRIORITY = 1;
- Thread.MAX_PRIORITY = 10;
- Thread.NORM_PRIORITY = 5;
- 使用以下方法改变或获取优先级,优先级 设置要 先于线程启动
- getPriority() , setPriority(int xxx),
代码实现:
package thread;
/**
* 优先级设置
* @Author Janson
* @Date 2022/2/27 12:56
* @Version 1.0
*/
public class TestThreadPriority implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+
"线程的优先级为:"+
Thread.currentThread().getPriority()+"执行次数" + i);
}
}
public static void main(String[] args) {
TestThreadPriority testThreadPriority = new TestThreadPriority();
//线程1
Thread thread1 = new Thread(testThreadPriority,"1线程");
thread1.setPriority(Thread.MAX_PRIORITY);
thread1.start();
//线程2
Thread thread2 = new Thread(testThreadPriority,"2线程");
thread2.setPriority(8);
thread2.start();
//线程3
Thread thread3 = new Thread(testThreadPriority,"3线程");
thread3.setPriority(4);
thread3.start();
//线程4
Thread thread4 = new Thread(testThreadPriority,"4线程");
thread4.setPriority(1);
thread4.start();
//线程5
Thread thread5 = new Thread(testThreadPriority,"5线程");
thread5.setPriority(6);
thread5.start();
System.out.println("main线程的优先级为:" + Thread.currentThread().getPriority());
}
}
(8)守护线程 Daemon
- 线程分为用户线程和守护线程
- 虚拟机必须确保用户线程执行完毕
- 虚拟机不用等待守护线程执行完毕
- 如,后台记录操作日志,监控内存,垃圾回收等待。
- 守护线程会一直执行到所有线程终止,守护线程才会慢慢停止
代码实现:
package thread;
/**
* 守护进程
* 线程分为 用户线程 和 守护线程,setDaemon() 中的参数默认为false,既默认线程为用户线程
* 守护线程会一直执行到所有线程终止,守护线程才会慢慢停止
* 用户线程就是执行自己的生命周期
* @Author Janson
* @Date 2022/2/27 14:50
* @Version 1.0
*/
public class TestThreadDaemon {
public static void main(String[] args) {
God god = new God();
Person person = new Person();
Thread thread = new Thread(god);
//This method must be invoked before the thread is started.
thread.setDaemon(true);
thread.start();
new Thread(person).start();
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝守护者你");
}
}
}
class Person implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("人的一生要活得快快乐乐------" + i);
}
System.out.println("-----------good bye-----------world");
}
}
四、线程同步(synchronized,ReentrantLock,两种锁机制)
(1)锁机制
多个线程操作同一个资源时,造成线程不安全问题,例如上万人抢一百张票,就会出现数据紊乱问题,就需要线程同步解决问题。
- 采用线程锁,synchronized ,既可以锁方法,又可以 锁代码块
- ReentrantLock 只能锁代码块
synchronized (obj){
//需要锁的执行体
}

代码实现:买票案例
package thread.sysChronize;
/**
* 线程同步
* @Author Janson
* @Date 2022/2/27 15:45
* @Version 1.0
*/
public class TestThreadSysChronized {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"小黑").start();
new Thread(buyTicket,"小白").start();
new Thread(buyTicket,"小黄牛").start();
//String name = buyTicket.getClass().getName();
//System.out.println(name);
//String simpleName = buyTicket.getClass().getSimpleName();
//System.out.println(simpleName);
}
}
class BuyTicket implements Runnable{
private int ticket = 10;
//线程停止标志位
private boolean flag = true;
@Override
public void run() {
while (flag){
buy();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//采用线程锁,synchronized ,既可以锁方法,又可以 锁代码块
public synchronized void buy(){
//判断是否有票
if (ticket<=0){
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "买到了第 " + ticket-- + " 票");
}
}
代码实现二——银行转账案例
package thread.sysChronize;
import java.util.Arrays;
/**
* 银行账户转账案例,学习 线程不安全情况下, 加锁解决线程不安全问题
* @Author Janson
* @Date 2022/2/28 18:54
* @Version 1.0
*/
public class TestSyncBank{
private static final int ACCOUNTS = 100;
private static final int INITIALBALANCE = 1000;
private static final int MAXAMMOUNT = 1000;
public static void main(String[] args) {
Bank bank = new Bank(ACCOUNTS,INITIALBALANCE);
//可重入锁,在run方法中加,或者 在 lambda 表达式中加上,
//ReentrantLock reentrantLock = new ReentrantLock();
for (int i = 0; i < ACCOUNTS; i++) {
int fromaccount = i;
//调用线程接口,采用lambda 表达式进行,也可以实现通过该接口,重写run方法
Runnable runnable = ()->{
//reentrantLock.lock();
int toaccount = (int) (ACCOUNTS * Math.random());
int amount = (int) (MAXAMMOUNT * Math.random());
//调用转账方法
bank.transfer(amount,fromaccount,toaccount);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//reentrantLock.unlock();
}
};
new Thread(runnable).start();
}
}
}
class Bank{
private int[] account;
private int initBalance;
public Bank(int account,int initBalance) {
this.account = new int[account];
this.initBalance = initBalance;
Arrays.fill(this.account,this.initBalance);
}
//转账方法
//给 transfer 加锁synchronized,不然会出现线程不安全 ,加锁在方法上
public synchronized void transfer(int amount,int fromaccount,int toaccount){
System.out.println(Thread.currentThread());
if (account[fromaccount] >= amount){
account[fromaccount] -= amount;
}else
return;
System.out.printf("%d from %d to %d\n",amount,fromaccount,toaccount);
account[toaccount] += amount;
System.out.println("各个账户的总余额:" + getTotalBalance());
}
//获取所有账户的余额,正常来说 是 100000
private int getTotalBalance() {
int sum = 0;
for (int i = 0; i < account.length; i++) {
sum += account[i];
}
return sum;
}
}
(2)死锁
- 多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形,某一个同步代码块同时拥有“两个以上对象的锁”时,就可能发生死锁。


被折叠的 条评论
为什么被折叠?



