一.进程和线程
1.1进程的定义:
一个具有独立功能的程序关于某个数据集合的一次运行活动。
一个进程由一个或多个线程运行,线程是操作系统分配CPU运行时间最小单位。
1.2线程的定义:
线程是进程中可独立调度执行的子任务,一个进程可以有一个或多个线程,它们共享所属进行所拥有的资源。
二.多线程
多线程是这样一种机制,它允许在程序中“并行”执行多个指令流,每个指令流被称作一个线程,彼此间的执行互相独立。多线程需要操作系统的支持,WIN32平台支持多线程程序,允许程序中存在多个线程。
在单CPU系统中,系统把CPU的时间片按照调度算法分配给各个线程,因此各线程实际上是分时执行的,而在多CPU的 计算机系统中,同一个程序的不同线程可以分配到不同的CPU上去执行。多个线程的执行是并发的,也就是逻辑上“同时”,而不是物理上的“同时”。
如果系统只有一个CPU,那么真正的“同时”是不可能的,但是由于CPU的速度非常快,用户感觉不到其中的区别,因此我们也不用关心它,只需要设想各个线程是同时执行即可。
三.线程的运行
一个程序并发的运行多个线程,这些线程同时在多处理器上运行,也可以在单处理器系统中分享CPU时间。
四.多线程的优点
多线程使程序反应更快,交互性更强,并能提供执行效率。
多线程使得程序员可以编写高效的程序,因此它允许使用大多数程序中的空闲时间,大多数IO设备,包括网络端口、磁盘驱动器和键盘,都比CPU运行得慢,因此一个程序的运行时间大部分经常花费在等待设备传输信息,通过多线可以利用这个空闲时间。
五.线程的创建
在Java中,每个程序至少自动拥有一个线程,称为主线程,当程序加载到内存时,启动主线程,如果需要使用其他线程,则可以采用以下两种方式创建新的线程:
一种是扩展java.lang.Thread类,用它覆盖Thread类的run()方法;
另一种是编写一个类,使之实现java.lang.Runnable接口,然后再Thread构造函数中使用它。
第一种方式只能在类没有扩展其他任何类的情况下才能使用,因为Java不允许多重继承。因此,如果一个类要继承其它的类,最好选用第二种方法,这样会有更大的灵活性。下面分别介绍两种创建线程的方法。
5.1扩展Thread类
类Thread位于java.lang包中,由于java.lang包自动被导入每个java程序中,所以可以直接使用类Thread而无需再Java程序开始处编写import语句,这也说明了Java对线程支持的彻底性。
使用扩展Thread类的方式创建并执行一个线程,需要执行下面4个步骤:
(1)扩展java.lang.Thread的类;
(2)用希望的执行代码来实现run()方法;
(3)通过new关键字实例化该类的一个新对象(即一个线程);
(4)通过调用start()方法启动线程。
class MyThread extends Thread{//(1)继承Thread类 public void run(){//(2)重写run方法 for(int i=0;i<10;i++){ System.out.println("myThread:hello world"); try{ Thread.sleep(50); }catch(InterruptedException e){ e.printStackTrace(); } } } } public static void main(String[] args){ MyThread myThread=new MyThread();//(3)实例化线程对象 myThread.start();//(4)启动该线程 }
5.2实现Runnable接口
利用Runnable接口创建和运行线程的编程步骤为:
第1步:定义一个Runnable接口的实现类,如MyThread,其内必须实现Runnable接口所声明的run()方法。
定义Runnable接口的方法如下:
public class yourThread implements Runnable{ public void run(){ ...//需要以线程方式运行的代码 } }
第2步:创建一个Thread类的对象,即创建一个线程,调用Thread类带Runnable引用作为形参的构造方法,把Runnable接口实现类对象传递给Thread类的对象即传递给新线程,为新线程提供程序代码和数据。如:
yourThread yourt=new yourThread(); Thread tt=new Thread(yourt);
第3步:用线程对象调用start()方法启动线程。如:tt.start();
定义一个实现了Runnable接口的类,将该类的对象作为Thread类的构造方法的参数,生成的Thread对象即为想要创建的线程,这样的线程同样通过start()方法启动。
class MyThread1 implements Runnable{//(1)实现Runnable接口 public void run(){ for(int i=0;i<100;i++){ System.out.println("myThread1:hello world"); try{ Thread.sleep(50); }catch(InterruptedException e){ e.printStackTrace(); } } } }
MyThread1 myThread1=new MyThread1();//(2)构造实现了Runnable接口的类对象 Thread mThread =new Thread(myThread1);//(3)创建线程对象 mThread.start();//(4)启动线程
5.3从以上创建线程的实例可以看出,构造线程体的两种方法各自的优缺点分析如下:
(1)使用Runnable接口
可以从其他类继承,当一个线程已继承了另一个类时,就只能用实现Runnable接口的方法来创建线程;
便于保持程序风格的一致性。
(2)扩展Thread类
不能再从其他类继承,适用于单继承线程情况;
编写简单,可以直接操作线程。
由以上分析可知,两种方法各有利弊,读着应该根据实际情况灵活运用。
六.线程的状态与控制
6.1在这里需要明确的是:无论采用扩展Thread类还是实现Runable接口的方法来实现应用程序的多线程能力,都需要在该类中定义用于完成实际功能的run()方法,这个run()方法称为线程体(Thread body)。
6.2按照线程从产生到灭亡分为新建、就绪、运行、挂起、死亡等5种状态。
(1)新建状态:
线程在已经利用new关键字创建但是还未执行的这段时间里,处于一种特殊的新建状态中,此时,线程对象已经被分配了内存空间,私有数据已经被初始化,但是该线程尚未被调度。
此时的线程可以被调度,变成可运行状态,也可以被杀死,变成死亡状态。
(2)就绪状态:
在处于创建状态的线程中调用start()方法将线程的状态转换为就绪状态。这时,线程已经得到除CPU时间之外的其他系统资源,只等JVM(Java虚拟机)的线程调度器按照线程的优先级对该线程进行调度,从而使该线程拥有能够获得CPU时间片的机会。
(3)运行状态:
运行状态表明线程正在运行,该线程已经拥有了对CPU的控制权。这个线程一直运行到运行完毕,除非该线程主动放弃CPU的控制权或者CPU的控制权被优先级更高的线程抢占。处在运行状态的线程在下列情况下将让出CPU的控制权:
①有比当前进程优先级更高的线程进入可运行状态;
②线程主动睡眠一段时间;
③线程运行完毕;
④线程在等待某一资源。
(4)挂起状态:
如果一个线程处于挂起状态,那么暂时这个线程无法进入就绪队列。处于挂起状态的线程通常需要由某些事件才能唤醒,至于由什么事件唤醒该线程,则取决于其挂起的原因。
处于睡眠状态的线程必须被挂起一段固定的时间,当睡眠时间结束时就变成可运行状态;因等待资源或消息而被挂起的线程则需要由一个外来事件唤醒。
(5)死亡状态:
正常情况下run()返回使得线程死亡。调用stop()或destory()亦有同样效果,但是不被推荐,因为前者会产生异常,后者是强制终止,不会释放内存。
6.3同其他Java类一样,Thread类中也有许多有用的方法,在这里只介绍部分常用的方法:
①public void suspend():挂起线程,可能引起死锁。
②public static void sleep(long millis):使当前执行的线程睡眠指定的时间。
参数millis是线程睡眠的毫秒数。如果这个线程已经被别的线程中断,就会产生InterruptedException异常。
③public void start():使线程由新建状态变成可运行状态。
④public void run():java运行系统调用该方法来执行线程。
用户线程类必须覆盖该方法并且提供线程执行的代码。程序中它永远不能被可运行对象直接调用。
⑤public final void stop():停止(杀死)当前线程。
⑥public final boolean isAlive():测试线程是否处于活动状态,即已启动,但还没有终止。
⑦public static boolean isinterrupted():测试当前线程是否被中断。
⑧public final void setPriority(int new):改变线程的优先级。
⑨public static Thread currenthread():返回当前执行线程对象的引用。
⑩public final void notify():唤醒一个等待中的线程。
七.线程的同步
7.1线程同步的概念
①由于同一进程的多个线程共享同一片存储空间,在带来方便的同时,也带来了访问冲突这个严重的问题。Java语言提供了专门机制以解决这种冲突,有效避免了同一个数据对象被多个线程同时访问,这套机制就是线程同步。
②线程同步是指Java避免多个线程同时访问一个数据而造成数据混乱的方法。它可以避免多个线程同时访问相同的数据产生线程之间的争抢,避免一个线程刚生成的数据又会被其他数据生成的数据所覆盖。
③Java用监视器手段来完成线程的同步。就好像监视器把受保护的资源外面添加了一把锁,而这把锁只有一把钥匙。每一个线程只有在得到这把钥匙之后才可以对被保护的资源执行操作线程,而其他的线程只能等待,直到能拿到这把钥匙。
④Java使用关键字synchronized来实现多线程的同步,线程同步有两种实现方法,一种是方法同步,另一种是对象同步。
7.2方法同步
①一个类中任何方法都可以设计成为synchronized方法,以防止多线程数据崩溃。当一个线程进入synchronized方法后,能保证在其他任何线程访问这个方法之前完成自己的一次执行。如果一个线程试图访问一个已经启动的synchronized方法,则这个线程必须等待,直到已启动线程执行完毕,释放这个synchronized方法后才能访问。
②通过在方法声明中加入synchronized关键字来声明synchronized方法:
private static synchronized void addAPenny(PiggyBank bank){ int newBalance=bank.getBalance()+1; try{ Thread.sleep(5); }catch(InterruptedException e){ System.out.println(e); } bank.setBalance(newBanlance); }
7.3对象同步
synchronized关键字除了可以放在方法声明中表示整个方法为同步方法外,还可以放在对象前面限制一段代码,当某个对象用synchronized修饰时,表明该对象在任何一个时刻只能由一个线程访问。例如:
class AddPennyThread extends Thread{ public void run(){ synchronized(bank){ int newBalance=bank.getBalance()+1; try{ Thread.sleep(0); } catch(InterruptedException e){ System.out.println(e); } bank.setBalance(newBalance); } } }
7.4 Semaphore工作原量
(1)Semaphore是一个计数信号量、Semaphore通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。
(2)每个线程必须从信号量Semaphore获取许可,才可以访问资源。
(3)没有获取信号量Semaphore许可的线程只能等待。
(4)Semaphore构造方法
①public Semaphore(int permits)
创建具有给定的许可数和非公平的公平设置
②public Semaphore(int permits,boolean fair)
创建具有给定的许可数和给定的公平设置
7.5 Semaphore方法
(1)acquire()
从此信号量获取一个许可,在提供一个许可前一直将线程阻塞
acquire(int permits)
从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞
(2)release()
释放一个许可,将其返回给信号量。
release(int permits)
释放给定数目的许可,将其返回到信号量。