并发与并行: 并发:指两个或多个事件在同一个时间段内发生。 并行:指两个或多个事件在同一时刻发生(同时发生)。
在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分时交替运行的时间是非常短的。而在多个CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行, 即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核CPU,便是多核处理器,核越多,并行处理的程序越多,能大大的提高电脑运行的效率。
注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为线程调度,哪个线程先抢占到CPU资源就先执行。
注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是 模拟出来的多线程,即一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为 切换的很快,所以就有同时执行的错觉。
程序、进程和线程: 程序:为了完成特定的任务,用某种计算机语言来编写的一组指令的集合。
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。一个应用程序可同时运行多个进程;进程是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。进程是程序的一次执行过程,如果一个程序关闭,进程就会消失。 比如运行中的QQ,运行中的音乐播放器等。
线程:是进程的进一步的划分,是一个程序内部的一条独立的执行路径,一个进程是由若干个线程组成的。比如wps是一个程序,启动之后,会开启一个进程,wps中的如果第一个词输入的是英文,首字母会大写,那么这个首字母大写的操作其实就是由一个线程来控制的。进程如果关闭,这个进程下的所有的线程也会消失,如果线程关闭,进程还会存在。线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程。 如编写播放视频的软件的代码就是程序,进程指操作系统开始运行这个程序,如看视频。 一个进程可以有多个线程,如视频中同时听声音、看图像、显示字幕。
我们可以再电脑底部任务栏,右键----->打开任务管理器,可以查看当前任务的进程。
分时调度:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。 抢占式调度:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。 抢占式调度详解 : 大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,感觉这些软件好像在同一时刻运行着。 实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是 在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
在程序运行时,即使没有自己创建线程,后台也会存在多个线程, 如gc线程、主线程;
package com.wanbangee.thread; public class ThreadDemo01 { public static void main(String[] args) { //实际上java程序中的mian函数就是一个线程,这个线程叫做程序的主线程 Thread thread = Thread.currentThread();//获得当前运行的线程的对象 String threadName = thread.getName();//获取该线程名称 System.out.println(threadName); } }单线程和多线程如何区分呢? 单线程的执行时可以用一条线串起来的,也就是说执行有先后顺序(如果一条线串不起来,那么就是多线程),多线程指的是有多个线程同时执行,先后执行的顺序是按照线程抢占系统资源(CPU资源)的顺序来的。
package com.wanbangee.thread; public class Demo02 { //单线程:可以用一条线串起来的,执行有先后顺序 public static void main(String[] args) { System.out.println(Thread.currentThread().getName() + "线程"); eat(); sleep(); } public static void eat() { System.out.println("吃饭"); } public static void sleep() { System.out.println("睡觉"); } }什么情况下需要使用多线程呢?
程序需要同时执行两个或者多个任务程序需要实现一些需要等待的任务时,如用户的输入,文件的读写操作,网络操作,搜索等等。(在等待时做别的事)需要一些后台运行的程序(守护线程-垃圾回收)1.通过继承Thread类。2.通过实现Runnable接口完成。3.实现Callable接口4.线程池 Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体(run()方法)来代表这段程序流。
通过继承Thread类完成的多线程,操作步骤如下:
定义线程类(子类)继承Thread类。复写Thread类中的run方法,该run()方法的方法体就代表了线程要完成的任务,把 run()方法称为线程执行体。在测试类中创建Thread子类的实例,即创建了线程对象。启动多线程,线程对象调用start()方法来启动该线程。 package com.wanbangee.thread; public class TestThreadDemo02 { public static void main(String[] args) { ThreadDemo02 th = new ThreadDemo02(); th.setName("线程A");//在启动线程前设置线程名称 //th.run();充其量就是调用类中的run方法,可以用一根线串起来,先执行run方法再执行for循环,就是个单线程。 th.start(); //这个start()方法子类是从Thread类中继承来的,启动子线程,会调用系统方法 ,系统方法再调用run方法 //这个是子线程,main是主线程,每次运行的结果都不同,没有任何运行规则的,抢占到CPU资源就能先运行 //th.start();//子线程只能启动一次,如果试图再次启动,则出现异常IllegalThreadStateException ThreadDemo02 th1 = new ThreadDemo02("线程B"); th1.start(); //主线程中的for循环 for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + ":" +i); } } } /* 需要实现多线程的类 * - 继承Thread类 * - 复写run方法 * - 多线程要完成的工作,写在run方法中 */ class ThreadDemo02 extends Thread{ public ThreadDemo02() { super(); } public ThreadDemo02(String name) { super(name); } @Override public void run() { for (int i = 1; i <= 5; i++) { //获取当前正在运行的线程的名称 System.out.println(Thread.currentThread().getName() + ":" +i); } } }从上面的程序运行结果来看,在三个线程运行的时候,线程的运行出现了交互式的,不是按照程序的顺序执行的,这就是因为线程在运行过程中抢占系统CPU的资源。
采用 java.lang.Runnable ,只需要重写run方法即可。步骤如下: 1.定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。 2.创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。 new个Thread就是new个线程,这个线程如何启动,Thread对象调用Thread类中start()方法。 3.Thread构造器,public Thread (Runable target){} //Runable target就是Runable接口实现类的对象。 4.调用线程对象的start()方法来启动线程。
package com.wanbangee.thread; public class TestRunnableDemo01 { public static void main(String[] args) { /* runnable接口中并没有启动线程的方法 RunnableDemo01 ru1 = new RunnableDemo01(); ru1.start(); 这是错误的*/ //只有Thread类的对象或者其子类对象才是线程的对象 RunnableDemo01 ru1 = new RunnableDemo01(); Thread th1 = new Thread(ru1);//通过Thread类的有参构造器来取得线程的对象 th1.setName("甲");//在启动线程前设置线程名称 th1.start();//启动线程 Thread th2 = new Thread(ru1,"乙");//取得线程的对象的同时设置线程对象名字 th2.start();//启动线程 for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + ":"+i); } } } class RunnableDemo01 implements Runnable{ @Override public void run() { for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + ":"+i); } } }在启动的多线程的时候,需要先通过Thread类的有参构造方法Thread(Runnable target) 构造出线程对象,然后调用Thread 的start()方法来运行多线程代码。 Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。 而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。 不管是继承Thread类还是实现 Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。
如果一个类继承Thread,则不适合资源共享。但是如果实现了Runable接口的话,则很容易的实现资源共享。根据面向对象思想,少用继承多用实现。 实现Runnable接口比继承Thread类所具有的优势: public class Thread extends Object implements Runnable//Thread类也是实现类Runnable接口
1.多个线程可以共享同一个接口实现类的对象,很适合多个线程来处理同一个资源。 2.接口的实现避免了java中的单继承的局限性。 3.增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。 4.线程池只能放入实现Runable或Callable的类线程,不能直接放入继承Thread的类。 在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。 1.通过继承Thread类完成卖票程序:
package com.wanbangee.thread; public class TestSaleTicket01 { public static void main(String[] args) { SaleTicket01 th1 = new SaleTicket01(); SaleTicket01 th2 = new SaleTicket01(); SaleTicket01 th3 = new SaleTicket01(); th1.setName("售票窗口1"); th2.setName("售票窗口2"); th3.setName("售票窗口3"); th1.start(); th2.start(); th3.start(); } } //通过继承Thread类完成卖票程序 //每个售票窗口都可以有10张票可卖,因为操作卖票的类的对象实例化多次,得加static实现数据共享 class SaleTicket01 extends Thread{ private static int ticket = 10;//用static修饰,避免一张票被卖3次,实现数据共享。 //继承Thread的线程不能直接共享资源(要加static) public void run() { while(true) { if(ticket > 0) { System.out.println(Thread.currentThread().getName()+"售票成功,剩余票数:" + ticket--); }else { break; } } } }2.通过实现Runnable接口完成卖票程序:
package com.wanbangee.thread; public class TestSaleTicket02 { public static void main(String[] args) { SaleTicket02 saleTicket02 = new SaleTicket02(); Thread th1 = new Thread(saleTicket02); Thread th2 = new Thread(saleTicket02); Thread th3 = new Thread(saleTicket02); th1.setName("售票窗口1"); th2.setName("售票窗口2"); th3.setName("售票窗口3"); th1.start(); th2.start(); th3.start(); } } //通过实现Runnable接口完成卖票程序 //即使不使用static修饰票数,实现Runnable接口完成的卖票程序依然共享10张票数,因为我们这个操作卖票的类的对象只实例化了一次。 class SaleTicket02 implements Runnable{ private int ticket = 10; public void run() { while(true) { if(ticket > 0) { System.out.println(Thread.currentThread().getName()+"售票成功,剩余票数:" + ticket--); }else { break; } } } }发现在卖票程序中,继承Thread类完成的必须使用static修饰多个线程共享的属性(不用static修饰,会出现一张票被卖3次,就不能数据共享。),而实现Runnable接口完成的多线程不需要使用static修饰就可以完成多个线程的共享。 继承:线程代码存放在Thread子类run方法中 实现:线程代码存放在接口的实现类run方法中 以后开发中,我们实现多线程一般都使用实现Runnable接口的方式。
构造方法:
public Thread() :分配一个新的线程对象。 public Thread(String name) :分配一个指定名字的新的线程对象。 public Thread(Runnable target) :分配一个带有指定目标新的线程对象。 public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。Thread类中常用的线程的操作方法:
public static Thread currentThread() :返回当前正在执行的线程对象的引用。 public String getName() :获取当前线程名称。 public final boolean isAlive():判断线程是否还活着 public boolean isInterrupted():判断线程是否中断 public void interrupt():中断线程 public final void setName(String name):设置线程的名称,在线程启动之前设置名称 public void start() :启动线程,Java虚拟机调用此线程的run方法。 public Thread.State getState():获取线程的状态 public static void sleep(long millis):线程休眠,单位为毫秒,抱着资源睡觉 public static void yield():线程让步(挂起),暂停当前线程的执行, 让出CPU资源给优先级更高的线程或者相同优先级的线程运行,若没有相同或者更高优先级的线程,这个方法可以忽略。 sleep和yield都可用Thread访问,放在哪个线程体中就表示当前线程休眠、让步。 public final void join() :当某个程序的执行过程中线程A对象调用join()方法时, 其他线程将被阻塞,直到A线程执行完成为止。join()在哪个线程体中就阻塞哪个线程 public final void setPriority(int newPriority)设置线程的优先级 - public static final int MAX_PRIORITY : 最高优先级常量 - public static final int NORM_PRIORITY:默认优先级常量 - public static final int MIN_PRIORITY:最低优先级 public final int getPriority():获得线程的优先级在Object类中也提供了线程的操作方法[线程通信,这些方法只能在同步锁中使用]:
public final void wait() :线程等待 public final void wait(long timeout) :线程等待,可以设置具体等待的毫秒值 public final void notify():线程唤醒 public final void notifyAll():唤醒所有线程 package com.wanbangee.thread; public class TestThreadMethod01 { public static void main(String[] args) { ThreadMehod01 method01 = new ThreadMehod01(); Thread th1 = new Thread(method01); th1.setName("AAA"); Thread th2 = new Thread(method01); th2.setName("BBB"); Thread th3 = new Thread(method01); th3.setName("CCC"); th1.start(); th2.start(); th3.start(); } } class ThreadMehod01 implements Runnable{ //子类复写后的方法抛出的异常不能大于父类被复写方法抛出的异常 @Override public void run() { for(int i = 0;i<5 ;i++) { Thread th = Thread.currentThread();//获取当前正在运行的线程 System.out.println(th.getName() + "---"); try { if(th.getName().equals("BBB")) { th.join(); //join()在哪个线程体中就阻塞哪个线程。 //此线程阻塞,for循环阻塞,程序红着一直没停止卡在循环那 //Thread.yield();//线程让步:其他线程先执行,直到其他线程执行完成之后继续执行当前线程 } } catch (Exception e) { e.printStackTrace(); } } } } package com.wanbangee.thread; public class TestThreadDemo02 { public static void main(String[] args) { ThreadMehod011 method01 = new ThreadMehod011(); Thread th1 = new Thread(method01); th1.setName("AAA"); Thread th2 = new Thread(method01); th2.setName("BBB"); Thread th3 = new Thread(method01); th3.setName("CCC"); //当某个程序的执行过程中线程A对象调用join()方法时,其他线程将被阻塞,直到A线程执行完成为止。 th2.start(); try { th2.join(); //th2先执行,执行完再其它线程执行 } catch (InterruptedException e) { e.printStackTrace(); } th1.start(); th3.start(); } } class ThreadMehod011 implements Runnable{ //子类复写后的方法抛出的异常不能大于父类被复写方法抛出的异常 @Override public void run() { for(int i = 0;i<5 ;i++) { System.out.println(Thread.currentThread().getName()+i); } } }优先级高的线程可能先执行。高优先级的线程优先抢占资源,但是不一定能够抢到,所以优先级高的线程可能先执行。主线程的优先级是默认优先级。
public final void setPriority(int newPriority)设置线程的优先级 - public static final int MAX_PRIORITY : 最高优先级常量 - public static final int NORM_PRIORITY:默认优先级常量 - public static final int MIN_PRIORITY:最低优先级 public final int getPriority():获得线程的优先级 package com.wanbangee.thread; public class ThreadDemo05 { public static void main(String[] args) { System.out.println(Thread.currentThread().getName() + ":" +Thread.currentThread().getPriority()); System.out.println("Max:" + Thread.MAX_PRIORITY); //10 System.out.println("Norm:" + Thread.NORM_PRIORITY); //5 System.out.println("Min:" + Thread.MIN_PRIORITY); //1 } } //发现主线程(main)的优先级是默认优先级。任何程序的运行都有这2个线程,一个是守护线程(垃圾回收机制线程),一个是主线程。 如何使一个线程变成守护线程呢:在这个线程启动(start())之前,调用public final void setDaemon(true)方法,这个线程就变成了守护线程。public boolean isDaemon()判断该线程是否为守护线程。 线程调度器:Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。线程调度器按照线程的优先级决定调度哪个线程来执行。 优先级的设定建议在start()调用前 (不管什么设置都要放在启动前) 注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高后调用优先级低的线程。 线程分为用户线程和守护线程(daemon) 虚拟机必须确保用户线程执行完毕; 虚拟机不用等待守护线程执行完毕; 守护线程默默为用户线程服务的,如后台记录操作日志、监控内存使用等。
在JDK中有个枚举类:Thread.State枚举中,列出线程的几种状态,我们在使用线程的时候,必须在主线程中创建新的线程对象,Java中使用Thread类或其子类对象来表示线程对象,在线程对象的一个完整的生命周期中,通常都要经历一下的五种状态:
新建:当一个Thread类的对象或者其子类的对象实例化的时候,新的线程对象就处于新建状态就绪:处于新建状态的线程,调用start()方法后,将进入到线程队列抢占CPU的资源,当时资源还没有抢到,这个时候就处于就绪状态运行:当就绪状态的线程抢占到了CPU资源后,便进入运行状态阻塞:在某种特殊情况下,正在运行的线程,被人为挂起,或者等待键盘输入输出操作时,这个时候让出CPU资源,并临时性的中止自己的执行,这个状态叫做阻塞死亡:线程完成它的全部工作或者被提前强制性的中止。start:就緒(1.调用start方法进入就绪状态,就绪状态表示线程具备多线程的条件。2.在运行过程中遇到了阻塞,阻塞事件解除会进入就绪状态,比如说时间到了join执行完了阻塞解除。3.在运行中让出CPU的调度yield,运行状态让出CPU调度直接进入就绪状态。JVM切换,从本地线程切换其它线程,它内部根据自己的算法切换。) 进入就绪状态后通过CPU调度,调度到了进入运行状态,运行状态run我们控制不了由CPU去控制,就绪状态和运行状态统一叫Runnable,在运行过程中遇到了阻塞事件 1.sleep()抱着资源睡觉, 2.join() 3.wait(), 4.其它操作如IO流阻塞 read() write() 终止线程的方式有2种: 1线程正常执行完毕(自己有次数的限制) 2外部干涉(加入标识),不要使用stop(),destroy()方法,这样的方法过时了不安全。
package com.wanbangee.thread; public class Demo01 implements Runnable { //1加入标识,标记线程体是否可以运行 private boolean flag = true; @Override public void run() { //2关联标识,true(运行),false(停止) while(flag) { System.out.println("11"); } } //3对外提供方法改变标识 public void temp() { this.flag = false; } public static void main(String[] args) { Demo01 d = new Demo01(); new Thread(d).start();//死循环 for (int i = 0; i < 10; i++) { if(i == 5) { d.temp();//线程终止 System.out.println("线程结束"); } System.out.println("main主线程:"+i); } } }阻塞: Sleep(休眠,抱着资源睡觉,线程从运行状态进入阻塞状态,当时间到了,从阻塞状态回到就绪状态,等待CPU的调度) sleep(时间)指定当前线程阻塞的毫秒数; • sleep存在异常InterruptedException; • sleep时间达到后线程进入就绪状态; • sleep可以模拟网络延时、倒计时等。 • 每一个对象都有一个锁,sleep不会释放锁;(不会释放资源、占着茅坑,针对wait()来说)
public class Demo02 implements Runnable { private int num =10; public static void main(String[] args) { new Thread(new Demo02()).start(); } @Override public void run() { while(true) { if(num<=0) { break; } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"剩余票数:" + --num); } } }Yield(让步,写在哪个线程体中哪个线程就让步,直接从运行状态转入就绪状态,让出CPU的调度,避免当前线程占用CPU过久,大家重新来竞争,让CPU调度器重新调度,sleep和yield一样都是静态方法直接写在线程体中) • 让步线程,让当前正在执行线程暂停 • 不是阻塞线程,而是将线程从运行状态进入就绪状态 • 让cpu调度器重新调度,大家重新来竞争
public class Demo02 implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName()+"start"); Thread.yield();//可能让步成功,CPU调别人去了,也可能cpu回来调自己了,因为它是重回调度器等待调度,大家公平竞争 System.out.println(Thread.currentThread().getName()+"end"); } public static void main(String[] args) { new Thread(new Demo02(),"线程a:").start(); new Thread(new Demo02(),"线程b:").start(); } }Join(当某个程序的执行过程中线程A对象调用join()方法时,其他线程将被阻塞,直到A线程执行完成为止) join不是静态方法得用对象调,写在哪个线程体中,当前那个线程体就被阻塞了,要弄清谁被阻塞了。
public class Demo02 implements Runnable { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("join...."+i); } } public static void main(String[] args) { Thread t = new Thread(new Demo02()); t.start(); for (int i = 0; i < 10; i++) { if(i == 5) { try { t.join(); //t线程先执行,main....5后最后打印 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("main...."+i); } } }多个线程执行的不确定性引起的执行结果不稳定,比如我们多个线程售票,可能会出现一张票被过多线程卖的情况,造成操作的不完整,导致数据的破坏,这种情况我们叫做线程安全的问题。 排队上车:线程安全低效。蜂拥上车:线程不安全高效。
package com.wanbangee.thread; public class ThreadDemo04 { public static void main(String[] args) throws Exception { SaleTicketX x1 = new SaleTicketX();//并不是一个线程对象,而是一个普通对象,线程对象必须是Thread对象或者其子类对象 Thread th1 = new Thread(x1);//新建线程对象 th1.setName("售票点A"); th1.setPriority(Thread.MAX_PRIORITY);//设置最高优先级 th1.start(); Thread th2 = new Thread(x1); th2.setName("售票点B"); th2.start(); // th1.join(); Thread th3 = new Thread(x1); th3.setName("售票点C"); th3.start(); Thread th4 = new Thread(x1); th4.setName("售票点D"); th4.start(); Thread th5 = new Thread(x1); th5.setName("售票点E"); th5.start(); } } class SaleTicketX implements Runnable{ //实际上SaleTicketX 并不是多线程的类 private int ticket = 10;//总共10张票 @Override public void run() { //每一个售票窗口(线程)卖五次,一次卖一张 for(int i = 0;i<5;i++) { if(ticket > 0) { try { Thread.sleep(500); //当前线程休眠1秒中 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "售票成功,剩余票数为:" + -- ticket); }else { System.out.println(Thread.currentThread().getName() + "售票失败,请等待下一趟车次"); } } } }以上的程序出现的情况是一张票可能会被卖好几次,原因在于一个线程判断剩余票数是大于0 的,而后该线程CPU资源丢失进行释放(挂起或休眠),其它线程进来发现有票就卖这张票,此时丢失资源的线程又获得了资源,而后继续卖同一张票,这就是我们说的线程安全的问题。 这种线程安全的问题我们目标有两种方式解决:
使用同步代码块使用同步方法 不管是同步代码块还是同步方法,实际上都是给执行过程上锁,叫做同步锁。 线程A,B,C抢占资源,现在B线程获得了CUP资源,在进入同步代码或者同步方法的时候,会给代码上锁,导致B线程没有运行结束的情况下,其他的线程即使抢占了CPU的资源也进入不了同步方法或者同步代码块中执行。(只有B线程才能执行,不管B线程是休眠还是挂起丢失资源,其它线程都进不来,只有等B线程执行完成后,又所有的线程一起抢占CPU资源,或许是A、B、C线程抢占到,不论谁抢占到资源都上锁。) 范例:使用同步代码块: class SaleTicketX implements Runnable{ //实际上SaleTicketX 并不是多线程的类 private int ticket = 10;//总共10张票 @Override public void run() { //每一个售票窗口(线程)卖五次,一次卖一张 for(int i = 0;i<5;i++) { synchronized (this) { if(ticket > 0) { // try { // Thread.sleep(500); //当前线程休眠1秒中 // } catch (InterruptedException e) { // e.printStackTrace(); // } System.out.println(Thread.currentThread().getName() + "售票成功,剩余票数为:" + -- ticket); }else { System.out.println(Thread.currentThread().getName() + "售票失败,请等待下一趟车次"); } } } } }范例:使用同步方法:
class SaleTicketX implements Runnable{ //实际上SaleTicketX 并不是多线程的类 private int ticket = 10;//总共10张票 @Override public void run() { //每一个售票窗口(线程)卖五次,一次卖一张 for(int i = 0;i<5;i++) { this.saleTicket();//调用同步方法 } } public synchronized void saleTicket() { if(ticket > 0) { // try { // Thread.sleep(500); //当前线程休眠1秒中 // } catch (InterruptedException e) { // e.printStackTrace(); // } System.out.println(Thread.currentThread().getName() + "售票成功,剩余票数为:" + -- ticket); }else { System.out.println(Thread.currentThread().getName() + "售票失败,请等待下一趟车次"); } } }上锁之后缺点也是比较明显的,如果多个线程执行的是同一个同步代码块,那么效率会比较低,因为存在排队的问题。排队上车:线程安全低效。蜂拥上车:线程不安全高效。
package com.wanbangee.java; public class TestSaleTicket02 { public static void main(String[] args) { SaleTicket02 saleTicket02 = new SaleTicket02(); Thread th1 = new Thread(saleTicket02); Thread th2 = new Thread(saleTicket02); Thread th3 = new Thread(saleTicket02); th1.setName("售票窗口1"); th2.setName("售票窗口2"); th3.setName("售票窗口3"); th1.start(); th2.start(); th3.start(); } } /** * 实现接口完成买票程序 * @author Administrator * */ class SaleTicket02 implements Runnable{ int ticket = 10; String str = new String(); public void run() { while(true) { /** * 使用同步代码块 synchronized (object){ * } * 实际上object就是同步监视器,任何一个类的对象都可以充当这个监视器,俗称:锁 * 但是需要注意的是,这个同步监视器必须多个线程共有一个,这个多个线程才能够同步执行 * 在实现的操作中,可以使用this来充当同步监视器 */ synchronized (this) { if(ticket > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"售票成功,剩余票数:" + ticket--); }else { break; } } } } } package com.wanbangee.java; public class TestSaleTicket01 { public static void main(String[] args) { SaleTicket01 th1 = new SaleTicket01(); SaleTicket01 th2 = new SaleTicket01(); SaleTicket01 th3 = new SaleTicket01(); th1.start(); th1.setName("售票窗口1"); th2.start(); th2.setName("售票窗口2"); th3.start(); th3.setName("售票窗口3"); } } /** * 通过继承Thread类完成买票程序 * @author Administrator * */ class SaleTicket01 extends Thread{ static int ticket = 10; static Object object = new Object(); public void run() { while(true) { /** * 同步监听器(锁) 对象必须是多个线程共享的一个,这些线程才会进行同步操作。 * - 必须使用静态对象才能够完成 - 不能使用this,因为this在多个线程中表示多个对象,this的意思是当前对象。 */ synchronized (object) { if(ticket > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"售票成功,剩余票数:" + ticket--); }else { break; } } } } }解决懒汉式单例设计模式的线程安全问题:
package com.wanbangee.thread; public class SingletonDemo { static Person p1; static Person p2 ; public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { p1 = Person.getPerson(); } }).start(); new Thread(new Runnable() { @Override public void run() { p2 = Person.getPerson(); } }).start(); System.out.println(p1 == p2); } } class Person{ private static Person person = null; private Person() { } public static Person getPerson() { if(person == null) { person = new Person(); } return person; } }以上的程序,是多个线程在获取懒汉式单例对象的时候,有可能获取到的是不同的对象,那么就不能称之为单例,为了解决这个问题,我们必须上锁。如下代码所示在获取单例对象的方法上进行上锁,或者使用同步代码块上锁。
public static Person getPerson() { synchronized (Person.class) { //这里(this)不能用,static和this不能同时存在 if(person == null) { person = new Person(); } } return person; }并发:同一个对象多个线程同时操作。 线程安全:在并发时保证数据的正确性、效率尽可能高。(加Synchronized)
线程同步就是同一个资源被多个人同时使用时(并发),要保证数据的准确性。 线程同步实现的2个条件:1形成队列 2加上锁机制,多线程中锁机制已经写好了使用synchronized关键字,加上synchronized同步后性能会下降。
Synchronized 同步方法缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。 将Synchronized放在方法修饰符后。(锁方法,是锁对象,锁对象的资源) 同步块: synchronized (obj){ } obj称之为同步监视器 obj可以是任何对象,但是推荐使用共享资源作为同步监视器 同步方法中无需指定同步监视器,因为同步方法的同步监视器是this即该对象本身,或class
线程的死锁指的是不同的线程占用对方的同步资源不放弃,都在等待对方线程放弃自己需要的同步资源,就形成了线程的死锁。对方都在等对方结束再执行,结果所有人都在等待。 死锁:大家抱着资源都不释放,各自等待。死锁在同步锁上产生的。 死锁:多个线程各自占有一些共享资源,并且互相等待其他线程占有的资源才能进行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情形。某一个同步块同时拥有“两个以上对象的锁”时,就可能会发生“死锁”的问题。 过多的同步可能造成死锁。 死锁:过多的同步可能造成相互不释放资源,从而相互等待,一般发生于同步中持有多个对象的锁。
package com.wanbangee.thread; public class ThreadDemo06 { static StringBuffer sb1 = new StringBuffer(); static StringBuffer sb2 = new StringBuffer(); public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { synchronized (sb1) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } sb1.append("AA"); synchronized (sb2) { sb2.append("BB"); } } } }).start(); new Thread(new Runnable() { @Override public void run() { synchronized (sb2) { sb2.append("BB"); synchronized (sb1) { sb1.append("AA"); } } } }).start(); } }死锁的解决:
尽量减少同步资源的定义最好定义专门的算法或者原则进行规定使用Object类中的wait(),notify()和notifyAll()方法来完成线程的通信。 wait():令当前的线程挂起并放弃CPU资源,放弃同步资源,使别的线程可以访问并修改共享资源,而当前的线程排队再次等候资源的访问 notify():唤醒正在排队等待同步资源的线程中优先级最高的线程结束等待 notifyAll():唤醒所有的正在排队等待同步资源的线程结束等待 这三个方法还只有在同步方法或者同步的代码块中使用才有效,否则会出现异常。 范例:使用两个线程打印1-100数字,两个线程交替打印。
package com.wanbangee.thread; public class ThreadDemo07 { public static void main(String[] args) { PrintNumber pn = new PrintNumber(); Thread th1 = new Thread(pn); th1.setName("th-1"); th1.start(); Thread th2 = new Thread(pn); th2.setName("th-2"); th2.start(); } } class PrintNumber implements Runnable{ int number = 1; @Override public void run() { while(true) { synchronized (this) { this.notify();//唤醒其他线程 if(number <= 100) { System.out.println(Thread.currentThread().getName() + "---" + number++); } try { this.wait();//当前线程让步,释放同步资源 } catch (InterruptedException e) { e.printStackTrace(); } } } } }线程间通信概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。 比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题。 为什么要处理线程间通信: 多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行, 那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。 如何保证线程间通信有效利用资源: 多个线程在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。 就是多个线程在操作同一份数据时, 避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。 什么是等待唤醒机制: 这是多个线程间的一种协作机制。谈到线程我们经常想到的是线程间的竞争(race),比如去争夺锁,线程间也会有协作机制。就是在一个线程进行了规定操作后,就进入等待状态(wait()), 等待其他线程执行完他们的指定代码过后再将其唤醒(notify());在有多个线程进行等待时, 如果需要,可以使用 notifyAll()来唤醒所有的等待线程。 wait/notify 就是线程间的一种协作机制。 等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下: wait:线程不再活动,不再参与调度,因此不会浪费 CPU 资源,也不会去竞争锁了,这时的线程状态即是 WAITING。它还要等着别的线程执行一个特别的动作,也即是“通知(notify)”在这个对象上等待的线程从wait 中释放出来,重新进入到调度队列(ready queue)中 notify:则选取所通知对象的 wait中的一个线程(优先级高的)释放; notifyAll:则释放所通知对象的 wait set 上的全部线程。 注意: 哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以需要再次尝试去获取锁(很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。 总结如下: 如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态; 否则,线程就从 WAITING 状态又变成 BLOCKED 状态 调用wait和notify方法需要注意的细节: wait方法与notify方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。 wait方法与notify方法是属于Object类的方法的。因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。 wait方法与notify方法必须要在同步代码块或者是同步中使用。因为:必须要通过锁对象调用这2个方法。
• 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费; • 如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止; • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。
分析:这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖,互为条件。 • 对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费 • 对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费 • 在生产者消费者问题中,仅有synchronized是不够的,因为还需要等待和通知 • synchronized可阻止并发更新同一个共享资源,实现了同步,但是不能用来消息的传递,等待通知是object提供的方法wait(),notify() • synchronized不能用来实现不同线程之间的消息传递(通信)
线程通信生产者和消费者问题由2种实现: 1.队列方式,这个队列叫缓冲区, 解决方式1:并发协作模型“生产者/消费者模式” 管程法(用并发容器来实现) • 生产者:负责生产数据的模块(这里模块可能是:方法、对象、线程、进程); • 消费者:负责处理数据的模块(这里模块可能是:方法、对象、线程、进程); • 缓冲区:消费者不能直接使用生产者的数据,它们之间有个“缓冲区”; 生产者将生产好的数据放入“缓冲区”,消费者从“缓冲区”拿要处理的数据。 生产者和消费者之间不用直接打交道。 这个“缓冲区”要提供什么功能,需要并发什么操作,生产者往里面添数据,消费者往里面拿数据,这个容器有并发的操作,存(容器空间不够不能存了需要等待)和取(空容器没有数据需要等待,等待生产者生产,一旦生产了就通知你消费,一旦消费者拿走产品了,又通知生产者往里面添产品)。
Wait()和sleep()阻塞不一样,Wait()会释放锁,(调用notify()或notifyAll()进入就绪状态) sleep()抱着资源睡觉
package com.sxt.cooperation; /** * 协作模型:生产者消费者实现方式一:管程法 * 借助缓冲区 */ public class CoTest01 { public static void main(String[] args) { SynContainer container = new SynContainer(); new Productor(container).start(); new Consumer(container).start(); } } //生产者 class Productor extends Thread{ SynContainer container ; public Productor(SynContainer container) { this.container = container; } public void run() { //生产 for(int i=0;i<100;i++) { System.out.println("生产-->"+i+"个馒头"); container.push(new Steamedbun(i) ); } } } //消费者 class Consumer extends Thread{ SynContainer container ; public Consumer(SynContainer container) { this.container = container; } public void run() { //消费 for(int i=0;i<100;i++) { System.out.println("消费-->"+container.pop().id+"个馒头"); } } } //缓冲区 class SynContainer{ Steamedbun[] buns = new Steamedbun[10]; //存储容器 int count = 0; //计数器 //存储 生产 public synchronized void push(Steamedbun bun) { //何时能生产 容器存在空间 //不能生产 只有等待 if(count == buns.length) { try { this.wait(); //线程阻塞 消费者通知生产解除 } catch (InterruptedException e) { } } //存在空间 可以生产 buns[count] = bun; count++; //存在数据了,可以通知消费了 this.notifyAll(); } //获取 消费 public synchronized Steamedbun pop() { //何时消费 容器中是否存在数据 //没有数据 只有等待 if(count == 0) { try { this.wait(); //线程阻塞 生产者通知消费解除 } catch (InterruptedException e) { } } //存在数据可以消费 count --; Steamedbun bun = buns[count] ; this.notifyAll(); //存在空间了,可以唤醒对方生产了 return bun; } } //馒头 class Steamedbun{ int id; public Steamedbun(int id) { this.id = id; } }解决方式2:并发协作模型“生产者/消费者模式” 信号灯法(红绿灯法) final void wait() 表示线程一直等待,直到其他线程通知,与sleep抱着资源睡觉不同,Wait()会释放锁 final void wait(long timeout) 指定等待的毫秒数 final void notifiy() 唤醒一个处于等待状态的线程 final void notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 均是java.lang.Object类的方法, 都只能在同步方法或者同步代码块中使用,否则会抛出状态异常,因为必须锁定它才能进行等才能进行唤醒。
package com.sxt.cooperation; /** * 协作模型:生产者消费者实现方式二:信号灯法 * 借助标志位 * */ public class CoTest02 { public static void main(String[] args) { Tv tv =new Tv(); new Player(tv).start(); new Watcher(tv).start(); } } //生产者 演员 class Player extends Thread{ Tv tv; public Player(Tv tv) { this.tv = tv; } public void run() { for(int i=0;i<20;i++) { if(i%2==0) { this.tv.play("奇葩说"); }else { this.tv.play("太污了,喝瓶立白洗洗嘴"); } } } } //消费者 观众 class Watcher extends Thread{ Tv tv; public Watcher(Tv tv) { this.tv = tv; } public void run() { for(int i=0;i<20;i++) { tv.watch(); } } } //同一个资源 电视 class Tv{ String voice; //信号灯 //T 表示演员表演 观众等待 //F 表示观众观看 演员等待 boolean flag = true; //表演 public synchronized void play(String voice) { //演员等待 if(!flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //表演 System.out.println("表演了:"+voice); this.voice = voice; //唤醒 this.notifyAll(); //切换标志 this.flag =!this.flag; } //观看 public synchronized void watch() { //观众等待 if(flag) { try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //观看 System.out.println("听到了:"+voice); //唤醒 this.notifyAll(); //切换标志 this.flag =!this.flag; } }和sleep()不同,Wait()会释放锁,可以吧这个锁交给别人去用,否则这个资源就一直被自己持有了,Wait()同样是阻塞的一种但会释放锁,什么时候被唤醒?对方调用notify(),从阻塞状态到运行状态。
线程池概念: 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作, 无需反复创建线程而消耗过多资源。
课程小结:
程序,进程和线程之间的关系,线程是进程的进一步的划分,进程如果关闭,这个进程下的所有的线程也会消失,如果线程关闭,进程还会存在多线程的实现方式,目前我们有两个,一个是继承Thread类,二是实现Runnable接口,继承Thread的线程不能直接共享资源,只有Thread类的对象或者其子类对象才是线程的对象,两种方式实习多线程的区别和联系: Thread类也是实现了Runnable接口实现Runnbale完成多线程依旧需要使用Thread类进行启动Thread的构造方法中传递的参数就是Runnable接口的对象Thread类完成多线程不适合线程之间的资源共享,而Runnable接口完成的多线程可以继承Thread类有Java单继承的局限,而实现Runnable接口是多继承多实现 多线程操作方法几乎都在Thread类中定义,另外Object类中wait()和notify(),notifyAll()方法 多线程的调用可以设置优先级的方式完成,优先级总共有三个,最高、默认和最低,都在Thread类中以静态常量的方式定义,而后线程就可以设置这三个常量为线程的有限,优先级高的线程可能先执行,主线程main的优先级是默认的。 线程的生命周期实际上就是线程的状态的问题,总共有5个:新建,就绪,运行,阻塞、死亡,线程到达每种状态,都有响应的方法。 通过同步代码块或者同步方法对多个线程执行同一个操作进行同步,表示多个线程需要排队等待执行,效率会比较低,但是安全性会比较高,我们一般称没有同步的操作为线程不安全的,而进行了线程同步的操作称为线程安全的。 过多的同步,可能会造成线程的死锁,比如两个线程相互等待对方的资源,就会造成死锁的出现。 线程的通信,通过wait()和notify()|notifyAll()方法进行,这写方法必须写在同步方法或者同步代码块中,wait表示让步,等待,notify唤醒其他等待或者让步的线程。随着互联网的系统的要求越来越高,具体表现人任务多,用户数量庞大,导致我们线程安全性的要求也越来越高,传统的线程安全靠的同步方法和同步代码块,但是同步方法和同步代码块虽然可以解决线程安全的问题,但是效率很低,所以在JDK1.5之后,Java提供了java.util.concurrent包,在此包中增加了并发线程很多实用的工具类,用于定义类似于线程的自定义系统,包括线程池,异步的IO操作和轻量级的任务框架。可以提供可调用的、灵活的线程池。还提供了用于多线程上下文中的Collection的实现等等。java.util.concurrent包我们简称juc。
Volatile关键字的作用,当多个线程共享数据的时候,可以保证主存中的数据可见,相较于synchronized关键,又是一种较为轻量级的同步策略。
package com.wanbangee.juc; /** * 内存可见性问题 * @author Administrator * */ public class TestVolatile { public static void main(String[] args) { ThreadDemo td = new ThreadDemo(); new Thread(td).start(); while(true) { if(td.isFlag()) { System.out.println("---------------"); break; } } } } class ThreadDemo implements Runnable{ private volatile boolean flag = false; @Override public void run() { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } flag = true; System.out.println("flag = " + this.isFlag()); } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } }需要注意的点是:
volatile关键字不具备 互斥性volatile关键字不保证变量的原子性由于volatile不保证原子性,所以我们在开发中,共享数据可以使用原子变量。原子变量定义在java.util.concurrent.atomic包中。其中定义了如AtomicInteger 这个类,表示是int类型的原子变量类型,类似于int的包装类Integer。
package com.wanbangee.juc; import java.util.concurrent.atomic.AtomicInteger; /** * i++ 的原子性问题 * * int i = 10; * i = i ++; * System.out.println(i);//10 * * 分解i++ * int temp = 10; * i = i+1; * i = temp; * */ public class TestAtomicDemo { public static void main(String[] args) { AtomicDemo atomicDemo = new AtomicDemo(); for(int i = 0;i<10;i++) { new Thread(atomicDemo).start(); } } } class AtomicDemo implements Runnable{ // private volatile int number = 0; private AtomicInteger number = new AtomicInteger(0);//使用原子变量 @Override public void run() { try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getNumber()); } public int getNumber() { return number.getAndIncrement();//表示自增后获取, 也就是 自增操作 } }原子变量的CAS算法:Compare and Swap,比较和替换。所有的原子变量类型都是实现了CAS算法的,才能够保证变量的原子性。CAS算法是硬件对并发操作支持的,其中包含了三个操作。
读取内存值 A执行线程获得新值B读取内存值C 仅仅只有A==C的时候,更新内存值 为B,否则什么也不做。 这就是所谓的CAS算法。实际上在JDK1.8之后,对于线程安全的问题,几乎都采用了CAS算法来解决。JDK1.5在java.util.concurrent包中提供了多种并发容器类来改进同步容器的性能,ConcurrentHashMap就是JDK1.5之后增加的一个线程安全的哈希表,对于多线程的同步操作来说,ConcurrentHashMap介于HashMap和HashTable之间的,内部采用的是锁分段机制替代HashTable的独占锁,进而提升性能。此包中还是提供了用于多线程中Collection的实现: ConcurrentLinkedDeque, ConcurrentLinkedQueue, ConcurrentSkipListSet, CopyOnWriteArrayList, CopyOnWriteArraySet,这些支持并发容器的性能都由于同步的容器,比如:Collections.synchronizedList(new ArrayList());//基于同步方法的策略完成同步容器。
package com.wanbangee.juc; import java.util.Iterator; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; public class CollectionThreadTest { public static void main(String[] args) { CollectionThread ct = new CollectionThread(); for(int i = 0;i<10;i++) { new Thread(ct).start(); } } } class CollectionThread implements Runnable{ // private static List<String> list = Collections.synchronizedList(new ArrayList<String>());//基于同步方法的策略完成同步容器 private static List<String> list = new CopyOnWriteArrayList<>();//采用了锁分段机制的支持并发的同步容器类 static { list.add("AA"); list.add("BB"); list.add("CC"); } @Override public void run() { Iterator<String> it = list.iterator(); while(it.hasNext()) { System.out.println(it.next()); list.add("AA"); } } }CountDownLatch是一个同步的辅助类,目的是在完成一组线程执行操作之前,它允许一个或者多个线程一直等待。 计算程序执行时间:
package com.wanbangee.juc; import java.util.concurrent.CountDownLatch; public class TestCountDownLatch { public static void main(String[] args) { CountDownLatch latch = new CountDownLatch(50);//闭锁50个线程 LatchDemo latchDemo = new LatchDemo(latch); long start = System.currentTimeMillis(); for(int i = 0;i<50;i++) { new Thread(latchDemo).start(); } try { latch.await();//等待50个闭锁线程执行完成之后再执行 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } //要等50个线程执行完成之后,主线程再运行 long end = System.currentTimeMillis(); System.out.println("执行时间为:" + (end - start)); } } class LatchDemo implements Runnable{ private CountDownLatch latch; public LatchDemo(CountDownLatch latch) { this.latch = latch; } @Override public void run() { try { for(int i = 0;i<100;i++) { if(i % 2 ==0) { System.out.println(i); } } } finally { this.latch.countDown();//添加到闭锁中 } } }在JDK1.5中提供了线程的另外一种实现方式,即实现Callable接口,这个接口类似于Runnable接口,两者都是为哪些实例可能被另外一个线程执行的类设计的,不过Runnable接口不会有返回值,而且无法抛出受检查的异常,但是Callable接口可以有返回值,但是返回使用FetureTask接受,FutureTask也可以作为闭锁
package com.wanbangee.juc; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class TestCallable { public static void main(String[] args) { //实例化线程操作类对象 CallableDemo callableDemo = new CallableDemo(); //执行call方法,需要使用FutureTask或者其实现类来接收call方法的返回值 FutureTask<Integer> result = new FutureTask<>(callableDemo); //启动线程 new Thread(result).start(); Integer sum; try { sum = result.get();//接收结果 // --------------- 存在一个隐式闭锁 System.out.println("---------------"); System.out.println("相加的结果为:" + sum); } catch (Exception e) { e.printStackTrace(); } } } class CallableDemo implements Callable<Integer>{ @Override public Integer call() throws Exception { Thread.sleep(200); int sum = 0; System.out.println("+++++++++++++++"); for(int i = 0;i <= 100000;i++) { sum += i; } return sum; } }线程安全的实现方式有三种:
同步方法(隐式同步锁)同步代码块(隐式同步锁)同步锁(显示同步锁) 显示同步锁是在JDK1.5之后出现的,在编写程序的时候,程序根据自己的需求,手动的添加上锁和解锁的操作。 package com.wanbangee.juc; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TestLock { public static void main(String[] args) { LockDemo lockDemo = new LockDemo(); new Thread(lockDemo,"窗口1").start(); new Thread(lockDemo,"窗口2").start(); new Thread(lockDemo,"窗口3").start(); new Thread(lockDemo,"窗口4").start(); new Thread(lockDemo,"窗口5").start(); } } class LockDemo implements Runnable{ private int ticket = 10; private Lock lock = new ReentrantLock();//实例化同步锁 @Override public void run() { for(int i = 0;i<10;i++) { lock.lock();//上锁 try { if(ticket > 0) { System.out.println(Thread.currentThread().getName()+" 买票成功,剩余票数:" + --ticket); } } finally { lock.unlock();//解锁,不管有没有异常,咋都要解锁 } } } }Condition接口描述了可能会与锁有关的条件变量,这些变量在用法上和Object类中的wait()、notify()、notifyAll()方法类似。但是Condition中对应的方法await()、signal()和signalAll()方法。
package com.wanbangee.juc; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class ConditionTest { public static void main(String[] args) { CoditionDemo cd = new CoditionDemo(); new Thread(new Runnable() { @Override public void run() { for(int i = 0;i<10;i++) { cd.threadA(); } } },"线程A").start(); new Thread(new Runnable() { @Override public void run() { for(int i = 0;i<10;i++) { cd.threadA(); } } },"线程B").start();; } } class CoditionDemo{ private Lock lock = new ReentrantLock();//获得同步锁 private Condition conditionA = lock.newCondition(); // private Condition conditionB = lock.newCondition(); public void threadA(){ lock.lock();//上锁 try { conditionA.signalAll();//唤醒其他线程 System.out.println(Thread.currentThread().getName()); try { conditionA.await();//线程 } catch (InterruptedException e) { e.printStackTrace(); } } finally { lock.unlock();//解锁 } } // public void threadB(){ // // lock.lock();//上锁 // try { // conditionB.signalAll(); // System.out.println(Thread.currentThread().getName()); // try { // conditionB.await(); // } catch (InterruptedException e) { // e.printStackTrace(); // } // // } finally { // lock.unlock();//解锁 // } // // } }对共享的数据进行操作的时候,我们读来说,实际上并不需要上锁,只有写的时候才上锁,所以正常情况应该是:
读读 不需要所写写 需要上锁的写读 需要上锁 在juc中,提供ReadWirteLock锁进行读写操作,维护了一哆锁,一个用于读的操作,一个用于写的操作,如果都是读的操作,那么多个线程可以同时持有读锁,但是写锁必须是一个线程独占。 范例: package com.wanbangee.juc; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class TestReadWriteLock { public static void main(String[] args) { ReadWriteDemo rt = new ReadWriteDemo(); //一个线程负责写 new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub rt.set((int)Math.random()*100); } },"写线程").start(); //多个线程同时读 for(int i = 0;i<10;i++) { new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub rt.get(); } }).start(); } } } class ReadWriteDemo{ private int number = 0; private ReadWriteLock lock = new ReentrantReadWriteLock();//获得读写锁的实例 //写 public void set(int number) { lock.writeLock().lock();//上写锁 try { System.out.println(Thread.currentThread().getName()); this.number = number; } finally { lock.writeLock().unlock();//关闭锁 } } //读 public void get() { lock.readLock().lock();//上读锁 try { System.out.println(Thread.currentThread().getName() + ":" + number); } finally { lock.readLock().unlock();//关闭锁 } } }线程池提供了一个线程队列,队列中保存着所有等待任务的线程,当分配任务时,线程池中的某个线程执行任务,执行完成,这个线程又回到线程池等待新的任务,这样就避免了创建和销毁线程的额外开销,提升响应速度。 在我们java.util.concurrent包中,线程池最高的父接口叫做Executor。继承关系如下:
package com.wanbangee.juc; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class TestPoolDemo { public static void main(String[] args) { // new Thread(new Runnable() { // public void run() { // System.out.println(1); // } // }).start(); // new Thread(new Runnable() { // public void run() { // System.out.println(2); // } // }).start(); // new Thread(new Runnable() { // public void run() { // System.out.println(3); // } // }).start(); // new Thread(new Runnable() { // public void run() { // System.out.println(4); // } // }).start(); //创建线程池 ExecutorService pool = Executors.newFixedThreadPool(5);//创建带有5个线程的线程池 //分配任务 pool.submit(new Runnable() { @Override public void run() { System.out.println(1); } }); pool.submit(new Runnable() { @Override public void run() { System.out.println(2); } }); pool.submit(new Runnable() { @Override public void run() { System.out.println(3); } }); pool.submit(new Runnable() { @Override public void run() { System.out.println(4); } }); pool.submit(new Runnable() { @Override public void run() { System.out.println(5); } }); pool.submit(new Runnable() { @Override public void run() { System.out.println(6); } }); Future<Integer> future = pool.submit(new Callable<Integer>() {//分配Callable实现的接口 @Override public Integer call() throws Exception { int sum = 0; for(int i = 0;i<=100;i++) { sum += i; } return sum; } }); try { System.out.println(future.get());//接收通过Callable接口实现的多线程的返回值 } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } //关闭线程池 pool.shutdown();//分配的任务全部完成之后,关闭线程池(隐式闭锁) } }可以安排在指定的延迟后运行线程。使用ScheduleExecutorService即使实现除了线程池的接口也是继承了线程调度。其中有submit方法可以给线程分配任务,schedule方法既可以分配任务也可以做线程调度。
package com.wanbangee.juc; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class TestScheduleExecutorService { public static void main(String[] args) { //创建可调度的线程池 ScheduledExecutorService pool = Executors.newScheduledThreadPool(5); List<Future<Integer>> list = new ArrayList<>(); for(int i = 0;i<5;i++) { Future<Integer> fu = pool.schedule(new Callable<Integer>() { @Override public Integer call() throws Exception { int result = (int) Math.round(Math.random()*9+1); return result; } }, 1, TimeUnit.SECONDS); list.add(fu); } pool.shutdown(); for (Future<Integer> future : list) { try { System.out.println(future.get());//打印线程执行的结果 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }通过Timer和Timetask(实现简单任务调度),我们可以实现定时启动某个线程。 • java.util.Timer:类似闹钟的功能,本身实现的就是一个线程。 • java.util.TimerTask:一个抽象类,该类实现了Runnable接口,所以该类具备多线程的能力。
package com.sxt.others; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Timer; import java.util.TimerTask; /** * 任务调度: Timer 和TimerTask类 */ public class TimerTest01 { public static void main(String[] args) { Timer timer = new Timer(); //执行安排 //timer.schedule(new MyTask(), 1000); //执行任务一次 //timer.schedule(new MyTask(), 1000,200); //执行多次 Calendar cal = new GregorianCalendar(2099999,12,31,21,53,54); timer.schedule(new MyTask(), cal.getTime(),200); //指定时间 } } //任务类 class MyTask extends TimerTask{ @Override public void run() { for(int i=0;i<10;i++) { System.out.println("放空大脑休息一会"); } System.out.println("------end-------"); } }QUARTZ(任务调度框架,实现复杂任务调度,这个框架已经集成到spring里面) 练习:
编写一个程序,开启三个线程,这个三个线程的名称分别是A,B,C,每个线程将自己的名称在屏幕上打印10次,要求交替输出。 比如:ABCABCABC… package com.wanbangee.juc; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TestPrintABC { public static void main(String[] args) { PrintABCDemo abc = new PrintABCDemo(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { abc.loopA(i); } } },"A").start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { abc.loopB(i); } } },"B").start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { abc.loopC(i); } } },"C").start(); } } class PrintABCDemo{ //定义当前运行的线程的标识符 private int number = 1; private Lock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); public void loopA(int i ) {//i是循环次数 lock.lock();//上锁 try { if(this.number != 1) { condition1.await();//等待 } System.out.println(Thread.currentThread().getName() + "\t" + i); //唤醒其他线程 condition2.signal(); number = 2; } catch(Exception e){ }finally { lock.unlock();//解锁 } } public void loopB(int i ) {//i是循环次数 lock.lock();//上锁 try { if(this.number != 2) { condition2.await();//等待 } System.out.println(Thread.currentThread().getName() + "\t" + i); //唤醒其他线程 condition3.signal(); number = 3; } catch(Exception e){ }finally { lock.unlock();//解锁 } } public void loopC(int i ) {//i是循环次数 lock.lock();//上锁 try { if(this.number != 3) { condition3.await();//等待 } System.out.println(Thread.currentThread().getName() + "\t" + i); //唤醒其他线程 condition1.signal(); number = 1; } catch(Exception e){ }finally { lock.unlock();//解锁 } } }