线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。(来源百度百科)
最简单的理解就是在Java中,当我们启动main函数时其实就启动了一个JVM进程,而main函数所在的线程就是这个进程中的一个线程,也称为主线程
在Java中创建线程有三种方式,分别是
/**
* @author Woo_home
* @create by 2019/10/24
*/
public class RunnableTask implements Runnable{
@Override
public void run() {
System.out.println("Thread running......");
}
public static void main(String[] args) throws InterruptedException{
RunnableTask task = new RunnableTask();
new Thread(task).start();
new Thread(task).start();
}
}
/**
* @author Woo_home
* @create by 2019/10/24
*/
public class ThreadTest extends Thread{
@Override
public void run() { //继承Thread类并重写run()方法
System.out.println("Thread running......");
}
public static void main(String[] args) {
ThreadTest test = new ThreadTest();//创建线程
test.start();//启动线程
}
}
/**
* @author Woo_home
* @create by 2019/10/24
*/
//类似Runnable
public class CallerTask implements Callable<String> {
@Override
public String call() throws Exception {
return "Thread running......";
}
public static void main(String[] args) {
//创建异步任务
FutureTask<String> futureTask = new FutureTask<String>(new CallerTask());
//启动线程
new Thread(futureTask).start();
try {
//等待任务执行完毕,并返回结果
String result = futureTask.get();
System.out.println(result);
}catch (Exception e){
e.printStackTrace();
}
}
}
CallerTask类实现了Callable接口的call()方法,在main函数内首先创建了一个FutureTask对象(构造函数为CallerTask的实例),然后使用创建的FutureTask对象作为任务创建了一个线程并且启动它,然后通过futureTask.get()等待任务执行完毕并返回结果
Runnable接口其实非常简单,只是定义了一个无参无返回值的抽象run()方法,具体代码如下:
public interface Runnable {
public abstract void run();
}
在很多时候JDK中代表线程的就只有Thread这个类,而线程执行单元就是run()方法,可以用过继承Thread类然后重写run()方法来实现自己的业务逻辑,也可以实现Runnable接口实现自己的业务逻辑,如:
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run(); //这个run()就是Runnable接口中的run()方法
}
}
总结:使用继承的方式的好处是方便传参,你可以在子类里面添加成员变量,通过set方法设置参数或者通过构造是函数进行传递,而如果使用Runnable方式,则只能使用主线程里面被声明为final的变量。不好的地方是Java并不支持多继承,如果继承了Thread类,那么子类不能再继承其他类,而Runnable则没有这个限制。继承Thread和实现Runnable都没办法拿到任务的返回结果,但是FutureTask方式可以
如果从代码结构的本身来讲肯定是使用 Runnable 是最方便的,因为其可以避免单继承的局限,同时也可以更好的进行功能的扩展
但是从结构上也需要观察 Thread 与 Runnable 的联系
public class Thread extends Object implements Runnable {}
从代码中可以发现在 Thread 类也是 Runnable 接口的子类,那么在之前继承 Thread 类的时候实际上重写的还是 Runnable 接口的 run() 方法
在多线程的设计之中,使用了代理设计模式的结构,用户自定义的线程主体只是负责项目核心功能的实现,而所有的辅助实现全部交由 Thread 类来处理,关于代理设计模式可以看这里 代理设计模式
在进行 Thread 启动多线程的时候调用的是 start() 方法,而后找到的是 run() 方法,那么 start() 是如何调用 run() 方法的呢?
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
当我们通过 Thread 类的构造方法传递了一个 Runnable 的接口对象的时候,那么该接口对象将被 Thread 类中的 target 属性所保存,在 start() 方法执行的时候会调用 Thread 类中的 run() 方法,而这个 run() 方法会去调用 Runnable 接口子类被重写过的 run() 方法
多线程开发的本质实际上是在于多个线程可以进行同一个资源的抢占,那么 Thread 主要描述的是线程,而资源的描述是通过 Runnable 来完成的
举例:(模仿一个商品抢购的例子)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
class ProductThread implements Runnable {
private int product = 10; // 商品数量
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (this.product > 0) {
System.out.println("抢购,product = " + this.product--);
}
}
}
}
public class Demo {
public static void main(String[] args) {
ProductThread product = new ProductThread();
new Thread(product).start(); // 第一个线程启动
new Thread(product).start(); // 第二个线程启动
new Thread(product).start(); // 第三个线程启动
}
}
输出:
抢购,product = 10
抢购,product = 8
抢购,product = 9
抢购,product = 6
抢购,product = 5
抢购,product = 4
抢购,product = 3
抢购,product = 2
抢购,product = 1
抢购,product = 7
可以到一个线程对象代表着一个用户,三个线程对象(用户)抢购 10 个商品
对于多线程的开发而言,编写程序的过程之中总是按照:定义的线程主体类,而后通过 Thread 类进行线程的启动,但是并不意味着你调用了 start() 方法,线程就已经开始运行了,因为整体的线程处理有自己的一套运行的状态
多线程的运行状态是不确定的,那么在程序的开发之中为了可以获取到一些需要使用到线程只能依靠线程的名字来进行操作。所以线程的名字是一个至关重要的概念,这样在 Thread 类之中就提供有线程名称的处理
对于线程对象的获得是不可能只是依靠一个 this 来完成的,因为线程的状态是不可控的,但是有一点可以明确的是,所有的线程对象一定要执行 run() 方法,那么这个时候可以考虑获取当前线程,在 Thread 类里面提供了获取当前线程的方法
举例:(观察线程的命名操作)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class Demo1 {
public static void main(String[] args) {
MyThread thread = new MyThread();
new Thread(thread,"线程 A").start(); // 设置线程的名字
new Thread(thread).start(); // 未设置线程的名字
new Thread(thread,"线程 B").start(); // 设置线程的名字
}
}
输出:
从输出可以发现当开发者为线程设置名字的时候就使用设置的名字,如果没有设置名字,则会自动生成一个不重复的名字,比如我们在上述的代码中再加几个线程
public class Demo1 {
public static void main(String[] args) {
MyThread thread = new MyThread();
new Thread(thread,"线程 A").start(); // 设置线程的名字
new Thread(thread).start(); // 未设置线程的名字
new Thread(thread).start();
new Thread(thread).start();
new Thread(thread,"线程 B").start(); // 设置线程的名字
}
}
可以发现,如果没有命名的线程会自动产生一个编号来代替线程的名字,来保证不重复,那么这种编号是如何产生的呢?其实这种属性命名主要是依靠了 static 属性来完成的,在 Thread 类里面定义有如下操作:
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
从代码中可以发现,如果是没有命名的线程会执行这个构造方法,这个构造方法的特点就是 nextThreadNum(),我们来看下 nextThreadNum() 这个方法,发现这是一个静态的方法,所以这也就是为什么会产生不重复的编号了
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
我们再来举个例子
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
public class Demo1 {
public static void main(String[] args) {
MyThread thread = new MyThread();
new Thread(thread,"线程对象").start(); // 设置线程的名字
thread.run(); // 对象直接调用 run() 方法
}
}
通过此时的代码可以发现当使用了 “thread.run()” 直接在主方法之中调用线程类对象中的 run() 方法所获得的线程对象的名字为 “main” ,所以可以得出一个结论:主方法也是一个线程。那么现在的问题来了,所有的线程都是在进程上的一个划分,那么进程在哪里?每当使用 Java 命令执行程序的时候就表示启动了一个 JVM 的进程,一台电脑上可以同时启动若干个 JVM 进程,所以 JVM 进程都会有各自的线程
在任何的开发之中,主线程可以创建若干个子线程。然而创建子线程的目的就是可以将一些复杂逻辑或者比较耗时的逻辑交由子线程去处理
举例: (子线程处理)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
public class Demo1 {
public static void main(String[] args) {
System.out.println("1、执行操作任务一");
new Thread(() -> { // 交由子线程去计算
int temp = 0;
for (int i = 0; i < Integer.MAX_VALUE; i++) {
temp += i;
}
}).start();
System.out.println("2、执行操作任务二");
System.out.println("n、执行操作任务N");
}
}
如果说现在希望某一个线程可以暂缓执行,那么就可以使用休眠处理,在 Thread 类中定义的休眠方法如下:
在进行休眠的时候有可能会产生中断异常 “InterruptedException”,中断异常属于 Exception 的子类,所以证明该异常必须进行处理
举例:(观察线程休眠处理)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
public class Demo1 {
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(1000); // 暂缓执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread1").start();
}
}
输出:
休眠的主要特点就是可以自动实现线程的唤醒,以继续进行后续的处理。但是需要注意的是,如果现在你有多个线程对象,那么休眠也是有先后顺序的
举例:(产生多个线程对象进行休眠处理)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
public class Demo1 {
public static void main(String[] args) {
for (int num = 0; num < 5; num++) {
new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + ".i = " + i);
try {
Thread.sleep(1000); // 暂缓执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"Thread1 - " + num).start();
}
}
}
此时产生五个线程对象,并且这五个线程对象执行的方法体是相同的。但是此时从程序执行的感觉上来讲好像是若干个线程一起进行了休眠,而后一起进行了自动唤醒
在上面我们讲过线程的休眠里面提供有一个中断异常,实际上就证明线程的休眠是可以被打断的,而这种打断肯定是由其它线程完成的。在 Thread 类里面提供有这种中断执行的方法
举例:(观察线程的中断处理操作)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("线程的启动");
try {
Thread.sleep(10000); // 休眠 10 s
System.out.println("线程的关闭");
} catch (InterruptedException e) {
System.out.println("O_O");
}
});
thread.start(); // 开始执行
Thread.sleep(1000);
if (!thread.isInterrupted()) { // 判断该线程中断了没
System.out.println("线程中断了");
thread.interrupt(); // 线程中断
}
}
}
输出:
所有正在执行的线程都是可以被中断的,中断线程必须进行异常的处理
所谓的线程强制执行指的是当满足于某些条件之后,某一个线程对象将可以一直独占资源,一直到该线程的程序执行结束
举例:(先来观察一下没有强制执行的程序)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行.i = " + i);
}
},"正常的线程执行操作");
thread.start();
for (int i = 0; i < 100; i++) {
Thread.sleep(100);
System.out.println("main number = " + i);
}
}
}
部分运行截图
这个时候可以发现主线程和子线程都在交替执行者,但是如果说现在你希望主线程独占执行。那么就可以利用 Thread 类中的方法
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread(); // 主线程
Thread thread = new Thread(() -> {
for (int i = 0; i < 100; i++) {
if (i == 3) {
try {
mainThread.join(); // main 线程要先执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行.i = " + i);
}
},"正常的线程执行操作");
thread.start();
for (int i = 0; i < 100; i++) {
Thread.sleep(100);
System.out.println("main线程 number = " + i);
}
}
}
部分运行截图
在进行线程强制执行的时候一定要获取强制执行线程对象之后才可以执行 join() 调用
线程的礼让就是指先将资源让出去让别的线程先执行。线程的礼让可以使用 Thread 中提供的方法:
举例:(使用线程礼让操作)
为了方便,我们直接把上面的代码拿过来修改吧,如下:
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 0; i < 100; i++) {
if (i % 3 == 0) {
Thread.yield(); // 线程礼让
System.out.println("线程礼让执行");
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行.i = " + i);
}
},"正常的线程执行操作");
thread.start();
for (int i = 0; i < 100; i++) {
Thread.sleep(100);
System.out.println("main线程 number = " + i);
}
}
}
礼让执行的时候每一次调用 yield() 方法都只会礼让一次当前的资源
从理论上来讲线程的优先级越高越有可能先执行(越有可能先抢占到资源)。在 Thread 类里面针对于优先级提供了两个处理方法
在进行优先级定义的时候都是通过 int 类型的数字来完成的,而对于此数字的选择在 Thread 类里面定义了三个常量
举例:(观察优先级)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () -> {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
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); // 将 threadA 设置为最低优先级
threadB.setPriority(Thread.MIN_PRIORITY); // 将 threadB 设置为最低优先级
threadC.setPriority(Thread.MAX_PRIORITY); // 将 threadC 设置为最高优先级
threadA.start();
threadB.start();
threadC.start();
}
}
输出:
前面我们说过主方法也是一个线程也叫主线程,那么主线程的优先级呢?
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
public class Demo1 {
public static void main(String[] args) {
// 默认的线程
System.out.println(new Thread().getPriority());
// 主方法线程
System.out.println(Thread.currentThread().getPriority());
}
}
输出结果为 5,前面我们说过中等优先级的默认值也是 5,那么也就是说主线程属于中等优先级,而默认创建的线程也是中等优先级
总结一下:只是优先级高的有可能先执行
在多线程的处理当中,可以利用 Runnable 描述多个线程操作的资源,而 Thread 描述的是每一个线程对象,于是当多个线程访问统一资源的时候如果处理不当就会产生数据的错误操作
下面编写一个简单的商品抢购程序,将创建若干个线程对象实现抢购的处理操作
举例:(实现商品抢购)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
class ProductThreadDemo implements Runnable {
private int products = 10; // 总商品数为 10
@Override
public void run() {
while (true) {
if (this.products > 0) {
System.out.println(Thread.currentThread().getName() + "抢购.product" + this.products--);
} else {
System.out.println("------ 商品已经抢购完咯 ------");
break;
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
ProductThreadDemo demo = new ProductThreadDemo();
new Thread(demo,"用户 A").start();
new Thread(demo,"用户 B").start();
new Thread(demo,"用户 C").start();
}
}
此时的线程将创建三个线程对象,并且这三个线程对象将进行 10 个商品的抢购,从输出可以看到没什么问题
此时的程序在进行抢购的处理的时候并没有任何的问题(假象),下面可以模拟一下抢购中的延迟操作,就是加个 Thread.sleep(100)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
class ProductThreadDemo implements Runnable {
private int products = 10; // 总商品数为 10
@Override
public void run() {
while (true) {
if (this.products > 0) {
try {
Thread.sleep(100); // 模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢购.product" + this.products--);
} else {
System.out.println("------ 商品已经抢购完咯 ------");
break;
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
ProductThreadDemo demo = new ProductThreadDemo();
new Thread(demo,"用户 A").start();
new Thread(demo,"用户 B").start();
new Thread(demo,"用户 C").start();
}
}
现在出现了一个用户抢到商品 0 和 -1 的情况,也就是说商品明明已经被用户们抢购完了,你现在还来个用户抢够到商品,上哪去找商品发给用户呢?
这个时候追加了延迟问题就暴露出来了,而实际上这个问题一直都在(自己画的图,不好看勿喷)
经过上述的分析之后已经可以确认同步问题所产生的主要原因了,那么下面就需要进行同步问题的解决,但是解决同步问题的关键是锁,那么什么是锁呢?指的是当某一个线程执行操作的时候,其它线程外面等待;
如果想要在程序当中实现这把锁的功能,就可以使用 synchronized 关键字来实现,利用此关键字可以定义同步方法或同步代码块,在同步代码块的操作里面的代码只允许一个线程执行
// 用法
synchronized(同步对象) {
同步代码块操作
}
一般要进行同步对象处理的时候可以采用当前对象 this 进行同步
举例:(利用同步代码块解决数据同步访问问题)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
class ProductThreadDemo implements Runnable {
private int products = 10; // 总商品数为 10
@Override
public void run() {
while (true) {
synchronized (this) { // 每次只允许一个线程访问
if (this.products > 0) {
try {
Thread.sleep(100); // 模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢购.product" + this.products--);
} else {
System.out.println("------ 商品已经抢购完咯 ------");
break;
}
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
ProductThreadDemo demo = new ProductThreadDemo();
new Thread(demo,"用户 A").start();
new Thread(demo,"用户 B").start();
new Thread(demo,"用户 C").start();
}
}
此时就不会出现商品抢购为负数的问题了,但是加入同步处理之后,程序的整体性能下降了。同步实际上会造成性能的降低
那么如何实现同步方法呢?只需要在方法定义上使用 synchronized 关键字即可
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
class ProductThreadDemo implements Runnable {
private int products = 10; // 总商品数为 10
public synchronized boolean sale() {
if (this.products > 0) {
try {
Thread.sleep(100); // 模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢购.product" + this.products--);
return true;
} else {
System.out.println("------ 商品已经抢购完咯 ------");
return false;
}
}
@Override
public void run() {
while (this.sale()) {
;
}
}
}
public class Demo1 {
public static void main(String[] args) {
ProductThreadDemo demo = new ProductThreadDemo();
new Thread(demo,"用户 A").start();
new Thread(demo,"用户 B").start();
new Thread(demo,"用户 C").start();
}
}
在多线程的开发过程之中最为著名的案例就是生产者与消费者操作,该操作的主要流程如下:
可以将生产者与消费者定义为两个独立的线程类对象,但是对于现在生产的数据,可以使用如下的组成:
既然生产者与消费者是两个独立的线程,那么这两个独立的线程之间就需要有一个数据的保存的集中点,那么可以定义一个单独的 Message 类实现数据的保存
举例:(生产者与消费者的实现)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
// 消息中心
class Message {
private String title;
private String content;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
// 生产者
class Producer implements Runnable{
private Message message;
public Producer(Message message) {
this.message = message;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
this.message.setTitle("Java");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.message.setContent("生产者与消费者");
} else {
this.message.setTitle("Python");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.message.setContent("数据分析");
}
}
}
}
// 消费者
class Consumer implements Runnable {
private Message message;
public Consumer(Message message) {
this.message = message;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.message.getTitle() + " - " + this.message.getContent());
}
}
}
public class Demo1 {
public static void main(String[] args) {
Message message = new Message();
new Thread(new Producer(message)).start(); // 启动生产者线程
new Thread(new Consumer(message)).start(); // 启动消费者线程
}
}
如果要解决问题的话,首先要解决的就是数据同步处理的问题了,如果要想解决数据同步最简单的做法就是使用 synchronized 关键字定义同步代码块或同步方法,于是这个时候对于同步的处理就可以直接在 Message 类中完成
举例:(解决同步操作)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
class Message {
private String title;
private String content;
public synchronized void set(String title, String content) {
this.title = title;
try {
Thread.sleep(10);
} 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;
}
}
class Producer implements Runnable{
private Message message;
public Producer(Message message) {
this.message = message;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
this.message.set("Java","生产者与消费者");
} else {
this.message.set("Python","数据分析");
}
}
}
}
class Consumer implements Runnable {
private Message message;
public Consumer(Message message) {
this.message = message;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.message.get());
}
}
}
public class Demo1 {
public static void main(String[] args) {
Message message = new Message();
new Thread(new Producer(message)).start(); // 启动生产者线程
new Thread(new Consumer(message)).start(); // 启动消费者线程
}
}
在进行同步处理的时候肯定需要有一个同步的处理对象,那么此时肯定要将同步操作交由 Message 类处理是最合适的。这个时候返现数据已经可以正常的保持一致了,但是对于重复操作的问题依然存在
如果说现在想要解决生产者与消费者的问题,那么最好的解决方案就是使用等待与唤醒机制。而对于等待与唤醒机制的操作主要依靠的是 Object 类中提供的方法处理的:
如果此时有若干个等待线程的话,那么 notify() 表示的是唤醒第一个等待的,而其它的线程继续等待;而 notifyAll() 表示唤醒所以的线程,哪个线程的优先级高就有可能先执行
对于当前的问题主要的解决方法应该是通过 Message 类完成处理
举例:(修改 Message 类)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
class Message {
private String title;
private String content;
/**
* 表示生产或者消费的形式
* 如果 flag = true 表示允许生产,但是不允许消费
* 如果 flag = false 表示允许消费,不允许生产
*/
private boolean flag;
public synchronized void set(String title, String content) {
if (!this.flag) { // 无法进行生产,应该等待被消费
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) { // 还未生产,需要等待
try {
super.wait();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
return this.title + " -- " + this.content;
} finally { // 不管如何都要执行
this.flag = true; // 继续生产
super.notify(); // 唤醒等待线程
}
}
}
class Producer implements Runnable{
private Message message;
public Producer(Message message) {
this.message = message;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
this.message.set("Java","生产者与消费者");
} else {
this.message.set("Python","数据分析");
}
}
}
}
class Consumer implements Runnable {
private Message message;
public Consumer(Message message) {
this.message = message;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(this.message.get());
}
}
}
public class Demo1 {
public static void main(String[] args) {
Message message = new Message();
new Thread(new Producer(message)).start(); // 启动生产者线程
new Thread(new Consumer(message)).start(); // 启动消费者线程
}
}
这种处理形式就是在进行多线程开发过程之中最原始的处理方案,整个的等待、同步、唤醒机制都是由开发者自行通过原生代码实现控制
我们知道虽然可以通过继承 Thread 类来实现多线程的定义,但是在 Java 程序里面对于继承永远都是存在有单继承局限的,所以在 Java 里面又提供了有第二种多线程的主体定义结构形式:实现 java.lang.Runnable 接口,此接口定义如下:
// 从 JDK 1.8 引入 Lambda 表达式之后就变为了函数式的接口
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
举例:通过 Runnable 实现多线程主体类
class ThreadDemoTest implements Runnable { // 线程的主体类
private String title;
public ThreadDemoTest(String title) {
this.title = title;
}
@Override
public void run() { // 线程的主体方法
for (int i = 0; i < 10; i++) {
System.out.println(this.title + "运行,x = " + i);
}
}
}
由于不再是继承 Thread 父类了,那么对于此时的 ThreadDemoTest 类中也不再支持有 start() 这个继承方法。可以如果不使用 Thread.start() 方法是无法进行多线程的启动的,那么这个时候就需要去观察一下 Thread 类所提供的构造方法了
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
Thread(Runnable target, AccessControlContext acc) {
init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name) {
init(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}
虽然 Thread 的构造有很多,但是这里我们只需要看下第二个构造方法就够了
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
Java 里面提供有一个 java.lang.Thread 的程序类,那么一个类只要继承了此类就表示这个类可以直接实现多线程处理了
class ThreadDemoTest extends Thread { // 线程的主体类
private String title;
public ThreadDemoTest(String title) {
this.title = title;
}
@Override
public void run() { // 线程的主体方法
for (int i = 0; i < 10; i++) {
System.out.println(this.title + "运行,i = " + i);
}
}
}
多线程要执行的功能都应该在 run() 方法中进行定义。需要说明的是:在正常的情况下如何产生实例化对象,而后去调用类中提供的方法,但是 run() 方法是不能够被直接调用的,因为这里面牵扯到一个操作系统的资源调度问题
举个例子:
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
class ThreadDemoTest extends Thread { // 线程的主体类
private String title;
public ThreadDemoTest(String title) {
this.title = title;
}
@Override
public void run() { // 线程的主体方法
for (int i = 0; i < 10; i++) {
System.out.println(this.title + "运行,x = " + i);
}
}
}
public class Test {
public static void main(String[] args) {
new ThreadDemoTest("线程 A").run();
new ThreadDemoTest("线程 B").run();
new ThreadDemoTest("线程 C").run();
}
}
运行这个程序我们可以发现线程对象都是顺序执行的,并没有起到多线程的作用,我们想要的交替执行
线程 A运行,x = 0
线程 A运行,x = 1
线程 A运行,x = 2
线程 A运行,x = 3
线程 A运行,x = 4
线程 A运行,x = 5
线程 A运行,x = 6
线程 A运行,x = 7
线程 A运行,x = 8
线程 A运行,x = 9
线程 B运行,x = 0
线程 B运行,x = 1
线程 B运行,x = 2
线程 B运行,x = 3
线程 B运行,x = 4
线程 B运行,x = 5
线程 B运行,x = 6
线程 B运行,x = 7
线程 B运行,x = 8
线程 B运行,x = 9
线程 C运行,x = 0
线程 C运行,x = 1
线程 C运行,x = 2
线程 C运行,x = 3
线程 C运行,x = 4
线程 C运行,x = 5
线程 C运行,x = 6
线程 C运行,x = 7
线程 C运行,x = 8
线程 C运行,x = 9
Process finished with exit code 0
所以要想启动多线程必须使用 start() 方法完成,也就是 public void start()
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
class ThreadDemoTest extends Thread { // 线程的主体类
private String title;
public ThreadDemoTest(String title) {
this.title = title;
}
@Override
public void run() { // 线程的主体方法
for (int i = 0; i < 10; i++) {
System.out.println(this.title + "运行,x = " + i);
}
}
}
public class Test {
public static void main(String[] args) {
new ThreadDemoTest("线程 A").start();
new ThreadDemoTest("线程 B").start();
new ThreadDemoTest("线程 C").start();
}
}
通过此时的调用,可以发现虽然调用了 start() 方法,但是最终执行的是 run() 方法,并且所有的线程对象都是交替执行的
线程 A运行,x = 0
线程 A运行,x = 1
线程 A运行,x = 2
线程 A运行,x = 3
线程 A运行,x = 4
线程 A运行,x = 5
线程 A运行,x = 6
线程 A运行,x = 7
线程 A运行,x = 8
线程 A运行,x = 9
线程 B运行,x = 0
线程 B运行,x = 1
线程 B运行,x = 2
线程 B运行,x = 3
线程 B运行,x = 4
线程 B运行,x = 5
线程 B运行,x = 6
线程 B运行,x = 7
线程 B运行,x = 8
线程 C运行,x = 0
线程 C运行,x = 1
线程 C运行,x = 2
线程 C运行,x = 3
线程 C运行,x = 4
线程 C运行,x = 5
线程 C运行,x = 6
线程 C运行,x = 7
线程 C运行,x = 8
线程 C运行,x = 9
线程 B运行,x = 9
为什么多线程的启动不是直接使用 run() 方法?而是必须使用 Thread 类中的 start() 方法呢?
如果想搞清楚这个问题,最好的做法就是看下 start() 方法是如何实现的
可以看到 Thread 从 1.0 就开始有了
下面这块就是 Thread 的 start() 方法的源码:
public synchronized void start() {
// 判断线程的状态
if (threadStatus != 0)
// 抛出一个异常
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0(); // 在 start() 方法里面调用了一个 start0() 方法
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
// start0() 只定义了方法名称,但是没有实现
private native void start0();
例如下面的代码执行就会抛出异常
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
class ThreadDemoTest extends Thread { // 线程的主体类
private String title;
public ThreadDemoTest(String title) {
this.title = title;
}
@Override
public void run() { // 线程的主体方法
for (int i = 0; i < 10; i++) {
System.out.println(this.title + "运行,x = " + i);
}
}
}
public class Test {
public static void main(String[] args) {
ThreadDemoTest test = new ThreadDemoTest("启动线程");
test.start();
test.start(); // 重复启动线程
}
}
从图中可以知道 JNI 的用处了,就是用来匹配不同的操作系统
任何情况下,只要定义了多线程,多线程的启动永远只有一种方案:Thread 类中的 start()方法
举例:
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/20
*/
class ThreadDemoTest implements Runnable { // 线程的主体类
private String title;
public ThreadDemoTest(String title) {
this.title = title;
}
@Override
public void run() { // 线程的主体方法
for (int i = 0; i < 10; i++) {
System.out.println(this.title + "运行,x = " + i);
}
}
}
public class Test {
public static void main(String[] args) {
Thread threadA = new Thread(new ThreadDemoTest("线程 A 启动"));
Thread threadB = new Thread(new ThreadDemoTest("线程 B 启动"));
Thread threadC = new Thread(new ThreadDemoTest("线程 C 启动"));
threadA.start(); // 启动多线程
threadB.start(); // 启动多线程
threadC.start(); // 启动多线程
}
}
输出:
线程 A 启动运行,x = 0
线程 A 启动运行,x = 1
线程 A 启动运行,x = 2
线程 A 启动运行,x = 3
线程 A 启动运行,x = 4
线程 A 启动运行,x = 5
线程 A 启动运行,x = 6
线程 A 启动运行,x = 7
线程 A 启动运行,x = 8
线程 A 启动运行,x = 9
线程 C 启动运行,x = 0
线程 C 启动运行,x = 1
线程 C 启动运行,x = 2
线程 C 启动运行,x = 3
线程 C 启动运行,x = 4
线程 C 启动运行,x = 5
线程 C 启动运行,x = 6
线程 C 启动运行,x = 7
线程 C 启动运行,x = 8
线程 C 启动运行,x = 9
线程 B 启动运行,x = 0
线程 B 启动运行,x = 1
线程 B 启动运行,x = 2
线程 B 启动运行,x = 3
线程 B 启动运行,x = 4
线程 B 启动运行,x = 5
线程 B 启动运行,x = 6
线程 B 启动运行,x = 7
线程 B 启动运行,x = 8
线程 B 启动运行,x = 9
这个时候的多线程实现里面可以发现,由于只是实现了 Runnable 接口对象,所以此时线程主体类上就不再有单继承局限了,那么这样的设计才是一个标准性的设计
可以发现从 JDK 1.8 开始,Runnable 接口使用了函数式接口的定义,所以也可以直接利用 Lambda 表达式进行线程类的实现定义
举例:(使用 Lambda 实现多线程定义)
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
String title = "线程对象 - " + i;
Runnable runnable = () -> {
for (int j = 0; j < 10; j++) {
System.out.println(title + "运行,j = " + j);
}
} ;
new Thread(runnable).start();
}
}
}
输出:
线程对象 - 1运行,j = 0
线程对象 - 1运行,j = 1
线程对象 - 1运行,j = 2
线程对象 - 1运行,j = 3
线程对象 - 1运行,j = 4
线程对象 - 1运行,j = 5
线程对象 - 1运行,j = 6
线程对象 - 1运行,j = 7
线程对象 - 1运行,j = 8
线程对象 - 1运行,j = 9
线程对象 - 0运行,j = 0
线程对象 - 0运行,j = 1
线程对象 - 0运行,j = 2
线程对象 - 0运行,j = 3
线程对象 - 0运行,j = 4
线程对象 - 0运行,j = 5
线程对象 - 2运行,j = 0
线程对象 - 0运行,j = 6
线程对象 - 2运行,j = 1
线程对象 - 0运行,j = 7
线程对象 - 2运行,j = 2
线程对象 - 0运行,j = 8
线程对象 - 2运行,j = 3
线程对象 - 0运行,j = 9
线程对象 - 2运行,j = 4
线程对象 - 2运行,j = 5
线程对象 - 2运行,j = 6
线程对象 - 2运行,j = 7
线程对象 - 2运行,j = 8
线程对象 - 2运行,j = 9
将代码再精简一点就是:
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
String title = "线程对象 - " + i;
new Thread(() -> {
for (int j = 0; j < 10; j++) {
System.out.println(title + "运行,j = " + j);
}
}).start();
}
}
}
输出:
线程对象 - 0运行,j = 0
线程对象 - 2运行,j = 0
线程对象 - 1运行,j = 0
线程对象 - 1运行,j = 1
线程对象 - 1运行,j = 2
线程对象 - 1运行,j = 3
线程对象 - 1运行,j = 4
线程对象 - 2运行,j = 1
线程对象 - 0运行,j = 1
线程对象 - 2运行,j = 2
线程对象 - 1运行,j = 5
线程对象 - 1运行,j = 6
线程对象 - 1运行,j = 7
线程对象 - 1运行,j = 8
线程对象 - 1运行,j = 9
线程对象 - 2运行,j = 3
线程对象 - 0运行,j = 2
线程对象 - 2运行,j = 4
线程对象 - 0运行,j = 3
线程对象 - 0运行,j = 4
线程对象 - 0运行,j = 5
线程对象 - 0运行,j = 6
线程对象 - 0运行,j = 7
线程对象 - 0运行,j = 8
线程对象 - 0运行,j = 9
线程对象 - 2运行,j = 5
线程对象 - 2运行,j = 6
线程对象 - 2运行,j = 7
线程对象 - 2运行,j = 8
线程对象 - 2运行,j = 9
所以说在开发中对于多线程的实现,优先考虑的就是 Runnable 接口的实现,并且永恒都是通过 Thread 类对象启动多线程
从最传统的开发来讲如果要进行多线程的实现肯定依靠的就是 Runnable,但是 Runnable 接口有一个缺点:当线程执行之后无法获取一个返回值,所以从 JDK1.5之后就提出了一个新的线程实现接口:java.util.concurrent.Callable 接口
首先来观察一下这个接口的定义:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
可以发现 Callable 定义的时候可以设置一个泛型,该泛型的类型就是返回的数据类型,这样的好处就是可以避免向下转型所带来的安全隐患
来看下 Callable 的结构图
举例:(使用 Callable 实现多线程处理)
package com.java.springtest.test;
import org.omg.PortableServer.THREAD_POLICY_ID;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* @author Woo_home
* @create by 2020/1/20
*/
class ProductThread implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println("线程执行.i = " + i);
}
return "线程执行完毕";
}
}
public class Demo {
public static void main(String[] args) throws Exception{
FutureTask<String> task = new FutureTask<>(new ProductThread());
new Thread(task).start();
System.out.println("【线程返回数据】" + task.get());
}
}
输出:
面试题:Runnable 与 Callable 的区别是什么?
在多线程操作之中如果要启动多线程肯定使用的是 Thread 类中的 start() 方法,而如果对于多线程需要进行停止处理,Thread 类原本提供有 stop() 方法,但是对于这个方法从 JDK1.2 版本开始就已经被弃用了,而且一直到现在也不建议出现在你的代码之中,而且除了 stop() 方法之外还有几个方法也被弃用了:
之所以弃用这些方法是因为这些方法有可能导致线程的死锁,所以从 JDK1.2 开始就都不建议使用了,如果这个时候要想实现线程的停止需要通过一种柔和的方式进行
举例:(实现线程柔和的停止)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/22
*/
public class ThreadTest {
public static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
long num = 0;
while (flag) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在运行.num = " + num++);
}
},"执行线程").start();
Thread.sleep(200); // 休眠 200 ms
flag = false; // 停止线程
}
}
输出:
万一现在有其它线程去控制这个 flag 的内容,那么这个时候对于线程的停止也不是说停就停止的,而是会在执行中会判断 flag 的内容来完成的
在多线程里面可以进行守护线程的定义,也就是说如果线程主线程的程序或者其它的线程还在执行的时候守护线程将一直存在,并且运行在后台状态
在 Thread 类里面提供有如下守护线程的操作方法:
举例:(线程守护的例子)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/22
*/
public class ThreadTest {
public static void main(String[] args) {
Thread userThread = new Thread(() -> {
for (int num = 0; num < 100; num++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在运行.num = " + num++);
}
},"用户线程"); // 完成核心的业务
Thread daemonThread = new Thread(() -> {
for (int num = 0; num < Integer.MAX_VALUE; num++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在运行.num = " + num++);
}
},"守护线程"); // 完成核心的业务
daemonThread.setDaemon(true); // 设置为守护线程
userThread.start();
daemonThread.start();
}
}
部分运行截图:
可以发现所有的守护线程都是围绕在用户线程的周围,如果程序执行完毕了,守护线程也就消失了,在整个的 JVM 里面最大的守护线程就是 GC 线程
程序执行中 GC 线程会一直存在,如果程序执行完毕,GC 线程也将消失(最大的守护线程)
在多线程的定义之中,volatile 关键字主要是在属性定义上使用的,表示此属性为直接数据操作,而不进行副本的拷贝处理。这样的话在一些书上就将其错误的理解为同步属性了
在正常进行变量处理的时候往往会经历如下几个步骤:
如果一个属性上追加了 volatile 关键字,表示的就是不使用副本,而是直接操作原始变量,相当于节约了:拷贝副本、重新保存的步骤
举例:(使用 volatile 关键字)
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/22
*/
class Product implements Runnable {
private volatile int product = 10; // 直接内存操作
@Override
public void run() {
while (this.product > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢购.product = " + this.product--);
}
}
}
public class Demo2 {
public static void main(String[] args) {
Product product = new Product();
new Thread(product,"用户 A").start();
new Thread(product,"用户 B").start();
new Thread(product,"用户 C").start();
}
}
输出:
从输出可以看到 volatile 并不解决同步的问题,只是解决了拷贝副本、重新保存的步骤
为此我们还需修改一下程序,如下:
package com.java.springtest.test;
/**
* @author Woo_home
* @create by 2020/1/22
*/
class Product implements Runnable {
private volatile int product = 10; // 直接内存操作
@Override
public void run() {
synchronized (this) {
while (this.product > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "抢购.product = " + this.product--);
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
Product product = new Product();
new Thread(product,"用户 A ").start();
new Thread(product,"用户 B ").start();
new Thread(product,"用户 C ").start();
}
}
输出:
更多关于 volatile 与 synchronized 关键字的介绍请看这里 volatile 与 synchronized 详解
请解释 volatile 与 synchronized 的区别?