一、线程简介(和狂神学的线程总结)
进程(process):在操作系统中运行的程序就是进程,比如你的QQ,播放器,游戏,ide等等。
线程(Thread):一个进程可以有多个线程,如视频中同时听声音,看图像,看弹幕等等。
总结:一个进程中包含多个线程。
二、线程实现(重点)
1、继承Thread类(重点)
package 基础;
//创建线程方式一:继承Thread类,重写run方法,调用start开启线程
//总结:注意,线程开启不一定立即执行,由cpu调度执行
public class Test_Thread1 extends Thread {
// 重写run方法:直接输入run+回车
@Override
public void run() {
// run方法线程体
for (int i = 0; i < 2000; i++) {
System.out.println("线程"+i);
}
}
public static void main(String[] args) {
// main线程,主线程
// 创建一个线程对象(注意:这里的new就是当前类的名字)
Test_Thread1 thread1 = new Test_Thread1();
//调用start()方法开启线程
thread1.start();//run方法和main方法两个方法是随机执行的
// thread1.run();//先执行run方法,在执行00000000进程
for (int i = 0; i < 2000; i++) {
System.out.println("00000000进程"+i);
}
}
}
2、实现Runnable接口(重点)
package 基础;
//线程创建方式2:实现Runnable接口,重写run方法,执行线程需要丢入Runnable接口实现类,调用start方法
public class Test_Runnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("Runnable"+i);
}
}
public static void main(String[] args) {
// 创建Runnable接口的实现类对象
Test_Runnable runnable =new Test_Runnable();
// 创建线程对象,通过线程对象来开启我们的线程,代理
// Thread thread = new Thread(runnable);
// thread.start();
new Thread(runnable).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main"+i);
}
}
}
2.1、继承Thread类和实现Runnable接口对比
2.2、多线程模拟抢票
package 多线程;
public class Test_GrabTickets implements Runnable{
private int tickets =10;//设置票数
@Override
public void run() {
while (true){
if (tickets<=0) break;
try {
Thread.sleep(500);//设置线程延迟500毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了第"+tickets--+"张票");
// Thread.currentThread().getName() 获取线程的名字
}
}
public static void main(String[] args) {
Test_GrabTickets Tickets = new Test_GrabTickets();
new Thread(Tickets,"大海").start();//线程名字叫大海
new Thread(Tickets,"老师").start();//线程名字叫老师
new Thread(Tickets,"黄牛").start();//线程名字叫黄牛
}
}
2.3、龟兔赛跑
package 多线程;
/**
* 龟兔赛跑思路
* 1、首先来个赛带距离,然后要离终点越来约进
* 2、判断比赛是否结束
* 3、打印出胜利者
* 4、龟兔赛跑开始
* 5、故事中是乌龟赢的,兔子需要睡觉,所以我们来模拟兔子睡觉
* 6、终于,乌龟赢得了比赛
*/
public class Race implements Runnable{
private static String winner;
@Override
public void run() {
for (int i = 0; i <=100; i++) {
// 模拟兔子休息
if(Thread.currentThread().getName().equals("兔子")&&i%10==0){
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 判断比赛结果
boolean flag = gameOver(i);
// 如果比赛结束了,就停止程序
if (flag){
break;
}
System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
}
}
// 判断是否完成比赛
private boolean gameOver(int steps){
// 判断是否有胜利者
if(winner!=null){
return true;
}{
if (steps>=100){
winner = Thread.currentThread().getName();
System.out.println("winner is "+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
Race race = new Race();
new Thread(race,"乌龟1111111111").start();
new Thread(race,"兔子").start();
}
}
3、实现Callable接口(了解)
3.1、实现callbale的步骤
1、实现Callable接口,需要返回值类型
2、重写call方法,需要抛出异常
3、创建目标对象
3、创建执行对象:ExecutorService service= Executors.newFixedThreadPool(3);
3代表3个线程
5、提交执行结果:Future r1 =service.submit(callable);
6、获取结果:Boolean rs1 =r1.get();
7、关闭服务:service.shutdownNow();
3.2、callbale的好处
1、可以抛出异常
2、可以定义返回值
3.3、代码实现
package 多线程;
import java.util.concurrent.*;
/**
* callable的好处
* 1、可以抛出异常
* 2、可以定义返回值
*/
public class Test_Callable implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
for (int i = 0; i < 2; i++) {
System.out.println(Thread.currentThread().getName()+" Callable");
}
return false;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Test_Callable callable = new Test_Callable();
// 1、创建执行服务
ExecutorService service= Executors.newFixedThreadPool(3);
// 2、提交执行
Future<Boolean> r1 =service.submit(callable);
Future<Boolean> r2 =service.submit(callable);
Future<Boolean> r3 =service.submit(callable);
// new Thread((Runnable) r1).start();
// new Thread((Runnable) r2).start();
// new Thread((Runnable) r3).start();
// 3、获取线程返回值
Boolean rs1 =r1.get();
Boolean rs2 =r2.get();
Boolean rs3 =r3.get();
System.out.println(rs1);
System.out.println(rs2);
System.out.println(rs3);
// 关闭服务
service.shutdownNow();
}
}
4、静态代理
package 多线程;
/**
*静态代理模式总结:
* 真实对象和代理对象都要实现同一个接口
* 代理对象要代理真实角色
*
* 好处:
* 代理对象可以做很多真实对象做不了的事情
* 真实对象专注做自己的事情
*
*/
public class jiehun {
public static void main(String[] args) {
you you = new you();
new Thread( ()-> System.out.println("我爱你")).start();
new WeddingCompany(new you()).HappyMarry();//HappyMarry()就是Start的底层原理
// WeddingCompany weddingCompany = new WeddingCompany(new you());
// weddingCompany.HappyMarry();
}
}
//写了一个接口
interface Marry{
void HappyMarry();
}
//真实角色,你要去结婚
class you implements Marry{
@Override
public void HappyMarry() {
System.out.println("很开心,我要结婚了");
}
}
//代理角色,帮助你结婚
class WeddingCompany implements Marry{
// 代理谁 真实目标角色
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void HappyMarry() {
after();
this.target.HappyMarry();//这就是真实对象结婚
before();
}
private void after(){
System.out.println("结婚之前,布置现场");
}
private void before(){
System.out.println("结婚之后,收尾款");
}
}
5、Lambda表达式
Lambda表达式推导过程
//lambda推导过程,从1开始,到6结束
public class Test_Lambda2 {
//3、静态内部类
static class Like2 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda2");
}
}
public static void main(String[] args) {
new Like().lambda();
new Like2().lambda();
//4、局部内部类
class Like3 implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda3");
}
}
new Like3().lambda();
//5、匿名内部类,没用类的名称,必须借助接口或者父类
ILike like = new ILike() {
@Override
public void lambda() {
System.out.println("I like lambda4");
}
};
like.lambda();
//6、用lambda简化
like = () -> {
System.out.println("I like lambda5");
};
like.lambda();
}
}
//1、定义一个函数式接口(任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口)
interface ILike{
void lambda();
}
//2、实现类
class Like implements ILike{
@Override
public void lambda() {
System.out.println("I like lambda");
}
}
Lambda表达式简化过程
// 总结:lambda表达式只有在一行代码的情况下,才能简化成为一行。
// 如果有多行,那么就用代码块包裹
// 前提是接口为函数式接口(就是接口里面只能有一个方法)
// 多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号
public class Test_Lambda {
public static void main(String[] args) {
// 1、lambda表达式简化
ILove love=(int a) ->{
System.out.println("I love you "+a);
};
// 简化1.参数类型
love = (a) ->{
System.out.println("I love you "+a);
};
// 简化2.简化括号
love = a -> {
System.out.println("I love you "+a);
};
// 简化3.去掉花括号
love =a -> System.out.println("I love you "+a);
love.love(222);
}
}
//1、定义一个函数式接口(任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口)
interface ILove{
void love(int a);
}
三、线程状态
3.1、线程停止
//测试stop
//1、建议线程正常停止——》利用次数,不建议死循环
//2、建议使用标志位——》设置一个标志位
//3、不要使用stop或者destroy等过时或者jdk不建议使用的方法
public class Test_StopThread implements Runnable{
//1、设置一个标志位
private boolean flag=true;
@Override
public void run() {
int i=0;
while (flag){
System.out.println("run.....Thread"+i++);
}
}
//2、设置一个公开的方法停止线程,转换标志位
public void stop(){
this.flag=false;
}
public static void main(String[] args) {
Test_StopThread thread = new Test_StopThread();
new Thread(thread).start();
for (int i = 0; i < 1000; i++) {
System.out.println("main "+i);
if(i==900){
thread.stop();//调用stop方法切换标志位,让线程停止
System.out.println("线程停止了");
}
}
}
}
3.2、线程休眠
3.2.1、模拟网络延迟
//模拟网络延时的作用:放大问题的发生性
public class Test_Sleep implements Runnable{
private int tickets =10;//设置票数
@Override
public void run() {
while (true){
if (tickets<=0) break;
// 模拟网络延时
try {
Thread.sleep(500);//设置线程延迟500毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"抢到了第"+tickets--+"张票");
// Thread.currentThread().getName() 获取线程的名字
}
}
public static void main(String[] args) {
Test_Sleep test_sleep = new Test_Sleep();
new Thread(test_sleep,"大海").start();
new Thread(test_sleep,"老师").start();
new Thread(test_sleep,"黄牛").start();
}
}
3.2.2、模拟倒计时和打印系统时间
import java.text.SimpleDateFormat;
import java.util.Date;
public class Test_Sleep2 {
public static void main(String[] args) throws InterruptedException {
// printfDown();
tenDown();
}
// 打印系统时间方法
public static void printfDown() throws InterruptedException {
Date startTime = new Date(System.currentTimeMillis());//打印当前系统时间
while (true){//写个死循环,让时间一直打印
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime=new Date(System.currentTimeMillis());//更新当前时间
}
}
// 模拟倒计时方法
public static void tenDown() throws InterruptedException {
int num=10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if(num<0){
break;
}
}
}
}
3.3、线程礼让(yield)
//测试线程礼让
//礼让线程,让当前正在执行的线程暂停,但不阻塞
//让线程从运行状态转换为就绪状态
//礼让不一定成功,看cpu心情
public class Test_yield {
public static void main(String[] args) {
new Thread(new MyYield(),"a").start();
new Thread(new MyYield(),"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"线程开始执行");
Thread.yield();//线程礼让
System.out.println(Thread.currentThread().getName()+"线程开始执行");
}
}
3.4、Join合并线程
package 多线程;
//Join合并线程,待此线程执行完成后,在执行其他线程,其他线程阻塞
//可以想象成插队
public class Test_Join implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("线程vip来了"+i);
}
}
public static void main(String[] args) throws InterruptedException {
Test_Join join = new Test_Join();
Thread thread = new Thread(join);
thread.start();
for (int i = 0; i < 1000; i++) {
if (i==200){
thread.join();//让生成的线程插队,main主线程等待
}
System.out.println("main"+i);
}
}
}
3.5、线程的六种状态
package 多线程;
/**
* 线程状态。线程可以处于下列状态之一:
* NEW:至今尚未启动的线程处于这种状态。
* RUNNABLE:正在 Java 虚拟机中执行的线程处于这种状态。
* BLOCKED:受阻塞并等待某个监视器锁的线程处于这种状态。
* WAITING:无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
* TIMED_WAITING:等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
* TERMINATED:已退出的线程处于这种状态。
* 在给定时间点上,一个线程只能处于一种状态。这些状态是虚拟机状态,它们并没有反映所有操作系统线程状态。
*
* 注意:线程中断或者结束,一旦进入死亡状态,就不能再次启动
*/
public class Test_State {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{//lambda表达式
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(200);//线程延迟5秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("/");
});
//观察状态
Thread.State state = thread.getState();
System.out.println(state);//输出状态
//观察启动后
thread.start();//启动线程
state = thread.getState();//更新线程状态
System.out.println(state);//输出状态
while (state!=Thread.State.TERMINATED){//只要线程不终止,就一直输出状态
Thread.sleep(100);
state = thread.getState();//更新线程状态
System.out.println(state);//输出状态
}
}
}
3.6、线程优先级(Priority:优先)
//优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度
//优先级高的不一定先执行,大多数情况下还是优先级高的先执行
public class Test_Priority {
public static void main(String[] args) {
// 主线程默认优先级,默认值为:main-->5 但却是最先执行的
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
MyPriority myPriority = new MyPriority();
Thread t1=new Thread(myPriority,"t1");
Thread t2=new Thread(myPriority,"t2");
Thread t3=new Thread(myPriority,"t3");
Thread t4=new Thread(myPriority,"t4");
Thread t5=new Thread(myPriority,"t5");
Thread t6=new Thread(myPriority,"t6");
// 先设置优先级,在启动
t1.start();//t1-->5 不写默认值为5
t2.setPriority(1);//设置优先级
t2.start();//启动线程
t3.setPriority(4);
t3.start();
t4.setPriority(Thread.MAX_PRIORITY);//MAX_PRIORITY=10
t4.start();
t5.setPriority(8);
t5.start();
t6.setPriority(7);
t6.start();
}
}
class MyPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
}
}
3.7、守护线程
线程分为用户线程和守护线程。
虚拟机必须确保**用户线程**执行完毕(用户线程:main())
虚拟机不用等待**守护线程**执行完毕(守护线程:后台记录日志,监控内存,垃圾回收gc())
//测试守护线程
public class TestDaemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true);//默认是false表示是用户线程,正常的线程都是用户线程
//true表示的是守护线程
thread.start();//上帝守护线程启动
new Thread(you).start();
}
}
//上帝
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("上帝保佑着你");
}
}
}
//你
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("你一生都开心的活着");
}
System.out.println("goodbye world");
}
}
四、线程同步(重点)
4.1、线程同步介绍
线程同步:多个线程操作同一个资源
并发:同一个对象被多个线程同时操作
线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
4.2、线程同步实现
4.2.1、同步方法
package sun;
import javax.xml.namespace.QName;
//Unsafe:不安全的
//不安全的买票
//线程不安全,有负数
public class UnsafeBuyTicket {
public static void main(String[] args) {
BuyTicket buyTicket = new BuyTicket();
new Thread(buyTicket,"1").start();
new Thread(buyTicket,"2").start();
new Thread(buyTicket,"3").start();
}
}
class BuyTicket implements Runnable{
private int ticketNums=10;//票
boolean flag = true;
@Override
public void run() {
// 买票
while (flag){
try {
buy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// synchronized同步方法,锁的是this
private synchronized void buy() throws InterruptedException {
if(ticketNums<=0){
flag=false;
return;//停止
}
// 模拟延时
Thread.sleep(100);
// 买票
System.out.println(Thread.currentThread().getName()+"拿到"+ticketNums--);
}
}
4.2.2、同步块
package sun;
/**
* @Package : sun
* @Author : YYH
* @Date : 2023-02-16 16:29
* @Desc :
*/
public class UnsafeBank {
public static void main(String[] args) {
// 账户
Account account = new Account(1000,"结婚基金");
Drawing you = new Drawing(account,50,"你");
Drawing girlFriend = new Drawing(account,100,"girlFriend");
you.start();
girlFriend.start();
}
}
//账户
class Account {
int money;//余额
String name;//卡名
public Account (int money,String name){
this.money=money;
this.name=name;
}
}
//银行:模拟取款
class Drawing extends Thread{
Account account;//账户
int drawingMoney;//取了多少钱
int nowMoney;//现在手里有多少钱
public Drawing(Account account, int drawingMoney, String name) {
super(name);
this.account=account;
this.drawingMoney=drawingMoney;
}
// 取钱
// synchronized默认锁的是this
@Override
public void run() {
// 锁的对象是变化的量,需要增删改的
synchronized (account){
if (account.money-drawingMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够,取不了");
return;
}
try {
Thread.sleep(100);//延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
// 卡内余额=余额-你取的钱
account.money=account.money-drawingMoney;
// 你手里的钱
nowMoney=nowMoney+drawingMoney;
System.out.println(account.name+"余额为:"+account.money);
// Thread.currentThread().getName()=this.getName();
System.out.println(this.getName()+"手里的钱:"+nowMoney);
}
}
}
4.3、安全线程CopyOnWriteArrayList
4.4、死锁
死锁:多个线程相互抱着对方需要的资源,然后形成僵持
死锁产生的四个条件:
1、互斥条件:一个资源每次只能被一个进程使用。
2、请求与保持条件:一个进程因请求的资源而阻塞时,对已获得的资源保持不放。
3、不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。
4、循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
上面列出了死锁的四个必要条件,我们只要想办法破坏其中的任意一个或多个条件就可以避免死锁的发生。
package thread;
public class DeadLock {
public static void main(String[] args) {
Makeup g1 = new Makeup(1,"灰姑娘1111111");
Makeup g2 = new Makeup(0,"白雪公主");
g1.start();
g2.start();
}
}
//口红
class Lipstick{
}
//镜子
class Mirror{
}
class Makeup extends Thread{
// 需要的资源只有一份,用static来保证只有一份
static Lipstick lipstick = new Lipstick();
static Mirror mirror = new Mirror();
int choice;//选择
String girlName;//使用化妆品的人
Makeup(int choice,String girlName){
this.choice=choice;
this.girlName=girlName;
}
@Override
public void run() {
//化妆
try {
makeup();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 化妆,互相持有对方的锁,就是需要拿到对方的资源
private void makeup() throws InterruptedException {
if (choice==0){
synchronized (lipstick){//获得口红的锁
System.out.println(this.girlName+"获得口红的锁");
Thread.sleep(100);
}
synchronized (mirror){//一秒钟后想获得镜子
System.out.println(this.girlName+"获得镜子的锁");
}
}else {
synchronized (mirror){//获得镜子的锁
System.out.println(this.girlName+"获得镜子的锁");
Thread.sleep(100);
}
synchronized (lipstick){//一秒钟后想获得口红
System.out.println(this.girlName+"获得口红的锁");
}
}
}
}
4.5、Lock锁
Lock(锁)
1、从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当
2、java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
3、ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
package lock;
import java.util.concurrent.locks.ReentrantLock;
public class TestLock {
public static void main(String[] args) {
Lock lock = new Lock();
new Thread(lock).start();
new Thread(lock).start();
new Thread(lock).start();
}
}
class Lock implements Runnable{
int ticketNums = 10;
// 1、定义lock锁
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();//2、加锁
if (ticketNums>0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();//异常
}
System.out.println(ticketNums--);
}else {
break;
}
}finally {
lock.unlock();//3、解锁(如果代码有异常,要将unlock()写入finally语句块)
}
}
}
}
4.6、synchronized 与Lock的对比
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁) synchronized是隐式锁,出了作用域自动释放
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:
Lock >同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)
五、线程池
package thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//测试线程池
public class TestPool {
public static void main(String[] args) {
// 1、创建扶我,创建线程池
// newFixedThreadPool参数为:线程池大小
ExecutorService service = Executors.newFixedThreadPool(10);
// 2、执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 3、关闭链接
service.shutdown();
}
}
class MyThread implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
六、总结
package thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class huigu {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//Thread方法实现线程
new MyThread1().start();
//Runnable方法实现线程(最常用)
new Thread(new MyRunnable()).start();
//Callable方法实现线程(可以获得返回值)
FutureTask<Integer> futureTask = new FutureTask(new MyCallable());
new Thread(futureTask).start();
Integer integer = futureTask.get();
System.out.println(integer);
}
}
//Thread方法实现线程
class MyThread1 extends Thread {
@Override
public void run() {
System.out.println("MyThread");
}
}
//Runnable方法实现线程
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("MyRunnable");
}
}
//Callable方法实现线程
class MyCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
System.out.println("MyCallable");
return 100;
}
}