多线程面试题
多线程有几种实现方案,分别是哪几种?
方式一:定义一个子类继承Thread,然后重写run方法,将需要给子线程执行的代码放在run方法里面;然后创建子类对象,通过子类对象调用start方法
方式二:定义一个子类实现Runnable接口,实现run方法,将需要给子线程执行的代码放在run方法里面;然后在创建线程时,
将子类对象当做参数传递,然后调用线程的start方法
同步有几种方式,分别是什么?
锁对象:可以是任意对象,所有的线程需要使用同一个锁对象
方式一:使用同步代码块
方式二:使用同步方法
方式三:使用JDK1.5新特性的Lock锁对象
启动一个线程是run()还是start()?它们的区别?
run:就是一个普通的方法
start:会和系统进行交互,启动线程,启动线程之后会默认去调用run方法
sleep()和wait()方法的区别
sleep:不能被唤醒,但是可以被中断;调用sleep时,锁对象不会释放
wait:可以被中断,可以被唤醒;wait只能在同步代码中使用锁对象来调用;调用wait时,锁对象会释放
线程的生命周期图
看图解
生产者和消费者:wait,notify,notifyAll
1.多线程的创建和原理
/* * 一个线程一个栈,堆和方法区是共享的 * * 为什么要重写run()方法:因为子线程启动之后会默认去调用run方法,所以需要被子线程执行的代码就放在run方法中 启动线程使用的是那个方法:start 线程能不能多次启动:不行 run()和start()方法的区别: t.run();//这样调用就相当于一个对象调用一个方法,这个run方法还是在主线程中执行的 t.start();//首先会创建一个子线程,然后子线程默认就去调用线程中的run方法,所以run方法是在子线程中执行 */ public class ThreadDemo { public static void main(String[] args) { //创建子线程对象 MyThread t = new MyThread(); //t.run();//这样调用就相当于一个对象调用一个方法,这个run方法还是在主线程中执行的 t.start();//首先会创建一个子线程,然后子线程默认就去调用线程中的run方法,所以run方法是在子线程中执行 System.out.println("over"); } } // 一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法 class MyThread extends Thread { @Override public void run() { // 交个子线程做的事情 for (int i = 0; i < 10; i++) { System.out.println(i); } } }
2.多线程
/* * 如何获取线程的名称:public final String getName()返回该线程的名称 * 如何设置线程的名称:public final void setName(String name)改变线程名称,使之与参数 name 相同 * 如何获取主线程的名称:public static Thread currentThread()返回对当前正在执行的线程对象的引用 */ public class ThreadDemo2 { public static void main(String[] args) { //创建子线程并启动 MyThread2 t = new MyThread2(); t.start(); //如何获取线程的名称:public final String getName()返回该线程的名称 System.out.println(t.getName());//Thread-0 t.setName("子线程1"); MyThread2 t2 = new MyThread2(); t2.start(); System.out.println(t2.getName());//Thread-1 t2.setName("子线程2"); MyThread2 t3 = new MyThread2("子线程3"); t3.start(); System.out.println(t3.getName());//子线程3 //如何获取主线程的名称:public static Thread currentThread()返回对当前正在执行的线程对象的引用 String name = Thread.currentThread().getName(); System.out.println(name);//main Thread.currentThread().setName("主线程"); //获取主线程的名称 System.out.println(Thread.currentThread().getName()+"---over"); } } //创建一个子类继承Thread,然后重写run方法 class MyThread2 extends Thread{ public MyThread2(){ } public MyThread2(String name){ super(name); } @Override public void run() { for(int i=0; i<10; i++){ System.out.println(getName()+"---"+i); } } }
3.sleep的用法
//sleep用法 public class ThreadDemo3 { public static void main(String[] args) { MyThread3 t = new MyThread3(); t.start(); //public static void sleep(long millis)在指定的毫秒数内让当前正在执行的线程休眠 try { Thread.sleep(1000);//主线程睡眠 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("over"); } } class MyThread3 extends Thread{ @Override public void run() { for(int i=0; i<10; i++){ System.out.println(getName()+"---"+i); } } }
4.join
//join用法 public class ThreadDemo4 { public static void main(String[] args) { // 面试题:让t先执行,然后t2执行,然后t3执行 MyThread4 t = new MyThread4(); t.start(); // 让主线程等待t线程之后完毕之后再运行 try { //public final void join()如果join()方法在一个线程实例上调用,当前运行着的线程将阻塞直到这个线程实例完成了执行之后再执行 t.join();// 让当前线(主线程)程等待t线程执行完毕之后再执行 } catch (InterruptedException e) { e.printStackTrace(); } MyThread4 t2 = new MyThread4(); t2.start(); // 让主线程等待t线程之后完毕之后再运行 try { t2.join();// 让当前线(主线程)程等待t2线程执行完毕之后再执行 } catch (InterruptedException e) { e.printStackTrace(); } MyThread4 t3 = new MyThread4(); t3.start(); } } class MyThread4 extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(getName() + "---" + i); } } }
5.stop用法和替代方案
//stop用法和替代方案 public class ThreadDemo5 { public static void main(String[] args) { MyThread5 t = new MyThread5(); t.start(); t.setRunning(false); System.out.println("over"); } } class MyThread5 extends Thread { private boolean isRunning = true; @Override public void run() { for (int i = 0; i < 10; i++) { // 当i等于3的时候,终止当前的子线程 if (i == 3) { // stop();//已过时,应该由一个变量来决定是否终止线程 isRunning = false; } if (isRunning) { System.out.println(getName() + "---" + i); } else { return; } } System.out.println("哈哈哈"); } public void setRunning(boolean b) { isRunning = b; } }
6.interrupt用法:必须线程是阻塞状态才能看到效果
//interrupt用法:必须线程是阻塞状态才能看到效果 public class ThreadDemo6 { public static void main(String[] args) { MyThread6 t = new MyThread6(Thread.currentThread());//把主线程对象传递给子线程 t.start(); try { Thread.sleep(100);//让主线程休眠100毫秒,确保子线程是睡眠状态 } catch (InterruptedException e) { //e.printStackTrace(); System.out.println(Thread.currentThread().getName()+"阻塞状态被中断"); } t.interrupt();//中断子线程 try { //Thread.sleep(2000);//主线程阻塞了1秒钟 t.join();//让主线程等待t线程之后完毕之后再执行 } catch (InterruptedException e) { //e.printStackTrace(); System.out.println(Thread.currentThread().getName()+"阻塞状态被中断"); } System.out.println("over"); } } class MyThread6 extends Thread { private Thread mainThread;//主线程 public MyThread6(Thread mainThread) { this.mainThread = mainThread; } @Override public void run() { mainThread.interrupt();//立马打破主线程的阻塞状态 try { sleep(2000);//子线程睡眠2秒钟 } catch (InterruptedException e) { //e.printStackTrace(); System.out.println(Thread.currentThread().getName()+"阻塞状态被中断"); } for (int i = 0; i < 10; i++) { System.out.println(getName() + "---" + i); } } }
7.创建方式2
//创建并启动线程方式二 public class ThreadDemo7 { public static void main(String[] args) { // 然后可以分配该类的实例 MyRunnable task = new MyRunnable();// 一个任务可以被多个线程执行 // 在创建 Thread 时作为一个参数来传递并启动 Thread t = new Thread(task, "子线程1"); t.start();// 子线程启动之后会去调用task的run方法,那么run方法就在子线程中执行了 Thread t2 = new Thread(task, "子线程2"); t2.start(); Thread t3 = new Thread(task, "子线程3"); t3.start(); try { t.join(); t2.join(); t3.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("over"); } } // 创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread // 时作为一个参数来传递并启动。 class MyRunnable implements Runnable { @Override public void run() { // 存放给子线程执行的代码 for (int i = 0; i < 10; i++) { // 获取当前线程的名字 System.out.println(Thread.currentThread().getName() + "---" + i); } } }
8.创建方式3
public class ThreadDemo { public static void main(String[] args) { //传统方式一:继承Thread,重写run方法,创建子类实例调用start方法 new MyThread().start();//Thread-0:继承Thread方式创建子线程 //匿名内部类方式一:匿名内部类重写Thread的run方法方式创建子线程 new Thread(){ public void run() { System.out.println(getName()+":匿名内部类重写Thread的run方法方式创建子线程"); }; }.start();//Thread-1:匿名内部类重写Thread的run方法方式创建子线程 //传统方式二:实现Runnable接口,实现run方法 new Thread(new MyRunnable()).start();//Thread-2:实现Runnable接口方式创建子线程 //匿名内部类方类方式二:匿名内部类实现Runnable接口的方式创建子线程 new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+":匿名内部类实现Runnable接口的方式创建子线程"); } }).start();//Thread-3:匿名内部类实现Runnable接口的方式创建子线程 System.out.println(Thread.currentThread().getName()+":over");//main:over } } //传统方式一:继承Thread,重写run方法,创建子类实例调用start方法 class MyThread extends Thread{ @Override public void run() { System.out.println(getName()+":继承Thread方式创建子线程"); } } //传统方式二:实现Runnable接口,实现run方法 class MyRunnable implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+":实现Runnable接口方式创建子线程"); } }
9.生产者和消费者模式
public class Apple { private String name; private float price; public Apple() { super(); } public Apple(String name, float price) { super(); this.name = name; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public float getPrice() { return price; } public void setPrice(float price) { this.price = price; } @Override public String toString() { return "Apple [name=" + name + ", price=" + price + "]"; } }
消费者
import java.util.concurrent.LinkedBlockingQueue; //消费者 public class CustomerRunnable implements Runnable { private LinkedBlockingQueue<Apple> queue; public CustomerRunnable(LinkedBlockingQueue<Apple> queue) { this.queue = queue; } @Override public void run() { while (true) { synchronized (queue) { // 如果队列中没有产品了,那么就wait当前的消费线程 while (queue.size() == 0) { try { queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } try { // 从queue中取出产品消费 System.out.println(Thread.currentThread().getName()); //Apple apple = queue.poll();//没有值就返回null Apple apple = queue.take();//底层通过它自身的锁对象调用了await方法,那么这行代码就阻塞在这里了,那么queue锁对象就没有被释放 System.out.println(Thread.currentThread().getName() + "消费了:" + apple.getName()); } catch (Exception e) { e.printStackTrace(); } // 唤醒生产者去生产 queue.notifyAll(); } // 模拟现实生活中的延迟 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
生产者、
import java.util.Random; import java.util.concurrent.LinkedBlockingQueue; //生产者 public class ProductRunnable implements Runnable { private LinkedBlockingQueue<Apple> queue; public ProductRunnable(LinkedBlockingQueue<Apple> queue) { this.queue = queue; } @Override public void run() { Random r = new Random(); while (true) { synchronized (queue) { // 只要queue里面存放的产品大于或者等于10个了,生产者线程就wait while (queue.size() >= 10) { try { queue.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } // 开始生产 Apple apple = new Apple("苹果" + r.nextInt(1000), r.nextInt(100) + 1); try { // 把苹果放进队列中 queue.put(apple); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "生产了:" + apple.getName()); // 唤醒消费者去消费 queue.notifyAll(); } //模拟现实生活中的延迟 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
入口
import java.util.concurrent.LinkedBlockingQueue; /* * wait,notify,notifyAll为什么定义在Object类中呢? * 因为锁对象可以是任意对象,又因为wait,notify,notifyAll必须通过锁对象调用; * 那么也就是说wait,notify,notifyAll是可以被任意对象调用方法,那么可以被任意对象调用的方法应该定义在Object类中 * * wait,notify,notifyAll它们必须在同步代码中,使用相同的锁对象进行调用 * * wait被调用的时候,当前的锁对象会释放 */ public class ThreadTest { public static void main(String[] args) { // 创建一个阻塞队列LinkedBlockingQueue LinkedBlockingQueue<Apple> queue = new LinkedBlockingQueue<Apple>(); ProductRunnable productRunnable = new ProductRunnable(queue); new Thread(productRunnable, "生产者1").start(); new Thread(productRunnable, "生产者2").start(); new Thread(productRunnable, "生产者3").start(); new Thread(productRunnable, "生产者4").start(); new Thread(productRunnable, "生产者5").start(); CustomerRunnable customerRunnable = new CustomerRunnable(queue); new Thread(customerRunnable, "消费者1").start(); new Thread(customerRunnable, "消费者2").start(); new Thread(customerRunnable, "消费者3").start(); new Thread(customerRunnable, "消费者4").start(); new Thread(customerRunnable, "消费者5").start(); } }
10 火车站售票之多人同时购票的实现
/* * 需求:春节某火车站正在售票,假设还剩100张票,而它有3个售票窗口售票,请设计一个程序模拟该火车站售票。 在真实生活中,售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟,每次卖票延迟100毫秒 两种方式实现 继承Thread类 实现Runnable接口 */ public class SaleTicketDemo { public static void main(String[] args) { // 继承Thread类方式: // new SaleTicketThread("窗口1").start(); // new SaleTicketThread("窗口2").start(); // new SaleTicketThread("窗口3").start(); // 实现Runnable接口方式: TicketRunnable ticketRunnable = new TicketRunnable(); new Thread(ticketRunnable, "窗口1").start(); new Thread(ticketRunnable, "窗口2").start(); new Thread(ticketRunnable, "窗口3").start(); } }
票数的实现
public class SaleTicketThread extends Thread { // 总共100张票 private static int ticket = 100; public SaleTicketThread(String name) { super(name); } @Override public synchronized void run() {//不能使用同步方法,因为同步方法的锁对象是this;而我们这个SaleTicketThread创建了三个对象,就有三个this while (true) { // synchronized (Object.class) { if (ticket > 0) { System.out.println(getName() + "出售了第" + ticket + "张票"); ticket--;// 票需要-1 } else { return; } // } // 模拟现实生活中卖票的延迟 try { sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }模拟售票
mport java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /* * 如何判断一个程序有没有线程安全问题? 是否存在多线程环境 是否存在共享数据 是否存在多条语句操作共享数据 怎么解决线程同步安全问题呢? 把存在线程安全问题的代码锁起来,让任意时刻只有一个线程能访问 解决方式一:同步代码块 synchronized(锁对象){ //相当于上锁 存在线程安全问题的代码 //相当于解锁 } 解决方式二:同步方法 将整个方法都锁起来,在方法的修饰符位置上加synchronized 锁对象:可以是任意对象,多个线程使用的是必须同一个锁对象 同步代码块:可以是任意对象,多个线程使用的是同一个锁对象 非静态同步方法:锁对象是this 静态同步方法:锁对象是当前字节码文件对象 解决方式三:使用Lock锁对象 Lock lock = new ReentrantLock(); lock.lock();//上锁 lock.unlock();//释放锁 HashTable:用的就是同步方法 ConcurrentHashMap:用的就是同步代码块 */ public class TicketRunnable implements Runnable { // 总共100张票 private int ticket = 100; private Lock lock = new ReentrantLock(); @Override public void run() { while (true) { // synchronized (Object.class) { lock.lock();//上锁 if (ticket > 0) { System.out.println(Thread.currentThread().getName() + "出售了第" + ticket + "张票"); ticket--;// 票需要-1 } else { return; } lock.unlock();//释放锁 // } // 模拟现实生活中卖票的延迟 try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }
电脑执行流程
线程生命周期图解
生产者和消费者设计模式图解
多线程的引入
售票解决方案