多线程

线程优势:

进程之间不能共享内存,线程之间共享内存比较容易。

系统创建进程时要为进程重新分配系统资源,创建线程代价小,多线程比多进程效率高。

Java内置多线程功能支持。

 

1.继承thread类创建线程类。

1.定义Thread子类,重写run()方法,线程执行体。

2.创建子类的实例,来创建线程对象。

3.调用线程对象的start()方法来启动线程。

用继承Thread类的方法来创建线程类,多个线程之间无法共享线程类的实例变量。

 至少有一个main主线程。再创建子线程。

 

2.实现Runnable接口创建线程类。

1.定义Runnable接口的实现类,并重写run(),线程执行体。

2.创建Runnable实现类的实例,并以此实例作为Thread的target来创建对象,该Thread对象才是真正的线程对象。

Runnable对象仅作为Thread对象的target,实现类里面的run()仅作为线程执行体,实际的线程对象仍然是Thread实例,只是改Thread线程负责执行target的run方法。

3.调用对象的start()来启动线程。

用Runnable接口方式创建的多个线程可以共享线程类的实例变量。

程序创建的Runnable对象只是线程的target,多个线程可以共享同一个Target.

 

3,使用Callable和Future创建线程。

call()作为线程执行体,有返回值,可以声明抛出异常。

 CAllable对象作为Thread的target。线程执行体是callable对象的call方法。

Future接口来代表call方法的返回值,,并提供了FutureTask实现类,实现了Futrue结论,也实现了Runnable接口,可以作为Thread类的target。

 

 

对比:

使用接口方式创建多线程优缺点:

线程类只是实现了Runable接口和callable接口,还可以继承其它类。

这种方式,多个线程可以共享同一个target对象,适合多个相同线程来处理同一份资源,可以将CPU,代码,数据分开,模型更清晰

劣势:编程稍微复杂,如需要访问当前线程,则必须使用Thread.currentThread()方法。

继承Thread类的优缺点:

劣势:继承了Thread类,不能继承其它父类。

优势:编写简单,直接使用This可以获得当前线程。

 

一般推荐采用接口方式创建多线程。

 

 

线程生命周期:

新建(New) 就绪(Runnable) 运行(Running) 阻塞(Bocked) 死亡(Dead)

刚刚new了一个线程后,线程处于新建状态,只由JVM分配内存,初始化成员变量。

调用start()后,线程进入就绪状态,JVM为其创建方法调用栈,程序计数器,表示线程可以运行。

启动线程start(),系统会把run方法当成执行体来处理,但如果直接调用线程run(),那run()方法立即执行,在run()方法返回之前其它线程无法并发执行。如果直接调用run()方法,系统把线程对象当成一个普通对象,普通方法处理。

只能对新建状态的线程调用start()方法,否则引发IllegalThreadStateException

调用start()方法之后,线程进入就绪状态,等待执行。

如果希望start()后子线程立即执行,可以Thread.sleep(1)来让当前运行的线程睡眠1毫秒,1毫秒内CPU也不会空闲,会立即执行另一个就绪状态的线程。

 

抢占调度策略。

线程进入阻塞状态:

1.线程调用sleep()方法主动放弃所占用的处理器资源。

2.线程调用了一个阻塞式IO方法,在方法返回之前该线程被阻塞。

3.线程试图获得一个同步监视器,但该同步监视器正在被其他线程持有 。

4.线程在等待某个通知notify

5.程序调用了线程的suspend()方法把线程挂起,但这个方法容易造成死锁。

 

被阻塞的线程可以恢复就绪状态。

1.调用sleep()方法的线程经过指定时间。

2.线程调用的阻塞式IO方法返回。

3.线程成功获得了同步监视器、。

4.线程等待的通知被其它线程发出。

5.处于挂起状态的线程被调用了resume()恢复了。

 

 

线程死亡:

1.run()或call()方法执行结束,线程结束。

2.线程抛出一个未捕获的Exception或Error。

3.直接调用线程的stop()方法来结束线程,容易造成死锁。

主线程结束时,其它线程不受影响,一旦子线程启动,和主线程相同地位。

判断是否死亡 ,isAlive(),就绪,运行,阻塞状态时返回true,新建,死亡状态返回false。

已死亡的线程无法再启动。

 

 

控制线程:

join 线程:当某线程执行中调用其它线程的join方法时,调用线程阻塞,直到被join方法加入的join线程执行结束。

join()三种重载形式。

 

后台线程:在后台运行,为其它线程提供服务,也叫守护线程,精灵线程。

JVM垃圾回收线程是典型后台线程。

特征:如果所有前台线程死亡,后台线程自动死亡。

Thread对象的setDaemon(true)方法可指定线程为后台线程。

主线程默认是前台线程, 前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。

将某线程设置为后台线程,必须在线程启动前设置。setDaemon(true)必须在start()之前调用,否则会引发异常。

 

线程睡眠:sleep

让当前执行的线程暂停一段时间,进入阻塞状态,调用Thread类的静态sleep()方法。

当前线程调用sleep方法进入阻塞状态后,睡眠时间内不会获得执行机会,sleep用来暂停程序执行。

线程让步:yield

Thread类的静态方法yield,可以让当前执行的线程暂停,不会阻塞,会将线程转入就绪状态。

yield只是让当前线程暂停,系统线程调度重新调度。

调用yield方法暂停后,只有优先级与当前线程相同,或者更高的处于就绪状态的线程才会执行。

 

sleep和yield区别:
1.sleep暂停当前线程,会给其他线程执行机会,无论优先级,但yield只会给优先级相同或者更高的线程执行机会。

2.sleep会将线程转入阻塞状态,直到经过阻塞时间才会就绪状态。yield强制进入就绪状态。

3.sleep声明抛出InterruptException 异常,所以调用sleep要么捕捉该异常,要么先是声明抛出异常,yield没有声明抛出异常。

4.sleep比yield有更好的移植性,通常不建议使用yield来控制并发执行。

 

改变线程优先级:

优先级高的线程获得较多执行机会。

每个线程默认优先级与创建它的父线程相同,默认,main线程有普通优先级,

Thread类提供setPriority(),getPrioruty(),1~10.

MAX_PRIORITY=10

MIN_PRIORITY=1

NORM_PRIORITY=5

 

线程同步:

线程安全问题:

同步监视器。

同步代码块:synchronized(obj)

obj是同步监视器。

线程开始执行同步代码块之前,必须先获得对同步监视器的锁定。

任何时刻只有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该线程释放对同步监视器的锁定。

加锁——修改——释放锁。

保证并发线程在任意时刻只有一个线程可以进入修改共享资源的代码区,所以同一时刻最多有一个线程t区,从而保证线程安全。

 

同步方法,使用synchronized修饰。

通过使用同步方法可以方便的实现线程安全的类,

1.该类对象可以被多个线程安全的访问。

2.每个线程调用该对象的任意方法之后都将得到正确结果。

3.每个线程调用该对象的任一方法之后,对象的状态仍然合理。

不可变类总是线程安全的,因为对象状态不可变,可变对象需要额外的方法保证线程安全。

可变类的线程安全是以降低程序运行效率为代价的。

 1.不要对线程安全类的所有方法都同步,只对会改变共享资源的方法同步。

2.如果可变类有两种运行环境:单线程环境和多线程环境,可以为可变类提供两种版本。

单线程用StringBuilder 多线程用StringBuffer

 

释放同步监视器的锁定:不显示释放

释放同步监视器:

1.线程的同步方法,同步代码块执行结束,线程释放同步监视器。

2.县城在同步代码块。同步方法中遇到break,return 终止代码块的执行,线程会释放同步监视器。

3.线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致代码块异常结束是,会释放同步监视器。

4.线程执行同步代码块时,程序执行了同步监视器对象的wait()方法,当前线程暂停,释放同步监视器。

不释放:

1.线程执行同步代码块时,程序调用sleep(),yield()方法来暂停当前线程执行,不会释放同步监视器。

2.线程执行同步代码块时,其它线程调用该线程的suspend()方法把线程挂起,不会释放。

 

同步锁:Lock对象

lock是控制多个线程对共享资源进行访问的工具。每次只能有一个线程对lock对象加锁,线程开始访问共享资源之前应先获得lock对象。

在线程安全控制中常用可重入锁ReentrantLock,可以显示加锁,释放锁。

 

死锁:两个线程相互等待对方释放同步监视器时会发生死锁,一旦出现死锁,程序不会提示,但所有线程处于阻塞状态。

系统中出现多个同步监视器时,可能会发生死锁。

 

线程通信:

Object类里面的wait(),notify(),notifyAll()方法,由同步监视器对象来调用。

1.对使用synchronized修饰的同步方法,因为该类的默认实例this 就是同步监视器,所以可以在同步方法中直接调用这三个方法。

2.对使用synchronized修饰的同步代码块,同步监视器是括号里的对象,所以必须使用该对象调用三个方法。

 

wait():导致当前进程等待,直到其它线程调用该同步监视器的notify()方法来唤醒该线程。调用wait()的当前线程会释放对该同步监视器的锁定。

notify():唤醒在词同步监视器上等待的单个线程,如果所有线程都在此同步监视器上等待,则会任意唤醒一个。

notifyAll():唤醒在同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。

 

使用Condition控制线程通信:

使用Lock对象来同步时,用Condition类来保持协调。

await():类似wait(),导致当前线程等待。

signal():唤醒在此lock对象上等待的单个线程。

signalAll():唤醒在此lock对象上等待的所有线程。

 

使用阻塞队列控制线程通信。BlockingQueue

BlockingQueue接口,是Queue的子接口。作为线程同步的工具。

特征:当生产者线程试图向BlockingQueue中放入元素时,如果队列已满,则该线程被阻塞。

当消费者试图取出元素时,如果队列已空,线程被阻塞。

put():将元素放入队列中。

take():从队列头部取出元素。

 

线程池:

线程池在系统启动时创建大量空闲线程,程序将Runnable对象或Callable对象传给线程池,线程池就会启动一个线程来执行他们的run()或call(),当方法结束后,线程不死亡,再次回到线程池里成为空闲状态。

使用线程池可以有效控制系统中并发线程的数量,当系统中包含大量并发线程时,系统性能急剧下降,线程池的最大线程数可以控制系统中并发线程数不超过此数。

 

posted @ 2019-03-05 21:37  旺仔流奶  阅读(115)  评论(0编辑  收藏  举报