目录
一.线程与进程
1.进程与线程的概念
线程:线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
进程:操作系统中一个程序的执行周期。
进程与线程的比较:
- 与进程相比,线程更加的“轻量级”,创建、撤销一个线程,比启动、撤销一个进程开销要小得多。一个进程中的所有线程共享此进程的所有资源。
- 没有进程就没有线程,进程一旦终止,其内的线程也将不复存在。
- 进程是操作系统资源调度的基本单位,进程可以独享资源; 线程是操作系统任务执行的进步单位,线程需要依托进程提供的资源,无法独立申请操作系统资源。
2.线程状态
二.Java多线程实现
1.继承Thread类实现多线程
java.lang.Thread是线程操作的核心类,新建一个线程最简单的办法就是直接继承Thread类,而后覆写run()方法。
无论哪种方式实现多线程,线程启动一定要调用Thread类提供的start()方法!!!
注意:线程的start()方法只能调用一次,多次会抛出异常。
那么,为什么要调用start()方法去实现run()方法,而不是直接实现run()方法?
我们来看一下线程创建的调用流程:
Java调用Thread的start()方法 -> start0()是本地方法,在start()方法里会调用start0()方法,start0()方法是被JVM调度使用的 -> JVM进行资源调度、系统分配 -> run()是Java中的方法,执行线程的具体操作任务(具体实现过程可以自行查看源码)
如果直接调用run()方法,那仅仅是一个普通方法,程序是顺序执行的,要等到上一个run()方法执行完毕后才可以执行下一个run()方法。
如果使用start()方法来启动线程,才是真正实现了多线程。通过Thread类的start()方法来启动一个线程,这时线程处于就绪状态,并没有运行,可以接着执行下面的代码,无需等到run()方法执行完毕。然后通过Thread类调用run()方法来完成具体的操作。
举个栗子:
package www.like.java;
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.println(this.title + "、" + i);
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("线程1");
MyThread myThread2 = new MyThread("线程2");
myThread1.start();
myThread2.start();
}
}
运行结果:
我们可以观察到,线程1和线程2是交替执行的,两个线程的执行顺序是随机的。
2.实现Runnable接口来实现多线程
上图为Thread类的源码,是Thread类的一个构造方法。可以看出来,我们可以直接通过构造方法传进Runnable对象,然后启动线程。
举个栗子:
package www.like.java;
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);
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("线程1");
MyThread myThread2 = new MyThread("线程2");
new Thread(myThread1).start();
new Thread(myThread2).start();
}
}
运行结果:
继承Thread类与实现Runnable接口的关系:
- Thread类(本身就实现了Runnable接口)与自定义类(实现了Runnable接口),是一个典型的代理设计模式。
Thread类负责辅助真实业务操作(资源调度、创建线程并启动)。
自定义线程类负责真实业务的实现(run()具体要做哪些事)。
2.使用Runnable接口实现的多线程程序类可以更好地描述共享的概念(多个线程共同使用一个Runnable),不是说Thread不能。
举个卖票的例子来看看两者的差别:
package www.like.java;
class MyThread extends Thread{
private String title;
private int ticket = 10;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {
for (int i = 0; i < 10; i++){
System.out.println(this.title + "还剩下" + ticket-- + "张票");
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("售票人1");
MyThread myThread2 = new MyThread("售票人2");
myThread1.start();
myThread2.start();
}
}
我们可以看到运行结果,两个线程是交替执行的,但是各卖各的票,互不干预。
package www.like.java;
class MyThread implements Runnable{
private String title;
private int ticket = 10;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {
for (int i = 0; i < 10; i++){
System.out.println(this.title + "还剩下" + ticket-- + "张票");
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread("售票人");
Thread t1 = new Thread(myThread);
Thread t2 = new Thread(myThread);
Thread t3 = new Thread(myThread);
t1.start();
t2.start();
t3.start();
}
}
两个线程共同卖10张票,实现了共享。
3.实现Callable接口实现多线程
我们先来看一下Callable接口的源码:
我们在实现Callable接口之后,需要实现call()方法来完成相应的操作。与Thread类和Runnable接口不同的是,Callable是有返回值的。
继承Callable接口而后覆写call()方法,有返回值!!!
举个栗子:
package www.like.java;
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() {
for(int i = 0; i < 10; i++){
System.out.println("还剩下"+this.ticket--+"张票");
}
return "票卖完了";
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread(); //Callable接口的对象
FutureTask<String> futureTask = new FutureTask<>(myThread); //既可以传Callable对象,也可以传Runnable对象
//futureTask可以直接给Thread,相当于扔了个Runnable对象(FutureTask是Runnable的子类)
Thread thread1 = new Thread(futureTask);
Thread thread2 = new Thread(futureTask);
thread1.start();
thread2.start();
try {
System.out.println(futureTask.get()); //get()接收Callable的返回值
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
三.多线程的常用操作方法
1.线程的命名与取得
I.通过构造方法在创建线程时设置线程名称
II.取得线程名称
III.设置线程名称
注意:无论在线程启动前还是启动后都可以修改名字。
2.线程休眠
线程休眠:让当前线程暂缓执行,等到了预计时间后,再恢复执行。
线程休眠会交出CPU,但不会释放锁。
线程休眠:运行状态--->阻塞状态 不会释放锁、立马交出CPU
休眠时间结束后:阻塞状态--->就绪状态
3.线程让步
线程让步:暂停执行当前的线程对象,并执行其他线程。
线程让步:运行状态--->就绪状态 不会释放锁
yield()方法会让当前线程交出CPU,同样不会释放锁。但是yield()方法无法控制具体交出CPU的时间,并且yield()方法只能让拥有相同优先级的线程有获取CPU的机会。
4.等待其他线程终止
等待该线程终止。如果在主线程中调用该方法,会让主线程休眠。让调用该方法的线程先执行完毕后再恢复执行主线程。(哪怕子线程休眠,主线程也不会执行,必须等子线程执行完毕)
join()方法:执行状态--->阻塞状态
join()方法结束:阻塞状态--->就绪状态
举个栗子:
package www.like.java;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
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) {
System.out.println("main线程开始。。。");
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"子线程A");
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main线程结束");
}
public static void printTime(){
Date date = new Date();
DateFormat dateFormat = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
String str = dateFormat.format(date);
System.out.println(str);
}
}
在主线程中对子线程调用join()方法,主线程会等子线程执行完毕再开始执行。
5.线程停止
5.1设置标记位停止线程(推荐)
举个栗子:
package www.like.java;
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) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"子线程A");
thread.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
myThread.setFlag(false);
}
}
5.2 调用Thread类的stop方法强制停止线程
该方法不安全,已经被Deprecated。
5.3调用Thread类的 Interrupt 方法
interrupt()方法只是将线程状态置为中断状态而已,他不会中断一个正在运行的线程。此方法只是给线程传递一个中断信号,程序可以根据此信号来判断是否需要终止。
如果线程当前处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已;如果当前线程的状态处于阻塞状态,那么在将中断标志设置为true后,会有以下情况:
当线程中使用了wait,sleep以及join方法导致此线程阻塞,则这个interrupt方法会在线程中抛出InterrputException,并且将线程的中断状态由true置为false。
举个栗子:
package www.like.java;
class MyThread implements Runnable{
@Override
public void run() {
int i = 1;
while(true){
boolean bool = Thread.currentThread().isInterrupted(); //bool记录是否中断,默认没有中断
System.out.println(Thread.currentThread().getName()+"第"+i+"次执行"+bool);
if (bool){
//当发生中断时,线程退出
System.out.println("线程退出。。。");
break;
}
i++;
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread,"子线程A");
thread.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.interrupt();
}
}
当出现第一个true(即第一次中断)时,线程退出。
6.线程优先级
线程优先级是指线程优先级越高,越有可能先执行,但仅仅是有可能而已。
设置优先级:
取得优先级:
Thread类中的三个常量来代表优先级(1,5,10)
MAX_PRIORITY = 10;
NORM_PRIORITY = 5;
MIN_PRIORITY = 1;
主线程只是一个普通优先级而已。
线程具有继承性:只是继承优先级而已。
例:从A线程启动B线程,则B和A的优先级是一样的。
7.守护线程
守护线程时一种特殊的线程,有称为陪伴线程。Java中有两种线程:用户线程和守护线程。
Thread类提供isDaemon()区别两种线程:返回false表示该线程为用户线程,否则为守护线程。
典型的守护线程就是垃圾回收线程。
Thread提供setDaemon()将用户线程设置为守护线程。
thread1.setDaemon(true);//将子线程1设置成守护线程,默认为用户线程
只要当前JVM进程中存在任何一个用户线程没有结束,守护线程就在工作,只有当最后一个用户线程结束时,守护线程才会随着JVM一同停止工作。