多线程程序将单个任务按照功能分解成多个子任务来执行,每一个子任务称为一个线程,多个线程共同完成主任务的运行过程,这样能够缩短用户等待时间,提升服务效率。本篇博客将继续介绍Java开发中多线程的使用。html
接↷Java高级编程--多线程(一)java
目录:算法
☍ 线程的同步设计模式
☍ 线程的通讯安全
要想实现多线程,必须在主线程中建立新的线程对象。Java语言使用Thread类
及其子类的对象来表示线程,在它的一个完整的生命周期中一般要经历以下的五种状态:并发
➢ 新建:
当一个Thread类或其子类的对象被声明并建立时,新生的线程对象处于新建状态app
➢ 就绪:
处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具有了运行的条件,只是没分配到CPU资源ide
➢ 运行:
当就绪的线程被调度并得到CPU资源时,便进入运行状态,run()方法定义了线程的操做和功能
➢ 阻塞:
在某种特殊状况下,被人为挂起或执行输入输出操做时,让出 CPU 并临时中
止本身的执行,进入阻塞状态
➢ 死亡:
线程执行完了全部工做或线程被提早强制性地停止或出现异常致使结束
多个线程执行的不肯定性引发执行结果的不稳定。多个线程对资源的共享,会形成操做的不完整性,会破坏数据。此时须要线程的同步来解决问题。
如窗口售票:
售票代码:
class SellTicket extends Thread{ private static int ticketNum = 100; public SellTicket(String windownName){ super.setName(windownName); } @Override public void run() { while(true){ if(ticketNum > 0){ try { sleep(100); //阻塞,放大线程安全问题 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + "窗口卖票,票号为:" + ticketNum--); }else{ System.out.println("票已卖光"); break; } } } } public class Thread_SellTicket { public static void main(String[] args) { SellTicket t1 = new SellTicket("窗口1"); SellTicket t2 = new SellTicket("窗口2"); SellTicket t3 = new SellTicket("窗口3"); t1.start(); t2.start(); t3.start(); } }
结果(存在线程安全问题):
提出问题:
线程安全问题,如卖票的多线程中,出现了重票和错票
问题缘由:
当某个线程操做还没有完成时,其余线程进入操做了共享的资源,致使共享数据的错误
解决思路:
当一个线程a在操做共享资源时,其余线程不能参与进来, 直到线程a操做完成时,其余线程才能够开始操做共享资源。这种状况即便线程a出现了阻塞也不能改变。
解决方法:
在Java中,经过同步机制来解决线程的安全问题(synchronized)
☃ 方法一:同步代码块
☃ 方法二:同步方法
同步机制一:同步代码块
synchronized (同步监视器/锁:对象){ // 须要被同步的代码; } //如在窗口售票例子中经过Runnable接口实现的多线程中使用同步代码块 public void run() { while(true){ synchronized(this){//synchronized(obj){ //this指当前线程对象,在runnable实现方式中惟一(runnable对象传入Thread构造器中需惟一) if(ticketNum > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "窗口卖票,票号为:" + ticketNum--); }else{ System.out.println("票已卖光"); break; } } } } //如在窗口售票例子中经过继承Thread类实现的多线程中使用同步代码块 public void run() { while(true){ synchronized(TS_SellTicket.class){ //synchronized(obj){ 类的class也是对象,TS_SellTicket为继承Thread的子类 if(ticketNum > 0){ try { sleep(100);//阻塞 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName() + "窗口卖票,票号为:" + ticketNum--); }else{ System.out.println("票已卖光"); break; } } } }
同步机制二:同步方法
若是操做的共享数据的代码完整的声明在一个方法中,能够将这个方法声明为同步的
权限修饰符 synchronized 方法名(){//操做共享数据的代码} //如在窗口售票例子中经过Runnable接口实现的多线程中使用同步方法 public void run() { while(true){ printInfo(); if (ticketNum<=0){ System.out.println("票已售完"); break; } } } public synchronized void printInfo(){ if(ticketNum > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "窗口卖票,票号为:" + ticketNum--); } } //如在窗口售票例子中经过继承Thread类实现的多线程中使用同步代方法 public void run() { while(true){ printInfo(); if(ticketNum <= 0){ System.out.println("票已售光"); } } } // public synchronized void printInfo(){ 同步监视器不惟一 public static synchronized void printInfo(){ //同步监视器默认为当前类(Thread子类.class) try { sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if(ticketNum > 0){ System.out.println(Thread.currentThread().getName() + "窗口卖票,票号为:" + ticketNum--); } }
synchronized的监视器/锁
☄ 任意对象均可以做为同步锁
☄ 同步方法的锁(默认):静态方法(类名.class)、非静态方法(this)
☄ 同步代码块:本身指定(如Object obj),不少时候也是指定为this(runnable接口实现类方式)或类名.class(Thread继承方式)
说明
☃ 操做共享数据的代码为须要同步的代码,不能多也不能少
☃ 共享数据:多个线程共同操做的变量。(售票例子中为票ticket)
☃ 同步监视器/同步锁:任何一个类的对象均可以充当锁。
➥ 多个线程必须共用一把锁(同一个对象)
➥ 一个线程类中的全部静态方法共用同一把锁(类名.class),全部非静态方
法共用同一把锁(this),同步代码块(指定需谨慎)
肯定同步机制的使用
◌ 一、代码是否存在线程安全?
◌ 二、代码存在线程安全如何解决?
◌ 二、注意选中要同步的代码范围
同步锁的释放
☃ 当前线程的同步方法/同步代码块执行结束或执行了线程对象的stop()方法。
☃ 当前线程在同步代码块、同步方法中遇到break/return终止了该代码块/该方法的继续执行。
☃ 当前线程在同步代码块/同步方法中出现了未处理的Error或Exception,致使异常结束。
☃ 当前线程在同步代码块/同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
不会释放锁的说明
➥ 线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行,此时只是让出了cpu资源,当前线程并未结束
➥ 线程执行同步代码块时,其余线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁(同步监视器),需注意的是应尽可能避免使用suspend()和resume()来控制线程
单例模式之懒汉式(线程安全补充)
单列模式详解见连接↷Java面向对象--单例(Singleton)设计模式和main方法
class Bank extends Thread{ private Bank(){} //私有化构造器 private static Bank instance = null; //私有静态对象变量,先步new出实例对象 // public static synchronized Bank getInstance(){ public static Bank getInstance(){ //提供获取私用静态对象的方法,若静态单例对象为null,new出实例对象,不然直接返回 if (instance == null) { //效率更高 synchronized (Bank.class) { if(instance == null){ instance = new Bank(); } } } return instance; } @Override public void run() { getInstance(); } } public class Singleton_ThreadSave { public static void main(String[] args) { } }
线程的死锁问题
◌ 死锁缘由
不一样的线程分别占用对方须要的同步资源,而且都在等待对方放弃本身须要的同步资源,此时就造成了线程的死锁
◌ 死锁状态
出现死锁后,不会出现异常,不会有错误提示,只是全部线程都处于阻塞状态,没法继续运行,程序没法结束
◌ 解决方法
专门的算法逻辑;尽可能减小同步资源的定义;尽可能避免多层嵌套同步
死锁代码演示:
public class Dead_Lock { public static void main(String[] args) { dead_lock(); } public static void dead_lock(){ StringBuffer s1 = new StringBuffer(); StringBuffer s2 = new StringBuffer(); new Thread(){ @Override public void run() { synchronized (s1){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } s1.append("c"); s2.append("3"); synchronized (s2){ s1.append("d"); s2.append("4"); System.out.println(s1); System.out.println(s2); } } } }.start(); //使用匿名对象建立线程 new Thread(new Runnable() { @Override public void run() { synchronized (s2){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } s1.append("a"); s2.append("1"); synchronized (s1){ s1.append("b"); s2.append("2"); System.out.println(s1); System.out.println(s2); } } } }).start(); } }
同步机制三:Lock锁
☃ 从JDK 5.0开始,Java提供了更强大的线程同步机制——经过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。
☃ java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源以前应先得到Lock对象
☃ ReentrantLock 类实现了 Lock ,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较经常使用的是ReentrantLock,能够显式加锁、释放锁。
Lock锁使用代码演示:
class L_Thread implements Runnable{ private int ticket = 100; //一、定义lock锁 private ReentrantLock lock = new ReentrantLock(true); //参数为boolean型,线程是否先到先执行,不会出现一个线程连续执行屡次 @Override public void run() { while (true){ try{ //二、调用lock锁 lock.lock(); if (ticket > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "窗口卖票,票号为:" + ticket--); }else { break; } }finally { //三、调用unlock释放锁 lock.unlock(); } } } } public class LockTest { public static void main(String[] args) { L_Thread l = new L_Thread(); Thread t1 = new Thread(l,"窗口1"); Thread t2= new Thread(l,"窗口2"); Thread t3 = new Thread(l,"窗口3"); t1.start(); t2.start(); t3.start(); } }
注意: 加锁后不要忘记释放锁,若是同步代码有异常,要将unlock()写入finally语句块
synchronized与Lock的对比
◌ 相同点
两者都能解决线程的安全问题
◌ 不一样点
◌ 三种同步机制的使用优先级(视具体状况而定)
Lock锁 ➠ 同步代码块(已经进入了方法体,分配了相应) ➠ 同步方法
(在方法体以外)
线程的通讯经过wait()方法和notify()/notifyAll()方法实现
经过线程通讯使用线程1和线程2交替打印1-100:
交替打印也可经过ReentrantLock类的ReentrantLock(true)构造器实现
class TC_Thread implements Runnable{ private int index = 1; @Override public void run() { while(true){ synchronized (this){ notify(); //唤醒被wait的线程 if (index <= 100) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + index++); try { wait(); //阻塞当前线程 } catch (InterruptedException e) { e.printStackTrace(); } } else{ break; } } } } } public class ThreadCommunication { public static void main(String[] args) { TC_Thread tc = new TC_Thread(); Thread t1 = new Thread(tc,"线程1"); Thread t2 = new Thread(tc,"线程2"); t1.start(); t2.start(); } }
wait()与notify()和notifyAll()
由于这三个方法必须由锁对象调用,而任意对象均可以做为synchronized的同步锁,
所以这三个方法声明在Object类中
➢ wait():
一旦执行此方法,令当前线程挂起并放弃CPU、同步资源进行等待,使别的线程可访问并修改共享资源,当前线程就进入阻塞状态,并释放同步监视器,等候其余线程调用notify()或notifyAll()方法唤醒,唤醒后等待从新得到对监视器的全部权后才能继续执行。
➢ notify():
一旦执行此方法,就会唤醒被wait的一个线程,若是有多个线程被wait,就唤醒优先级高的线程
➢ notifyAll():
一旦执行此方法,就会唤醒被wait的全部线程
wait()方法的使用
☃ 在当前线程中调用方法:对象名.wait()。
☃ 使当前线程进入等待(某对象)状态,直到另外一线程对该对象发出 notify(或notifyAll)为止。。
☃ 调用方法的必要条件:当前线程必须具备对该对象的监控权(加锁)。
☃ 调用此方法后,当前线程将释放对象监控权,而后进入等待。
☃ 在当前线程被notify后,要从新得到监控权,而后从断点处继续代码的执行。
notify()/notifyAll()方法的使用
☃ 在当前线程中调用方法: 对象名.notify()。
☃ 功能:唤醒等待该对象监控权的一个/全部线程。
☃ 调用方法的必要条件:当前线程必须具备对该对象的监控权(加锁)。
注意:这三个方法只有在synchronized方法或synchronized代码块中才能使用,不然会报
java.lang.IllegalMonitorStateException异常
注意:wait()、notify()、notifyAll()三个方法的调用者必须与同步代码块或同步方法中的同步监视器/锁相同,不然会出现 IllegalMonitorStateException异常
sleep()和wait()方法的异同点
◌ 相同点
一旦执行方法,均可以使得当前线程进入阻塞状态
◌ 不一样点
两个方法的声明位置不一样:Thread类中声明sleep(),Object类中声明wait();
调用场景不一样:sleep()能够在任何须要的场合调用,wait()只能在synchronized同步代码块或同步方法中调用
是否释放同步监视器:若是两个方法都使用在synchronized中,sleep()不会自动释放锁,wait()会自动释放锁
Runnable接口和Callable接口对比
◌ 相比run方法,call()方法有返回值,在须要获取返回值的状况下很适用
◌ 能够抛出异常从而被外层的操做获取异常信息
◌ callable是支持泛型的返回值
◌ Callable须要借助FutureTask类,好比获取返回结果
Future接口
◌ 能够对具体Runnable、Callable任务的执行结果进行取消、查询是
否完成、获取结果等。
◌ FutrueTask是Futrue接口的惟一的实现类
◌ FutureTask 同时实现了Runnable、Future接口。它既能够做为
Runnable被线程执行,又能够做为Future获得Callable的返回值
Callable接口方式实现线程步骤
↬ 建立Callable实现类,定义Callable实现类对象c
↬ 定义FutureTask对象f,将Callable实现类对象c做为FutureTask构造器的参数
↬ 定义Thread线程对象t,将FutureTask对象f做为Thread构造器的参数
↬ Thread对象t调用start()方法启动线程
↬ 若要得到Callable实现类对象的call()方法返回值,需启动线程后使用FutureTask对象f调用get()方法
Callable接口实现多线程代码演示:
//一、建立Callable实现类 class CT_Thread implements Callable { //二、实现call()方法 @Override public Object call() throws Exception { int sum = 0; for (int i = 0; i <= 100; i++) { if(i % 2 == 0){ System.out.println(i); sum += i; } } return sum; } } public class CallableTest { public static void main(String[] args) { //三、定义Callable接口实现类对象 CT_Thread ct = new CT_Thread(); //四、定义FutureTask对象,并将Callable接口实现类对象做为FutureTask构造器的参数 FutureTask fu= new FutureTask(ct); //FutureTask构造器参数为Callable实现类对象 //五、定义Thread对象,将FutureTask对象做为参数传递到Thread类的构造器中,Thread对象调用start()方法启动线程 Thread t1 = new Thread(fu); t1.start(); try { //六、FutureTask对象的get()方法返回值为构造器参数Callable实现类重写的call()的返回值(可省略此步,须要获取call()方法返回值时写) Object sum = fu.get(); System.out.println("总和为:"+sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
常常建立和销毁、使用量特别大的资源,好比并发状况下的线程,对性能影响很大。提早建立好多个线程,放入线程池中,使用时直接获取,使用完
放回池中。能够避免频繁建立销毁、实现重复利用。相似生活中的公共交通工具。
使用线程池的优势
◌ 提升响应速度(减小了建立新线程的时间)
◌ 下降资源消耗(重复利用线程池中线程,不须要每次都建立)
◌ 便于线程管理(提供了不少方法)
↬ corePoolSize:核心池的大小
↬ maximumPoolSize:最大线程数
↬ keepAliveTime:线程没有任务时最多保持多长时间后会终止
↬ ......
线程池相关API
☃ JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors。
☃ ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor。
↬ void execute(Runnable command) :执行任务/命令,没有返回值,通常用来执行
Runnable
↬
Callable
↬ void shutdown() :关闭链接池
☃ Executors:工具类、线程池的工厂类,用于建立并返回不一样类型的线程池。
↬ Executors.newCachedThreadPool():建立一个可根据须要建立新线程的线程池
↬ Executors.newFixedThreadPool(n); 建立一个可重用固定线程数的线程池
↬ Executors.newSingleThreadExecutor() :建立一个只有一个线程的线程池
↬ Executors.newScheduledThreadPool(n):建立一个线程池,它可安排在给定延迟后运
行命令或者按期地执行。
线程池方式建立线程代码演示:
class NumberThread implements Runnable{ @Override public void run() { for (int i = 0; i <= 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName()+":"+i); } } } } class NumberThread1 implements Runnable{ @Override public void run() { for (int i = 0; i <= 100; i++) { if(i % 2 != 0){ System.out.println(Thread.currentThread().getName()+":"+i); } } } } public class ThreadPool { public static void main(String[] args) { //一、提供指定线程数量的线程池 ExecutorService service = Executors.newFixedThreadPool(10); //设置线程池的属性 ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; service1.setCorePoolSize(15); //核心池的大小 service1.setMaximumPoolSize(10); //最大线程数 //二、执行指定线程的操做,,须要提供实现Runnable接口或Callable接口实现类的对象做为execute的参数 service.execute(new NumberThread()); //适合于Runnable service.execute(new NumberThread1()); //service.submit(); //适合于Callable service.shutdown(); //关闭线程池 } }
本博客与CSDN博客༺ཌ༈君☠纤༈ད༻同步发布