多线程,就是说一个应用程序有多条执行路径,每当我们打开一个应用程序的时候,就相当于打开了一个进程,而进程中执行的操作,就是线程。
以优酷为例,打开优酷就相当于打开一个进程,下载视频的操作就是线程,多线程就是同时下载多个视频。
在Java中有两种方式实现多线程分别是继承Thread类和实现Runnable接口。
一、继承Thread类Thread类中有一个run方法,但是这个run方法需要我们重写,故我们需要自定义一个类来继承Thread类,然后在自定义的类中重写这个run方法,重写完了就可以创建一个对象了,用该对象去调用这个方法,但是由于这是多线程,如果是直接调用run方法,无法实现多线程,然后就有了一个新的的方法start方法供我们使用,此方法会自动调用run方法。
程序代码如下:
public class MyThread extends Thread {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":" + i);
}
}
public static void main(String[] args) {
//创建多线程对象
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
MyThread myThread3 = new MyThread();
//设置每个对象的名字
myThread1.setName("刘");
myThread2.setName("亦");
myThread3.setName("菲");
//调用start()方法,其内部调用了run()方法,实现了多线程
//在这里直接调用run()方法,不能实现多线程
myThread1.start();
myThread2.start();
myThread3.start();
}
}
运行结果如下:
由结果可以看出,在跑这个循环的时候,并不是myThread1 跑完再跑myThread2,而是抢着跑的,这就是多线程的体现,也就是抢
为了解决单继承的局限性,因为如果某个类已经有父亲了,那么它就不能再继承Thread来实现多线程了,所以就有了实现接口的多线程,我们大部分使用的方法也是用接口来实现多线程。接口方式实现多线程我们需要自定义一个类去实现Runnable接口,然后在自定义的类中重写run方法,再然后去创建一个自定义类的对象,最后创建一个Thread类的对象,并且把刚刚创建的自定义对象作为参数传递。
注意:其实Thread源码中,Thread类也实现了Runnable接口
程序代码:
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int x = 0; x < 100; x++) {
//因为Runnable中没有getName方法,故用Thread中方法获取当前线程对象来实现
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
public static void main(String[] args) {
//在这里只需创造一个接口子类对象,后面直接用mr就行了
MyRunnable mr = new MyRunnable();
// Thread t1 = new Thread(mr);
// Thread t2 = new Thread(mr);
// t1.setName("胡");
// t2.setName("歌");
//上面这种方法不是很常用,可以用下面这个
Thread t1 = new Thread(mr, "胡");
Thread t2 = new Thread(mr, "歌");
t1.start();
t2.start();
}
}
运行结果如下:
四、线程中断interrupt()
- 继承Thread类不适合资源的共享,而实现Runnable接口很容易实现资源的共享
- 实现Runnable接口适合多个相同的程序代码的线程去处理同一个资源
- 避免了Java中的单继承限制
- 增强了程序的健壮性,代码可以被多个线程共享
程序代码如下:
public class MyThread extends Thread {
public void run() {
System.out.println("线程开始");
try {
// 在这里是线程休息2秒,如果线程在2秒内被中断,则执行catch
Thread.sleep(2000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("线程中断");
}
System.out.println("线程结束");
}
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
try {
Thread.sleep(1000);
mt.interrupt();// 这里的意思是线程执行如果超过1秒就被中断
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("中断线程");
}
}
}
运行结果:
多线程中,线程同步问题,为了讲解此问题,举一个火车卖票的例子,假如有3个窗口卖100张票,然后消费者去买票,当然了是三个窗口同时卖票,模拟一下此过程:
public class SynThread implements Runnable{
private int t = 100;
@Override
public void run() {
// 为了演示多线程的同步问题,在多线程同步中,
// 可能出现1号窗口还正在卖,而还没卖出去的时候,二号窗口刚好开始卖,导致一张票卖了两次
// 所以在这里我加上在卖票时候的一个延迟
while (t > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 这里调用的是当前线程的名字
System.out.println(Thread.currentThread().getName() + ":" + t--);
}
}
public static void main(String[] args) {
// 创建一个MyRunnable类的对象,由于此对象没有继承Thread类,所以下面还需创建Thread对象
SynThread st = new SynThread();
// 用此对象去创建Thread对象,并且给每个对象赋值,也就是起个名子
Thread t1 = new Thread(st, "刘");
Thread t2 = new Thread(st, "亦");
Thread t3 = new Thread(st, "菲");
// 这里是启动线程的意思
t1.start();
t2.start();
t3.start();
}
}
运行结果:
从上面的结果很容易看出,第100张票卖了3次,这肯定是不符合实际的,所以我们需要解决此问题,就是在某个窗口卖该张票的时候,其他窗口不能卖,也就是对该票进行加锁,即用synchronized()方法,其中有两种方式实现同步,分别是同步代码块和同步方法
(1)同步对象public class SynThread implements Runnable {
private int t = 100;
// 方法一同步代码块
public void run() {
while (t > 0) {
// 同步对象的锁是this
synchronized (this) {
if (t > 0) {// 在这里可能出现在t--之前进来消费者,故加个判断
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 这里调用的是当前线程的名字
System.out.println(Thread.currentThread().getName() + ":" + t--);
}
}
}
}
public static void main(String[] args) {
// 创建一个MyRunnable类的对象,由于此对象没有继承Thread类,所以下面还需创建Thread对象
SynThread st = new SynThread();
// 用此对象去创建Thread对象,并且给每个对象赋值,也就是起个名子
Thread t1 = new Thread(st, "刘");
Thread t2 = new Thread(st, "亦");
Thread t3 = new Thread(st, "菲");
// 这里是启动线程的意思
t1.start();
t2.start();
t3.start();
}
}
(2)同步方法
public class SynThread implements Runnable {
private int t = 100;
// 方法二同步方法
public void run() {
while (t > 0) {
sellt();
}
}
// 此方法为同步方法
private synchronized void sellt() {
if (t > 0) {// 在这里可能出现在t--之前进来消费者,故加个判断
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 这里调用的是当前线程的名字
System.out.println(Thread.currentThread().getName() + ":" + t--);
}
}
public static void main(String[] args) {
// 创建一个MyRunnable类的对象,由于此对象没有继承Thread类,所以下面还需创建Thread对象
SynThread st = new SynThread();
// 用此对象去创建Thread对象,并且给每个对象赋值,也就是起个名子
Thread t1 = new Thread(st, "刘");
Thread t2 = new Thread(st, "亦");
Thread t3 = new Thread(st, "菲");
// 这里是启动线程的意思
t1.start();
t2.start();
t3.start();
}
}
两种方式运行结果:
六、实现Callable接口实现多线程 1、Callable接口介绍Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。
2.实现Callable接口,重写call()方法public class MyCallable implements Callable3.使用工厂类Executors的静态方法newFixedThreadPool()方法创建线程池,返回ExecutorService接口的实现类对象,然后调用submit()方法提交线程任务{ //V call() 由于该方法的返回值是个泛型V,所以具体的返回类型可以自己定义,下面返回值设置为了String @Override public String call() throws Exception { return "Callable接口实现多线程"; } }
public static void main(String[] args) throws Exception {
//参数表示此线程池里有多少可用的线程数量
ExecutorService es = Executors.newFixedThreadPool(3);
//提交线程任务,返回一个Future接口,参数为Callable实现类对象
Future future = es.submit(new MyCallable());
//获取返回值
String s = future.get();
System.out.println(s);
}
运行结果:
实现Callable接口的完整程序代码:
import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class MyCallable implements Callable七、进程与线程{ //V call() 由于该方法的返回值是个泛型V,所以具体的返回类型可以自己定义,下面返回值设置为了String @Override public String call() throws Exception { return "Callable接口实现多线程"; } public static void main(String[] args) throws Exception { //参数表示此线程池里有多少可用的线程数量 ExecutorService es = Executors.newFixedThreadPool(3); //提交线程任务,返回一个Future接口,参数为Callable实现类对象 Future future = es.submit(new MyCallable()); //获取返回值 String s = future.get(); System.out.println(s); } }
- 进程是指运行中的应用程序,每一个进程都有自己独立的内存空间。比如:打开优酷,就是打开一个进程
- 线程是指进程中的一个执行流程,有时也称为执行情景。比如:打开优酷后下载一个视频,就是一个线程
- 一个进程可以由多个线程组成,即在一个进程中可以同时运行多个不同的线程,它们分别执行不同的任务。比如:在优酷中同时下载多个视频,就是多线程
- 当进程内的多个线程同时运行,这种运行方式称为并发运行。



