在应用程序中,不同的程序块也是可以同时运行的,这种多个程序块同时运行的现象被称作并发执行。
多线程就是指一个应用程序中有多条并发执行的线索,每条线索都被称作一个线程,它们会交替执行,彼此间可以进行通信。在计算机中,所有的应用程序都是由CPU执行的,对于一个CPU而言,在某个时间点只能运行一个程序,也就是说只能执行一个进程。操作系统会为每一个进程分配一段有限的CPU使用时间,CPU在这段时间中执行某个进程,然后会在下一段时间切换到另一个进程中去执行。由于CPU运行速度很快,能够在极短的时间内在不同的进程之间进行切换,所以给人以同时执行多个程序的感觉。
程序:是一组计算机能识别和执行的指令
进程:是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位是操作系统结构的基础。
线程:是进程中一个实体,是被系统独立调度和分派的基本单位,不拥有系统资源,只有一点儿在运行中不可或缺的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源,
并发:是指一个时间段中有n个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但在任一时刻点上只有一个程序在处理机上运行(宏观是同时,微观上是顺序执行的)
并行:一组程序按独立异步的速度执行,不等于时间上的重叠(同一个时刻发生)
同步:指两个或两个以上随时间变化的量在变化过程中保持一定的相对关系
异步:不需要共同的时钟,也就是接收方不知道发送方什么时候发送,所在发送的信息中就要有提示接收方开始接收的信息。
在一个进程中还可以有多个执行单元同时运行,这些执行单元可以看作程序执行的一条条线索,被称为线程。操作系统中的每一个进程中都至少存在一个线程,多线程程序在运行时,每个线程之间都是独立的,它们可以并发执行。
线程的创建:Java提供了两种多线程实现方式,一种是继承java.lang包下的Thread类,覆写Thread类的run()方法,在run()方法中实现运行在线程上的代码;另一种是实现java.lang.Runnable接口,同样是在run()方法中实现运行在线程上的代码。在Thread类中,提供了一个start()方法用于启动新线程,线程启动后,系统会自动调用run()方法。
1 package thread; 2 3 import java.util.Random; 4 5 class ThreadObject extends Thread { 6 // 属性 7 private String name; 8 // 方法 9 public ThreadObject() {} 10 public ThreadObject(String name) { 11 this.name = name; 12 } 13 14 @Override 15 public void run() { 16 Random random = new Random(); 17 for (int i = 0; i < 100; i++) { 18 int num = random.nextInt(); 19 20 System.out.printf("%s-%03d: %d ", name, (i+1), num); 21 try { 22 Thread.sleep(0); 23 } catch (InterruptedException e) { 24 e.printStackTrace(); 25 } 26 } 27 } 28 } 29 30 public class ThreadDemo { 31 32 public static void main(String[] args) { 33 ThreadObject t1 = new ThreadObject("T1"); 34 ThreadObject t2 = new ThreadObject("T2"); 35 36 t1.start(); //t1.run(); // 启动线程,随机执行 37 t2.start(); //t2.run(); 38 39 System.out.println("主线程结束!"); 40 } 41 42 }
1 package runnable; 2 3 import java.util.Random; 4 5 class MyRunnable implements Runnable { 6 // 属性 7 private String name; 8 // 方法 9 public MyRunnable() {} 10 public MyRunnable(String name) { 11 this.name = name; 12 } 13 14 @Override 15 public void run() { 16 Random random = new Random(); 17 for (int i = 0; i < 100; i++) { 18 int num = random.nextInt(); 19 20 System.out.printf("%s-%03d: %d ", name, (i+1), num); 21 try { 22 Thread.sleep(1); 23 } catch (InterruptedException e) { 24 e.printStackTrace(); 25 } 26 } 27 } 28 } 29 30 public class RunnableDemo { 31 32 public static void main(String[] args) { 33 MyRunnable r1 = new MyRunnable("T1"); 34 MyRunnable r2 = new MyRunnable("T2"); 35 36 Thread t1 = new Thread(r1); 37 Thread t2 = new Thread(r2); 38 39 t1.start(); //t1.run(); // 启动线程,随机执行 40 t2.start(); //t2.run(); 41 42 System.out.println("主线程结束!"); 43 } 44 45 }
Runnable接口相对于继承Thread类来说,有以下显著好处:
1、适合多个相同程序代码的线程去处理同一个资源的情况,把线程同程序代码、数据有效分离,很好地体现了面向对象的设计思想
2、可以避免由于java的单继承带来的局限性。
线程的生命周期:
在Java中,任何对象都有生命周期,线程也不例外,它也有自己的生命周期。 当Thread对象创建完成时,线程的生命周期便开始了。当run()方法中代码正常执行完毕或者线程抛出一个未捕获的异常( Exception)或者错误(Error)时,线程的生命周期便会结束。线程整个生命周期可以分为五个阶段,分别是新建状态(New)、就绪状态(Runnable)、运行状态( Running)阻基状态( Blorked)和死亡状态( Terminated),线程的不同状态表明了线程当前正在进行的活动。在程序中,通过一些操作,可以使线程在不同状态之间转换。
新建状态:Java虚拟机为其分配了内存,没有表现出任何线程的动态特征
运行状态:只有处于就绪状态的线程才能转换到运行状态
阻塞状态:线程从阻塞状态只能进入就绪状态
死亡状态:现程的run()方法正常执行完毕或者线程抛出一个未捕获的异常(Exception)、错误(Error),线程就进人死亡状态。一旦进人死亡状态,线程将不再拥有运行的资格,也不能再转换到其他状态。
线程调度:程序中的多个线程是并发执行的,某个线程若想被执行必须要得到CPU的使用权。Java 虚拟机会按照特定的机制为程序中的每个线程分配CPU的使用权。这种机制被称作线程的调度。
在计算机中,线程调度有两种模型,分别是分时调度模型和抢占式调度模型。所谓分时调度模型是指让所有的线程论流获得CPU的使用权,并且平均分配每个线器古用的CPL的时间片。抢占式调度模型是指让可运行池中优先级高的线程优先占用一CPU.而对于优先级相同的线程.随机选择一个线程使其占用CPU.当它失去了CPU的使用权后.再随机选择其他线程获取CPU使用权。Java 虚拟机默认采用抢占式调度模型,大多数情况下程序员不需要去关心它,但在某些特定的需求下需要改变这种模式由程序自己来控制CPU的调度。
在应用程序中如果要对线程进行调度,最直接的方式就是设置线程的优先级。优先级越高的线程获得CPU执行的机会越大而优先级越低的线程获得CPU执行的机会越小。线程的优先级用1~10之间的教水表示,数字越大优先级越高。除了可以直接使用数字表示线在的优先级,还可以使用Thread类中提供的一个牵态常品表示线程的优先级
Thread类的优先级常量
static int MAX PRIORITY表示线程的最高优先级,相当于值10
static int MIN PRIORITY表示线程的最低优先级,相当于值1
static int NORM PRIORITY表示线程的普通优先级,相当于值5
程序 在运行期间处于就绪状态的每个线程都有自己的优先级,例如main线程具有普通优先级。然而线程优先级不是固定不变的,可以通过Thread类的setPriority(intnewPriority)方法对其进行设置:该方法中的参数newPriority接收的是1~10之间的整数或者Thread类的三个静态常量。
线程的休眠:如果希望人为的控制线程,使正在执行的线程暂停,将CUP让给别的线程,这时可以使用静态方法sleep(long millis),该方法可以让当前正在执行的线程暂停一段时间,进入休眠等待状态,sleep()是静态方法,只能控制当前正在运行的线程的休眠,而不能控制其他线程休眠。当休眠时间借宿后,线程就会返回到就绪状态,而不是立即开始执行。
线程让步:
线程让步可以通过yield()方法 来实现,该方法和sleep()方法有点相似,都可以让当前正在运行的线程暂停,区别在于yieldO)方法不会阻塞该线程,它只是将线程转换成就绪状态,让系统的调度器重新调度一次。当某个线程调用yieldO方法之后,只有与当前线程优先级相同或者更高的线程才能获得执行的机会。
线程插队:当在某个线程中调用共他线理的jin()方法时,调用的线程将被阻塞直到被join方法加人的线程执行完成后它才会继续运行
线程的同步:即限制某个资源在同一时刻只能被一个线程访问,为了实现这种限制,Java中提供了同步机制。当多个线程使用同一个共享资源时,可以将处理共享资源的代码放置在一个代码块中,使用synchronized关键字修饰,被称作同步代码块。
synchronized(lock){//lock是一个锁对象,它是同步代码块的关键 操作共享资源代码块 }
当线程执行同步代码块低:,首先会检:查锁对象的标志位,默认情况下标志位为1.此时线程会执行同步代码块,同时将锁对象的标志位置为0。当一个新的线程执行到这段同步代码块时.由于锁对象的标志位为0,新线程会发生阻塞,等待当前线程执行完同步代码块后.锁对象的标志位被置为1,新线程才能进人同步代码块执行其中的代码。循环往复,直到共享资源被处理完为止
注意:同步代码块中的锁对象可以是任意类型的对象,但多个线程共享的锁对象必须是唯一的。
同步方法:在方法前面同样可以使用synchronized关键字来修饰,被修饰的方法为同步方法,它能实现和同步代码块同样的功能,具体语法如下:
synchronized 返回值类型 方法名(【参数1,。。。。。。】){}
被synchronized修饰的方法在某一时刻只允许一个线程访问,访问该方法的其他线程都会发生阻塞,直到当前线程访问完毕后,其它线程才有机会执行方法。同步方法也有锁,它的锁就是当前调用该方法的对象,也就是this指向的对象
Java中静态方法的锁是该方法所在类的class对象,该对象可以直接用“类名.class”的方式获取
死锁问题:两个线程在运行时都在等待对方的锁,这样便造成了程序的停滞,这种现象称为死锁。
在Object类中提供了wait()、notify()、notifyAll()方法用于解放线程间的通信问题。
notifyAII()方法唤醒该线程为止
woidnotify()唤 醒此同步锁上等待的第一个调用wait()方法的线程
roid notifyAll()唤 醒此同步锁上调用wait()方法的所有线程
,其中wait()方法用于使当前线程进入等待状态,notify()和notifyAIlO方法用于唤醒当前处于等待状态的线程。需要注意的是.wait(O).notify()和notifyAII()这三个方法的调用者都应该是同步锁对象,如果这三个方法的调用者不是同步锁对象,Java虚拟机就会抛出IllegalMoniyorStateException异常。
同步就是指一个线程要等待另一个线程执行完毕之后才会继续执行的一种操作形式,但是如果在一个操作之中都是相互等着的话,那么就出现死锁(活锁)