我们可以先来看一幅图,多线程就算围绕这幅图展开的。
1. 进程与线程的概念
进程:操作系统中一个程序的执行周期。
线程:一个线程同时执行多个任务。通常来讲,每一个任务就称为一个线程。
进程与线程比较
- 与进程相比,线程更加“轻量级”,创建、撤销一个线程比启动、撤销一个进程开销要小的多。一个进程中的所有线程共享此进程的所有资源。
- 没有进程就没有线程,进程一旦终止,其内的线程也将不复存在。
- 进程是操作系统资源调度的基本单位,进程可以独享资源,线程需要依托进程提供的资源,无法独立申请操作系统资源,是操作系统任务执行的基本单位。
2. Java多线程实现
2.1 继承Thread类实现多线程
java.lang.Thread是线程操作的核心类。新建一个线程最简单的方法就是直接继承Thread,而后覆写run()方法(相当于主线程的main()方法)。
例:
package www.java.test;
class MyThread extends Thread{//新建一个线程直接继承Thread
private String title;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {//覆写run()方法
for(int i = 0; i < 10; i++){
System.out.println(this.title+",i = "+i);
}
}
}
public class Test{
public static void main(String[] args){
MyThread myThread1 = new MyThread("thread1");
MyThread myThread2 = new MyThread("thread2");
MyThread myThread3 = new MyThread("thread3");
myThread1.run();;
myThread2.run();
myThread3.run();
}
}
我们很容易联想到,既然覆写的是run()方法,那是不是调用run()方法来启动多线程呢?
结果:
我们发现结果是顺序的,并没有启动多线程。
再看看下边这段代码:
package www.java.test;
class MyThread extends Thread{//新建一个线程直接继承Thread
private String title;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {//覆写run()方法
for(int i = 0; i < 10; i++){
System.out.println(this.title+",i = "+i);
}
}
}
public class Test{
public static void main(String[] args){
MyThread myThread1 = new MyThread("thread1");
MyThread myThread2 = new MyThread("thread2");
MyThread myThread3 = new MyThread("thread3");
myThread1.start();;
myThread2.start();
myThread3.start();
}
}
多个线程交替出现,说明实现了多线程。
其实调用start()最终的目的也是为了调用run()方法,那为什么不直接调用run(),而是通过start()来调用run()呢?
我们可以查看start()源码,就可以总结出java线程创建的流程:start(Java方法)–>start0(JVM)–>进行资源调度,系统分配(JVM)–>run(Java方法)执行线程的具体操作任务
总结:无论那种方式实现多线程,线程启动一定调用Thread类提供的start()方法。
每一个线程start()方法只能调用一次,多次调用会出现异常(java.lang.IllegalThreadStateException)。
2.2 实现Runnable接口来实现多线程
使用Runnable接口虽然解决了单继承局限问题,但接口中是没有start()方法的,该如何启动多线程呢?
Thread类有一个构造函数是这样的public Thread(Runnable target)
,可以接收一个Runnable对象。我们只需要把Runnable对象传进去,然后再调用start()方法就可以启动多线程了。
例:
package www.java.test;
class MyThread implements Runnable{//新建一个线程直接继承Thread
private String title;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {//覆写run()方法
for(int i = 0; i < 10; i++){
System.out.println(this.title+",i = "+i);
}
}
}
public class Test{
public static void main(String[] args){
MyThread myThread1 = new MyThread("thread1");
MyThread myThread2 = new MyThread("thread2");
MyThread myThread3 = new MyThread("thread3");
new Thread(myThread1).start();
new Thread(myThread2).start();
new Thread(myThread3).start();
}
}
使用Lamdba表达式进行Runnable对象创建:
package www.java.test;
public class Test{
public static void main(String[] args){
Runnable runnable = ()-> System.out.println("hello world");
new Thread(runnable).start();
}
}
使用匿名内部类实现Runnable对象创建:
package www.java.test;
public class Test{
public static void main(String[] args){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello world");
}
}).start();
}
}
2.3 继承Thread类与实现Runnable接口的关系
- Thread类与自定义线程类(实现了Runnable接口),是一个典型的代理设计模式。Thread类负责辅助真实业务操作(资源调度,创建线程并启动),自定义线程类负责真实业务的实现(run()方法具体要做什么)。
- 使用Runnable接口实现的多线程程序类可以更好的描述共享的概念。
使用Thread类实现数据共享:
package www.java.test;
class MyThread extends Thread{
private int ticket = 10;
@Override
public void run() {
while(this.ticket > 0){
System.out.println(Thread.currentThread().getName() +"剩余票数" + ticket--);
}
}
}
public class Test{
public static void main(String[] args){
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
MyThread myThread3 = new MyThread();
myThread1.start();
myThread2.start();
myThread3.start();
}
}
通过输出结果我们可以发现,三个线程各自卖各自的,并没有实现数据共享。
使用Runnable实现多线程:
package www.java.test;
class MyThread implements Runnable{
private int ticket = 10;
@Override
public void run() {
while(this.ticket > 0){
System.out.println(Thread.currentThread().getName() +"剩余票数" + ticket--);
}
}
}
public class Test{
public static void main(String[] args){
MyThread myThread = new MyThread();
new Thread(myThread).start();
new Thread(myThread).start();
new Thread(myThread).start();
}
}
2.4 实现Callable实现多线程—当线程有返回值时,只能用Callable实现多线程
在juc包中,是JDK-1.5新增的开发程序编程包
实现Callable接口,而后覆写call()方法,有返回值:
V call() throws Exception;
Future<v> 接口:取得Callable接口的返回值
v get() throws InterruptedException,ExecutionException;
例:
package www.java.test;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyThread implements Callable<String> {
private int ticket = 10;
@Override
public String call() throws Exception {
while(this.ticket > 0){
System.out.println(Thread.currentThread().getName() +"剩余票数" + ticket--);
}
return "不好意思,票卖完了";
}
}
public class Test{
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> task = new FutureTask<>(new MyThread());
new Thread(task).start();
new Thread(task).start();
new Thread(task).start();
System.out.println(task.get());
}
}
3. 多线程常用操作方法
3.1 线程命名与取得
- 通过构造方法在创建线程时设置名称
public Thread(String name)
public Thread(Runnable target, String name)
- 取得线程名称
public final String getName()
- 创建线程后,设置线程名称
public final synchronized void setName(String name)
3.2 线程休眠方法sleep()----单位为毫秒
线程休眠:让当前线程暂缓执行,等到了预计时间后再恢复执行。
线程休眠会交出cpu,但不会释放锁。
public static native void sleep(long mills) throws InterruptedException;
例:
package www.java.test;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyThread implements Runnable {
@Override
public void run() {
for(int i = 0; i < 100; i++){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程:"+Thread.currentThread().getName()+",i = "+i);
}
}
}
public class Test{
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
new Thread(myThread).start();
new Thread(myThread).start();
new Thread(myThread).start();
}
}
我们可以让程序跑起来,观察它的输出情况,或许会给我们一种错觉,就算三个线程是同时休眠的,但所有代码都是依次进入run()的。
我们还会发现打印速度是不同的,那是因为线程休眠会立马交出cpu,但不知道什么时候回来,cpu输出时间不定,打印速度不同,是因为获取cpu是随机的,时间不均匀,但休眠时间是相同的。
我们可以来验证一下它到底是不是立马交出CPU,我们可以同时启动3个线程,然后观察打印结果。
package www.java.test;
class MyThread implements Runnable{
@Override
public void run() {
for(int i=0; i<3; i++){
System.out.println(Thread.currentThread().getName()+"、"+i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test{
public static void main(String[] args){
MyThread mt = new MyThread();
new Thread(mt,"A").start();
new Thread(mt,"B").start();
new Thread(mt,"C").start();
}
}
会发现,三个线程是交替出现的。这就说明它是立马交出CPU的。
3.3 线程让步(yield()方法)
暂停执行当前的线程对象,并执行其他线程。
yield()方法会让当前线程交出cpu,同样不会释放锁。但是yield()方法无法控制具体交出cpu的时间,并且yield()方法只能让拥有相同优先级的线程有获取cpu的机会。
例:
package www.java.test;
import java.util.concurrent.ExecutionException;
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
Thread.yield();
System.out.println("当前线程:" + Thread.currentThread().getName() + ",i = " + i);
}
}
}
public class Test{
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
new Thread(myThread).start();
new Thread(myThread).start();
new Thread(myThread).start();
}
}
可以来证明一下yield()不是立马交出CPU,只需要把上边证明sleep()的代码该一下就行,然后观察输出。
package www.java.test;
class MyThread implements Runnable{
@Override
public void run() {
for(int i=0; i<3; i++){
System.out.println(Thread.currentThread().getName()+"、"+i);
Thread.yield();
}
}
}
public class Test{
public static void main(String[] args){
MyThread mt = new MyThread();
new Thread(mt,"A").start();
new Thread(mt,"B").start();
new Thread(mt,"C").start();
}
}
注意看输出结果,我们会发现A线程都执行完了才交出CPU,这就说明它并没有立即交出CPU,它还一直占有着CPU。
3.4 join()方法—等待其他线程终止
等待该线程终止。如果在主线程中调用该方法,会让主线程休眠,让调用该方法的线程先执行完毕再恢复执行主线程。
会释放锁
join()方法只是对Object提供的wait()做了一层包装而已。
我们可以先通过两段代码的对比来看join()方法的功能。
package www.java.test;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutionException;
class MyThread implements Runnable {
@Override
public void run() {
for(int i = 0; i < 3; i++){
System.out.println(Thread.currentThread().getName()+"、"+i);
}
}
}
public class Test{
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("主线程开始");
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"子线程A");
thread.start();
System.out.println("主线程结束");
}
public static void printTime(){
Date date=new Date();
DateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time=format.format(date);
System.out.println(time);
}
}
通过输出结果我们可以发现:子线程还没执行完主线程就已经执行完毕,主线程的结束不一定在子线程之后。表明主线程和子线程是一块走的。
我们现在调用join()方法来观察输出:
package www.java.test;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutionException;
class MyThread implements Runnable {
@Override
public void run() {
System.out.println("主线程休眠开始");
Test.printTime();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程休眠结束");
Test.printTime();
}
}
public class Test{
public static void main(String[] args) throws ExecutionException, InterruptedException {
System.out.println("主线程开始");
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"子线程A");
thread.start();
thread.join();
System.out.println("主线程结束");
}
public static void printTime(){
Date date=new Date();
DateFormat format=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time=format.format(date);
System.out.println(time);
}
}
会发现:在主线程中,子线程调用了join()方法后,会一直等到子线程的run()方法执行完之后,才会继续执行主线程。
3.5 线程停止(手工停止)
子线程正常停止是子线程的run()方法执行完,所有的代码执行完,线程就停止了。
3.5.1 设置标记位停止线程
例:
package www.java.test;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutionException;
class MyThread implements Runnable {
private boolean flag = true;
@Override
public void run() {
int i = 1;
while(flag){
System.out.println(Thread.currentThread().getName()+"第"+i+"次执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
}
}
public void setFlag(boolean flag){
this.flag = flag;
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"子线程A");
thread.start();
Thread.sleep(5000);
myThread.setFlag(false);
}
}
我们可以看到子线程执行了5次之后就停止了,那是因为主线程休眠了5秒之后,手动设置标志位把子线程停止了。
3.5.2 调用Thread类的stop()方法强制停止线程,该方法不安全,已经被废弃。
例:
package www.java.test;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutionException;
class MyThread implements Runnable {
private boolean flag = true;
@Override
public void run() {
int i = 1;
while(flag){
System.out.println(Thread.currentThread().getName()+"第"+i+"次执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
}
}
public void setFlag(boolean flag){
this.flag = flag;
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"子线程A");
thread.start();
Thread.sleep(5000);
thread.stop();
}
}
它也可以停止线程,但为什么说它不安全呢?
如果while()循环里的语句是这样的:
while(flag){
int x = 10;
int y = 20;
}
如果采用第一种方法停止线程,一旦进入while循环,它就会等x和y全部赋值完毕才会停止;但如果采用stop()停止线程,如果是在x = 10与y = 20之间调用的,则会强制停止,这样y就没有被赋值,就产生了废弃数据。
3.5.3 调用Thread类的interrupt()方法
interrupt()方法只是将线程状态置为中断状态而已,它不会中断一个正在运行的线程。此方法只是给线程传递一个中断信号,程序可以根据此信号来判断是否需要终止。
例:
package www.java.test;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutionException;
class MyThread implements Runnable {
@Override
public void run() {
int i = 1;
while(true){
boolean bool = Thread.currentThread().isInterrupted();
System.out.println(Thread.currentThread().getName()+"第"+i+"次执行");
System.out.println(bool);
i++;
}
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"子线程A");
thread.start();
Thread.sleep(3000);
thread.interrupt();
}
}
我们可以观察输出,前3秒的时候,bool的值都是false,后边bool的值一直都是true,因为调用了interrupt(),但子线程并没有停止,它一直在执行。所以它只是把这个状态置为了true,如果要想停止子线程,就要根据这个状态来做相应的操作。
用intrerrupt()退出线程:
package www.java.test;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutionException;
class MyThread implements Runnable {
@Override
public void run() {
int i = 1;
while(true){
boolean bool = Thread.currentThread().isInterrupted();
System.out.println(Thread.currentThread().getName()+"第"+i+"次执行");
System.out.println(bool);
if(bool){
System.out.println("线程退出");
break;
}
i++;
}
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"子线程A");
thread.start();
Thread.sleep(3000);
thread.interrupt();
}
}
我们可以看到第一个true的时候,线程就退出了。这就和第一种方法一样,只不过第一种方法是自己设置一个标记位,而interrupt()是系统设置一个标记位。
但它有一个特殊点就是:当线程中使用了wait、sleep、join导致线程阻塞,则interrupt()方法会在线程中抛出InterruptedException,并且将线程的中断状态由true置为false。
我们可以看看这段代码的输出:
package www.java.test;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutionException;
class MyThread implements Runnable {
@Override
public void run() {
int i = 1;
while(true){
boolean bool = Thread.currentThread().isInterrupted();
System.out.println(Thread.currentThread().getName()+"第"+i+"次执行");
System.out.println(bool);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(bool){
System.out.println("线程退出");
break;
}
i++;
}
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"子线程A");
thread.start();
Thread.sleep(3000);
thread.interrupt();
}
}
我们看到在3秒之后,调用了interrupt()是抛了一个异常,但它并没有停止子线程,并且bool的值被改为了false。
我们到底怎样做才能用interrupt()来停止线程呢?看下边这段代码:
package www.java.test;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutionException;
class MyThread implements Runnable {
@Override
public void run() {
int i = 1;
while(true){
try {
boolean bool = Thread.currentThread().isInterrupted();
System.out.println(Thread.currentThread().getName()+"第"+i+"次执行");
System.out.println(bool);
if(bool){
System.out.println("线程退出");
}
i++;
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("异常抛出,线程停止");
Boolean bool = Thread.currentThread().isInterrupted();
System.out.println("Catch块中的中断状态为"+bool);
return;
}
}
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"子线程A");
thread.start();
Thread.sleep(3000);
thread.interrupt();
}
}
当线程中用了sleep,它就会抛出异常,能进入catch块就说明一定调用了interrupt(),我们可以在catch块中来进行线程终止,直接让它return就行。但会发现,它的状态标志一直都是false。
3.6 线程优先级(1-10)
线程优先级是指优先级越高越有可能先执行,但仅仅是有可能而已。
设置优先级
public final void setPriority(int newPriority)
取得优先级
public final int getPriority()
Thread类中有3个常量来判断最小优先级、中等优先级和最高优先级分别是:
MAX_PRIORITY = 10;
NORM_PRIORITY = 5;
MIN_PRIORITY = 1;
例:
package www.java.test;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutionException;
class MyThread implements Runnable {
@Override
public void run() {
for(int i = 0; i < 5; i++){
System.out.println(Thread.currentThread().getName()+"、"+i);
}
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread,"子线程A");
Thread thread2 = new Thread(myThread,"子线程B");
Thread thread3 = new Thread(myThread,"子线程C");
thread3.setPriority(Thread.MAX_PRIORITY);
thread2.setPriority(Thread.NORM_PRIORITY);
thread1.setPriority(Thread.MIN_PRIORITY);
thread1.start();
thread2.start();
thread3.start();
}
}
我们在设置优先级的时候,子线程C的优先级最高,下来是B,最后是A,我们现在来看看输出结果。
发现并不设置的优先级最高它就先执行,设置优先级只是建议系统先执行哪个,但具体执行哪个是有系统决定的。
我们可以观察一下主线程的优先级:
public class Test{
public static void main(String[] args) throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"、"+Thread.currentThread().getPriority());
}
}
所以主线程只是一个普通优先级而已。
线程具有继承性,只是继承优先级而已。
从A线程启动B线程,则B和A线程的优先级是一样的。
例:主线程的优先级是5,那如果在主线程中启动一个子线程,那么这个子线程的优先级和主线程的优先级一样都是5。
package www.java.test;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutionException;
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"、"+Thread.currentThread().getPriority());
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"、"+Thread.currentThread().getPriority());
new Thread(new MyThread(),"子线程A").start();
}
}
我们可以再看一个例子:
package www.java.test;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutionException;
class A implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"、"+Thread.currentThread().getPriority());
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"、"+Thread.currentThread().getPriority());
new Thread(new A(),"孙子线程B").start();
}
}
public class Test{
public static void main(String[] args) throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"、"+Thread.currentThread().getPriority());
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"子线程A");
thread.setPriority(10);
thread.start();
}
}
由于B线程是在A线程中启动的,所以B和A的优先级一样。
3.7 守护线程
守护线程是一种特殊的线程,又称为陪伴线程。java中一共有两种线程:用户线程和守护线程。
Thread类提供isDaemon()区别两种线程:返回false表示该线程为用户线程;否则为守护线程。典型的守护线程就是垃圾回收线程。主线程main是用户线程。
例:
package www.java.test;
class A implements Runnable {
private int i;
@Override
public void run() {
try {
while (true) {
i++;
System.out.println("线程名称:" + Thread.currentThread().getName() + ",i=" + i +
",是否为守护线程:"
+ Thread.currentThread().isDaemon());
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("线程名称:" + Thread.currentThread().getName() + "中断线程了");
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new A(),"子线程A");
// 设置线程A为守护线程,此语句必须在start方法之前执行
thread1.setDaemon(true);
thread1.start();
Thread thread2 = new Thread(new A(),"子线程B");
thread2.start();
Thread.sleep(3000);
// 中断非守护线程
thread2.interrupt();
Thread.sleep(10000);
System.out.println("代码结束");
}
}
我们提供输出结果可以发现,当用户线程B被中断后,守护线程并没有停止,因为主线程还没有结束,所以说明只有当所有用户线程都结束之后,守护线程才会结束。