JUC01

it2025-05-03  4

文章目录

①. 多线程的概述②. 多线程的实现方式③. 设置和获取线程名称④. 线程优先级⑤. 线程控制⑥. 线程的生命周期⑦. 线程同步①. 买票案例出现的两个问题②. 同步代码块[synchronized]③. 同步方法 ⑧. 生产者和消费者模式⑨. Callable接口(创建线程)

①. 多线程的概述

1>.多线程的概述(面试高频问点)

①. 进程和线程 线程就是程序执行的一条路径,一个进程中可以包含多条线程多线程并发执行可以提高程序的效率,可以同时完成多项工作举例: [你打开一个word就是一个进程开启了,这个时候你重启后在打开word,这个时候有一个点击恢复的按钮,这就是一个线程,可能这个线程你看不到,你打字的时候,单词打错了,word中会有一个波浪线,这也是一个线程] ②. 多线程并行和并发的区别 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行(需要多核CPU)并发是指两个任务都请求运行,而处理器只能接收一个任务,就是把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行(12306抢票的案例) ③. wait | sleep的区别?功能都是当前线程暂停,有什么区别? wait放开手去睡,放开手里的锁;wait是Object类中的方法sleep握紧手去睡,醒了手里还有锁 ;sleep是Thread中的方法 ④. synchronized 和 lock的区别?

②. 多线程的实现方式

2>. 多线程的实现方式

①. 继承Thread //注意:打印出来的结果会交替执行 public class ThreadDemo{ public static void main(String[] args) { //4.创建Thread类的子类对象 MyThread myThread=new MyThread(); //5.调用start()方法开启线程 //[ 会自动调用run方法这是JVM做的事情,源码看不到 ] myThread.start(); for (int i = 0; i < 100; i++) { System.out.println("我是主线程"+i); } } } class MyThread extends Thread{ //2.重写run方法 public void run(){ //3.将要执行的代码写在run方法中 for(int i=0;i<100;i++){ System.out.println("我是线程"+i); } } } ②. 实现 Runnable 接口 源码分析如下:

public class RunnableDemo { public static void main(String[] args) { //4.创建Runnable的子类对象 MyRunnale mr=new MyRunnale(); //5.将子类对象当做参数传递给Thread的构造函数,并开启线程 //MyRunnale taget=mr; 多态 new Thread(mr).start(); for (int i = 0; i < 1000; i++) { System.out.println("我是主线程"+i); } } } //1.定义一个类实现Runnable class MyRunnale implements Runnable{ //2.重写run方法 @Override public void run() { //3.将要执行的代码写在run方法中 for (int i = 0; i < 1000; i++) { System.out.println("我是线程"+i); } } } ③. 两种实现多线程方式的区别 (1).查看源码 a.继承Thread:由于子类重写了Thread类的run(),当调用start()时,直接找子类的 run()方法 b.实现Runnable:构造函数中传入了Runnable的引用,成员变量记住了它,start()调用 run()方法时内部判断成员变量Runnable的引用是否为空,不为空编译时看的是Runnable的run(), 运行时执行的是子类的run()方法 (2).继承Thread a.好处是:可以直接使用Thread类中的方法,代码简单 b.弊端是:如果已经有了父类,就不能用这种方法 (3).实现Runnable接口 a.好处是:即使自己定义的线程类有了父类也没有关系,因为有了父类可以实现接口,而且接口 可以多现实的 b.弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法, 代码复杂 ④. 匿名内部类实现线程的两种方式(了解) public class Tread_Demo { public static void main(String[] args) { new Thread(){ //1.继承Thread类 @Override public void run(){//2. 重写run方法 for (int i = 0; i <1000 ; i++) {//3.将要执行的代码写在run方法中 System.out.println("匿名Thread内部类"); } } }.start();//4.开启线程 new Thread( new Runnable() {//1.将runnable的子类对象传递给Thread的构造方法 @Override public void run() {//2. 重写run方法 for (int i = 0; i <1000 ; i++) {//3.将要执行的代码写在run方法中 System.out.println("匿名Runnable内部类"); } } } ).start();//4.开启线程 } }

③. 设置和获取线程名称

3>.设置和获取线程名称

①. void setName(String name):将此线程的名称更改为等于参数 name //FileWriter MyThread my1 = new MyThread(); MyThread my2 = new MyThread(); //void setName(String name):将此线程的名称更改为等于参数 name my1.setName("高铁"); my2.setName("飞机"); my1.start(); my2.start();

②. String getName( ):返回此线程的名称

③. 注意 要是类没有继承Thread,不能直接使用getName( ) ;要是没有继承Thread,要通过Thread.currentThread得到当前线程,然后调用getName( )方法

④. static Thread currentThread​( ) 返回对当前正在执行的线程对象的引用

⑤. 通过构造函数设置线程名称

Thread(String name):通过带参构造进行赋值Thread(Runnable target , String name): public class MyThread extends Thread { public MyThread() {} public MyThread(String name) { super(name); } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(getName()+":"+i); } } } //Thread(String name) MyThread my1 = new MyThread("高铁"); MyThread my2 = new MyThread("飞机"); my1.start(); my2.start();

④. 线程优先级

4>.线程优先级

①. 线程有两种调度模型 [ 了解 ] 分时调度模式:所有线程轮流使用CPU的使用权,平均分配每个线程占有CPU的时间片抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些 [ Java使用的是抢占式调度模型 ] ②. Thread类中设置和获取线程优先级的方法 public final void setPriority(int newPriority):更改此线程的优先级public final int getPriority():返回此线程的优先级a. 线程默认优先级是5;线程优先级范围是:1-10; b. 线程优先级高仅仅表示线程获取的CPU时间的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果 ThreadPriority tp1 = new ThreadPriority(); ThreadPriority tp2 = new ThreadPriority(); ThreadPriority tp3 = new ThreadPriority(); tp1.setName("高铁"); tp2.setName("飞机"); tp3.setName("汽车"); //设置正确的优先级 tp1.setPriority(5); tp2.setPriority(10); tp3.setPriority(1); tp1.start(); tp2.start(); tp3.start();

⑤. 线程控制

①. static void sleep(long millis):使当前正在执行的线程停留(暂停执行)指定的毫秒数 [ 休眠线程 ]

②. void join():当前线程暂停,等待指定的线程执行结束后,当前线程再继续 (相当于插队加入) void join(int millis):可以等待指定的毫秒之后继续 (相当于插队,有固定的时间)

③.void setDaemon​(boolean on):将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 [ 守护线程 ] (相当于象棋中的帅,要是帅没了,别的棋子都会没用了)

④. void yield():让出cpu的执行权 [ 礼让线程 ]

⑥. 线程的生命周期

6>.线程的生命周期(面试常问)

⑦. 线程同步

7>. 线程同步

①. 买票案例出现的两个问题

①. 出现的问题:①. 相同票数出现多次;②.出现了负票

②. 代码展示:

public class SellTicket implements Runnable { //定义一个成员变量表示有100张票 private int tickets=100; public void run(){ while (true){ if(tickets>0){ try { //通过sleep()方法来等待 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"张票"); }else{ //System.out.println(""); } } } } @SuppressWarnings("all") public class SellTicketDemo { public static void main(String[] args) { SellTicket st = new SellTicket(); Thread t1 = new Thread(st, "窗口1"); Thread t2 = new Thread(st, "窗口2"); Thread t3 = new Thread(st, "窗口3"); t1.start(); t2.start(); t3.start(); } } ③. 原因分析: 为什么会出现相同的票为什么会出现负票

②. 同步代码块[synchronized]

①. 为什么出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准) 是否有多线程坏境是否有共享数据是否有多条语句操作共享数据 ②. 如何解决多线程安全问题 基本思想:让程序没有安全问题的坏境把多条语句操作的共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可[掌握]

③. 怎么锁起来呢? synchronized(任意对象):相当于给代码加锁了,任意对象就可以看成是一把锁

④. 同步的好处和弊端

好处:解决了多线程的数据安全问题弊端:当线程很多时,因为每个线程都会判断同步上的锁,这是很浪费资源的,无形中会降低程序的运行效率 public class SellTicket implements Runnable { //定义一个成员变量表示有100张票 private int tickets=100; private Object obj=new Object(); public void run(){ while (true){ //这里放的锁要是同一把锁才可以 synchronized(obj){ if(tickets>0){ try { //通过sleep()方法来等待 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets--+"张票"); }else{ //System.out.println(""); } } } } }

③. 同步方法

①. 同步方法:就是把synchronized 关键字加到方法上 同步方法的锁对象是什么呢? this 格式:修饰符 synchronized 返回值类型 方法名(方法参数){ }

private int tickets = 100; private Object obj = new Object(); private int x = 0; @Override public void run() { while (true) { if (x % 2 == 0) { // synchronized (obj) { synchronized (this) { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票"); tickets--; } } } else { sellTicket(); } x++; } } private synchronized void sellTicket() { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票"); tickets--; } } } ②. 同步静态方法:就是把synchronized关键字加到静态方法上 格式:修饰符 static synchronized 返回值类型 方法名(方法参数){ } 同步静态方法的锁对象是什么呢? 类名.class public class SellTicket implements Runnable { private static int tickets = 100; private Object obj = new Object(); private int x = 0; @Override public void run() { while (true) { if (x % 2 == 0) { synchronized (SellTicket.class) { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票"); tickets--; } } } else { sellTicket(); } x++; } } private static synchronized void sellTicket() { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票"); tickets--; } } }

13. 线程通信

①. wait( ):一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器

②. notify():一旦执行了此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的线程

③. notifyAll( ) :一旦执行此方法,就会唤醒所有被wait的线程

说明: (1). wait( )、notify( )、notifyAll( ) 三个方法必须使用在同步代码块或同步方法中 (2).wait( )、notify( )、notifyAll( ) 三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则会出现IllegalMonitorStateException (3).wait( )、notify( )、notifyAll( ) 三个方法是定义在java.lang.Object类中

//使用两个线程打印 1-100 。线程1 线程2 交替打印 public class PrintNumDemo { public static void main(String[] args) { PrintNum pn=new PrintNum(); new Thread(pn,"线程1").start(); new Thread(pn,"线程2").start(); } } class PrintNum implements Runnable{ private int num=1; @Override public void run() { while(true){ synchronized (this) { this.notify(); try { //睡眠1s,更好地显示效果 Thread.sleep(100); if (num <= 100) { String name = Thread.currentThread().getName(); System.out.println(name + "打印" + num); num++; this.wait(); }else{ break; } } catch (InterruptedException e) { e.printStackTrace(); } } } } } /* 银行有一个账户: 有两个储户分别向同一个账户存3000 */ public class AccountTest { public static void main(String[] args) { //创建一个账号 Account a=new Account(); Customer c1=new Customer(a); Customer c2=new Customer(a); c1.setName("用户1"); c2.setName("用户2"); //开启线程 c1.start(); c2.start(); } } /* */ class Account{ private double balance; /* public Account(double balance) { this.balance = balance; }*/ //存钱 public synchronized void deposit(double amt) { if(amt>0){ notifyAll(); try { Thread.currentThread().sleep(1000); balance+=amt; String name = Thread.currentThread().getName(); System.out.println(name+"存钱成功,余额为"+balance); wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Customer extends Thread{ private Account a1; public Customer(Account account){ this.a1=account; } @Override public void run() { String name = Thread.currentThread().getName(); for (int i = 0; i < 3; i++) { a1.deposit(1000); } System.out.println("-----"+Thread.currentThread().getName()); } }

⑧. 生产者和消费者模式

8>. 生产者和消费者

①. 生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。所谓生产消费者问题,实际上主要是包含了两类线程: 一类是生产者线程用于生产数据一类是消费者线程用于消费数据 ②. 为了耦合生产者和消费者的关系,通常会采用共享的数据区域,就像一个仓库 生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为消费者只需要从共享数据区中获取数据,并不需要关心生产者的行为

③. 为了体现生产和消费过程总的等待和唤醒,Java就提供了几个方法供我们使用,这几个方法就在Object类中Object类的等待和唤醒方法(隐式锁) viod wait( ):导致当前线程等待,直到另一个线程调用该对象的notify()方法和notifyAll()方法void notify( ):唤醒正在等待对象监视器的单个线程void notifyAll( ):唤醒正在等待对象监视器的所有线程注意:wait、notify、notifyAll方法必须要在同步块或同步方法里且成对出现使用 /* 1.题目: 现在两个线程,可以操作初始值为0的一个变量,实现一个线程对该变量加1, 一个线程对该变量减1,交替执行,来10轮,变量的初始值为0 2.思想: 1.在高内聚低耦合的前提下,线程->操作->资源类 2.判断操作唤醒[生产消费中] 3.多线程交互中,必须要放置多线程的虚假唤醒,也即(判断使用while,不能使用if) * */ public class ThreadWaitNotifyDemo { public static void main(String[] args) { AirCondition airCondition=new AirCondition(); new Thread(()->{ for (int i = 1; i <11 ; i++) airCondition.increment();},"线程A").start(); new Thread(()->{ for (int i = 1; i <11 ; i++) airCondition.decrement();},"线程B").start(); new Thread(()->{ for (int i = 1; i <11 ; i++) airCondition.increment();},"线程C").start(); new Thread(()->{ for (int i = 1; i <11 ; i++) airCondition.decrement();},"线程D").start(); } } class AirCondition{ private int number=0; public synchronized void increment(){ //1.判断 /* if(number!=0){*/ while(number!=0){ try { // 第一次A进来了,在number++后(number=1) C抢到执行权,进入wait转态 //这个时候,A抢到cpu执行权,唤醒等待的线程,如果这个时候c又抢到cpu执行权 //那么number=2,如果这个时候,c在在number++后(number=2),notifyAll之前 //A又抢到执行权,进入wait转态..... this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //2.干活 number++; System.out.println(Thread.currentThread().getName()+":"+number); //3.唤醒 this.notifyAll(); } public synchronized void decrement(){ /*if (number==0){*/ while (number==0){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } number--; System.out.println(Thread.currentThread().getName()+":"+number); this.notifyAll(); } } ④. 使用Lock代替Synchronized来实现新版的生产者和消费者模式 (显示锁) ReentrantLock​( ):创建一个ReentrantLock的实例void lock( ):获得锁void unlock( ):释放锁 /* * 使用Lock代替Synchronized来实现新版的生产者和消费者模式 ! * */ @SuppressWarnings("all") public class ThreadWaitNotifyDemo { public static void main(String[] args) { AirCondition airCondition=new AirCondition(); new Thread(()->{ for (int i = 0; i <10 ; i++) airCondition.decrement();},"线程A").start(); new Thread(()->{ for (int i = 0; i <10 ; i++) airCondition.increment();},"线程B").start(); new Thread(()->{ for (int i = 0; i <10 ; i++) airCondition.decrement();},"线程C").start(); new Thread(()->{ for (int i = 0; i <10 ; i++) airCondition.increment();},"线程D").start(); } } class AirCondition{ private int number=0; //定义Lock锁对象 final Lock lock=new ReentrantLock(); final Condition condition = lock.newCondition(); //生产者,如果number=0就 number++ public void increment(){ lock.lock(); try { //1.判断 while(number!=0){ try { condition.await();//this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //2.干活 number++; System.out.println(Thread.currentThread().getName()+":\t"+number); //3.唤醒 condition.signalAll();//this.notifyAll(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } //消费者,如果number=1,就 number-- public void decrement(){ lock.lock(); try { //1.判断 while(number==0){ try { condition.await();//this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //2.干活 number--; System.out.println(Thread.currentThread().getName()+":\t"+number); //3.唤醒 condition.signalAll();//this.notifyAll(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } }

⑨. Callable接口(创建线程)

⑨>.创建线程的第三种方式(Callable接口)

①. Callable接口中的call方法和Runnable接口中的run方法的区别 是否有返回值是否抛异常落地方法不一样,一个是call() ,一个是run()

②. Future接口概述 FutureTask是Future接口的唯一的实现类FutureTask同时实现了Runnable、Future接口。它既可以作为Runnable被线程执行,又可以作为Futrue得到Callable的返回值 /* 创建线程的方式三: 实现callable接口 ---JDK 5.0 新增 1.创建一个实现Callable接口的实现类 2.实现call方法,将此线程需要执行的操作声明在call()中 3.创建callable接口实现类的对象 4.将此callable的对象作为参数传入到FutureTask构造器中,创建FutureTask的对象 5.将FutureTask对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用star 6.获取callable接口中call方法的返回值 * */ public class ThreadNew { public static void main(String[] args) { //3.创建callable接口实现类的对象 NumThead m=new NumThead(); //4.将此callable的对象作为参数传入到FutureTask构造器中,创建FutureTask的对象 FutureTask futureTask = new FutureTask(m); //5.将FutureTask对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法 //FutureTask类继承了Runnable接口 //new Runnable = futrueTask; new Thread(futureTask).start(); //6.获取callable接口中call方法的返回值 try { //get()方法返回值即为FutureTask构造器参数callable实现类重写的call方法的返回值 Object sum = futureTask.get(); System.out.println("总和是:"+sum); } catch (Exception e) { e.printStackTrace(); } } } //1.创建一个实现Callable接口的实现类 class NumThead implements Callable{ // class NumThead implements Callable<Integer>{ //2.实现call方法,将此线程需要执行的操作声明在call()中 @Override public Object call() throws Exception { //public Integer call() throws Exception { int sum=0; for(int i=1;i<=100;i++){ System.out.println(i); sum+=i; } return sum; } } ③.FutureTask原理解析 有了Runnable,为什么还要有Callable接口?我们假设一共有四个程序需要执行,第三个程序时间很长 | Runnable接口会按照顺序去执行,会依次从上到下去执行,会等第三个程序执行完毕,才去执行第四个 | Callable接口会把时间长的第三个程序单独开启一个线程去执行,第1、2、4 线程执行不受影响

例子: (1). 老师上着课,口渴了,去买水不合适,讲课线程继续,我可以单起个线程找班长帮忙买水, 水买回来了放桌上,我需要的时候再去get。 (2). 4个同学,A算1+20,B算21+30,C算31*40,D算41+50,是不是C的计算量有点大啊, FutureTask单起个线程给C计算,我先汇总ABD,最后等C计算完了再汇总C,拿到最终结果 (3). 高考:会做的先做,不会的放在后面做 **④. 注意事项 get( )方法建议放在最后一行,防止线程阻塞一个FutureTask,多个线程调用call( )方法只会调用一次如果需要调用call方法多次,则需要多个FutureTask** public class CallableDemo { public static void main(String[] args) throws Exception{ CallAble c=new CallAble(); FutureTask<Integer> futureTask=new FutureTask<>(c); new Thread(futureTask,"线程A").start(); new Thread(futureTask,"线程B").start(); Integer integer = futureTask.get(); System.out.println("integer = " + integer); } } class CallAble implements Callable<Integer>{ @Override public Integer call() throws Exception { System.out.println("欢迎你调用call方法"); return 6; } }

最新回复(0)