Java多线程 6种实现方式、线程同步的详细Demo、(等待唤醒机制 & 生命周期)【图解】【敲敲极详细!!】

多线程

作用:

  • 防止线程阻塞,执行某个耗时任务

  • 让多个程序能够看起来像是**“同时执行”**

  • 单独执行某个任务

1.进程和线程

进程: 正在执行的程序 【前台进程和后台进程或者一些服务进程】

线程: 一条执行路径

  • 一个进程可以包含一个线程,也可以包含多个线程
  • 一条线程包含了一条原子性语句,也可以包含n条原子性语句
  • 原子性语句: 不可再分割的语句 i ++ ; i = 10 ;
  • CPU在某一个时间刻度上只能够执行一条线程的一条原子性语句

2.多线程的特点

  1. 开启多线程可以提高CPU的使用率,从而间接提高进程的使用率

  2. CPU在分配给线程资源的时候是随机的

Java 虚拟机允许应用程序并发地运行多个执行线程。

  • 并发: 同一个时间段同时执行多个线程
  • 并行: 同一个时间刻度【不能分割的时间单位】下同时执行多个线程 多核处理器
  • 高并发: 线程数太大

3.多线程的实现方法(6种)

线程的封装:线程中需要用到的任务参数通过外界使用构造方法传入

3.1方式一:继承Thread类

  1. 自定义类MyThread继承Thread类

  2. MyThread类里面重写run()方法

  3. 创建线程对象

  4. 启动线程

public class ThreadDemo {
	public static void main(String[] args) {
		CalculateThread t2 = new CalculateThread(1, 1000);//创建线程对象
		t2.start();//启动线程
	}
}

class CalculateThread extends Thread {//继承Thread类
	private int m;
	private int n;
	public CalculateThread() {
		super();
	}
	public CalculateThread(int m, int n) {//任务参数通过外界使用构造方法传入
		super();
		this.m = m;
		this.n = n;
	}
	
	@Override
	public void run() {//重写run()方法
		int sum = 0;
		for (int i = m; i <= n; i++) {
			System.out.println("subThread: " + i);
			sum += i;
		}
		System.out.println(m + "~" + n + "的和为: " + sum);
	}
	
	public int getM() {
		return m;
	}
	public void setM(int m) {
		this.m = m;
	}
	public int getN() {
		return n;
	}
	public void setN(int n) {
		this.n = n;
	}
	
}

3.2方式二:实现Runnable接口

​ 1.自定义类MyRunnable实现Runnable接口
​ 2.重写run()方法
​ 3.创建MyRunnable类的对象
​ 4.创建Thread类的对象,并把步骤3创建的对象作为构造参数传递
​ 5.启动线程

public class ThreadDemo {
	public static void main(String[] args) {
		// 3.创建IteratorFileThread类的对象
		IteratorFileThread ift = new IteratorFileThread(new File("D:\\JavaSE"));
		// 4.创建Thread类的对象,并把步骤3创建的对象作为构造参数传递
		Thread t = new Thread(ift);
		CalculateThread t2 = new CalculateThread(1, 1000);
		// 5.启动线程
		t.start();
		t2.start();
		for (int i = 0; i <= 1000; i++) {
			System.out.println("mainThread: " + i);
		}
	}
}

// 开启线程一 遍历文件夹中所有的文件
class IteratorFileThread implements Runnable {
	
	private File srcFile;
	
	public IteratorFileThread() {}
	
	public IteratorFileThread(File srcFile) {
		this.srcFile = srcFile;
	}

	@Override
	public void run() {
		getAllFile(srcFile);
	}
	
	public static void getAllFile(File srcFile) {
		if (srcFile == null) {
			throw new RuntimeException("文件对象不能够为null!");
		}
		
		// 2.遍历文件对象下面所有的文件或者文件夹
		File[] files = srcFile.listFiles();
		
		if (files == null) {
			throw new RuntimeException("文件遍历失败!");
		}
		
		// 说明文件夹中没有文件
		if (files.length == 0) {
			return;
		}
		
		// 3.获取到每一个File对象
		for (File f : files) {
			// 4.判断该对象是文件还是文件夹
			if (f.isDirectory()) {
				// a.是文件夹 递归,回到第二步
				getAllFile(f);
			} else {
				String fileName = f.getName();
				// b.是文件
				System.out.println(fileName);
			}
		}
		
	}

	public File getSrcFile() {
		return srcFile;
	}

	public void setSrcFile(File srcFile) {
		this.srcFile = srcFile;
	}
	
}

3.3方式三:匿名内部类开启线程

3.3.1继承Thread方式
		new Thread() {
			@Override
			public void run() {
				for (int i = 1; i <= 1000; i++) {
					System.out.println("匿名内部类 继承Thread方式开启线程: " + i);
				}
			}
		}.start();
3.3.2实现Runnable方式
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 1; i <= 1000; i++) {
					System.out.println("匿名内部类 实现Runnable方式方式开启线程: " + i);
				}
			}
		}).start();
3.3.3Thread和Runnable镶嵌

如果继承Thread和实现Runnble同时实现,继承Thread优先

		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 1; i <= 1000; i++) {
					System.out.println("==>匿名内部类 实现Runnable方式方式开启线程: " + i);
				}
			}
		}) {
			public void run() {
				for (int i = 1; i <= 1000; i++) {
					System.out.println("==>匿名内部类 继承Thread方式开启线程: " + i);
				}
			};
		}.start();

3.4方式四:Lambda表达式方式

		new Thread(()-> {
			for (int i = 1; i <= 1000; i++) {
				System.out.println("==>Lambda表达式方式: " + i);
			}
		}) .start();

3.5方式五:实现Callable方式开启线程

重写run方法可以书写线程的任务,但是run方法是无参数无返回值的,同时没有异常

  • 所以开启线程的线程无法知道被开启线程的异常返回值
  • Java提供了Callable接口开启线程,借助一个中间类 FutrueTask
public class CallableTest {
	public static void main(String[] args) {
        //public interface RunnableFuture<V> extends Runnable, Future<V> 
        //RunnableFuture继承了 Runnable, Future,所以可以Thread多态调用Runnable的调用
        
        //public class FutureTask<V> implements RunnableFuture<V>
        //且public FutureTask(Callable<V> callable)
        //FutureTask是RunnableFuture子类,FutureTask包含Callable
        
        //Thread -> Runnable -> RunnableFuture -> FutureTask -> Callable
        
		FutureTask<Integer> task = new FutureTask<>(new MyCallable(1, 100));
		Thread t = new Thread(task);
		t.start();
		
		try {
			Integer result = task.get();
			System.out.println(result);
		} catch (InterruptedException | ExecutionException e) {
			e.printStackTrace();
		}
		
		System.out.println("over");
				
	}
}


class MyCallable implements Callable<Integer> {
	
	private int m;
	private int n;
	
	public MyCallable() {
		super();
	}
	
	public MyCallable(int m, int n) {
		super();
		this.m = m;
		this.n = n;
	}

	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int i = m; i <= n; i++) {
			System.out.println(Thread.currentThread().getName() + "|" + i);
			sum += i;
		}
		return sum;
	}
	
}

3.6方式六:线程池开启线程

Executors工厂类来产生线程池

3.6.1构造方法
说明
public static ExecutorService newCachedThreadPool()
public static ExecutorService newFixedThreadPool(int nThreads)
public static ExecutorService newSingleThreadExecutor()
public class ThreadPoolDemo {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// 创建线程池对象
		ExecutorService pool = Executors.newFixedThreadPool(3);
		// 将线程提交到线程中
		pool.submit(new MyRunnable());
		Future<Integer> future = pool.submit(new MyCallable(1, 50));
		pool.submit(new Runnable() {
			
			@Override
			public void run() {
				for (int i = 1; i <= 100; i++) {
					System.out.println(Thread.currentThread().getName() + "|" + i);
				}
			}
		});
		Integer result = future.get();
		System.out.println(result);
		
		pool.shutdown();
	}
}

class MyRunnable implements Runnable {

	@Override
	public void run() {
		for (int i = 1; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName() + "|" + i);
		}
	}
	
}

4.设置和获取线程名称

  • 通过构造方法
说明
Thread(String name)分配新的 Thread 对象。
Thread(Runnable target, String name)分配新的 Thread 对象。
  • 通过线程的成员方法
说明
public final String getName()
public final void setName(String name)
  • 通过静态方法
public static Thread currentThread()可以获取任意方法所在的线程名称
Thread.currentThread().getName();可以获取任意线程的线程名称
Thread.currentThread().setName();可以设置任意线程的线程名称
class NameRunnable implements Runnable {

	@Override
	public void run() {
		for (int i = 1; i <= 1000; i++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
}

4.1继承Thread开启线程和实现Runnable开启线程的区别

  1. 继承Thread的方式可以更为方便地访问线程的API
  2. 继承Thread方式如果一旦某个类已经继承了一个类,那么就不能够再继承Thread类 【单一继承性】

5.调度模型

Java使用的是抢占式调度模型

优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。 (并不是优先级高的先执行,只是过去执行权的可能性大)

  • 设置和获取线程的优先级
说明
public final int getPriority()
public final void setPriority(int newPriority)
		t1.setPriority(Thread.MAX_PRIORITY);	//10
		t2.setPriority(Thread.NORM_PRIORITY);	//5
		t3.setPriority(Thread.MIN_PRIORITY);	//1
		
		System.out.println(t1.getPriority());
		System.out.println(t2.getPriority());
		System.out.println(t3.getPriority());

6.线程休眠

该方法是一个阻塞方法,会阻塞正在执行的线程

sleep方法是一个静态方法,可以让任意线程睡眠

  • public static void sleep(long millis)
// 使用线程休眠模拟时钟
class SleepRunnable implements Runnable {

	@Override
	public void run() {
		while (true) {
			String dateStr = new SimpleDateFormat("yyyy年MM月dd号 HH点mm分ss秒").format(new Date());
			System.out.println("现在是北京时间: " + dateStr);
			
			// 线程休眠
			try {
				Thread.sleep(100000L);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

6.1实现Runnable的类不能用thorws抛异常

  • 因为Runnable没有抛出任何异常,子类抛出的异常不能大于父类,父类没有抛异常,子类也不能抛异常

6.2Thread & Runnable 不足

  • Thread和Runnable没有异常,子类也不能有异常,所以run只能使用try…catch处理异常,不用抛异常
  • run 方法的返回值类型是void,所以没有返回值

7.中断线程

说明
public final void stop()
public void interrupt()

7.1面试题: stop和interrupt的区别

  • stop:会中断子线程
  • interrupt:主线程向子线程抛出一个异常 【InterruptedException】,但线程能继续执行下去
    • 可以通过System.exit(0)来中断线程

8.后台线程

后台线程 / 守护线程 / 服务线程 / 非用户线程

一般来说,JVM(JAVA虚拟机)中一般会包括俩种线程,分别是用户线程和后台线程。

说明
public final void setDaemon(boolean on)用户线程的设置
  • 后台线程守护的是用户线程(后台线程不是不可或缺的)
  • 用户线程存在则守护线程存在,用户线程死亡了,那么所有的守护该用户线程的线程都将死亡

9.线程加入

说明
public final void join()非静态方法,需要执行某个线程对象去执行,是让join之外的线程处于阻塞状态
  • join需要加在某线程之后,而不是所有线程启动之后

10.线程礼让

唯一一个方法能够让线程从运行态到就绪态的方法

public static void yield()主动让出CPU的执行权,让出之后自己重新参与抢夺CPU资源的队列中重新抢

11.线程同步

线程同步的问题,简单来说根本原因就是 CPU在某一个时间刻度只能够执行一条原子性语句

11.1程序模拟该火车站售票

11.1.1线程同步实现的三个问题
  • 问题一: 继承Thread方式不能够共享同一份数据
    需要对数据进行static修饰

  • 问题二: 卖出了负票
    窗口2正在出售第1张票!!!
    窗口1正在出售第0张票!!!
    窗口3正在出售第-1张票!!!
    原因: 原子性语句导致的同时还对共享数据做了写操作

  • 问题三: 卖出了同票
    窗口1正在出售第53张票!!!
    窗口3正在出售第53张票!!!
    原因: 当多个线程争夺资源的时候,原子性语句导致的

11.1.1.1线程同步问题的产生原因:
  1. 必须存在多线程环境
  2. 必须多线程环境下存在多条原子性语句操作共享数据
  3. 并且对共享数据做写的操作
11.1.2解决方案

通过加锁【互斥锁 同步锁】可以解决以上不同步问题

  1. 同步代码块
		//格式:
			synchronized(对象){需要同步的代码;}
  • 特点:
    • 需要显示创建对象
    • 不知道在哪里加锁,在哪里释放锁(在出了同步代码块之后)
		// synchronized(对象){需要同步的代码;}
		while (true) {
			synchronized (MyLock.LOCKA) {//通过枚举,创建同一个锁对象
				if (tickets > 0) {
					// 模拟网络延迟
					try {
						Thread.sleep(100L);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票!!!");
				}
			}
		}
        //枚举
        enum MyLock {
            LOCKA,LOCKB,LOCKC
        }
  1. 同步方法

如果锁对象是this,就可以考虑使用同步方法

		//格式:
            public synchronized 返回值 方法名(参数列表) {
                    //需要同步的代码块
            }
  • 特点
    • 不知道锁对象是谁
    • 能够不用单独编写锁对象
    • 不知道在哪里加锁,在哪里释放锁 (出了同步方法就等于解锁)
		//public synchronized 返回值 方法名(参数列表) {
	      		//需要同步的代码块
	   	//	}
		 
	public synchronized void sellTickets() {
		if (tickets > 0) {
			// 模拟网络延迟
			try {
				Thread.sleep(100L);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票!!!");
		}
	}
  1. Lock锁 【JDK提供的一种锁对象】
  • 特点:
    • 明确锁对象
    • 在何时加锁以及何时释放锁非常清晰,更加满足了面向对象思想
class SellTicketRunnable implements Runnable {
	
	private int tickets = 100;
	private Lock lock = new ReentrantLock();//

	@Override
	public void run() {
        while (true) {
			lock.lock();//
			if (tickets > 0) {
				// 模拟网络延迟
				try {
					Thread.sleep(100L);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票!!!");
			}
			lock.unlock();//
		}
	}
}

11.2继承Thread和实现Runnable的区别?

好处

  1. 继承Thread的方式可以方便地访问线程的API

  2. 实现Runnable方式可以避免单继承带来的缺陷

  3. 实现Runnable方式可以在多个线程之间共享同一个Runnble接口,也就是说可以共享同一份数据【变量】和同一份代码【方法】

    		SellTicketRunnable sr = new SellTicketRunnable();
    		Thread t1 = new Thread(sr);
    		Thread t2 = new Thread(sr);
    		Thread t3 = new Thread(sr);
    

缺陷

  1. 继承Thread类每个线程都有自己的一份数据
  2. 继承Thread方式单一继承性
  3. 实现Runnable方式不能够直接访问线程的API,我们可以间接使用 Thread.currentThread() 这个方法来获取当前正在执行run方法的线程对象
    1. 然后通过这个对象来访问线程API。
11.2.1静态 / 非静态

静态方法: Class对象

  • 当使用的是Thread继承,run()重写的方法需要加上static,使所有成员共享同一个数据
//因为Thread继承需要创建三个对象,所以需要static是方法静态,
//Class<SellTicketThread> c1 = SellTicketThread.class(锁对象)
//同一个类下,不管创建多少个对象,都共用同一个class对象(.class字节码文件)
SellTicketThread t1 = new SellTicketThread();
SellTicketThread t2 = new SellTicketThread();
SellTicketThread t3 = new SellTicketThread();

class SellTicketThread extends Thread {
	
	private static int tickets = 100;
	
	@Override
	public void run() {
		sellTickets();
	}
	
	public static synchronized void sellTickets() {
		if (tickets > 0) {
			// 模拟网络延迟
			try {
				Thread.sleep(100L);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "张票!!!");
		}
	}
}

非静态方法: this

  • 指调用类的对象

12.死锁

死锁:指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。

public class DeadLockThread {
	public static void main(String[] args) {
		DieLock t1 = new DieLock(true);
		DieLock t2 = new DieLock(false);
		
		try {
			Thread.sleep(1000L);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		t1.start();
		t2.start();
	}
}

class DieLock extends Thread {
	private boolean flag;

	public DieLock() {
		super();
	}

	public DieLock(boolean flag) {
		super();
		this.flag = flag;
	}
	
	@Override
	public void run() {
		if (flag) {//1.  DieLock t1 = new DieLock(true);调用MyLock.LOCKA,进行上锁,
            //当CPU执行权被抢
			synchronized(MyLock.LOCKA) {
				System.out.println("if语句中的LOCKA锁");
				synchronized (MyLock.LOCKB) {
					System.out.println("if语句中的LOCKB锁");
				}
			}
		} else {//2.  DieLock t2 = new DieLock(false);调用MyLock.LOCKB,进行上锁,
			synchronized(MyLock.LOCKB) {
				System.out.println("else语句中的LOCKB锁");
				synchronized (MyLock.LOCKA) {
					System.out.println("else语句中的LOCKA锁");
				}
			}
		}
	}
}
//到时AB同时上锁,都在等待,B因为A上锁等待解锁,A因为B上锁等待解锁

//枚举,创建锁对象
enum MyLock {
	LOCKA, LOCKB
}

13.未捕获异常

替代JVM,自行捕获未捕获异常

说明
static interface Thread.UncaughtExceptionHandler这个就是未捕获异常处理器
public class ThreadDemo01 {
	public static void main(String[] args) {
		
		//1.捕获所有异常
		Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
        
		// 2.需求: 希望主线程未捕获异常自己处理 ,子线程还是虚拟机处理 
		Thread.currentThread().setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
			
			@Override
			public void uncaughtException(Thread t, Throwable e) {
				System.out.println(t.getName() + "线程出现了问题!");
				System.out.println(e);
			}
		});
		ExceptionThread t = new ExceptionThread();
		t.setName("子线程");
        // 3.需求: 希望子线程未捕获异常自己处理 ,主线程还是虚拟机处理
		t.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
			
			@Override
			public void uncaughtException(Thread t, Throwable e) {
				System.out.println(t.getName() + "线程出现了问题!");
				System.out.println(e);
			}
		});
		t.start();
		
		System.out.println("start");
		int a = 10;
		int b = 0;
		System.out.println(a/b);
		System.out.println("end");
		
	}
}
class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {

	@Override
	public void uncaughtException(Thread t, Throwable e) {
		/*
		 * t: 表示出现异常的线程对象
		 * e: 表示出现的异常信息
		 */
		System.out.println(t.getName() + "线程出现了问题!");
		e.printStackTrace();
	}
	
}

class ExceptionThread extends Thread {
	@Override
	public void run() {
		
		for (int i = 1; i <= 100; i++) {
			System.out.println(getName() + ":" + i);
			if (i == 10) {
				System.out.println(i / 0);
			}
		}
	}
}

14.线程组

ThreadGroup :为了更加方便批量管理线程

14.1线程组和线程池有关系吗?

  • 没有关系,
    • 单个线程也可以使用线程组
    • 线程池也可以使用线程组进行管理
public class ThreadDemo {
	public static void main(String[] args) {
		
		ThreadGroup tg1 = new ThreadGroup("三国演义");
		ThreadGroup tg2 = new ThreadGroup("西游记");
		
		MyRunnable mr = new MyRunnable();
		Thread t1 = new Thread(tg1, mr, "诸葛亮");
		Thread t2 = new Thread(tg1, mr, "赵云");
		Thread t3 = new Thread(tg1, mr, "吕布");

		Thread t4 = new Thread(tg2, mr, "二郎神");
		Thread t5 = new Thread(tg2, mr, "嫦娥");
		Thread t6 = new Thread(tg2, mr, "铁扇公主");
		System.out.println(t6.toString());
		
		tg1.setDaemon(true);
		System.out.println(tg1.getName());
		
//		t1.start();
//		t2.start();
//		t3.start();
//		t4.start();
//		t5.start();
//		t6.start();
        
        //集合也可以对线程进行管理
		List<Thread> threads = new ArrayList<Thread>();
		threads.add(t1);
		threads.add(t2);
		threads.add(t3);
		threads.add(t4);
		threads.add(t5);
		threads.add(t6);
		
		for (Thread thread : threads) {
			thread.start();
		}
		
		
	}
}

class MyRunnable implements Runnable {

	@Override
	public void run() {
		for (int i = 1; i <= 100; i++) {
			System.out.println(Thread.currentThread().getName() + "|" + i);
		}
	}
	
}

15.等待唤醒机制

线程通信的一种方式 wait() notify()

在这里插入图片描述

15.1为什么 wait和nofity这些方法不设计在线程Thread类里面,而设计在Object类中?

  • 因为锁对象是任意对象,所以锁的类型是Object类型

16.生命周期

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值