进程与线程
进程:操作系统中一个程序的执行周期称为一个进程
线程:一个程序同时执行多个任务。通常,每一个任务就称为一个线程。与进程相比较,线程更”轻量级”,创建、撤销一个线程比启动一个新进程开销要小的多。没有进程就没有线程,进程一旦终止,其内的线程也将不复存在。
多进程与多线程区别:本质区别在于,每个进程拥有自己的一整套变量,而线程则共享数据。共享变量使得线程之间的通信比进程之间通信更有效、更方便
线程状态
Java多线程实现
1、继承Thread类实现多线程
java.lang.Thread是一个线程操作的核心类。新建一个线程最简单的方法就是直接继承Thread类,而后覆写该类中的run()方法(就相当于主类中的main方法)
例:定义线程的主体
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 = " + i);
}
}
}
当现在有了线程的主体类之后,很自然我们就会想到产生线程类的实例化对象而后调用run()方法。实际上,我们不能够直接去调用run()方法。
例::观察调用run()方
public class TestDemo {
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();
}
}
这个时候只是做了一个顺序打印,和多线程一点关系都没有。正确启动多线程的方式是调用Thread类中的start()方法。
启动多线程:public synchronized void start()此方法会自动调用线程的run()方法。
例:正确的线程启动
public class TestDemo {
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();
}
}
正确结果之一
所有的线程对象变成了交替执行
2、Runnable()接口实现多线程
Thread类的核心功能是进行线程的启动。如果一个类为了实现多线程直接去继承Thread类就会有但继承局限。在java中又提供有另外一种实现模式:Runnable接口。
观察Runnable接口:
例:利用Runable接口实现线程主体类
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 = " + i);
}
}
}
这样写以后,新的问题就产生了。此时MyThread类继承的不再是Thread类而实现了Runnable接口,虽然解决了单继承局限问题,但是没有start()方法被继承了。那么此时就需要关注Thread类提供的构造方法。
Thread类提供的构造方法:public Thread(Runnable target)可以接收Runnable接口对象。
因此,启动一个线程:
例:
public class TestDemo {
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();
}
}
这个时候就启动了多线程。多线程的启动永远都是Thread类的start()方法。
这个时候需要注意的是,对于此时的Runnable接口对象可以采用匿名内部类或者Lambda表达式来定义。
例:使用匿名内部类进行Runnable对象创建
package www.bit.java.testdemo;
public class TestDemo {
public static void main(String[] args) {
new Thread( new Runnable() {
@Override
public void run() {
System.out.println("Hello World");
}
} ).start();
}
}
例:使用Lamdba表达式进行Runnable对象创建
package www.bit.java.testdemo;
public class TestDemo {
public static void main(String[] args) {
Runnable runnable = () -> System.out.println("Hello World");
new Thread(runnable).start();
}
}
3、 Callable实现多线程
从JDK1.5开始追加了新的开发包:java.uti.concurrent。这个开发包主要是进行高并发编程使用的,包含很多在高并发操作中会使用的类。在这个包里定义有一个新的接口Callable。
Runnable中的run()方法没有返回值,它的设计也遵循了主方法的设计原则:线程开始了就别回头。但是很多时候需要一些返回值,例如某些线程执行完成后可能带来一些返回结果,这种情况下就只能利用Callable来实现多线程。
例:使用Callable定义线程主体类
class MyThread implements Callable<String> {
private int ticket = 10 ; // 一共10张票
@Override
public String call() throws Exception {
while(this.ticket>0){
System.out.println("剩余票数:"+this.ticket -- );
}
return "票卖完了,下次吧。。。" ;
}
}
不管何种情况。如果要想启动多线程只有Thread类中的start()方法。
来看Callable的继承树:
例:启动并取得多线程的执行结果
public class TestDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<String> task = new FutureTask<>(new MyThread()) ;
new Thread(task).start();
new Thread(task).start();
System.out.println(task.get());
}
}
运行结果
三种启动线程的方法已经介绍完毕了
介绍一下Thread与Runnable区别
首先从使用形式来讲,明显使用Runnable实现多线程要比继承Thread类要好,因为可以避免但继承局限。
除了这点以外,Thread和Runnable还有什么区别呢?
来看Thread类的定义
public class Thread implements Runnabl
Thread类是Runnable接口的子类,那么Thread类一定覆写了Runnable接口的run()方法
//Thread的构造函数
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
在多线程的处理上使用的就是代理设计模式。除了以上的关系之外,实际上在开发之中使用Runnable还有一个特点:使用Runnable实现的多线程的程序类可以更好的描述出程序共享的概念(并不是说Thread不能)
例:使用Thread实现数据共享(产生若干线程进行同一数据的处理操作)
package www.bit.java.testdemo;
class MyThread extends Thread {
private int ticket = 10 ; // 一共10张票
@Override
public void run() {
while(this.ticket>0){
System.out.println("剩余票数:"+this.ticket -- );
}
}
}
public class TestDemo {
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
new MyThread().start();
}
}
运行结果
此时启动三个线程实现卖票处理。结果变为了卖各自的票。
例:使用Runnable实现共享
package www.bit.java.testdemo;
class MyThread implements Runnable {
private int ticket = 10 ; // 一共10张票
@Override
public void run() {
while(this.ticket>0){
System.out.println("剩余票数:"+this.ticket -- );
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread mt = new MyThread() ;
new Thread(mt).start();
new Thread(mt).start();
}
}
运行结果
Runnable实现的多线程的程序类可以更好的描述出程序共享的概念。
多线程的常用操作方法
1、线程的命名与取得
多线程的运行状态是不确定的,所以对于多线程操作必须有一个明确标识出线程对象的信息,这个信息往往通过名称来描述。在Thread类中提供有如下的线程名称方法:
要想取得线程的对象,在Thread类中提供有一个方法取得当前线程对象:
public static native Thread currentThread();
例:观察线程名称取得
package www.bit.java.testdemo;
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10 ; i++) {
System.out.println("当前线程:"+Thread.currentThread().getName()+" ,i = "+i);
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread mt = new MyThread() ;
new Thread(mt).start(); // 没有设置名字
new Thread(mt).start(); // 没有设置名字
new Thread(mt,"yuisama").start(); // 有设置名字
}
}
通过上述代码发现,如果没有设置线程名字,则会自动分配一个线程名字。需要主要的是,线程名字如果要设置请避免重复,同时中间不要修改。
package www.bit.java.testdemo;
class MyThread implements Runnable {
@Override
public void run() {
System.out.println("当前线程:" + Thread.currentThread().getName());
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.run(); // 直接通过对象调用run()方法
new Thread(mt).start(); // 通过线程调用
}
}
通过以上程序我们发现,主方法本身就是一个线程,所有的线程都是通过主线程创建并启动的。
疑问:进程在哪?
实际上每当使用了java命令去解释程序的时候,都表示启动了一个新的JVM进程。而主方法只是这个进程上的一个线程而已。
在讲线程的其他常用方法以前我们先来看张图
2、线程休眠(sleep()方法)
线程休眠:指的是让线程暂缓执行一下,等到了预计时间之后再恢复执行。
线程休眠会交出CPU,让CPU去执行其他的任务。但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。
方法:
public static native void sleep(long millis) throws InterruptedException
休眠时间使用毫秒作为单位
例:处理休眠操作
package www.bit.java.testdemo;
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000 ; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("当前线程:" + Thread.currentThread().getName()+" ,i = " +i);
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread mt = new MyThread();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
}
}
通过代码观察会错误的认为这三个线程是同时休眠的,但是千万要记住,所有的代码是依次进入到run()方法中的。
真正进入到方法的对象可能是多个,也可能是一个。进入代码的顺序可能有差异,但是总体的执行是并发执行。
3、线程让步(yideld()方法)
暂停当前正在执行的线程对象,并执行其他线程。
意思就是调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
例:观察yield方法
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){
MyThread mt = new MyThread();
new Thread(mt).start();
new Thread(mt).start();
new Thread(mt).start();
}
}
4、join()方法
等待该线程终止。意思就是如果在主线程中调用该方法时就会让主线程休眠,让调用该方法的线程run方法先执行完毕之后在开始执行主线程。
例:观察join()方法
class MyThread implements Runnable {
@Override
public void run() {
try {
System.out.println("主线程睡眠前的时间");
Test.printTime();
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName());
System.out.println("睡眠结束的时间");
Test.printTime();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException{
MyThread mt = new MyThread();
Thread thread = new Thread(mt,"子线程A");
thread.start();
System.out.println(Thread.currentThread().getName());
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);
}
}
5、线程停止
多线程中有三种方式可以停止线程。
设置标记位,可以是线程正常退出。
使用stop方法强制使线程退出,但是该方法不太安全所以已经被废弃了。
使用Thread类中的一个 interrupt() 可以中断线程
例:设置标记为使线程退出
class MyThread implements Runnable {
private boolean flag = true;
@Override
public void run() {
int i = 1;
while (flag) {
try {
Thread.sleep(1000);
System.out.println("第"+i+"次执行,线程名称为:"+Thread.currentThread().getName());
i++;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread,"子线程A");
thread1.start();
Thread.sleep(2000);
myThread.setFlag(false);
System.out.println("代码结束");
}
}
例:使用stop方法使线程退出
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread,"子线程A");
thread1.start();
Thread.sleep(3000);
thread1.stop();
System.out.println("代码结束");
使用stop方法强制使线程退出,但是该方法不太安全所以已经被废弃了。
**为什么说不安全呢?因为stop会解除由线程获取的所有锁定,当在一个线程
对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,假如
一个线程正在执行:synchronized void { x = 3; y = 4;} 由于方法是同步
的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x
= 3;时,被调用了 stop()方法,即使在同步块中,它也会马上stop了,这样
就产生了不完整的残废数据。**
例:使用Thread.interrupt()
class MyThread implements Runnable {
private boolean flag = true;
@Override
public void run() {
int i = 1;
while (flag) {
try {
/**
* 这里阻塞之后,线程被调用了interrupte()方法,
* 清除中断标志,就会抛出一个异常
* java.lang.InterruptedException
*/
Thread.sleep(1000);
boolean bool = Thread.currentThread().isInterrupted();
if (bool) {
System.out.println("非阻塞情况下执行该操作。。。线程状态" + bool);
break;
}
System.out.println("第"+i+"次执行,线程名称为:"+Thread.currentThread().getName());
i++;
} catch (InterruptedException e) {
System.out.println("退出了");
/**
* 这里退出阻塞状态,且中断标志被系统会自动清除,
* 并且重新设置为false,所以此处bool为false
*/
boolean bool = Thread.currentThread().isInterrupted();
System.out.println(bool);
//退出run方法,中断进程
return;
}
}
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread,"子线程A");
thread1.start();
Thread.sleep(3000);
thread1.interrupt();
System.out.println("代码结束");
}
}
interrupt() 方法只是改变中断状态而已,它不会中断一个正在运行的线程。这一方法实际完成的是,给受阻塞的线程发出一个中断信号,这样受阻线程就得以退出阻塞的状态。
然而interrupte()方法并不会立即执行中断操作;具体而言,这个方法只会给线程设置一个为true的中断标志(中断标志只是一个布尔类型的变量),而设置之后,则根据线程当前的状态进行不同的后续操作。如果,线程的当前状态处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已;如果线程的当前状态处于阻塞状态,那么在将中断标志设置为true后,还会有如下三种情况之一的操作:
如果是wait、sleep以及jion三个方法引起的阻塞,那么会将线程的中断标志重新设置为false,并抛出一个InterruptedException;
如果在中断时,线程正处于非阻塞状态,则将中断标志修改为true,而在此基础上,一旦进入阻塞状态,则按照阻塞状态的情况来进行处理;例如,一个线程在运行状态中,其中断标志被设置为true之后,一旦线程调用了wait、jion、sleep方法中的一种,立马抛出一个InterruptedException,且中断标志被程序会自动清除,重新设置为
false。
通过上面的分析,我们可以总结,调用线程类的interrupted方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。因此,通过interrupted方法真正实现线程的中断原理是:开发人员根据中断标志的具体值,来决定如何退出线程。
6、线程优先级
线程的优先级指的是,线程的优先级越高越有可能先执行,但仅仅是有可能而已。
在Thread类中提供有如下优先级方法:
设置优先级:
public final void setPriority(int newPriority)
取得优先级:
public final int getPriority()
对于优先级设置的内容可以通过Thread类的几个常量来决定
最高优先级:public final static int MAX_PRIORITY = 10;
中等优先级:public final static int NORM_PRIORITY = 5;
最低优先级:public final static int MIN_PRIORITY = 1;
例:设置优先级
package www.bit.java.testdemo;
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5 ; i++) {
System.out.println("当前线程:" + Thread.currentThread().getName()+" ,i = " +i);
}
}
}
public class TestDemo {
public static void main(String[] args) {
MyThread mt = new MyThread();
Thread t1 = new Thread(mt,"1") ;
Thread t2 = new Thread(mt,"2") ;
Thread t3 = new Thread(mt,"3") ;
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t3.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();
t3.start();
}
}
主方法是一个线程,那么主线程的优先级是什么呢?
public class TestDemo {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getPriority());
}
}
主方法只是一个中等优先级
线程具有继承性
线程是有继承关系的,比如当A线程中启动B线程,那么B和A的优先级将是一样的
例:观察线程继承性
class A implements Runnable {
@Override
public void run() {
System.out.println("A的优先级为:" + Thread.currentThread().getPriority());
Thread thread = new Thread(new B());
thread.start();
}
}
class B implements Runnable {
@Override
public void run() {
System.out.println("B的优先级为:" + Thread.currentThread().getPriority());
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new A());
thread.setPriority(Thread.MAX_PRIORITY);
thread.start();
}
}
7、守护线程
守护线程是一种特殊的线程,它属于是一种陪伴线程。简单点说 java 中有两种线程:用户线程和守护线程。可以通过isDaemon()方法来区别它们:如果返回false,则说明该线程是“用户线程”;否则就是“守护线程”。典型的守护线程就是垃圾回收线程。只要当前JVM进程中存在任何一个非守护线程没有结束,守护线程就在工作;只有当最后
一个非守护线程结束时,守护线程才会随着JVM一同停止工作。
注意:主线程main是用户线程。
例:观察守护线程
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是用户线程当它中断了之后守护线程还没有结束,是因为主线程(用户线程)还没有结束,所以说明是所有的用户线程结束之后守护线程才会结束。