Java基础回顾系列-高级编程之多线程编程

版权声明:不存在一劳永逸的技术 只存在不断学习的人。本文为博主原创文章,未经博主允许不得转载。交流联系QQ:1120121072 https://blog.csdn.net/u013474568/article/details/85262776

Java多线程编程

进程、线程概念

Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

进程和线程的区别
进程
应用程序的执行实例,有独立的内存空间和系统资源。一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
线程
CPU调度和分派的基本单位,进程中执行运算的最小单位,可完成一个独立的顺序控制流程。多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
进程和线程的关系:

  1. 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
  2. 资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
  3. 处理机分给线程,即真正在处理机上运行的是线程。
  4. 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。

创建一个线程

Java 提供了三种创建线程的方法:

  • 通过实现 Runnable 接口;
  • 通过继承 Thread 类本身;
  • 通过 CallableFuture 创建线程。

通过继承Thread来创建线程

写一个类继承自 Thread 类,重写 run 方法。用 start 方法启动线程。 创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能执行。该方法尽管被列为一种多线程实现方式,但是本质上也是实现了 Runnable 接口的一个实例。

package javase.util;

/**
 * 继承Thread实现多线程
 */
class MyThread extends Thread {
	public String title;

	public MyThread(String title) {
		this.title = title;
	}
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(this.title+":执行 i = " + i);
		}
	}
}

public class TestMain {
	public static void main(String[] args) {
		new MyThread("线程A").start();
		new MyThread("线程B").start();
		new MyThread("线程C").start();
	}
}

程序运行结果如下:

线程B:执行 i = 0
线程A:执行 i = 0
线程C:执行 i = 0
线程A:执行 i = 1
线程B:执行 i = 1
线程A:执行 i = 2
线程C:执行 i = 1
线程A:执行 i = 3
线程B:执行 i = 2
线程A:执行 i = 4
线程C:执行 i = 2
线程A:执行 i = 5
线程B:执行 i = 3
线程A:执行 i = 6
线程C:执行 i = 3
线程A:执行 i = 7
线程B:执行 i = 4
线程A:执行 i = 8
线程C:执行 i = 4
线程A:执行 i = 9
线程B:执行 i = 5
线程C:执行 i = 5
线程B:执行 i = 6
线程C:执行 i = 6
线程B:执行 i = 7
线程C:执行 i = 7
线程B:执行 i = 8
线程C:执行 i = 8
线程B:执行 i = 9
线程C:执行 i = 9

MyThread thread = new MyThread("线程A");
thread.start();
// 出错,线程对象只允许启动一次:Exception in thread "main" java.lang.IllegalThreadStateException
// thread.start();

通过实现 Runnable 接口来创建线程

写一个类实现 Runnable 接口,实现 run 方法。用 new Thread(Runnable target).start() 方法来启动。
注意:Runnable接口是函数式接口@FunctionalInterface,只有一个run()方法

@FunctionalInterface
public interface Runnable {
	public abstract void run();
}

Lambda实现:

package javase.util;

public class TestMain {
	public static void main(String[] args) {
		for (int i = 1; i <= 3; i++) {
			String title = "线程对象"+i;
			// 函数式Runnable接口实现
			new Thread(()->{
				for (int j = 0; j < 10; j++) {
					System.out.println(title+ "执行 i = " + j);
				}
			}).start();
		}
	}
}

package javase.util;

/**
 * 实现Runnable接口创建多线程
 */
class MyThread implements Runnable {
	private String title;
	public MyThread(String title) {
		this.title = title;
	}
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(this.title+":执行 i = " + i);
		}
	}
}

public class TestMain {
	public static void main(String[] args) {
		new Thread(new MyThread("线程A")).start();
		new Thread(new MyThread("线程B")).start();
		new Thread(new MyThread("线程C")).start();
	}
}

通过 Callable 和 Future 创建线程

  1. 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
  2. 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
  3. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
  4. 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。

注意: Callable接口:与Runnable接口功能相似,用来指定线程的任务。其中的call()方法,用来返回线程任务执行完毕后的结果,call方法可抛出异常。

package javase.util;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

/**
 * 通过 Callable 和 Future(FutureTask) 创建线程
 */
class MyThread implements Callable<String> {
	private String title;
	public MyThread(String title) {
		this.title = title;
	}

	@Override
	public String call() throws Exception {
		for (int i = 0; i < 10; i++) {
			System.out.println(this.title+":执行 i = " + i);
		}
		return "多线程执行完毕";
	}
}

public class TestMain {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		FutureTask<String> futureTask = new FutureTask<String>(new MyThread("线程A"));
		new Thread(futureTask).start();
		String callStr = futureTask.get();
		System.out.println("callStr = " + callStr);
	}
}

面试题: 请解释 RunnableCallable的区别?。

  • Runnable是在JDK1.0的时候提出的多线程的实现接口,而Callable是在JDK1.5之后提出的;
  • java.lang.Runnable接口之中只提供有一个run()方法,并且没有返回值;
  • java.util.concurrent.Callable接口提供有call()方法,可以有返回值;.

创建线程的三种方式的对比

  1. 采用实现 RunnableCallable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
  2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。

线程的状态/生命周期

参考博文:线程状态 https://www.cnblogs.com/GooPolaris/p/8079490.html

线程的状态使用一个 枚举类型(Thread.State) 来描述的。这个枚举一共有6个值: NEW(新建)、RUNNABLE(运行)、BLOCKED(锁池)、TIMED_WAITING(定时等待)、WAITING(等待)、TERMINATED(终止、结束)。

线程共包括以下 5 种状态:
新建状态:
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
就绪状态:
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
运行状态:
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
阻塞状态:
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:

  • 等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
  • 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
  • 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。

死亡状态:
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

线程常用操作方法

线程的命名与获取

设置线程的名称:

  • 构造函数:public Thread​(String name)
  • 构造函数:public Thread​(Runnable target, String name)
  • 实例方法:public final void setName​(String name)

获取线程的名称:

  • 实例方法:public final String getName()
  • 静态方法:public static Thread currentThread().getName()
package javase.util;

/**
 * 实现Runnable接口创建多线程
 */
class MyThread implements Runnable {
	@Override
	public void run() {
		// 获取线程名称
		System.out.println(Thread.currentThread().getName());
	}
}

public class TestMain {
	public static void main(String[] args) {
		System.out.println("main主方法进程:"+ Thread.currentThread().getName());// main
		new Thread(new MyThread(), "线程A").start();// 线程A
		new Thread(new MyThread()).start(); // Thread-0
		new Thread(new MyThread()).start();// Thread-1
		new Thread(new MyThread(), "线程B").start();// 线程B
		Thread threadC = new Thread(new MyThread());
		threadC.setName("线程C");
		threadC.start();// // 线程C
	}
}

主线程子线程的关系

问题:

package javase.util;

public class TestMain {
	public static void main(String[] args) {
		System.out.println("执行任务一");

		int temp = 0;
		for (int i = 0; i < Integer.MIN_VALUE; i++) {
			temp += i;
		}
		
		// 以下的输出需要以上先执行完才会执行
		System.out.println("执行任务二");
		System.out.println("执行任务三");
	}
}

解决方案:

package javase.util;

public class TestMain {
	public static void main(String[] args) {
		System.out.println("执行任务一");
		
		new Thread(()->{
			int temp = 0;
			for (int i = 0; i < Integer.MIN_VALUE; i++) {
				temp += i;
			}
		});

		// 以下的输出不依赖于上面的执行,速度快多了
		System.out.println("执行任务二");
		System.out.println("执行任务三");
	}
}

线程休眠(暂停执行)

线程休眠:

  • 静态方法:public static void sleep​(long millis) throws InterruptedException
  • 静态方法:public static void sleep​(long millis, int nanos) throws InterruptedException

单个线程的休眠示例:

package javase.util;

public class TestMain {
	public static void main(String[] args) {
		new Thread(()->{
			for (int i = 0; i < 10; i++) {
				System.out.println(Thread.currentThread().getName()+",i = " + i);
				try {
					Thread.sleep(100);// 休眠100毫秒
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "线程对象").start();
	}
}

多个线程的示例:

package javase.util;

/**
 * 该程序执行上感觉上若干个线程一起休眠,一起唤醒,实际是有差别的
 */
public class TestMain {
	public static void main(String[] args) {
		Runnable runnable = ()->{
			for (int i = 0; i < 10; i++) {
				System.out.println(Thread.currentThread().getName()+",i = " + i);
				try {
					Thread.sleep(1000);// 休眠1秒
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		};
		// 创建5个线程对象
		for (int x = 1; x <= 5; x++) {
			new Thread(runnable, "线程对象"+x).start();
		}
	}
}

线程中断

在我们的程序中经常会有一些不达到目的不会退出的线程,例如:我们有一个下载程序线程,该线程在没有下载成功之前是不会退出的,若此时用户觉得下载速度慢,不想下载了,这时就需要用到我们的线程中断机制了,告诉线程,你不要继续执行了,准备好退出吧。当然,线程在不同的状态下遇到中断会产生不同的响应,有点会抛出异常,有的则没有变化,有的则会结束线程。本篇将从以下两个方面来介绍Java中对线程中断机制的具体实现:

  • Java中对线程中断所提供的API支持
  • 线程在不同状态下对于中断所产生的反应

Java中对线程中断所提供的API支持

在以前的jdk版本中,我们使用stop方法中断线程,但是现在的jdk版本中已经不再推荐使用该方法了,反而由以下三个方法完成对线程中断的支持。

public boolean isInterrupted():实例方法,主要用于判断当前线程对象的中断标志位是否被标记了,如果被标记了则返回true表示当前已经被中断,否则返回false。
public void interrupt():实例方法,该方法用于设置当前线程对象的中断标识位。
public static boolean interrupted() :静态的方法,用于返回当前线程是否被中断。

package javase.util;

/**
 * 线程中断:
 * 我们有一个下载程序线程,该线程在没有下载成功之前是不会退出的,若此时用户觉得下载速度慢,不想下载了,
 * 这时就需要用到我们的线程中断机制了,告诉线程,你不要继续执行了,准备好退出吧。
 */
public class TestMain {
	public static void main(String[] args) {
		Runnable runnable = ()->{
			// 以下模拟正常下载
			System.out.println("开始下载.......");
			for (int i = 0; i < 100; i++) {
				System.out.println("已下载进度" + i +"%");
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					// 中断了会报错
					//e.printStackTrace();
					System.out.println(Thread.currentThread().getName()+":中断下载");
					break;
				}
				
				/*
				// 是否中断
				if(Thread.interrupted()) {
					break;
				}
				*/
			}
		};
		Thread thread = new Thread(runnable, "下载任务进程");
		// 开始下载任务
		thread.start();


		// 以下模拟用户取消下载
		try {
			Thread.sleep(2000); // 用户等待2秒
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// 下载任务完成了吗?
		if(thread.isInterrupted()) {
			System.out.println("用户:这么快就下载好了,佩服!佩服!");
		}
		else {
			// 中断下载
			thread.interrupt();
			System.out.println("用户:这么慢还没下载好,不下载了!");
		}
	}
}

线程在不同状态下对于中断所产生的反应

参考博文:Java并发之线程中断[https://www.cnblogs.com/yangming1996/p/7612653.html]

线程强制执行

public final void join() throws InterruptedException

package javase.util;


/**
 * 主线程:main
 * 子线程:new Thread()
 * 现象:主线程以及子线程抢占交替执行
 */
public class TestMain {
	public static void main(String[] args) {
		// 子线程
		new Thread(()->{
			for (int i = 0; i < 5; i++) {
				System.out.println(Thread.currentThread().getName()+": "+ i);
			}
		}, "子线程").start();

		// 主线程
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName()+": "+ i);
		}
	}
}

执行结果:

main: 0
子线程: 0
main: 1
子线程: 1
子线程: 2
子线程: 3
main: 2
子线程: 4
main: 3
main: 4

强制执行:

package javase.util;


/**
 * 主线程:main
 * 子线程:new Thread()
 * 强制执行:一定需要先获取强制执行的线程对象,再使用join()方法
 */
public class TestMain {
	public static void main(String[] args) {
		// 获取主线程
		Thread mainThread = Thread.currentThread();

		// 子线程
		new Thread(()->{
			for (int i = 0; i < 5; i++) {
				// 满足条件时强制执行
				if(i == 2) {
					try {
						// 主线程先强制执行,此时主线程将全部执行之后,再执行子线程
						mainThread.join();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				System.out.println(Thread.currentThread().getName()+": "+ i);
			}
		}, "子线程").start();

		// 主线程
		for (int i = 0; i < 5; i++) {
			System.out.println(Thread.currentThread().getName()+": "+ i);
		}
	}
}

执行结果:

main: 0
main: 1
子线程: 0
main: 2
main: 3
main: 4
子线程: 1
子线程: 2
子线程: 3
子线程: 4

线程的礼让

public static void yield()

package javase.util;


/**
 * 主线程:main
 * 子线程:new Thread()
 * 线程的礼让
 */
public class TestMain {
	public static void main(String[] args) {
		// 子线程
		new Thread(()->{
			for (int i = 0; i < 10; i++) {
				// 满足条件时进行礼让
				if(i % 2 == 0) {
					// 进行礼让,让主线程先执行
					Thread.yield();
				}
				System.out.println(Thread.currentThread().getName()+": "+ i);
			}
		}, "子线程").start();

		// 主线程
		for (int i = 0; i < 20; i++) {
			System.out.println(Thread.currentThread().getName()+": "+ i);
		}
	}
}

执行结果:

子线程: 0
main: 0
main: 1
main: 2
main: 3
子线程: 1
main: 4
子线程: 2
main: 5
main: 6
main: 7
子线程: 3
main: 8
子线程: 4
main: 9
子线程: 5
main: 10
子线程: 6
main: 11
子线程: 7
main: 12
main: 13
main: 14
main: 15
main: 16
子线程: 8
main: 17
main: 18
main: 19
子线程: 9

线程的优先级

参考博文:Thread之五:线程的优先级https://www.cnblogs.com/duanxz/p/5226109.html】

具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。

  • 设置优先级:public final void setPriority​(int newPriority)
  • 获取优先级:public final int getPriority()

三个常量:

  • public static final int MAX_PRIORITY:10
  • public static final int NORM_PRIORITY:5
  • public static final int MIN_PRIORITY:1
package javase.util;

/**
 * 线程的优先级
 */
public class TestMain {
	public static void main(String[] args) {
		System.out.println("main主线程优先级:" + Thread.currentThread().getPriority()); // 中等:5
		System.out.println("默认线程优先级:" + new Thread().getPriority()); // 中等:5
		Runnable runnable = ()->{
			for (int i = 0; i < 10; i++) {
				System.out.println(Thread.currentThread().getName()+" 执行"+ i);
			}
		};
		Thread threadA = new Thread(runnable, "线程A");
		Thread threadB = new Thread(runnable, "线程B");
		Thread threadC = new Thread(runnable, "线程C");
		// 设置优先级
		threadA.setPriority(Thread.MIN_PRIORITY); // 设置优先级最低
		threadB.setPriority(Thread.NORM_PRIORITY); // 设置优先级中等
		threadC.setPriority(Thread.MAX_PRIORITY); // 设置优先级最高

		threadA.start();
		threadB.start();
		threadC.start();
	}
}

线程的同步与死锁

java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),
将会导致数据不准确,相互之间产生冲突,因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性。

参考博文:5个步骤,教你瞬间明白线程和线程安全
参考博文:java笔记–关于线程同步(7种同步方式)https://www.cnblogs.com/goody9807/p/6522176.html】

示例:

package javase.util;

/**
 * 卖票线程
 */
class TicketThread implements Runnable {
	int ticket = 10; // 总票数
	@Override
	public void run() {
		while (true) {
			if(ticket > 0){
				try {
					// 模拟网络延迟
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+" 执行"+ ticket--);
			}
			else {
				System.out.println("票卖完了");
				break;
			}
		}
	}
}

public class TestMain {
	public static void main(String[] args) {
		Runnable runnable = new TicketThread();
		Thread threadA = new Thread(runnable, "票贩子A");
		Thread threadB = new Thread(runnable, "票贩子B");
		Thread threadC = new Thread(runnable, "票贩子C");

		threadA.start();
		threadB.start();
		threadC.start();
	}
}

执行结果:

票贩子C 执行8
票贩子A 执行10
票贩子B 执行9
票贩子C 执行7
票贩子A 执行5
票贩子B 执行6
票贩子B 执行4
票贩子A 执行4
票贩子C 执行4

票贩子B 执行2
票卖完了
票贩子A 执行1
票卖完了
票贩子C 执行3
票卖完了

分析:
为什么会出现票数为负数?

synchronized关键字

synchronized关键字,就是用来控制线程同步的,保证我们的线程在多线程环境下,不被多个线程同时执行,确保我们数据的完整性,使用方法一般是加在方法上。
这样就可以确保我们的线程同步了,同时这里需要注意一个大家平时忽略的问题,首先synchronized锁的是括号里的对象,而不是代码,其次,对于非静态的synchronized方法,锁的是对象本身也就是this。
当synchronized锁住一个对象之后,别的线程如果想要获取锁对象,那么就必须等这个线程执行完释放锁对象之后才可以,否则一直处于等待状态。
注意点: 虽然加synchronized关键字,可以让我们的线程变得安全,但是我们在用的时候,也要注意缩小synchronized的使用范围,如果随意使用时很影响程序的性能,别的对象想拿到锁,结果你没用锁还一直把锁占用,这样就有点浪费资源。

package javase.util;

import java.util.Vector;
import java.util.concurrent.ConcurrentMap;

class TicketThread implements Runnable {
	int ticket = 1000;

	@Override
	public void run() {
		while (true) {
			synchronized (this) {
				if (ticket > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					ticket--;
					System.out.println(Thread.currentThread().getName() + ",还剩余" + this.ticket);
				} else {
					break;
				}
			}
		}
	}
}

public class TestMain {
	public static void main(String[] args) {
		TicketThread ticketThread = new TicketThread();
		new Thread(ticketThread).start();
		new Thread(ticketThread).start();
		new Thread(ticketThread).start();
	}
}
package javase.util;

import java.util.Vector;
import java.util.concurrent.ConcurrentMap;

class TicketThread implements Runnable {
	int ticket = 10;

	@Override
	public void run() {
		while (method()) {

		}
	}
	
	public synchronized boolean method(){
		if (ticket > 0) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			ticket--;
			System.out.println(Thread.currentThread().getName() + ",还剩余" + this.ticket);
			return true;
		}
		return false;
	}
}

public class TestMain {
	public static void main(String[] args) {
		TicketThread ticketThread = new TicketThread();
		new Thread(ticketThread).start();
		new Thread(ticketThread).start();
		new Thread(ticketThread).start();
	}
}

Lock类

综合实战:“生产者-消费者”模型

示例:

package javase.util;

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

class Producer implements Runnable {
	private Message msg ;
	public Producer(Message msg) {
		this.msg = msg ;
	}
	@Override
	public void run() {
		for (int x = 0 ; x < 100 ; x ++) {
			if (x % 2 == 0) {
				this.msg.setTitle("孙悟空");
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				this.msg.setContent("俺老孙来也");
			} else {
				this.msg.setTitle("猪八戒");
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				this.msg.setContent("散伙回高老庄");
			}
		}
	}
}
class Consumer implements Runnable {
	private Message msg ;
	public Consumer(Message msg) {
		this.msg = msg ;
	}
	@Override
	public void run() {
		for (int x = 0 ; x < 100 ; x ++) {
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(this.msg.getTitle() + "  -  " + this.msg.getContent());
		}
	}

}
class Message {
	private String title ;
	private String content ;
	public void setContent(String content) {
		this.content = content;
	}
	public void setTitle(String title) {
		this.title = title;
	}
	public String getContent() {
		return content;
	}
	public String getTitle() {
		return title;
	}
}

public class TestMain {
	public static void main(String[] args) {
		Message message = new Message();
		new Thread(new Producer(message)).start();    // 启动生产者线程
		new Thread(new Consumer(message)).start();    // 启动消费者线程
	}
}

孙悟空 - null
孙悟空 - null
孙悟空 - null
孙悟空 - null
孙悟空 - null
孙悟空 - null
孙悟空 - null
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
孙悟空 - 散伙回高老庄
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也
猪八戒 - 俺老孙来也

package javase.util;

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

class Producer implements Runnable {
	private Message msg ;
	public Producer(Message msg) {
		this.msg = msg ;
	}
	@Override
	public void run() {
		for (int x = 0 ; x < 100 ; x ++) {
			if (x % 2 == 0) {
				this.msg.set("孙悟空", "俺老孙来也");
			} else {
				this.msg.set("猪八戒", "散伙回高老庄");
			}
		}
	}
}
class Consumer implements Runnable {
	private Message msg ;
	public Consumer(Message msg) {
		this.msg = msg ;
	}
	@Override
	public void run() {
		for (int x = 0 ; x < 100 ; x ++) {
			System.out.println(this.msg.get());
		}
	}

}
class Message {
	private String title ;
	private String content ;
	public synchronized void set(String title,String content) {
		this.title = title ;
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.content = content ;
	}
	public synchronized String get() {
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return this.title + "  -  " + this.content ;
	}
}

public class TestMain {
	public static void main(String[] args) {
		Message message = new Message();
		new Thread(new Producer(message)).start();    // 启动生产者线程
		new Thread(new Consumer(message)).start();    // 启动消费者线程
	}
}

孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
猪八戒 - 散伙回高老庄
猪八戒 - 散伙回高老庄
猪八戒 - 散伙回高老庄
猪八戒 - 散伙回高老庄
猪八戒 - 散伙回高老庄

正常的:

package javase.util;

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

class Producer implements Runnable {
	private Message msg ;
	public Producer(Message msg) {
		this.msg = msg ;
	}
	@Override
	public void run() {
		for (int x = 0 ; x < 100 ; x ++) {
			if (x % 2 == 0) {
				this.msg.set("孙悟空", "俺老孙来也");
			} else {
				this.msg.set("猪八戒", "散伙回高老庄");
			}
		}
	}
}
class Consumer implements Runnable {
	private Message msg ;
	public Consumer(Message msg) {
		this.msg = msg ;
	}
	@Override
	public void run() {
		for (int x = 0 ; x < 100 ; x ++) {
			System.out.println(this.msg.get());
		}
	}

}
class Message {
	private String title ;
	private String content ;
	private boolean flag = true ; // 表示生产或消费的形式
	// flag = true:允许生产,但是不允许消费
	// flag = false:允许消费,不允许生产
	public synchronized void set(String title,String content) {
		if (this.flag == false) {	// 无法进行生产,应该等待被消费
			try {
				super.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.title = title ;
		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		this.content = content ;
		this.flag = false ; // 已经生产过了
		super.notify(); // 唤醒等待的线程
	}
	public synchronized String get() {
		if (this.flag == true) {	// 还未生产,需要等待
			try {
				super.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		try {
			return this.title + "  -  " + this.content ;
		} finally {	// 不管如何都要执行
			this.flag = true ; // 继续生产
			super.notify(); // 唤醒等待线程
		}
	}
}

public class TestMain {
	public static void main(String[] args) {
		Message message = new Message();
		new Thread(new Producer(message)).start();    // 启动生产者线程
		new Thread(new Consumer(message)).start();    // 启动消费者线程
	}
}

孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄
孙悟空 - 俺老孙来也
猪八戒 - 散伙回高老庄

多线程深入话题

优雅的停止线程

package javase.util;

/**
 * 优雅的停止线程
 */
public class TestMain {
	// 判断线程停止的条件
	private static boolean flag = true;

	public static void main(String[] args) {
		new Thread(()->{
			long num = 0;
			while (flag) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName()+" : num="+ num ++);
			}
		}, "线程A").start();
		// 模拟运行一段时间
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		// 优雅停止
		flag = false;
	}
}

后台守护线程

Java的线程分为两种:User Thread(用户线程)、DaemonThread(守护线程)。
只要当前JVM实例中尚存任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束是,守护线程随着JVM一同结束工作,Daemon作用是为其他线程提供便利服务,守护线程最典型的应用就是GC(垃圾回收器),他就是一个很称职的守护者。

设置守护线程:public final void setDaemon​(boolean on)
判断是否为守护线程:public final boolean isDaemon()

package javase.util;

/**
 * 后台守护线程:当用户线程完成之后,守护线程关闭
 */
public class TestMain {
	public static void main(String[] args) {
		Thread userThread = new Thread(()->{
			for (int i = 0; i < 10; i++) {
				System.out.println(Thread.currentThread().getName()+"正在运行,i = " + i );
			}
		}, "用户线程,完成核心业务");

		Thread daemonThread = new Thread(()->{
			for (int i = 0; i < Integer.MAX_VALUE; i++) {
				System.out.println(Thread.currentThread().getName()+"正在运行,i = " + i );
			}
		}, "守护线程");

		daemonThread.setDaemon(true); // 设置守护线程
		userThread.start();
		daemonThread.start();
	}
}

volatile关键字

在多线程中,volatile关键字主要是在属性定义上使用的,表示此属性为直接数据操作,而不进行副本的拷贝处理。

package javase.util;

class MyThead implements Runnable {
	// 直接内存操作。能更快对变量处理
	private volatile int ticket = 5;

	@Override
	public void run() {
		synchronized (this) {
			while (this.ticket > 0) {
				System.out.println(Thread.currentThread().getName() + "抢到了票,还剩" + this.ticket--);
			}
		}
	}
}

public class TestMain {

	public static void main(String[] args) {
		MyThead ticketThread = new MyThead();
		new Thread(ticketThread, "线程A").start();
		new Thread(ticketThread, "线程B").start();
		new Thread(ticketThread, "线程C").start();
	}
}

面试题:volatile和synchronized区别?

①volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
②volatile只能保证数据的可见性,不能用来同步,因为多个线程并发访问volatile修饰的变量不会阻塞。
synchronized不仅保证可见性,而且还保证原子性,因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行。多个线程争抢synchronized锁对象时,会出现阻塞。

多线程综合案例

数字加减

设计4个线程,两个线程执行加操作,两个线程执行减操作。

package javase.util;

/**
 * 定义操作的资源
 */
class Resource {
	private int num = 0; // 进行加减的数字
	private boolean flag = true; // 加减的切换,flag = true时,表示可以进行加法操作,不可以减法操作;flag = false时,表示不可以进行加法操作,可以减法操作

	/**
	 * 加法
	 * @throws InterruptedException
	 */
	public synchronized void add() throws InterruptedException{
		if(this.flag == false) { // 不可以加法,只能减法操作。先等待
			super.wait();
		}
		Thread.sleep(100);
		this.num++;
		System.out.println("【执行加法操作】"+Thread.currentThread().getName()+", num = " + num);
		this.flag = false;	// 加法操作之后,只能进行减法操作,需设置为false
		this.notifyAll(); // 唤醒多个线程
	}

	public synchronized void sub() throws InterruptedException{
		if(this.flag == true) { // 不可以减法,只能加法操作。先等待
			super.wait();
		}
		Thread.sleep(200);
		this.num--;
		System.out.println("【执行减法操作】"+Thread.currentThread().getName()+", num = " + num);
		this.flag = true; // 减法操作之后,只能进行加法操作,需设置为true
		this.notifyAll();
	}
}

/**
 * 加法操作的线程
 */
class AddThread implements Runnable {
	Resource resource;
	public AddThread(Resource resource) {
		this.resource = resource;
	}
	@Override
	public void run() {
		for (int i = 0; i < 50; i++) {
			try {
				resource.add();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

/**
 * 减法的线程
 */
class SubThread implements Runnable {
	Resource resource;
	public SubThread(Resource resource) {
		this.resource = resource;
	}
	@Override
	public void run() {
		for (int i = 0; i < 50; i++) {
			try {
				resource.sub();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

public class TestMain {
	public static void main(String[] args) {
		Resource resource = new Resource();
		AddThread addThread = new AddThread(resource);
		SubThread subThread = new SubThread(resource);
		// 两个加法线程
		new Thread(addThread, "A").start();
		new Thread(addThread, "B").start();

		// 两个减法线程
		new Thread(subThread, "X").start();
		new Thread(subThread, "Y").start();
	}
}

生产电脑

设计一个生产电脑和搬走电脑类,要求生产一台电脑就搬走一台电脑,如果没有新电脑生产出来则搬运工要等待新电脑的生产;如果生产出的电脑没有搬走,则要等待电脑搬走之后再生产,并统计出生产电脑的数量。

package javase.util;


class Producer implements Runnable {
	private Resource resource ;
	public Producer(Resource resource) {
		this.resource = resource ;
	}
	public void run() {
		for (int x = 0 ; x < 50 ; x ++) {
			try {
				this.resource.make();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	};
}
class Consumer implements Runnable {
	private Resource resource ;
	public Consumer(Resource resource) {
		this.resource = resource ;
	}
	public void run() {
		for (int x = 0 ; x < 50 ; x ++) {
			try {
				this.resource.get();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	};
}
class Resource {
	private Computer computer ;
	public synchronized void make() throws Exception {
		if (this.computer != null) {	// 已经生产过了
			super.wait();
		}
		Thread.sleep(100);
		this.computer = new Computer("MLDN牌电脑",1.1) ;
		System.out.println("【生产电脑】" + this.computer);
		super.notifyAll();
	}
	public synchronized void get() throws Exception {
		if (this.computer == null) {	// 没有生产过
			super.wait();
		}
		Thread.sleep(10);
		System.out.println("【取走电脑】" + this.computer);
		this.computer = null ; // 已经取走了
		super.notifyAll();
	}
}

class Computer {
	private static int count = 0 ; // 表示生产的个数
	private String name ;
	private double price ;
	public Computer(String name,double price) {
		this.name = name ;
		this.price = price ;
		count ++ ;
	}
	public String toString() {
		return "【第" + count + "台电脑】" + "电脑名字:" + this.name + "、价值:" + this.price;
	}
}

public class TestMain {
	public static void main(String[] args) {
		Resource res = new Resource() ;
		new Thread(new Producer(res)).start();
		new Thread(new Consumer(res)).start();
	}
}

竞拍抢答

实现一个竞拍抢答程序:要求设置三个抢答者(三个线程),而后同时发出抢答指令,抢答成功者给出成功提示,未抢答成功者给出失败提示。

package javase.util;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class MyThread implements Callable<String> {
	private boolean flag = true; // 抢答处理

	@Override
	public String call() throws Exception {
		synchronized (this) { // 数据同步
			if (this.flag == false) { // 抢答成功
				this.flag = true;
				return Thread.currentThread().getName() + "抢答成功!";
			}
			return Thread.currentThread().getName() + "抢答失败!";
		}
	}
}

public class TestMain {
	public static void main(String[] args) throws ExecutionException, InterruptedException {
		MyThread mt = new MyThread() ;
		FutureTask<String> taskA = new FutureTask<String>(mt) ;
		FutureTask<String> taskB = new FutureTask<String>(mt) ;
		FutureTask<String> taskC = new FutureTask<String>(mt) ;
		new Thread(taskA,"竞赛者A").start();
		new Thread(taskB,"竞赛者B").start();
		new Thread(taskC,"竞赛者C").start();
		System.out.println(taskA.get());
		System.out.println(taskB.get());
		System.out.println(taskC.get());
	}
}

猜你喜欢

转载自blog.csdn.net/u013474568/article/details/85262776