线程以及线程面试

DAY14

案例展示:(多线程的引入)
在这里插入图片描述

  • 一个程序只有一条执行路径的程序叫做单线程程序
  • 一个程序如果有多条执行路径,那么这程序就是多线程程序
  • 进程
  • 正在运行的程序,是系统进行资源分配和调用的独立单位
  • 每个进程都有它自己的内存空间和系统资源
  • 线程
  • 是进程中的单个顺序控制流,是一条执行路径
  • 一个进程如果只有一条执行路径,则称为单线程程序
  • 一个进程如果有多条执行路径,则称为多线程程序

注意事项:

  • 多进程的意义
  • 对于单核计算机来讲,游戏进程和音乐进程不是同时进行的
    因为CPU在某个时间点上只能做一件事情,计算机使在游戏进程和音乐进程间做着频繁的切换,且切换的速度很快,所以我们感觉不到游戏和音乐在同时进行,其实并不是同时进行
  • 多进程的作用不是提高程序的执行效率,而是提高CPU的使用率
  • 多线程的意义
  • 多个线程不是并发执行的
    因为多个线程共享同一个进程的资源(堆内存和方法区),但是栈内存是独立的,一个线程一个栈,所以他们仍然在抢CPU的资源进行,一个时间点上只能有一个线程执行,而且谁抢到,这个不一定,所以造成了线程运行的随机性
  • 并行与并发的区别
  • 并行是逻辑上同时发生,指在某一时间内同时运行多个程序
  • 并发是物理上同时发生,指在某一时间点同时运行多个程序
    ☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞第一种创建线程的方式☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞
  • 创建线程的两种方式
  • 是将类声明为Thread的子类,该子类应重写Thread类的run方法。
  • 然后,创建一个启动线程

代码实现:

package cn.edu360;

public class ThreadDemo {

	public static void main(String[] args) {
		MyThread thread = new MyThread();
		thread.start();//使该线程开始执行,Java虚拟机调用该线程的tun方法
		//thread.run();//相当于对象调用方法,还是在主线程执行
		System.out.println("over");
	}
}
class MyThread extends Thread{
	@Override
	public void run() {
		try {
			sleep(500);//让线程睡眠500毫秒,然后苏醒
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		for (int i = 0; i < 10; i++) {
			System.out.println(i);
		}
	}
}

注意事项:如果线程已经启动,再次启动会报错!

  • public final viod setName(String name)设置线程的名字
  • public final String getName()通过对象调用该方法返回线程的名字
  • public static Thread currentThread()当前执行的线程
  • Thread.currentThread()可以获得当前线程的对象
  • public static void sleep(long millis)表示引用的当前线程睡眠多少时间
  • public final void join()等待该线程终止(也就是:join方法通过实例调用所在的线程会等到该实例运行完毕之后再运行)
  • public static void yield()暂停当前正在执行的线程对象,并执行其他线程

代码实现:

package cn.edu360;

public class ThreadDemo {

	public static void main(String[] args) {
		MyThread thread = new MyThread();
		thread.start();
		try {
			thread.join();//表示当前线程会等待thread线程执行完毕之后再执行主线程
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("over");
	}
}
class MyThread extends Thread{
	@Override
	public void run() {
		try {
			sleep(500);//让线程睡眠500毫秒,然后苏醒
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		for (int i = 0; i < 10; i++) {
			System.out.println(i);
		}
	}
}
  • public void interrupt()中断线程的阻塞状态(配合join方法使用)

代码实现:

package cn.edu360;

public class ThreadDemo {

	public static void main(String[] args) {
		//通过构造方法将主线程对象传给子线程
		MyThread t = new MyThread(Thread.currentThread());
		t.start();
		try {
			t.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("over");
	}
}
class MyThread extends Thread{
	private Thread currentThread;

	public MyThread(Thread currentThread) {
		this.currentThread = currentThread;
	}

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			if(i == 3){//当i等于3的时候中端主线程的阻塞状态
				currentThread.interrupt();
			}
			System.out.println(i);
		}
	}
}
  • public void interrupt()中断线程的阻塞状态(配合sleep方法使用)

代码实现:

package cn.edu360;

public class ThreadDemo {

	public static void main(String[] args) {
		MyThread t = new MyThread();
		t.start();
		try {
			t.interrupt();
		} catch (Exception e) {
			e.printStackTrace();
		}
		System.out.println("over");
	}
}
class MyThread extends Thread{
	@Override
	public void run() {
		try {
			sleep(2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		for (int i = 0; i < 10; i++) {
			System.out.println(i);
		}
	}
}

线程的生命周期:

在这里插入图片描述

☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞第二种创建线程的方式☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞

  • 是声明实现Runnable接口的类
  • 该类然后实现run方法
  • 然后可以分配该类的实例
    在创建Thread时作为一个参数来传递并启动

代码实现:

package cn.edu360;

public class ThreadDemo {

	public static void main(String[] args) {
		MyRunnable task = new MyRunnable();
		//多个线程可以执行一个Runnable任务
		new Thread(task).start();
		new Thread(task).start();
	}
}
class MyRunnable implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(i);
		}		
	}
	
}

案例展示:火车站售票
春节某火车站正在售票,假设还剩100张票,而它有3个售票窗口售票,请设计一个程序模拟该火车站售票;在真实生活中,售票时网络是不能实时传输的,总是存在延时的情况,所以,早出手一张票以后,需要一点时间的延迟,该实现接口的方式实现买票程序,每次卖票延迟100毫秒
代码实现:

ThreadTest.java

package cn.edu360;

public class ThreadTest {

	public static void main(String[] args) {
		//继承Thread类
//		TicketThread t = new TicketThread("窗口1");
//		t.start();
//		TicketThread t2 = new TicketThread("窗口2");
//		t2.start();
//		TicketThread t3 = new TicketThread("窗口3");
//		t3.start();
		
		//实现Runnable接口
		TicketRunnable task = new TicketRunnable();
		new Thread(task, "窗口一").start();
		new Thread(task, "窗口二").start();
		new Thread(task, "窗口三").start();
	}

}

TicketThread.java

package cn.edu360;

public class TicketThread extends Thread{
	private static int ticket = 100;
	public TicketThread(String name) {
		super(name);
	}
	@Override
	public void run() {
		while(ticket > 0){
			System.out.println(getName()+"正在出售第"+ticket+"张票");
			ticket--;
		}
	}
}

TicketRunnable.java

package cn.edu360;

public class TicketRunnable implements Runnable {

	private int ticket = 100;
	@Override
	public void run() {
		while(ticket > 0){
			System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
			ticket--;
		}
	}

}

上述代码会出现线程同步安全问题;

  • 什么情况下会出现多线程安全问题
  • 多线程环境下
  • 存在共享数据
  • 共享数据被多条语句使用
  • 怎样解决线程安全问题了?
  • 就是让程序不存在线程安全问题,让操作共享数据的代码在任意时间只能被同一个线程所操作
  • 可以使用同步代码块实现
    在这里插入图片描述
    锁对象:锁对象可以是任意对象,多个线程使用的必须是同一把锁,也就是说必须是同一个对象,或者说锁对象必须唯一

代码修改:
TicketRunnable.java

package cn.edu360;

public class TicketRunnable implements Runnable {

	private int ticket = 100;
	@Override
	public void run() {
		synchronized (Object.class) {
			while(ticket > 0){
				System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
				ticket--;
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

}

同步代码块的优缺点:

  • 优点:解决了线程安全问题
  • 缺点:没出运行的时候都要检查锁对象是否被释放

同步方法:
就是在方法修饰符上加synchronized,将整个方法的代码都锁起来

  • 非静态同步方法的锁对象又是什么了?
    this
  • 静态方法的锁对象又是什么了?
    就是当前类的字节码文件对象
  • 锁对象
    锁对象可以是任意对象,多个线程使用的必须是同一把锁,也就是说必须是同一个对象,或者说锁对象必须唯一

注意事项:

  • 什么时候使用同步代码块,什么时候使用同步方法?
  • 当方法里面只有一部分代码存在安全问题时,使用同步代码块
  • 当方法里面所有的代码都存在安全问题时且当前锁对象可以this时使用同步方法

代码修改:
TicketRunnable.java

package cn.edu360;

public class TicketRunnable implements Runnable {

	private int ticket = 100;
	@Override
	public synchronized void run() {
		while(ticket > 0){
			System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
			ticket--;
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

锁对象什么时候释放:

  • 同步代码执行完毕
  • 线程进入等待状态时
  • 线程停止

线程等待与线程唤醒:

  • public final void wait()使线程等待
  • public final void notify()随机唤醒等待的其中一个线程
  • public final void notifyAll()唤醒所有在等待的线程,每个线程的唤醒顺序也是随机的
  • public void interrupt()可以中断等待的线程状态

注意上述方法必须在同步代码里面通过锁对象调用

注意事项:

  • 为什么wait,notify,notifyAll方法是定义在Object类中
  • 因为锁对象可以是任意对象,又因为wait,notify,notifyAll必须通过锁对象调用;
  • 所以任意对象都可以调用的方法应该定义在Object类中

锁:

  • JDK1.5之后将锁封装成了一个接口Lock,里面提供了上锁和解锁的方法
  • public ReentrantLock()
  • public void lock()
  • public void unlock()

代码修改:
TicketRunnable.java

package cn.edu360;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class TicketRunnable implements Runnable {

	private int ticket = 100;
	private Lock lock = new ReentrantLock();
	@Override
	public void run() {
		while(ticket > 0){
			lock.lock();//上锁
			System.out.println(Thread.currentThread().getName()+"正在出售第"+ticket+"张票");
			ticket--;
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			lock.unlock();//解锁
		}
	}
}

死锁:
A线程:在等待B线程释放锁
B线程:在等待A线程释放锁

注:尽量尽可能的多个线程使用同一把锁

代码实现:

package cn.edu360;

public class DieLock {

	public static void main(String[] args) {
		MyThread myThread = new MyThread(true);
		myThread.start();
		MyThread myThread2 = new MyThread(false);
		myThread2.start();
	}
}

class MyLock{
	public static Object lockA = new Object();
	public static Object lockB = new Object();
}
class MyThread extends Thread{
	private boolean flag;
	public MyThread(boolean flag) {
		this.flag = flag;
	}
	@Override
	public void run() {
		if(flag){
			synchronized (MyLock.lockA) {
				System.out.println("lockA");
				synchronized (MyLock.lockB) {
					System.out.println("lockB");
				}
			}
		}else{
			synchronized (MyLock.lockB) {
				System.out.println("lockB");
				synchronized (MyLock.lockA) {
					System.out.println("lockA");
				}
			}
		}
	}
}

线程池:
我们之前创建使用线程,使用完了就销毁了,因为创建线程是很消耗资源的,所以Java就提供了线程池这个技术来提高线程的使用效率;
可以通过Executors这个工厂类就可以返回我们想要的线程池,里面有不同种类的线程池,我们也可以根据自己的需求使用ThreadPoolExecutor自定义线程池
创建线程池的三种方式:

  • public static ExecutorService newCachedThreadPool()创建一个线程存活时间为60秒的线程池
  • public static ExecutorService newSingleThreadExecutor()创建一个线程池,里面只有一个线程,在某个线程被显示的关闭之前,池中的线程将一直存在
  • public static ExecutorService newFixedThreadPool(int nThreads)创建一个指定线程数的线程池。在某个线程被显示的关闭之前,池中的线程将一直存在

代码实现:

package cn.edu360;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class DieLock {

	public static void main(String[] args) throws Exception {
		//第一种方式
		ExecutorService threadPool = Executors.newCachedThreadPool();
		threadPool.execute(new MyThread());
		//第二种方式
		ExecutorService threadExecutor = Executors.newSingleThreadExecutor();
		threadExecutor.execute(new MyThread());
		//第三种方式
		ExecutorService threadPool2 = Executors.newFixedThreadPool(1);
		threadPool2.execute(new MyThread());
		/**
		 * 下述方式和join的含义相类似
		 */
		Future<String> future = threadPool.submit(new Callable<String>() {
			@Override
			public String call() throws Exception {
				Thread.sleep(5000);
				//要执行的代码
				return "执行以后的结果";
			}
		});
		String result = future.get();
		System.out.println(result);
		
		System.out.println("over");
	}
}

class MyThread implements Runnable{

	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(i);
		}
	}
	
}

定时器:

定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过TImer和TimerTask类来实现定义

  • Timer:一种工具,线程用其安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行
  • public Timer()创建一个新的定时器
  • public void schedule(TimerTask task,long delay)安排在指定延迟后执行指定的任务;task-----所要安排的任务;delay-----执行任务期的延迟时间,单位是毫秒
  • public void schedule(TimerTask task,long delay,long period)安排在指定延迟后执行指定的任务;task-----所要安排的任务;delay-----执行任务期的延迟时间,单位是毫秒;period-----执行各后续任务之间的时间间隔,单位是毫秒

代码实现:

package cn.edu360;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class DieLock {

	public static void main(String[] args) throws Exception {
		Timer timer = new Timer();
		
		myTimerTask timerTask = new myTimerTask();
		//timer.schedule(timerTask, 3000);
		//timer.schedule(timerTask, 3000, 1000);
		timer.schedule(timerTask, 1000, 1000);
	}
}

class myTimerTask extends TimerTask{

	@Override
	public void run() {
		//System.out.println("我要3秒后执行");
		//制作简单的闹钟
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		String value = format.format(new Date());
		System.out.println(value);
	}
	
}

☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞线程面试题汇总:☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞☞

  • 多线程有几种实现方案,分别是哪几种
  • 继承Thread类,重写run方法,然后创建该子类的实例,然后调用start方法
  • 实现Runnable接口,实现run方法,然后将该子类的实例作为参数传递给Thread,然后调用start方法
  • 同步有几种方式,分别是什么?
  • 锁对象:必须保证唯一或者说多个线程需要使用同一把锁
  • 同步代码块:同步代码块的锁对象可以是任意对象,只要保证多个线程使用的锁对象一致就可以了
    synchronized(锁对象){}
  • 同步方法:锁对象是this;将synchronized放在方法的修饰位置上
  • 静态同步方法的锁对象是当前类的字节码文件对象
  • 使用lock子类对象,创建ReentrantLock子类对象然后使用lock和unLock分别加锁和解锁
  • 启动一个线程是run()还是start()?它们有什么区别?
  • 使用start,start方法会启动一个线程,然后JVM在通过线程调用run方法
  • 直接调用run方法就相当于一个对象调用方法,最终还是运行在主线程中
  • sleep()和wait()方法的区别
  • sleep():是睡眠指定的时间,等到指定时间到了就苏醒
  • wait():只能在同步代码中通过锁对象调用,必须通过锁对象唤醒
  • 线程的生命周期(如上述图所画)

猜你喜欢

转载自blog.csdn.net/qq_36633450/article/details/84931962