多线程编程
Java多线程编程
进程、线程概念
Java 给多线程编程提供了内置的支持。 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
进程和线程的区别
进程
应用程序的执行实例,有独立的内存空间和系统资源。一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
线程
CPU调度和分派的基本单位,进程中执行运算的最小单位,可完成一个独立的顺序控制流程。多线程能满足程序员编写高效率的程序来达到充分利用 CPU 的目的。
进程和线程的关系:
- 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。
- 资源分配给进程,同一进程的所有线程共享该进程的所有资源。 同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。
- 处理机分给线程,即真正在处理机上运行的是线程。
- 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
创建一个线程
Java 提供了三种创建线程的方法:
- 通过实现
Runnable
接口; - 通过继承
Thread
类本身; - 通过
Callable
和Future
创建线程。
通过继承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 创建线程
- 创建
Callable
接口的实现类,并实现call()
方法,该call()
方法将作为线程执行体,并且有返回值。- 创建
Callable
实现类的实例,使用FutureTask
类来包装Callable
对象,该FutureTask
对象封装了该Callable
对象的call()
方法的返回值。- 使用
FutureTask
对象作为Thread
对象的target
创建并启动新线程。- 调用
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);
}
}
面试题: 请解释 Runnable
与Callable
的区别?。
- Runnable是在JDK1.0的时候提出的多线程的实现接口,而Callable是在JDK1.5之后提出的;
- java.lang.Runnable接口之中只提供有一个run()方法,并且没有返回值;
- java.util.concurrent.Callable接口提供有call()方法,可以有返回值;.
创建线程的三种方式的对比
- 采用实现
Runnable
、Callable
接口的方式创建多线程时,线程类只是实现了Runnable
接口或Callable
接口,还可以继承其他类。 - 使用继承
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());
}
}