1.进程:操作系统中一个程序的执行周期称为一个进程。
线程:一个程序同时执行多个任务。通常,每一个任务就称为一个线程。与进程相比,线程更“轻量级”,创建、撤销一个线程比启动一个新进程开销要小的多。没有进程就没有线程,进程一旦终止,其内的线程也将不复存在。
多进程应用:一个浏览器应用可以同时下载多个图片、音乐;一个Web服务器需要同时处理多个并发的请求。
2.线程状态
二、Java多线程实现
1.继承Thread类实现多线程
java.lang.Thread是一个线程操作的核心类。新建一个线程最简单的方法就是直接继承Thread类,而后覆写该类中的run()方法
如:定义线程的主体类
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()方法
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(); } }这时只是做了一个顺序打印,和多线程一点关系都没有。正确启动多线程的方式是调用Thread类中的start()方法。
如:正确启动多线程
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(); } }2.Runnable()接口实现多线程
Thread类的核心功能是进行线程的启动。如果一个类为了实现多线程直接去继承Thread类就会有单继承局限。在Java中又提供了另一种实现模式:Runnable接口。
如:利用Runnable接口实现线程主体类
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 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(); } }这个时候就启动了多线程,多线程的启动永远都是Thread类中的start()方法。
Runnable接口对象可以采用匿名内部类或者Lambda表达式来定义。
如:使用匿名内部类进行Runnable对象创建
public class Test{ public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("hello"); } }).start(); } }
如:使用Lambda表达式进行Runnable对象创建
public class Test{ public static void main(String[] args) { Runnable runnable = () -> System.out.println("hello"); new Thread(runnable).start(); } }3.Thread与Runnable的区别
从使用形式来讲,使用Runnable实现多线程要比继承Thread类要好,因为可以避免单继承局限。
线程类的继承结构
除以上关系外,使用Runnable还有一个特点:使用Runnable实现的多线程的程序类可以更好地描述出程序共享的概念。
如:使用Thread实现数据共享
class MyThread extends Thread{ private int ticket = 10; @Override public void run() { while(this.ticket > 0) { System.out.println("剩余票数:" + this.ticket--); } } } public class Test{ public static void main(String[] args) { new MyThread().start(); new MyThread().start(); new MyThread().start(); } }此时启动三个线程实现卖票处理,结果变为了卖各自的票。
如:使用Runnable实现共享
class MyThread implements Runnable{ private int ticket = 10; @Override public void run() { while(this.ticket > 0) { System.out.println("剩余票数 " + this.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();; } }Runnable实现的多线程的程序类可以更好地描述出程序共享的概念
4.Callable实现多线程
Runnable中的run()方法没有返回值,它的设计也遵循了主方法的设计原则:线程开始了就别回头。但是利用Callable来实现多线程,可以让线程执行后带来一些返回结果。
如:使用Callable定义线程主体类
class MyThread implements Callable<String>{ private int ticket = 10; @Override public String call() throws Exception { while(this.ticket > 0) { System.out.println("剩余票数:" + this.ticket--); } return "票卖完了"; } }
Callable实现多线程:
如:启动并取得多线程的执行结果
public class Test{ 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()); } }三、多线程的常用操作方法
1.线程的命名与取得
在Thread类中提供有如下的线程名称方法:
如:观察线程名称的取得
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 Test{ public static void main(String[] args) { MyThread myThread = new MyThread(); new Thread(myThread).start(); //没有设置名字 new Thread(myThread).start(); //没有设置名字 new Thread(myThread,"A").start(); //有设置名字 } }通过以上发现,如果没有设置线程名字,则会自动分配一个线程名字。需要注意的是,线程名字如果要设置避免重复,同时中间不要修改。
如:观察线程的执行结果
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 Test{ public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.run(); //直接通过对象调用run()方法 new Thread(myThread).start(); //通过线程调用 } }通过以上程序我们发现,主方法本身就是一个线程,所有的线程都是通过主线程创建并启动的。
实际上,每当使用java命令去解释程序的时候,都表示启动了一个JVM进程。而主方法只是这个进程上的一个线程而已。
线程的一张图:
2.线程休眠(sleep方法)
线程休眠:指的是让线程暂缓执行一下,等到了预计时间之后再恢复执行。
线程休眠会交出CPU,让CPU去执行其他的任务。但是sleep方法不会释放锁,也就是说如果当线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。
sleep方法:
public static native void sleep(long millis) throws InterruptedException休眠时间使用毫秒做单位。
如:处理休眠操作
class MyThread implements Runnable{ @Override public void run() { for(int i = 0;i < 10;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 Test{ public static void main(String[] args) { MyThread myThread = new MyThread(); new Thread(myThread).start(); new Thread(myThread).start(); new Thread(myThread).start(); } }通过观察结果我们会以为这三个线程是同时休眠的,但实际上不是的,所有的代码是依次进入到run()方法中的。
3.线程让步(yield()方法)
yield()方法:暂停当前正在执行的线程对象,并执行其他线程。
意思就是调用yield方法会让当前线程交出CPU权限,让CPU去执行其它的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
注意:调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间。
如:观察yield()方法
class MyThread implements Runnable{ @Override public void run() { // TODO Auto-generated method stub for(int i = 0;i < 10;i++) { Thread.yield(); System.out.println("当前线程:" + Thread.currentThread().getName() + ",i = " + i); } } } 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(); } }4.join()方法
join()方法:等待该线程终止。意思就是如果在主线程中调用该方法时就会让主线程休眠,让调用该方法的线程run方法先执行完毕之后在开始执行主线程。
如:观察join()方法
class MyThread implements Runnable{ public void run() { for(int i = 0;i < 10;i++) { System.out.println("当前线程" + Thread.currentThread().getName() + ",i = " + i); } } } public class Test{ public static void main(String[] args) throws InterruptedException { MyThread myThread1 = new MyThread(); MyThread myThread2 = new MyThread(); Thread A = new Thread(myThread1,"A"); Thread B = new Thread(myThread2,"B"); B.start(); B.join(); //等B线程执行完毕后再执行A线程 A.start(); } }5.线程停止
多线程有三种方式可以停止线程
(1)设置标记位,可以使线程正常退出。
(2)使用stop方法强制使线程退出,但是该方法不太安全。
(3)使用Thread类中的interrupt()可以中断线程
如:设置标记位使线程退出
class MyThread implements Runnable{ private boolean flag = true; @Override public void run() { // TODO Auto-generated method stub 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 thread = new Thread(myThread,"A"); thread.start(); Thread.sleep(5000); myThread.setFlag(false); System.out.println("代码结束"); } }
如:使用stop方法使线程退出
class MyThread implements Runnable{ @Override public void run() { int i = 0; while(true) { try { Thread.sleep(1000); System.out.println("第" + i + "次执行,线程名称为:" + Thread.currentThread().getName()); i++; } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } 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(); System.out.println("代码结束"); } }使用stop()方法强制使线程退出,但是该方法不太安全所以已经被废弃了。之所以不安全,是因为stop会解除由线程获取的所有锁定,当在一个线程对象上调用stop()方法时,这个线程对象所运行的线程就会立即停止,假如一个线程正在执行:synchronized void{x = 1;y = 2;} 由于方法是同步的,多个线程访问时总能保证x,y被同时赋值,而如果一个线程正在执行到x = 1;时,被调用了stop()方法,即使在同步块中,它也会马上stop了,这样就产生了不完整的数据。
如:使用Thread.interrupt()
class MyThread implements Runnable{ private boolean flag = true; @Override public void run() { int i = 0; while(flag) { try { 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("退出了"); boolean bool = Thread.currentThread().isInterrupted(); System.out.println(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(); System.out.println("代码结束"); } }interrupt()方法只是改变中断状态而已,它不会中断一个正在运行的进程。这一方法的实际完成是,给受阻塞的线程发出一个中断信号,这样受阻线程就得已退出阻塞的状态。然而interrupt()方法并不会立即执行中断操作;具体而言,这个方法只会给线程设置一个为true的中断标志(中断标志只是一个布尔类型的变量),而设置之后,则根据线程当前的状态进行不同的后续操作。如果,线程当前状态处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已;如果线程的当前状态处于阻塞状态,那么将中断标志设置为true后,还会有如下的操作之一:
1>如果是wait、sleep以及join三个方法引起的阻塞,那么会将线程的中断标志重新置为false,并抛出一个InterruptedException;
2>如果在中断时线程处于非阻塞状态,则将中断标志修改为true,而在此基础上,一旦进入阻塞状态,则按照阻塞状态的情况来进行处理。例如:一个线程在运行状态中,其中断标志被设置为true之后,一单线程调用了wait、join、sleep方法中的一种,立马抛出一个InterruptedException,且中断标志会被程序自动清除,重新设置为false。
总结:调用线程类的interrupt方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。
6.线程优先级
线程优先级:线程的优先级越高越有可能先执行,但仅仅是有可能而已。
在Thread类中提供有如下的优先级方法:
·设置优先级
public final void setPriority(int newPriority)
·取得优先级
public final int getPriority()对于优先级设置的内容可以通过Thread类的几个常量来决定:
(1)最高优先级:public final int MAX_PRORITY = 10;
(2)中等优先级:public final int NORM_PRORITY = 5;
(3)最低优先级:public final int MIN_PRORITY = 1;
如:设置优先级
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 Test{ public static void main(String[] args) { MyThread myThread = new MyThread(); Thread thread1 = new Thread(myThread,"A"); Thread thread2 = new Thread(myThread,"B"); Thread thread3 = new Thread(myThread,"C"); thread1.setPriority(Thread.MIN_PRIORITY); thread2.setPriority(Thread.NORM_PRIORITY); thread3.setPriority(Thread.MAX_PRIORITY); thread1.start(); thread2.start(); thread3.start(); } }
如:主线程的优先级(是中等优先级)
public class Test{ 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) { Thread thread = new Thread(new A()); thread.setPriority(Thread.MIN_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"); thread1.setDaemon(true); //设置A为守护线程 thread1.start(); Thread thread2 = new Thread(new A(),"B"); thread2.start(); Thread.sleep(3000); thread2.interrupt(); Thread.sleep(5000); System.out.println("代码结束"); } }可以看出,B是用户线程当它中断了之后守护线程还没有结束,是因为主线程(用户线程)还没有结束,所以说明是所有的用户线程结束之后守护线程才会结束。