Java进击(四)线程
一、线程、进程、多线程 概念
首先明白线程是什么?线程是一个程序内部的顺序控制流。线程经常和进程搞混,进程是一次程序的执行;线程可以看成是轻量级的进程。多进程:在操作系统中可能同时运行多个任务;多线程:在同一应用程序中有多个顺序流同时执行。
二、线程创建和启动
Java的线程是通过java.lang.Thread类实现的。每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()成为线程体。通过调用Thread类的start()方法来启动一个线程。
有两种方式创建新的线程:
第一种:实现接口
(1)定义线程类实现Runnable接口
(2)Thread myThread = new Thread(target)
(3)runnable中只有一个方法:publicvoid run()定义线程运行体
(4)使用Runnable接口可以为多个线程提供共享的数据
(5)在实现Runnable接口的类的run方法定义中可以使用Thread的静态方法:public static Thread currentThread()获取当前线程的引用
例子:
public class RunnableDemo{ public static void main(String[] args){ //只创建一个实现Runnable接口类的对象 TestThread threadobj = new TestThread(); System.out.println("Starting threads"); //只用一个Runnable类型对象为参数创建三个新线程 new Thread(threadobj,"Thread1").start(); new Thread(threadobj,"Thread2").start(); new Thread(threadobj,"Thread3").start(); System.out.println("Threads started,main ends\n"); } } class TestThread implements Runnable{ private int sleepTime; //构造函数 public TestThread(){ //获取随机休息时间 sleepTime=(int)(Math.random()*6000); } public void run(){ try{ System.out.println(Thread.currentThread().getName()+"going to sleep for"+sleepTime); Thread.sleep(sleepTime); }catch(InterruptedException exception){ } System.out.println(Thread.currentThread().getName()+"finished"); } }
运行结果:
第二种:继承
(1)可以定义一个Thread的子类并重写run方法:
class MyThread extends Thread{
public void run(){…}
}
(2)然后生成该类的对象:
MyThread myThread=newsMyThread(…)
例子:
public class ExtendsDemo{ public static void main(String[] args){ //创建并命名每个线程 TestThread thread1=new TestThread("thread1"); TestThread thread2=new TestThread("thread2"); TestThread thread3=new TestThread("thread3"); System.out.println("Starting threads"); //启动线程 thread1.start(); thread2.start(); thread3.start(); System.out.println("Threads started,main ends\n"); } } class TestThread extends Thread{ private int sleepTime; //构造函数 public TestThread(String name) { super(name); sleepTime=(int)(Math.random()*6000); } public void run(){ try{ System.out.println(getName()+"going to sleep for"+sleepTime); Thread.sleep(sleepTime); }catch(InterruptedException exception){ } System.out.println(getName()+"finished"); } }运行结果:
通过以上两种方法都可以创建线程,那么这两种方法有什么特点,我们分析一下:通过以上两个例子,通过运行结果我们看到实现Runnable接口方法创建的线程可以共享一个资源sleepTime,而继承Thread方法创建的线程没有共享一个资源,虽然执行的是相同的代码,但彼此相互独立,且各自拥有自己的资源,互不干扰。(需要注意一个子类只能继承一个父类,但是可以实现多个接口)
优缺点:
采用继承Thread类方式:
(1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。
(2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。
采用实现Runnable接口方式:
(1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标(target)对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
(2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。
在实际应用中根据情况选择合适的方法。
三、生产者--消费者
现实中我们经常会遇到很多有趣的情况:同时运行的几个线程需要共享一些数据,并且要考虑到彼此的状态和动作。比如在一个线程对共享数据进行操作时,在没完成相关操作之前,不允许其他线程打断,否则会破坏数据的完整性。也就是说,被多个线程共享的数据在同一时刻只运行一个线程处于操作之中,这就是同步控制中的一个问题——线程间的互斥。
我们模拟存票售票的例子感受一下:
public class SaveSellTickets{ public static void main(String[] args){ Tickets t =new Tickets(10); new Producer(t).start(); new Consumer(t).start(); } } class Tickets{ int number=0; int size; boolean available =false; public Tickets(int size){ this.size=size; } } class Producer extends Thread{ Tickets t = null; public Producer(Tickets t){ this.t = t; } public void run(){ while(t.number<t.size){ System.out.println("Producer puts ticket"+(++t.number)); t.available=true; } } } class Consumer extends Thread{ Tickets t =null; int i=0; public Consumer(Tickets t){ this.t = t; } public void run(){ while(i<t.size){ if(t.available ==true && i <= t.number){ System.out.println("Consumer buys ticket"+(++i)); if(i==t.number){ try{ Thread.sleep(5000);}catch(InterruptedException exception){}; t.available =false; } } } } }
运行结果:
现在是卖10张票,我们多卖些看看会出现什么情况,多运行几次会发现有时候程序就陷入死机了,为什么?仔细想想,如果程序运行到t.available=false之前,cpu切换到执行存票程序,存票将available置为true,但再次切换到售票之后,售票线程执行t.available=false,则由于售票序号大于总票数,且存票结束不再重置为true,那么程序陷入死循环。那么如何避免这种意外呢?
在java中,利用“锁旗标”可以实现线程间的互斥操作。看看如何修改代码:
最终优化,将两个需要互斥的方法放在Tickets类中实现:
public class NewTickets{ public static void main (String[] args){ Tickets t = new Tickets(10); new Producer(t).start(); new Consumer(t).start(); } } class Tickets{ int number=0; int size; int i = 0; boolean available =false; public Tickets(int size){ this.size=size; } public synchronized void put(){ System.out.println("Producer puts ticket"+(++number)); available=true; } public synchronized void sell(){ if(available == true && i <=number) System.out.println("Consumer buys ticket"+(++i)); if(i ==number) available=false; } } class Producer extends Thread{ Tickets t=null; public Producer(Tickets t){ this.t=t; } public void run(){ while(t.number<t.size) t.put(); } } class Consumer extends Thread{ Tickets t=null; public Consumer(Tickets t){ this.t=t; } public void run(){ while(t.i<t.size) t.sell(); } }
四、线程间通信
当线程在继续执行前需要等待一个条件时,仅有 synchronized 关键字是不够的。虽然 synchronized关键字阻止并发更新一个对象,但它没有实现线程间发信。Object 类为此提供了三个函数:wait()、notify() 和 notifyAll()。
还以售票为例子,要求存入一张票就售出一张票,售出后再存入,看代码:
class Tickets{ int number=0; int size; int i = 0; boolean available =false; public Tickets(int size){ this.size=size; } public synchronized void put(){ if(available)//如果还有票剩余则存票等带 try{wait();}catch(Exception e){} System.out.println("Producer puts ticket"+(++number)); available=true; notify();//存入后唤醒售票 } public synchronized void sell(){ if(! available)//如果没有存票则售票等待 try{wait();}catch(Exception e){} System.out.println("Consumer buys ticket"+(number)); available=false; notify();//售票后唤醒存票 if(number == size) number=size+1; } }
只需修改Tickets类的put和sell方法,结果如下:
五、总结
线程还有很多需要学习的东西,有效利用多线程的关键是理解程序是并发执行而不是串行执行的。比如程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。通过对多线程的使用,可以编写出非常高效的程序。不过值得注意的是,如果创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。在真正应用中,线程还有很多需要注意和思考的地方。
不足之处,敬请指正~~~
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。