之前一直是懵懂的使用Thread,对这个并没有太完整的概念。最近看了一些资料并在小项目中使用到了一些相关技术,现结合实际应用与查阅到的资料,聊着总结。
一、首先,线程和线程类的区别:
线程是硬件资源CPU调度任务执行的最小单元,是一个抽象的概念;线程类本质上就是一串可执行的代码,在Java中就是Thread.class文件。
Thread线程的定义如下
public class Thread extends Object implements Runnable;
从 Thread 类的定义可以清楚的发现,Thread 类也是 Runnable 接口的子类,但在Thread类中并没有完全实现 Runnable 接口中的 run() 方法,下面是 Thread 类的部分定义:
Private Runnable target;
public Thread(Runnable target,String name){
init(null,target,name,0);
}
private void init(ThreadGroup g,Runnable target,String name,long stackSize){
...
this.target=target;
}
public void run(){
if(target!=null){
target.run();
}
}
从定义中可以发现,在 Thread 类中的 run() 方法调用的是 Runnable 接口中的 run() 方法,也就是说此方法是由 Runnable 子类完成的,所以如果要通过继承 Thread 类实现多线程,则必须覆写 run()。
实际上 Thread 类和 Runnable 接口之间在使用上也是有区别的:
如果一个类继承 Thread类,则不适合于多个线程共享资源,而实现了 Runnable 接口,就可以方便的实现资源的共享。详情如下:
public class MyThread extends Thread {
private int breakfast = 5;
private String name;
public MyThread(String name){
this.name = name;
}
@Override
public void run(){
for(int i = 0;i < 10; i++){
if(breakfast > 0){
System.out.println("run: " + Thread.currentThread().getName() + "卖火车票---->" + (this.breakfast--));
} else {
break;
}
}
}
public static void main(String[] args) {
MyThread mt1= new MyThread("一号窗口");
MyThread mt2= new MyThread("二号窗口");
MyThread mt3= new MyThread("三号窗口");
mt1.start();
mt2.start();
mt3.start();
}
}
运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1QnMWfbW-1589613065361)(https://img-blog.csdn.net/20171121170007986?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGFveXVlZ29uZ3pp/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)]
public class MyRunnable implements Runnable {
private int breakfast = 5;
private String name;
@Override
public void run(){
for(int i = 0;i < 50; i++){
if(this.breakfast > 0){
System.out.println("run: " + Thread.currentThread().getName() + "卖火车票---->" + (this.breakfast--));
}else {
break;
}
}
}
public static void main(String[] args) {
//设计三个线程
MyRunnable mt = new MyRunnable();
Thread t1 = new Thread(mt,"一号窗口");
Thread t2 = new Thread(mt,"二号窗口");
Thread t3 = new Thread(mt,"三号窗口");
t1.start();
t2.start();
t3.start();
}
}
运行结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1dj0TC01-1589613065362)(https://img-blog.csdn.net/20171121170314277?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaGFveXVlZ29uZ3pp/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)]
很显然的,这两个运行结果相差甚远,这是为什么呢?现在我们来仔细分析下:
第一个结果是通过继承Thread来实现的,在执行前,通过关键字“new”创建了三个对象,并且这三个对象是相互独立的,彼此之间没有任何关联,因此在运行过程中就是各做各作,各自5张售出火车票,实际上就售出了15张火车票,这在实际中显然是矛盾的,相同的票怎么能出售三次呢?这就是数据无法共享引起的。
第二个结果是通过实现Runnable接口来实现的,通过关键字new 一个MyRunnable对象,然后再new三个线程,让这三个线程共同执行卖5张票的任务。有效避免了前面基础Thread引发的矛盾。
因此:如果一个类继承 Thread类,则不适合于多个线程共享资源,而实现了 Runnable 接口,就可以方便的实现资源的共享。
二、线程的5种状态:
任何线程一般具有5种状态,即创建,就绪,运行,阻塞,终止。下面分别介绍一下这几种状态:
创建状态
在程序中用构造方法创建了一个线程对象后,新的线程对象便处于新建状态,此时它已经有了相应的内存空间和其他资源,但还处于不可运行状态。新建一个线程对象可采用Thread 类的构造方法来实现,例如 “Thread thread=new Thread()”。
就绪状态
新建线程对象后,调用该线程的 start() 方法就可以启动线程。当线程启动时,线程进入就绪状态。此时,线程将进入线程队列排队,等待 CPU 服务,这表明它已经具备了运行条件。
运行状态
当就绪状态被调用并获得处理器资源时,线程就进入了运行状态。此时,自动调用该线程对象的 run() 方法。run() 方法定义该线程的操作和功能。
阻塞状态
一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入/输出操作,会让 CPU 暂时中止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep(),suspend(),wait() 等方法,线程都将进入阻塞状态,发生阻塞时线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。
死亡状态
线程调用 stop() 方法时或 run() 方法执行结束后,即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。
在此提出一个问题,Java 程序每次运行至少启动几个线程?
回答:至少启动两个线程,每当使用 Java 命令执行一个类时,实际上都会启动一个 JVM,每一个JVM实际上就是在操作系统中启动一个线程,Java 本身具备了垃圾的收集机制。所以在 Java 运行时至少会启动两个线程,一个是 main 线程,另外一个是垃圾收集线程。
三、取得和设置线程的名称:
class MyThread implements Runnable{
public void run(){
System.Out.Println(Thread.currentThread().getName());
}
};
public class ThreadDemo{
public static void main(String args[]){
MyThread my=new MyThread(); //定义Runnable子类对象
new Thread(my).start; //系统自动设置线程名称
new Thread(my,"线程A").start(); //手工设置线程名称
}
};
四、线程的强制运行
在线程操作中,可以使用 join() 方法让一个线程强制运行,线程强制运行期间,其他线程无法运行,必须等待此线程完成之后才可以继续执行。
class MyThread implements Runnable{
public void run(){ // 覆写run()方法
System.out.println(Thread.currentThread().getName()) ;
}
};
public class ThreadJoinDemo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
t.start() ; // 启动线程
try{
t.join() ; // 线程强制运行
}catch(InterruptedException e){
}
}
};
五、线程的休眠
在程序中允许一个线程进行暂时的休眠,直接使用 Thread.sleep() 即可实现休眠。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
try{
Thread.sleep(500) ; // 线程休眠
}catch(InterruptedException e){
}
System.out.println(Thread.currentThread().getName());
}
};
public class ThreadSleepDemo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
t.start() ; // 启动线程
}
};
六、中断线程
当一个线程运行时,另外一个线程可以直接通过interrupt()方法中断其运行状态。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
try{
Thread.sleep(10000) ; // 线程休眠10秒
}catch(InterruptedException e){
return ; // 返回调用处
}
}
};
public class ThreadInterruptDemo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
t.start() ; // 启动线程
try{
Thread.sleep(2000) ; // 线程休眠2秒
}catch(InterruptedException e){
}
}
};
七、后台线程
在 Java 程序中,只要前台有一个线程在运行,则整个 Java 进程都不会消失,所以此时可以设置一个后台线程,这样即使 Java 线程结束了,此后台线程依然会继续执行,要想实现这样的操作,直接使用 setDaemon() 方法即可。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
while(true){
System.out.println(Thread.currentThread().getName() + "在运行。") ;
}
}
};
public class ThreadDaemonDemo{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 实例化Runnable子类对象
Thread t = new Thread(mt,"线程"); // 实例化Thread对象
t.setDaemon(true) ; // 此线程在后台运行
t.start() ; // 启动线程
}
};
八、线程的优先级
在 Java 的线程操作中,所有的线程在运行前都会保持在就绪状态,那么此时,哪个线程的优先级高,哪个线程就有可能会先被执行。
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
System.out.println(Thread.currentThread().getName()) ; //取得当前线程的名字
}
};
public class ThreadPriorityDemo{
public static void main(String args[]){
Thread t1 = new Thread(new MyThread(),"线程A") ; // 实例化线程对象
Thread t2 = new Thread(new MyThread(),"线程B") ; // 实例化线程对象
Thread t3 = new Thread(new MyThread(),"线程C") ; // 实例化线程对象
t1.setPriority(Thread.MIN_PRIORITY) ; // 优先级最低
t2.setPriority(Thread.MAX_PRIORITY) ; // 优先级最高
t3.setPriority(Thread.NORM_PRIORITY) ; // 优先级最中等
t1.start() ; // 启动线程
t2.start() ; // 启动线程
t3.start() ; // 启动线程
}
};
线程将根据其优先级的大小来决定哪个线程会先运行,但是需要注意并非优先级越高就一定会先执行,哪个线程先执行将由 CPU 的调度决定。
九、线程的礼让
在线程操作中,也可以使用 yield() 方法将一个线程的操作暂时让给其他线程执行
class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 覆写run()方法
for(int i=0;i<5;i++){
try{
Thread.sleep(500) ;
}catch(Exception e){
}
System.out.println(Thread.currentThread().getName());
if(i==2){
Thread.currentThread().yield() ; // 线程礼让
}
}
}
};
public class ThreadYieldDemo{
public static void main(String args[]){
MyThread my = new MyThread() ; // 实例化MyThread对象
Thread t1 = new Thread(my,"线程A") ;
Thread t2 = new Thread(my,"线程B") ;
t1.start() ; t2.start() ;
}
};
十、同步以及死锁
一个多线程的程序如果是通过 Runnable 接口实现的,则意味着类中的属性被多个线程共享,那么这样就会造成一种问题,如果这多个线程要操作同一个资源时就有可能出现资源同步问题。
1、synchronized(同步对象){ 需要同步的代码 }
class MyThread implements Runnable{
private int ticket = 5 ; // 假设一共有5张票
public void run(){
for(int i=0;i<100;i++){
synchronized(this){ // 要对当前对象进行同步
if(ticket>0){ // 还有票
System.out.println("卖票:ticket = " + ticket-- );
}
}
}
}
};
public class SyncDemo02{
public static void main(String args[]){
MyThread mt = new MyThread() ; // 定义线程对象
Thread t1 = new Thread(mt) ; // 定义Thread对象
Thread t2 = new Thread(mt) ; // 定义Thread对象
t1.start() ; t2.start() ;
}
};
2、同步方法:最常用的例子就是单例。
Public static synchronized class a{
Public a instance;
Public a getInstance(){
If (instance == null){
Instance = new a();
}
Return instance;
}
}
好了,关于线程的问题,先总结这么多了,以后遇到新的问题在继续完善。