线程Thread与Runnable接口
之前一直是懵懂的使用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;}@Overridepublic 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;@Overridepublic 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;}
}
好了,关于线程的问题,先总结这么多了,以后遇到新的问题在继续完善。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
