Java多线程基础巩固系列

it2025-08-21  7

多线程

多线程一.创建方式(两个重要+一个了解)1. extends Thread(重要)2. implements Runnable(重要+广泛使用)3. implements Callable (了解) 二.案例1. 购买车票2. 龟兔赛跑3. 网图下载 (借助commons Io依赖) 三.静态代理(婚庆公司的例子)好处 四.Lambda表达式总结案例(记录向lambda演变的过程)1. 接口+普通实现类2. 接口+静态内部类3. 接口+局部内部类4. 接口+匿名内部类5. 接口+lambda简化(无参)6.接口+lambda简化(带参) 五. 线程状态(5种)六.线程停止1. 自然结束2. 使用标志位3.JDK提供的Stop(不推荐,还有一个destroy方法已废弃) 七.线程休眠sleep结论1. 10秒倒计时2. 打印系统时间3. 模拟网络延时(网络购票) 八.礼让线程yield(礼让不一定成功)结论 九.插队线程join总结 十.观测线程的状态state十一.线程的优先级结论 十二. 守护线程daemon -人生不过三万天结论 十三. 线程同步(重点+难点)队列+锁并发形成条件 十四.三大不安全案例1. 购票2.银行账户取钱3.线程不安全的集合arraylist 十五.同步方法和同步块1.同步方法 public synchronized void method(int args){}2.同步块 十六. CopyOnWriteArrayList- 十七.死锁总结案例(口红、镜子-化妆) 十八.Lock锁总结案例(买票)lock与synchronized的对比 十九.生产者消费者问题- 二十.管程法- 二十一. 信号灯法- 二十二. 线程池总结 二十三. 总结

多线程

一.创建方式(两个重要+一个了解)

1. extends Thread(重要)

2. implements Runnable(重要+广泛使用)

3. implements Callable (了解)

二.案例

1. 购买车票

2. 龟兔赛跑

3. 网图下载 (借助commons Io依赖)

三.静态代理(婚庆公司的例子)

好处

做很多真实对象无法做的事情**。比如,婚庆公司可以布置会场,可以做一些服务工作,而新人只需关注结婚本身,婚礼则由婚庆公司来主持和控场。

package com.zhagnshijie.thread; public class StaticProxy { public static void main(String[] args) { WeddingCompany weddingCompany = new WeddingCompany(new You()); weddingCompany.HappyMarry();//代理target进行结婚 } } interface Marry{ void HappyMarry(); } //真实对象 被代理对象 class You implements Marry{ @Override public void HappyMarry() { System.out.println("嘿嘿嘿,要结婚了,超开心!"); } } //代理 婚庆公司 class WeddingCompany implements Marry{ private Marry target; WeddingCompany(Marry target){ this.target = target; } @Override public void HappyMarry() { before(); this.target.HappyMarry(); after(); } private void before() { System.out.println("结婚之前,布置婚礼现场!"); } private void after() { System.out.println("结婚之后,收取尾款!"); } }

四.Lambda表达式

总结

1. lambda表达时前提函数式接口(任何接口,如果只包含**唯一的一个抽象方法**,那么就是一个函数式接口。对于函数式接口,我们可以通过lambda表达式来常见该接口的对象) 2. lambda表达式只有有一行代码的时候,才能把{}去掉简化成一行,如果有多行,必须使用{}包裹。 3. 多个参数,可以去掉参数类型,要去掉都去掉,不去都不去,但是()必须有! 4. 代码看起来简洁。 5. 去掉一堆没有意义的代码 ,只留下核心逻辑。

案例(记录向lambda演变的过程)

1. 接口+普通实现类

public interface Testlambda { public static void main(String[] args) { Like like = new Like(); like.lambda(); //I like lambda } } //0.定义一个函数式接口(一个接口,且仅包含一个方法) interface Ilike{ void lambda(); } //1.实现类 class Like implements Ilike{ @Override public void lambda() { System.out.println("I like lambda"); } }

2. 接口+静态内部类

public interface Testlambda { //2.静态内部类 static class Like implements Ilike{ @Override public void lambda() { System.out.println("I like lambda-静态内部类"); } } public static void main(String[] args) { Like like = new Like(); like.lambda(); //I like lambda-静态内部类 } } //0.定义一个函数式接口(一个接口,且仅包含一个方法) interface Ilike{ void lambda(); }

3. 接口+局部内部类

public interface Testlambda { public static void main(String[] args) { //3.局部内部类 class Like implements Ilike{ @Override public void lambda() { System.out.println("I like lambda-局部内部类"); } } Like like = new Like(); like.lambda(); //I like lambda-局部内部类 } } //0.定义一个函数式接口(一个接口,且仅包含一个方法) interface Ilike{ void lambda(); }

4. 接口+匿名内部类

public interface Testlambda { public static void main(String[] args) { //4.匿名内部类(没有类的名称,必须借助接口或父类) Ilike like = new Ilike() { @Override public void lambda() { System.out.println("I like lambda --匿名内部类"); } }; like.lambda();//I like lambda --匿名内部类 } } //0.定义一个函数式接口(一个接口,且仅包含一个方法) interface Ilike{ void lambda(); }

5. 接口+lambda简化(无参)

public interface Testlambda { public static void main(String[] args) { //4.jdk1.8 用lambda简化 Ilike like = () ->{ System.out.println("I like lambda --lambda简化"); }; like.lambda();//I like lambda --lambda简化 } } //0.定义一个函数式接口(一个接口,且仅包含一个方法) interface Ilike{ void lambda(); }

6.接口+lambda简化(带参)

public class Testlambda2 { public static void main(String[] args) { Show show = (a,b)-> System.out.println("跑通过了 程序!"+ a+" 年龄:"+b); show.showmsg("世界",18); //跑通过了 程序!世界 年龄:18 } } //0.定义一个接口 interface Show{ void showmsg(String a,Integer b); }

五. 线程状态(5种)

新建 就绪 等待 计时等待 阻塞 死亡

六.线程停止

1. 自然结束

2. 使用标志位

public class ThreadStop implements Runnable{ //1.确定一个标志位 private boolean flag = true; @Override public void run() { int i= 0; while (flag) { System.out.println("-------次线程跑到"+i++); } } //2.设置一个公开的方法停止线程,设置标志位 public void stop(){ this.flag = false; } public static void main(String[] args) { ThreadStop threadStop = new ThreadStop(); new Thread(threadStop).start(); for (int i = 0; i < 1000; i++) { System.out.println("主线程跑了"+i); if(i==800) { System.out.println("此线程要停止啦!!!"); threadStop.stop(); } } } }

3.JDK提供的Stop(不推荐,还有一个destroy方法已废弃)

七.线程休眠sleep

结论

放大问题的发生性。sleep(时间) 值得是当前线程阻塞的毫秒数。sleep存在异常InterruptedException。sleep时间到达以后线程进入就绪状态。sleep可以模拟网络延时,倒计时等。每个对象都有一把锁,sleep不会释放锁。

1. 10秒倒计时

public class TestSleep { public static void main(String[] args) { tenDown();//调用倒计时(直接调用类的方法,而不用new一个类的实例) } //倒计时10秒 public static void tenDown(){//类的静态方法,可以直接调用 int num =10; while(true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(num--); if(num<=0) break; } } }

2. 打印系统时间

public class SleepClock { public static void main(String[] args) { //打印当前系统时间 Date startTime = new Date(System.currentTimeMillis());//获取当前系统时间 while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime)); startTime = new Date(System.currentTimeMillis());//更新当前系统时间 } } }

3. 模拟网络延时(网络购票)

八.礼让线程yield(礼让不一定成功)

结论

礼让线程,让当前正在执行的线程暂停,但不阻塞将线程从运行状态转为就绪状态让cpu重新调度,礼让不一定成功,看cpu心情 public class YieldThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"开始执行"); Thread.yield();//礼让 System.out.println(Thread.currentThread().getName()+"停止执行"); } public static void main(String[] args) { YieldThread yieldThread = new YieldThread(); new Thread(yieldThread,"a").start(); new Thread(yieldThread,"b").start(); } }

九.插队线程join

总结

join合并线程,等待此线程执行完毕之后,再执行其他线程,其他线程阻塞。可以理解强制插队,可能在这之前是线程交替执行,一旦调用了A.join()方法之后,就一定是A线程执行完毕之后,再执行其他的。 public class TestJoin implements Runnable{ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("vip在执行----"+i); } } public static void main(String[] args) throws InterruptedException { TestJoin testJoin = new TestJoin(); Thread thread = new Thread(testJoin); thread.start(); for (int i = 0; i < 1000; i++) { System.out.println("主线程正在执行----"+i); if(i==500) { thread.join();//插队 } } } }

十.观测线程的状态state

public class ViewState implements Runnable{ @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("-----runing-----"); } } public static void main(String[] args) { ViewState viewState = new ViewState(); Thread thread = new Thread(viewState); Thread.State state = thread.getState();//获取启动钱的状态 System.out.println(state); thread.start(); state = thread.getState(); System.out.println(state); while(state!=Thread.State.TERMINATED){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } state = thread.getState(); System.out.println(state); } } }

十一.线程的优先级

结论

优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看cpu的调度。优先级MIN=1 MAX=10 NORMAL=5,超出这个范围会报错。

设置优先级

MyPriority myPriority = new MyPriority(); Thread thread1 = new Thread(myPriority); thread1.setPriority(2); thread1.start();

获取优先级(输出当前线程的优先级)

System.out.println(Thread.currentThread().getName()+"------正在执行"+"优先级是:"+Thread.currentThread().getPriority());

十二. 守护线程daemon -人生不过三万天

结论

线程分为用户线程,守护线程虚拟机必须确保用户线程执行完毕虚拟机不用等待守护线程执行完毕如后台记录操作日志,监控内存,垃圾回收等 public class DeamonThread{ public static void main(String[] args) { God god = new God(); You you = new You(); Thread threadYou = new Thread(you); threadYou.start(); Thread threadGod = new Thread(god); threadGod.setDaemon(true);//默认为false,为false的都为用户线程 threadGod.start(); } static class You implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("你正在运行---"); } } } static class God implements Runnable{ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("上帝守护着你---"+i); } } } } 你正在运行--- 你正在运行--- 你正在运行--- 你正在运行--- 你正在运行--- 你正在运行--- 你正在运行--- 你正在运行--- 你正在运行--- 你正在运行--- 上帝守护着你---0 上帝守护着你---1 上帝守护着你---2 上帝守护着你---3 Process finished with exit code 0

十三. 线程同步(重点+难点)队列+锁

并发

多个线程同时操作同一个对象。 类似于排队在同一个窗口买饭。

形成条件

队列+锁

十四.三大不安全案例

1. 购票

public class UnsafeBuyTickets { public static void main(String[] args) { BuyTickets buyTickets = new BuyTickets(); new Thread(buyTickets,"a").start(); new Thread(buyTickets,"b").start(); new Thread(buyTickets,"c").start(); } } class BuyTickets implements Runnable{ private int ticketsnum=10;//最初有十张票 boolean flag=true;//true可以买票 false不可以买票 @Override public void run() { //买票 while(flag){ buy(); if(ticketsnum<=0) flag = false; } } void buy(){ System.out.println(Thread.currentThread().getName()+"买了"+ticketsnum--+"号票"); } }

2.银行账户取钱

ublic class UnsafeAccount { public static void main(String[] args) { Account account = new Account(200,"结婚");//新建账户实例,初始化账户信息 DrawMoney you = new DrawMoney(account,50,"你"); DrawMoney firlFriend = new DrawMoney(account,100,"媳妇"); you.start(); firlFriend.start(); } } //账户 class Account{ int money; String name; public Account(int money,String name){ this.money = money; this.name = name; } } //银行 class DrawMoney extends Thread{ //账户 Account account; //当次取出多少钱 int drawingMoney; //当前手里多少钱 int nowMoney; public DrawMoney(Account account,int drawingMoney,String name){ super(name);//传入当前线程的名字。以便于下面使用this.getName()表达线程名称 this.account = account; this.drawingMoney=drawingMoney; } //取钱 @Override public void run() { //模拟网络延时 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } //判断有没有钱 if(account.money-drawingMoney<0){ System.out.println(Thread.currentThread().getName()+"钱不够,取不了!"); return; }else{ //银行卡内余额=余额-取出来的钱 account.money=account.money - drawingMoney; //手中的钱 nowMoney = nowMoney + drawingMoney; System.out.println("卡中余额:"+account.money+" 手中的钱:"+nowMoney); System.out.println(this.getName()+"手里的钱:"+nowMoney); } } }

3.线程不安全的集合arraylist

public class UnsafeList { public static void main(String[] args) { ArrayList<String> arrayList = new ArrayList<String>(); for (int i = 0; i < 10000; i++) { new Thread( ()->arrayList.add(Thread.currentThread().getName()) ).start(); } System.out.println(arrayList.size()); } } //这个可以验证arranList与Vector的线程是否安全的问题 public class UnsafeList { public static void main(String[] args) { ArrayList<String> arrayList = new ArrayList<String>(); //Vector<String> vector = new Vector<String>(); for (int i = 0; i < 10000; i++) { new Thread( ()->arrayList.add(Thread.currentThread().getName()) ).start(); //new Thread( ()->vector.add(Thread.currentThread().getName()) ).start(); } try { Thread.sleep(20000);//给线程一些时间,不然的话,你直接通过主线程打印出来的数据可能不是最终的。 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(arrayList.size()); //System.out.println(vector.size()); } }

十五.同步方法和同步块

1.同步方法 public synchronized void method(int args){}

每个对象对应一把锁,每个synchronized方法必须获得调用该方法的对象的锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,知道该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。

方法里面只有需要修改的内容才需要锁。

缺点 : 若一个大的方法申明为synchronized将会影响效率。

锁的太多,浪费资源。

2.同步块

synchronized (Obj) { }

Obj称为同步监视器 (Obj是执行过程中需要增删改查的对象,会改变的对象)

Obj可以是任意对象,但是推荐使用共享资源作为同步监视器同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class.

Obj同步监视器的执行过程

第一个线程访问,锁定同步监视器,执行其代码第二个线程访问,发现同步监视器被锁定,无法访问第一个线程访问完毕,解锁同步监视器第二个线程访问,发现同步监视器没有锁,然后锁定并访问

十六. CopyOnWriteArrayList

-

十七.死锁

总结

多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的清情形,某一个**同步块同时拥有“两个以上对象的锁“**时,可能会发生”死锁“问题。

产生死锁的四个必要条件

互斥条件:一个资源每次只能被一个进程使用

请求与保持条件:一个进程因请求资源而阻塞时,对已经获得的资源保持不放

不剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺

循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系

上述四个条件必要条件,只要破坏其中一个或一个以上,就可以比避免死锁的发生。

案例(口红、镜子-化妆)

package com.zhagnshijie.thread; public class DeadLock { public static void main(String[] args) { MakeUp girl1 = new MakeUp(0,"灰姑娘"); MakeUp girl2 = new MakeUp(1,"白雪公主"); new Thread(girl1,"灰姑娘").start(); new Thread(girl2,"白雪公主").start(); } } //口红 class LickRed{ } //镜子 class Mirror{ } //化妆 class MakeUp implements Runnable { //保证资源一种只有一份(口红、镜子各有一个),用static来保证 static private LickRed lickred = new LickRed(); //这里可能有问题 static private Mirror mirror = new Mirror(); private int choice; private String user; @Override public void run() {//线程体 makeup();//化妆 } //构造器是外部来调用的,所以肯定是public ... public MakeUp(int choice, String user) {//构造方法 this.choice = choice; this.user = user; } void makeup() { if (choice == 0) {//一上来就选择拥有口红 synchronized (lickred) { System.out.println(Thread.currentThread().getName() + "获得了口红"); try { Thread.sleep(1000);//防止一瞬间把两个资源都拿走 } catch (InterruptedException e) { e.printStackTrace(); } synchronized (mirror) { System.out.println(Thread.currentThread().getName() + "获得了镜子"); } } } else { synchronized (mirror) { //一上来就选择拥有镜子 System.out.println(Thread.currentThread().getName() + "获得了镜子"); try { Thread.sleep(2000);//防止一瞬间把两个资源都拿走 } catch (InterruptedException e) { e.printStackTrace(); } } synchronized (lickred) { System.out.println(Thread.currentThread().getName() + "获得了口红"); } } } }

十八.Lock锁

总结

jdk5.0之后,java提供了更强大的线程同步机制----通过显式定义同步锁对象来实现同步。同步锁使用lock来充当java.util.concurrent.locks接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在线程实现安全的控制中,比较常用的是ReentrantLock,可以显式加锁,释放锁

案例(买票)

public class LockTickets { public static void main(String[] args) { buyTicket buyTicket = new buyTicket(); new Thread(buyTicket,"a").start(); new Thread(buyTicket,"b").start(); new Thread(buyTicket,"c").start(); } } class buyTicket implements Runnable{ private final ReentrantLock reentrantLock = new ReentrantLock(); int num = 20; @Override public void run() { while (true){ reentrantLock.lock();//上锁 try{ if(num>0){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"购买了"+num--+"号票"); } else { break; } } finally { reentrantLock.unlock();//开锁 } } } }

lock与synchronized的对比

Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放

Lock只有代码块锁,synchronized有代码块锁和方法锁

使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供了更多的子类)

使用优先顺序

Lock -> 同步代码块 (已经进入了方法体,分配了相应资源) ->同步方法 (在方法体之外)

十九.生产者消费者问题

-

二十.管程法

-

二十一. 信号灯法

-

二十二. 线程池

总结

ExecutorService是真正的线程接口。常见的子类ThreadPoolExecutorExecutors:工具类、线程池的工厂类,用于创建返回不同的线程池 public class TestPool { public static void main(String[] args) { //1.创建服务 创建线程池 ExecutorService service = Executors.newFixedThreadPool(10);//创建一个有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()); } }

二十三. 总结

最新回复(0)