1、进程和线程的区别:
(1)进程可以理解为一个程序的执行。如果说QQ.
(2)线程可以理解为在一个进程中独立运行的子任务。比如说:QQ运行时有:和好友视频的线程,传输数据的线程,发送表情的线程等等。
多任务操作系统,如Windows系列就是通过多个线程来处理任务的。
多任务环境:异步(多个线程同时执行)。 单任务环境:异步(只有一个线程)。
2.使用多线程:
(1)一个程序在运行时,至少有一个线程在执行任务。比如public static void main()执行时,就是一个叫做main的线程在执行的。
获取当前线程的名字:
public static void main(String[] args) { System.out.println(Thread.currentThread().getName()); }
实现多线程主要有两种方法:继承Thread类,实现Runnable接口 。
1.1继承Thread类
class MyThread extends Thread{ @Override public void run() { System.out.println("MyThread"); } } public class Run { public static void main(String[] args) { System.out.println("开始运行:("+Thread.currentThread().getName()+")"); MyThread my=new MyThread(); my.start(); System.out.println("运行结束"); } }
从结果可以看出使用多线程时,代码运行结果与代码执行顺序是无关的。
(3)演示线程随机性:
class MyThread extends Thread{ @Override public void run() { System.out.println("MyThread开始运行:"); for(int i=0;i<100;i++) { try { Thread.sleep((int)Math.random()*1000); System.out.println("run="+this.getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Run { public static void main(String[] args) { MyThread thread=new MyThread(); thread.setName("mythread"); thread.start(); for(int i=0;i<100;i++) { try { Thread.sleep((int)Math.random()*1000); System.out.println("main="+Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } }
结果:
Thread类中的start() 方法通知“线程规划器”此线程已经准备完毕,等待调用线程对象的run()方法,也就是使线程得到运行,启动线程。如果调用Thread.run()就不是异步了,就是同步了,那么此线程对象并不交给"线程规划器"来进行处理。
1.2实现Runnable接口
(1)实例:
class MyRunnable implements Runnable{ @Override public void run() { System.out.println("MyRunnable开始运行:"); } } public class Run { public static void main(String[] args) { MyRunnable mr=new MyRunnable(); Thread thread=new Thread(mr); thread.start(); System.out.println("运行结束!"); } }
注意:使用Thread类的方法实现多线程是有局限性的,因为java是单根继承,不支持多根继承。所以为了改变这种限制,可以使用Runnable接口的方式来实现多线程技术。
3.实例线程和线程安全
(1)卖票(不共享数据的实例):
class MyRunnable implements Runnable{ private int count=5; @Override public void run() { while(count>0) { count--; System.out.println("由"+Thread.currentThread().getName()+"计算,卖出count="+count); } } } public class Run { public static void main(String[] args) throws InterruptedException { System.out.println("开始售票:"); MyRunnable a=new MyRunnable(); Thread aThread=new Thread(a,"A"); Thread bThread=new Thread(a,"B"); Thread cThread=new Thread(a,"C"); aThread.start(); bThread.start(); cThread.start(); Thread.sleep(1000); System.out.println("运行结束!"); } }
一共创建了3个线程,每个线程都有自己的count变量。自己减少自己的count变量的值,这样的情况是变量不共享。
如何设计三个线程同时卖票呢? 我们可不可以把count设置成为static呢?
(2)卖票(共享数据)
class MyRunnable implements Runnable{ public static int count=5; @Override public void run() {//这里为了防止某一线程直接运行完毕,其他线程得不到执行的机会,不用for循环 count--; System.out.println("由"+Thread.currentThread().getName()+"计算,卖出count="+count); } } public class Run { public static void main(String[] args) throws InterruptedException { System.out.println("开始售票:"); MyRunnable a=new MyRunnable(); Thread aThread=new Thread(a,"A"); Thread bThread=new Thread(a,"B"); Thread cThread=new Thread(a,"C"); Thread dThread=new Thread(a,"D"); Thread eThread=new Thread(a,"E"); aThread.start(); bThread.start(); cThread.start(); dThread.start(); eThread.start(); Thread.sleep(1000); System.out.println("运行结束!"); } }
可以看出,线程A和B和C都打印出了2,说明同时对count进行操作,会出现“非线程安全”问题。而我们要的结果不是重复的,而是递减的。
在某些JVM中,i--的操作被分成了3步:
<1>取得原有的i值; <2>计算i-1; <3>对i进行赋值操作。
在这三个步骤中,如果多个线程进行访问,那么一定会出现非线程安全问题。
这是我们可以使用顺序排队的方式进行减一的操作:
class MyRunnable implements Runnable{ public static int count=5; @Override synchronized public void run() {//这里为了防止某一线程直接运行完毕,其他线程得不到执行的机会,不用for循环 count--; System.out.println("由"+Thread.currentThread().getName()+"计算,卖出count="+count); } }
在run方法前面加上sychronized(同步)关键字,使多个线程在执行run方法的时候。以排队的方式进行。当一个方法调用run方法之前,先判断是否上锁。如果上锁,就说明其他线程在调用run方法,必须等待其他线程对该run方法调用结束了才能执行run方法。
sychronized可以在任意对象和方法上加锁,加锁的这段代码叫做“互斥区”或“临界区”。
3.1.留意i--与System.out.println()的异常
那如果将count--;放到System.out.println()中是否就可以不用同步锁了呢?class MyRunnable implements Runnable{ public static int count=5; @Override public void run() {//这里为了防止某一线程直接运行完毕,其他线程得不到执行的机会,不用for循环 System.out.println("由"+Thread.currentThread().getName()+"计算,卖出count="+(count--)); } }
程序运行后还是出现了非线程安全问题,因为:i--的操作是在进入println()之前发生的,所以有发生非线程安全问题的几率。
所以为了防止非线程安全问题的发生,还是要继续使用同步方法。
4.currentThread()方法:
文章一开始我们就介绍了这个方法,可以通过Thread.currentThread().getName()得到当前线程的名字。
通过这个方法,我们会发现一些问题:
class MyRunnable implements Runnable{ public static int count=3; public MyRunnable() { System.out.println("构造方法的打印:"+Thread.currentThread().getName()); } @Override public void run() { System.out.println("run方法的打印:"+Thread.currentThread().getName()); } } public class Run { public static void main(String[] args) throws InterruptedException { MyRunnable run=new MyRunnable(); Thread thread=new Thread(run,"run"); thread.start(); } }
我们发现,构造方法是main线程调用的。
5.isAlive()方法
方法isAlive()的功能是判断当前线程是否处于活动状态。什么是活动状态呢?活动状态就是线程已经启动但是尚未终止。线程处于正在运行或者准备运行的状态,就认为线程是存活的。
class MyThread extends Thread{ @Override public void run(){ System.out.println("run= "+this.isAlive()); } } public class Main{ public static void main(String[]args) { System.out.println("开始运行程序"); MyThread myThread=newMyThread(); System.out.println("begin=="+myThread.isAlive()); myThread.start(); System.out.println("end=="+myThread.isAlive()); }
结果为:
*在使用isAlive()方法时,如果将线程对象以构造参数的方式传递给Thread对象,进行start()启动时,运行结果是有差异的。造成这种差异主要是Thread.currentThread()和this的差异。
class CountOperate extends Thread{ public CountOperate() { System.out.println("CountOperate--begin:"); System.out.println("Thread.currentThread().getName():"+Thread.currentThread().getName()); System.out.println("Thread.currentThread().isAlive():"+Thread.currentThread().isAlive()); System.out.println("this.currentThread().getName():"+this.getName()); System.out.println("this.currentThread().isAlive():"+this.isAlive()); System.out.println("CountOprate--end"); } @Override public void run(){ System.out.println("run--begin:"); System.out.println("Thread.currentThread().getName():"+Thread.currentThread().getName()); System.out.println("Thread.currentThread().isAlive():"+Thread.currentThread().isAlive()); System.out.println("this.getName():"+this.getName()); System.out.println("this.isAlive():"+this.isAlive()); System.out.println("run--end"); } } public class Main{ public static void main(String[] args) { System.out.println("开始运行程序"); CountOperate countOperate=new CountOperate(); Thread thread=new Thread(countOperate,"countOprate"); thread.start(); } }运行结果:
6.停止线程
大多数停止线程使用的是Thread.interrupt()方法,尽管方法的名称是"停止,中止"的意思,但是这个方法不会终止一个正在运行中的线程,还需要加入一个判断才能完成线程的停止。
在Java中有三种可以终止正在运行的线程;
(1)使用退出标志,是线程正常退出,也就是当run方法完成后线程终止。
(2)使用stop方法强行终止线程,但是不推荐这种方法,因为stop和suspend及resume一样,都是过期作废的方法,使用它们可能会产生不可预料的后果。
(3)使用interrupt方法中断线程。
6.1停止不了的线程
class MyThread extends Thread{ public MyThread() { } @Override public void run(){ for (int i = 0; i <50000 ; i++) { System.out.println("i="+(i+1)); } } } public class Main{ public static void main(String[] args) throws InterruptedException { MyThread thread=new MyThread(); thread.start(); Thread.sleep(2000); thread.interrupt(); } }
结果:
从结果看,调用interrupt方法并没有停止线程。
6.2判断线程是否是停止状态
1)this.interrupted():测试当前线程是否已经中断。
2)this.isInterrupted():测试线程是否已经中断。
6.2.1 static boolean interrupted()
class MyThread extends Thread{ @Override public void run(){ for (int i = 0; i <50000 ; i++) { System.out.println("i="+(i+1)); } } } public class Main{ public static void main(String[] args) throws InterruptedException { MyThread thread=new MyThread(); thread.start(); Thread.sleep(1000); thread.interrupt(); System.out.println("是否停止1?="+thread.isInterrupted()); System.out.println("是否停止2?="+thread.isInterrupted()); System.out.println("end!"); } }
结果:
用isInterrupted来判断thread所代表的线程是否已经中断,但是,从控制台中显示,线程并未停止。证实了isInterrupted()方法的解释:测试当前线程是否已经中断。‘当前线程’是main,它从未中断过,所以打印的结果是两个false。
尝试让主线程中断:
class MyThread extends Thread{ @Override public void run(){ for (int i = 0; i <50000 ; i++) { System.out.println("i="+(i+1)); } } } public class Main{ public static void main(String[] args) throws InterruptedException { MyThread thread=new MyThread(); thread.start(); Thread.sleep(1000); thread.interrupt(); Thread.currentThread().interrupt(); System.out.println("是否停止1?="+Thread.interrupted()); System.out.println("是否停止2?="+Thread.interrupted()); System.out.println("end!"); } }
结果为:
第一次为true,说明interrupt设立的中止标记生效了,但是第二次却打印出了false。
这是为啥呢?
官方文档中说:方法interrupted测试当前方法是否已经中断。线程的中断状态由该方法清除。
因为第一次调用的时候清除了其中断状态,所以第二次调用interrupted返回的是false。
6.2.2 boolean isInterrupted()
将上面的例子改为isInterruptd()后:
class MyThread extends Thread{ @Override public void run(){ for (int i = 0; i <50000 ; i++) { System.out.println("i="+(i+1)); } } } public class Main{ public static void main(String[] args) throws InterruptedException { MyThread thread=new MyThread(); thread.start(); Thread.sleep(1000); thread.interrupt(); Thread.currentThread().interrupt(); System.out.println("是否停止1?="+thread.isInterrupted()); System.out.println("是否停止2?="+thread.isInterrupted()); System.out.println("end!"); } }
结果:
方法isInterrupted()并未消除状态标识,所以打印出了两个true。
总结两个方法:
this.interrupted():测试当前方法是否已经是中断状态,执行后有将状态清除为false的功能。
this.isInterrupted():测试当前方法是否是中断状态,但是不清除状态标识。
6.3 能停止的线程
6.3.1异常法
在主线程中使用interrupt()来中断MyThread线程,在MyThread线程中使用interrupted()方法确定线程已经中断。若中断就抛出InterruptedException()异常。
class MyThread extends Thread{ @Override public void run() { try { for (int i = 0; i < 500000; i++) { if (this.interrupted()) { System.out.println("已经是停止状态了。退出!"); throw new InterruptedException(); } System.out.println("i="+(i+1) ); } }catch(InterruptedException e){ System.out.println("进入catch块中了"); } } } public class Main{ public static void main(String[] args) throws InterruptedException { MyThread thread=new MyThread(); thread.start(); Thread.sleep(1000); thread.interrupt(); System.out.println("end!"); } }
6.3.2 使用return停止线程
将interrupted与return结合使用也能实现停止线程的效果。
class MyThread extends Thread{ @Override public void run() { for (int i = 0; i < 500000; i++) { if (this.interrupted()) { System.out.println("已经是停止状态了。退出!"); return; } System.out.println("i="+(i+1) ); } } }
class ThreadDemo extends Thread{ @Override public void run() { long beginTime=System.currentTimeMillis(); long count=0; for(int i=0;i<50000000;i++){ //Thread.yield(); count=count+(i+1); } long endTime=System.currentTimeMillis(); System.out.println("用时:"+(endTime-beginTime)+"毫秒!"); } } public class yieldDemo { public static void main(String[] args) { System.out.println("start Thread"); ThreadDemo thread=new ThreadDemo(); thread.start(); } }
public class Main{ public static void main(String[] args) throws InterruptedException { MyThread thread=new MyThread(); thread.start(); Thread.sleep(1000); thread.interrupt(); System.out.println("end!"); }}
但是最好别采用return来进行终止线程,防止方法中return过多,造成污染。推荐使用异常法进行控制线程的终止。
7.yield方法
yield方法的作用:放弃当前的CPU资源,将它让给其他的任务去占用CPU执行时间。但是放弃的时间不确定,有可能刚放弃,马上又获得CPU时间。
class ThreadDemo extends Thread{ @Override public void run() { long beginTime=System.currentTimeMillis(); long count=0; for(int i=0;i<50000000;i++){ //Thread.yield(); count=count+(i+1); } long endTime=System.currentTimeMillis(); System.out.println("用时:"+(endTime-beginTime)+"毫秒!"); } } public class yieldDemo { public static void main(String[] args) { System.out.println("start Thread"); ThreadDemo thread=new ThreadDemo(); thread.start(); } }
结果: |
解开//Thread.yield()后: |
8.线程的优先级
在操作系统中,线程可以划分优先级,优先级较高的线程得到的CPU资源较多,也就是CPU优先执行优先级较高的线程对象中的任务。设置优先级有助于帮助"线程规划器"确定在下一次选择哪一个线程来优先执行。
JDK的Thread类中使用3个常量来预置定义优先级的值:
public final static int MIN_PRIORITY=1; public final static int NORM_PRIORITY=5; public final static int MAX_PRIORITY=10;
实例:
class MyThread1 extends Thread{ @Override public void start() { System.out.println("MyThread run priority: "+this.getPriority()); MyThread2 thread2=new MyThread2(); thread2.start(); } } class MyThread2 extends Thread{ public void start() { System.out.println("mythread2 run priority:"+this.getPriority()); } } //优先级默认是5,调用的方法的优先级和被调用方法的优先级一致 public class Run1{ public static void main(String[] args) { MyThread1 thread1=new MyThread1(); thread1.start(); Thread.currentThread().setPriority(6); System.out.println("Main run priority:"+Thread.currentThread().getPriority()); MyThread2 thread2=new MyThread2(); thread2.start(); } }
结果:
线程的默认优先级是NORM_PRIORITY,值为5。通过上例发现:A线程调用B线程,A线程优先级和B线程优先级是相同的。
9.守护线程
在java中有两种线程,一种是用户线程,一种是守护(Daemon)线程。 什么是守护线程?守护线程的特性有陪伴的含义。当进程中没有非守护线程了,则守护线程自动销毁。典型的守护线程是垃圾回收机制。
class MyThread extends Thread { private int i=0; @Override public void run() { while(true){ try { i++; System.out.println("i="+(i)); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class Run2{ public static void main(String[] args) { try { MyThread thread=new MyThread(); thread.setDaemon(true); thread.start(); Thread.sleep(5000); System.out.println("我离开thread对象也不再打印了,也就是停止了!"); } catch (InterruptedException e) { e.printStackTrace(); } } }结果:
可以看出,当Main线程结束的时候,守护线程也结束了。