如果想在Java中实现多线程的定义,那么就需要有一个专门的线程主体类进行线程的执行任务的定义,而这个主体类的定义是有要求的,不许是实现特定的接口或者继承特定的父类才可以完成。
继承Thead类实现多线程
Java里面有一个java.lang.Thread的程序类,那么一个只要继承了此类就表示这个类为我们线程的主体类,但是并不是说这个类就可以实现多线程处理,因为还需要覆写Thread类中提供的一个run()方法,而这个方法就属于线程的主方法。需要说明的是:在正常情况下,如果想使用一个类中的方法,那么肯定要产生实例化对象,而后去调用类中提供的方法,但是run()方法是不能够直接被调用的,因为这里面牵扯到操作系统资源调度问题,所以要想启动多线程必须使用start()方法完成。虽然调用了start()方法,但是最终执行的是run()方法.
每一个线程类的对象只允许启动一次(start()方法),如果重复启动则抛出IllegalThreadStateException异常,该异常是RuntimeException的子类,
在Java程序执行的过程之中,考虑到对于不同层次开发者的需求,所以其支持有本地的操作系统函数调用,而这项技术就被称为JNI(Java Native Inteface)技术,但是Java开发过程中并不推荐这样使用,利用这项技术可以使用一些操作系统提供的底层函数,进行一些特处理,而在Thread类中提供的start0()就表示需要将此方法依赖于不同的操作系统实现。

任何情况下,只要定义了多线程,多线程的启动永远只有一种方案:Thread类中的start()方法。
基于Runnable接口实现多线程
虽然可以通过Thread类的继承来实现多线程的定义,但是在Java程序中对于继承永远都是存在单继承的局限的,所以在Java中又提供第二种多线程的主体定义结构形式:实现java.lang.Runnable接口,此接口定义如下:
@FunctionalInterface //从JDK1.8引入Lambda表达式后就变为了函数式的接口
public interface Runnable{
public void run();
}
可以发现从JDK1.8开始,Runnable接口使用了函数式接口定义,所以也可以直接使用Lambda表达式进行线程类实现。
在以后的开发之中对于多线程的实现,优先考虑的就是Runnable接口实现,并且通过Thread类启动多线程。

多线程的设计之中,使用了代理设计模式的结构,用户自定义的线程主体只是负责项目核心的实现,而所有辅助实现全部由Thread类处理。
在进行Thread启动多线程的时候调用的是start()方法,而后找到的是run()方法,但通过Thread类的构造方法传递了一个Runnable接口对象时,那么该接口对象将被Thread类中的target属性所保存,在start()方法执行时会调用Thread中的run()方法,而这个run()方法会去调用Runnable接口子类被覆写过的run()方法。
多线程开发的本质实质上是在于多个线程可以进行同一资源的抢占,那么Thread主要描述的是线程,而资源的描述是通过Runnable完成的。

class MyThread implements Runnable {//线程的主体类
private int ticket = 5;
@Override
public void run() {//线程的主体方法
for (int i = 0; i < 100; i++) {
if (this.ticket > 0)
System.out.printf("卖票,ticket = %s \n", this.ticket--);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyThread mt = new MyThread();
new Thread(mt).start();//第一个线程启动
new Thread(mt).start();//第二个线程启动
new Thread(mt).start();//第三个线程启动
}
}
通过内存分析图来分析本程序的执行结构。

Callable实现多线程
从最传统的开发来说,如果要进行多线程的实现肯定依靠Runnable,但是Runnable接口有一个缺点,当Runnable执行完成后无法获取一个返回值,所以从JDK1.5后提出了一个新的线程实现接口:java.util.concurrent.Callable接口,首先来观察这个接口的定义:
@FunctionalInterface
public interface Callable<V>{
public V call() throws Exception;
}
可以发现Callable定义的时候可以设置一个泛型,此泛型的类型就是返回数据的类型,这样的好处在于避免向下转型所带来的的安全隐患。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyThread implements Callable<String> {//线程的主体类
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println("*********** 线程、i = " + i);
}
return "线程执行完毕。";
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception {
FutureTask<String> task = new FutureTask(new MyThread());
new Thread(task).start();
System.out.println("【线程返回数据】" + task.get());
}
}

package com.msc.high;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyThread extends Thread {
private String title;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.printf("%s运行.i = %s \n", this.title, i);
}
}
}
class MyThreadB implements Runnable {//线程的主体类
private String title;
public MyThreadB(String title) {
this.title = title;
}
@Override
public void run() {//线程的主体方法
for (int i = 0; i < 10; i++) {
System.out.printf("%s运行.i = %s \n", this.title, i);
}
}
}
class MyCallable implements Callable<String>{
@Override
public String call() throws Exception{
for (int i = 0; i < 10; i++) {
System.out.println("*********** 线程、i = " + i);
}
return "执行完毕" ;
}
}
public class ThreadDemo {
public static void main(String[] args) {
new MyThread("线程对象B").start();
Thread threadA=new Thread(new MyThreadB("线程对象A"));
threadA.start();
Runnable run = () ->{
for (int i = 0; i < 10; i++) {
System.out.printf("run运行.i = %s \n", i);
}
} ;
new Thread(run).start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.printf("thread运行.i = %s \n", i);
}
}).start();
FutureTask<String> task = new FutureTask(new MyCallable());
new Thread(task).start();
try {
System.out.println("【线程返回数据】" + task.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
线程常用操作方法
线程的命名和取得
多线程的运行状态是不确定的,那么在程序的开发中为了可以获取到一些需要使用的线程就只能依靠线程名字来操作。
- 构造方法:public Thread(Runnable target,String name);
- 设置名字:public final void setName(String name);
- 取得名字:public final String getName();
对于线程对象的获得是不可能只是依靠一个this来完成的,因为线程的状态不可控,但是有一点是明确的,所有的线程对象一定要执行run()方法,那么这时候可以考虑获取当前线程,在Thread类中有提供获取当前线程的方法: - 获取当前线程:public static Thread currentThread();
当开发者为线程设置名字的时候就使用设置的名字,而如果没有设置名字,则会自动生成一个不重复的名字,这种自动属性命名主要依靠了static属性完成的
每当使用java命令执行程序的时候就表示启动了一个JVM的进程,一台电脑上可以同时启动若干个JVM进程,所以每一个JVM进程都会有各自的线程。主方法也是一个线程。
在任何的开发之中,主线程可以创建若干个子线程,创建子线程的目的是可以将一些复杂逻辑或者比较耗时的逻辑交由子线程处理;
线程的休眠
如果希望某个线程可以暂缓执行,那么就可以使用休眠的处理,在Thread类中定义的休眠方法如下:
- public static void sleep(long millis) throws InterruptedException
- public static void sleep(long millis, int nanos) throws InterruptedException
在进行休眠时可能或产生中断异常“InterruptedException”,中断异常属于Exception的子类,所以该异常必须进行处理。
休眠的主要特点是可以自动实现线程的唤醒,以继续进行后续的处理。但是需要注意的是,如果现在有多个线程对象,那么休眠也是有先后顺序的。
public class ThreadDemo {
public static void main(String[] args) throws Exception {
Runnable run=() -> {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "、i = " + i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int num = 0; num < 5; num++) {
new Thread(run, "线程对象 - " + num).start();
}
}
}

线程中断
在之前发现线程的休眠提供有一个中断异常,实际上就证明线程的休眠是可以被打断的,而这种打断肯定是由其他线程完成的。在Thread类里面提供有这种中断执行的处理方法:
- 判断线程是否被中断:public boolean isInterrupted();
- 中断线程执行:public void interrupt();
所有正在执行的线程都是可以被中断的,中断的线程必须进行异常的处理。
public class ThreadDemo {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(() -> {
System.out.println("*** 累了,我该睡觉了");
try {
Thread.sleep(10000);//预计准备休眠10秒
System.out.println("*** 睡足了,起床了");
} catch (InterruptedException e) {
System.out.println("被闹钟吵醒了");
}
});
thread.start();//开始睡
Thread.sleep(1000);
if (thread.isInterrupted()==false) {//判断该线程是否已中断
System.out.println("闹钟响了");
thread.interrupt();//中断执行
}
}
}
线程的强制执行
所谓的线程的强制执行指的是当满足于某些条件之后,某一个线程对象将可以一直独占资源,一直到该线程的程序执行结束。
- 强制执行 :public final void join(long millis) throws InterruptedException;
在进行线程强制执行的时候,一定要获取执行线程对象之后,才可执行join()的调用
Thread mainThread = Thread.currentThread();
Thread thread = new Thread(()->{
for (int i = 0; i < 100; i++) {
try {
if(i>50) mainThread.join();
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类中提供的方法:
- public static void yield();
礼让执行的时候每一次调用yield()方法都只会礼让一次当前的资源。
public class ThreadDemo {
public static void main(String[] args) throws Exception {
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);
}
}
}
线程优先级
从理论上来讲,线程的优先级越高,越有可能先执行(越有可能先抢占到资源)。在Thread类中针对优先级的操作提供有如下两个处理方法:
- 设置优先级:public final void setPriority(int newPriority);
- 获取优先级:public final int getPriority();
在金贤姬定义的时候都是通过int型的数字来完成的,而对于此,数字的选择在Thread类中就定义了三个常量: - 最高优先级:public static final int MAX_PRIORITY = 10;
- 中等优先级:public static final int NORM_PRIORITY = 5;
- 最低优先级:public static final int MIN_PRIORITY = 1;
主线程属于中等优先级,而默认创建的线程也是中等优先级。
public class ThreadDemo {
public static void main(String[] args) throws Exception {
Runnable run = () -> {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "执行。");
}
};
Thread threadA = new Thread(run, "线程对象A");
Thread threadB = new Thread(run, "线程对象B");
Thread threadC = new Thread(run, "线程对象C");
threadA.setPriority(Thread.MIN_PRIORITY);
threadB.setPriority(Thread.MIN_PRIORITY);
threadC.setPriority(Thread.MAX_PRIORITY);
threadA.start();
threadB.start();
threadC.start();
}
}
线程的同步
如果想程序中实现锁功能,就可以使用synchronized关键字来是吸纳,利用synchronized可以定义同步方法和同步代码块,在同步代码块的操作中的代码只允许一个线程执行。
1、利用同步代码块进行处理:
synchronized (同步对象){
同步代码操作;
}
一般要进行同步对象处理时,可以采用当前对象this进行同步。
class MyThread implements Runnable {
private int ticket = 1000;//总票数为10张
@Override
public void run() {
while (true) {
synchronized (this) {//每一次只允许一个线程进行访问
if (this.ticket > 0) {
try {
Thread.sleep(100);//模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
} else {
System.out.println("***** 票卖光了 *****");
break;
}
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception {
MyThread mt = new MyThread();
new Thread(mt, "票贩子A").start();
new Thread(mt, "票贩子B").start();
new Thread(mt, "票贩子C").start();
}
}
加入同步处理之后,程序的整体性能下降了。同步实际上会造成性能的降低。
2、利用同步方法解决:只需要在方法定义上使用synchronized关键字即可。
class MyThread implements Runnable {
private int ticket = 10;//总票数为10张
public synchronized boolean sale() {
if (this.ticket > 0) {
try {
Thread.sleep(100);//模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
return true;
} else {
System.out.println("***** 票卖光了 *****");
return false;
}
}
@Override
public void run() {
while (this.sale()) {}
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception {
MyThread mt = new MyThread();
new Thread(mt, "票贩子A").start();
new Thread(mt, "票贩子B").start();
new Thread(mt, "票贩子C").start();
}
}
死锁
死锁是在进行多线程同步的处理之中有可能产生的一种问题,所谓的死锁指的是若干个线程彼此互相等待的状态。
public class DeadLock implements Runnable {
private Producer producer = new Producer();
private Customer customer = new Customer();
public DeadLock() {
new Thread(this).start();
customer.say(producer);
}
public static void main(String[] args) {
new DeadLock();
}
@Override
public void run() {
producer.say(customer);
}
}
class Producer {
public synchronized void say(Customer customer) {
System.out.println("店员:先买单后吃饭");
customer.get();
}
public synchronized void get() {
System.out.println("收到钱,可以给你做饭了");
}
}
class Customer {
public synchronized void say(Producer producer) {
System.out.println("顾客:先吃饭后买单");
producer.get();
}
public synchronized void get() {
System.out.println("吃饱饭了,可以买单了");
}
}
“生产者-消费者”模型
在多线程的开发过程之中最为著名的案例就是生产者和消费者操作,该操作的主要流程如下:
- 生产者负责信息内容的生产;
- 每当生产者生产完成一项完整的信息之后消费者要从这里面取走信息;
- 如果当生产者没有生产则消费者要等待它生产完成,如果消费者还没有对信息进行消费,则生产者应该等待消费处理完成后再继续生产。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6n9i7yZW-1610793890084)(C:\Users\My\AppData\Roaming\Typora\typora-user-images\image-20210116182754436.png)]](https://i-blog.csdnimg.cn/blog_migrate/9f14cd58a72243bc61383ef4be1f5c1a.png)
线程等待与唤醒
如果说现在想解决生产者与消费者的问题,那么最好的解决方案就是使用等待与唤醒机制。而对于等待与唤醒的操作机制主要依靠是Object类中提供的方法处理的:
- 等待机制:
1、死等:public final void wait() throws InterruptedException;
2、设置等待时间(毫秒):public final void wait(long timeout) throws InterruptedException;
3、设置等待时间(纳秒):public final void wait(long timeout, int nanos) throws InterruptedException; - 唤醒第一个等待线程:public final void notify();
- 唤醒全部等待线程:public final void notifyAll();
如果此时有若干个等待线程的话,那么notify()标识的是唤醒第一个等待的,而其他的线程继续等待,而notifyAll()会唤醒所有等待的线程,哪个线程的优先级高就有可能先执行。
public class ThreadDemo {
public static void main(String[] args) throws Exception {
Message msg = new Message();
new Thread(new Producer(msg)).start();//启动生产者线程
new Thread(new Consumer(msg)).start();//启动消费者线程
}
}
class Producer implements Runnable {
private Message msg;
public Producer(Message msg) {
this.msg = msg;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 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 i = 0; i < 100; i++) {
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 (Exception 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 (Exception 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 ThreadDemo {
public static boolean flag = true;
public static void main(String[] args) throws Exception {
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毫秒
flag = false;//停止线程
}
}
现在假设有一个保镖,那么这个保镖一定是在雇主活着时候进行守护,雇主死了,保镖就没用了。所以在多线程中可进行守护线程的定义,也就是说如果现在主线程的程序或者其他线程还在执行的时候,那么守护线程将一直存在,并且运行在后台状态。
在Thread类中提供有如下的守护线程的操作方法:
- 设置为守护线程:public final void setDaemon(boolean on);
- 判断是否为守护线程:public final boolean isDaemon();
public class ThreadDemo {
public static void main(String[] args) throws Exception {
Thread userThread = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在运行、x = " + i);
}
}, "用户线程");//完成核心业务
Thread deamonThread = new Thread(() -> {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在运行、x = " + i);
}
}, "守护线程");//完成核心业务
userThread.start();
deamonThread.setDaemon(true);//设置为守护线程
deamonThread.start();
}
}
可以发现所有的守护线程都是围绕在用户线程的周围,如果程序执行完毕了,守护线程也就消失了,在整个JVM中最大的守护线程就是GC线程。
程序执行中GC线程会一直存在,如果程序执行完毕,GC线程也将消失
volatile关键字
在多线程定义中,volatile关键字主要是在属性上使用的,表示此属性为直接数据操作,而不进行副本的拷贝处理。在一些书上就错误的理解为同步属性了。
在正常进行变量处理的时候往往会经历如下的几个步骤:
- 获取变量的数据内容;
- 为变量进行数学计算;
- 将计算后的变量,保存到原始空间之中;
而如果一个属性上追加了volatile关键字,表示的就是不使用副本,而是直接操作原始变量,相当于节约了拷贝副本、重新保存的步骤。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aC3o63fm-1610793890086)(C:\Users\My\AppData\Roaming\Typora\typora-user-images\image-20210116184125179.png)]](https://i-blog.csdnimg.cn/blog_migrate/15cd7d00de12c4f695538b5818e5d538.png)
class MyThread implements Runnable {
private volatile int ticket = 5;//直接内存操作
@Override
public void run() {
synchronized (this){
while (this.ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception {
MyThread mt = new MyThread();
new Thread(mt, "票贩子A").start();
new Thread(mt, "票贩子B").start();
new Thread(mt, "票贩子C").start();
}
}
@Override
public void run() {
synchronized (this){
while (this.ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "卖票,ticket = " + this.ticket--);
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception {
MyThread mt = new MyThread();
new Thread(mt, “票贩子A”).start();
new Thread(mt, “票贩子B”).start();
new Thread(mt, “票贩子C”).start();
}
}
10万+

被折叠的 条评论
为什么被折叠?



