------- android培训、java培训、期待与您交流! ----------
计算机用户理所当然的认为他们的操作系统一次同时可以做多件事情。他们可以一边在WORD中写文档,同时听着歌,下载着文件。其实一个单独的应用程序也可以一次做多件事情。例如,音乐播放器必须同时读取来自网络的音频数据,解压它,管理播放,更新界面显示。即使是像WORD一样的文字编辑器也时刻准备着响应键盘和鼠标事件,同时不耽误格式化文本和刷新屏幕显示。像这样可以一次做多件事的软件就叫做并发软件。
Java平台被设计成从底层支持并发编程。它通过Java编程语言和类库来实现基本的并发性。同时,从Java1.5以后,Java平台也包括高级的并发API。本文主要介绍Java平台的基本并发性支持,同时也总结一些java.util.concurrent包中的高级API.
在并发编程中,有两个基本的执行单元:进程和线程。在Java编程语言中,并发编程主要考虑的是线程,当然也要考虑一些进程。多线程执行是Java平台的基本特征。每一个应用程序都至少有一个线程,也可以说至少有n个(如果把内存管理和信号处理的系统线程算在内)。但是从应用程序编写者的角度,你只启动了一个线程,叫做主线程。这个线程有能力创建其他的线程。
新建:创建线程对象
就绪:有执行资格,没有CPU执行权
运行:有执行资格,有CPU执行权
阻塞:没有执行资格,没有CPU执行权。类似暂停,之后还可以继续。
死亡:线程对象编程垃圾,等待被回收。
线程状态图如下:
上图中,当一个线程对象a调用join,实际上是使a获取CPU执行权,直到运行结束。之所以把setDeamon()方法运行到死亡的连接线上,因为,setDeamon()的意思是使调用这个方法的线程对象守护这行代码所在的线程,例如,t1.setDeamon();如果这行代码位于主线程,则意味着,t1线程必须守护主线程,也就是说,当主线程结束,t1线程在执行完最后一个时间片后,也要立即死亡,而不管run()方法中的代码是否执行完。
第一种方式:继承Thread类
步骤:
1.定义一个类继承Thread
2.复写Thread类中的run方法。该方法用来存放想在新线程执行的代码。
3.调用线程的start方法。该方法有两个作用,使创建好的线程开始执行,调用run方法
代码如下:
class MyThread extends Thread{ public void run(){ for (int i=0;i<60 ;i++ ) { System.out.println("OtherThread"+i); } } } class MultiThread{ public static void main(String[] args) { MyThread thead=new MyThread(); thead.start(); for (int i=0;i<60 ;i++ ) { System.out.println("MainThread"+i); } } }
运行后发现,每一次的执行结果都不相同,这就是多线程的一个特性:随机性。
可以形象的理解为多个线程在运行时互相抢夺CPU的使用权,谁抢到,谁执行。
如何用这种方式创建两个线程并交替运行:
class Test extends Thread{ private String name; public Test(String name) { this.name=name; } public void run(){ for (int i=0;i<60 ;i++ ) { System.out.println(this.name+" run "+i); } } } class MultiThread{ public static void main(String[] args) { Test t1=new Test("Thread1"); t1.start(); Test t2=new Test("Thread2"); t2.start(); for (int i=0;i<60 ;i++ ) { System.out.println("MainThread"+i); } } }
上面的代码中,我们自己定义了一个变量来标识不同的线程,其实Thread类里会自动分配给线程名字,获取的方法是getName().
Thread类里还有一个静态方法currentThread(),它用于获取当前执行线程的对象,和getName()方法配合代替println里的this.name方法,可以使代码更规范更具可读性。在Test类构造函数里可以调用父类构造函数,修改Thread类里分配给线程的默认名字。
优化后的代码如下:
class Test extends Thread{ public Test(String name) { super(name); } public Test(){} public void run(){ for (int i=0;i<5 ;i++ ) { //下面三句代码在这里是等价的 System.out.println(Thread.currentThread().getName()+" run "+i); // System.out.println(currentThread().getName()+" run "+i); // System.out.println(this.getName()+" run "+i); } } } class MultiThread{ public static void main(String[] args) { // Test t1=new Test("Thread1"); Test t1=new Test(); t1.start(); // Test t2=new Test("Thread2"); Test t2=new Test(); t2.start(); for (int i=0;i<5 ;i++ ) { System.out.println("MainThread"+i); } } }上面的程序运行结果如下图:
从运行结果可以看出:3个线程交替运行,Thread类默认分配给线程的名字是Thread-x,x为从0开始的整数。
第二种方式:实现Runnable接口
//定义一个类实现Runnable接口 class MyRunnableThread implements Runnable{ //把票数定义在这里可以使三个线程同时卖相同的100张票,而相互不重复,没有遗漏。 //如果把票数写到run方法里则会卖出300张票 private int tickets=1;//卖出的票数 public void run(){ while(tickets<=100){ System.out.println(Thread.currentThread().getName()+"卖出了第"+(tickets++)+"张票"); } } } class MultiThread{ public static void main(String[] args) { MyRunnableThread myThread=new MyRunnableThread(); Thread t1=new Thread(myThread); Thread t2=new Thread(myThread); Thread t3=new Thread(myThread); t1.start(); t2.start(); t3.start(); } }最后几个执行结果如下:
继承Thread方式与实现Runnable方式区别
//定义一个类实现Runnable接口 class MyRunnableThread implements Runnable{ //把票数定义在这里可以使三个线程同时卖相同的100张票,而相互不重复,没有遗漏。 //如果把票数写到run方法里则会卖出300张票 private int tickets=100;//剩下的票数 public void run(){ try{Thread.sleep(10);}catch(Exception e){} while(tickets>0){ System.out.println(Thread.currentThread().getName()+"卖出了第"+(tickets--)+"张票"); } } } class MultiThread{ public static void main(String[] args) { MyRunnableThread myThread=new MyRunnableThread(); Thread t1=new Thread(myThread); Thread t2=new Thread(myThread); Thread t3=new Thread(myThread); t1.start(); t2.start(); t3.start(); } }运行上面的程序可能会出现以下结果,您的结果可能与我不同:
其中的对象如同锁(也叫监视器)。持有锁的线程可以再同步中执行。没有锁的线程即使获得cpu的执行权,也进不去,因为没有获取锁。要使用线程同步,有两个前提,一个是必须要有两个以上线程,一个是多个线程使用同一个锁。必须保证同步代码块中只有一个线程在运行。使用同步代码块的好处是:解决了多线程的安全问题,弊端:多个线程需要判断锁,较为消耗资源。
class MyRunnableThread implements Runnable{ private int tickets=100;//卖出的票数 Object obj=new Object(); public void run(){ try{Thread.sleep(10);}catch(Exception e){} while(true){ synchronized(obj){//此处同步代码块中的代码越少越好,所以讲while循环中的条件挪出来放在if中,这样可以避免把这个while循环包括在内 if(tickets>0) System.out.println(Thread.currentThread().getName()+"卖出了第"+(tickets--)+"张票"); } } } } class MultiThread{ public static void main(String[] args) { MyRunnableThread myThread=new MyRunnableThread(); Thread t1=new Thread(myThread); Thread t2=new Thread(myThread); Thread t3=new Thread(myThread); t1.start(); t2.start(); t3.start(); } }这段代码运行后,结果是卖出的票不重不漏,但是线程交替的现象大大减少,甚至可能100张票都由一个线程来完成,这取决与你的CPU速度,速度越快,一个时间片可执行的一个线程的代码越多,一个线程卖出的票数也就越多,要想看到交替线程,需要把票数调到足够大才行。
class MyRunnableThread implements Runnable{ private int tickets=100;//卖出的票数 Object obj=new Object(); public void run(){ try{Thread.sleep(10);}catch(Exception e){} while(true){ sellTicket(); } } public synchronized void sellTicket(){ if(tickets>0) { String str=Thread.currentThread().getName()+"卖出了第"+(tickets--); System.out.println(str); try{Thread.sleep(10);}catch(Exception e){} } } } class MultiThread{ public static void main(String[] args) { MyRunnableThread myThread=new MyRunnableThread(); Thread t1=new Thread(myThread); Thread t2=new Thread(myThread); Thread t3=new Thread(myThread); t1.start(); t2.start(); t3.start(); } }同步函数用的锁是this.运行函数会发现先启动的线程会多卖出一些票这是正常现象。
静态同步函数
静态同步函数就是在同步函数的关键词前面加上static。形式如下:
public synchronized void sellTicket(){……}
静态同步方法的锁是方法所在类的字节码文件对象
class Single{ public static Single s=null; private Single(){} public static Single getInstance(){ if(s==null) //线程有可能在此挂起 s=new Single(); return s; } }当多个线程访问getInstance()方法时,多个线程有可能同时在执行到第6行的位置挂起,导致对象被重复创建,产生潜在的问题。应用本节所学习的多线程安全知识,可以妥善解决此问题,代码如下:
class Single{ public static Single s=null; private Single(){} public static Single getInstance(){ if(s==null) { synchronized(Single.class){ if (s==null) { s=new Single(); } } } return s; } }其中在synchronized外面加的判断语句是为了提高性能,防止多线程每一次访问都对锁进行一次判断。
所谓死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
最简单的制造死锁的java程序如下:
class DeadLock{ public static void main(String[] args) { Thread thread1=new Thread(new Thread1()); Thread thread2=new Thread(new Thread2()); thread1.start(); thread2.start(); } } class Thread1 implements Runnable{ public void run(){ synchronized(Locks.locka){ System.out.println("Thread1 access locka");//A synchronized(Locks.lockb){ System.out.println("Thread1 access lockb");//C } } } } class Thread2 implements Runnable{ public void run(){ synchronized(Locks.lockb){ System.out.println("Thread2 access lockb");//B synchronized(Locks.locka){ System.out.println("Thread2 access locka");//D } } } } class Locks{ static Object locka=new Object(); static Object lockb=new Object(); }
运行结果如下
从结果可以看出,线程1和线程2都没有访问到内层嵌套的同步代码块,线程1访问到A语句时将锁locka关闭,这样当线程2想访问D语句时,被锁locka锁死无法继续,此时线程2挂起,等待locka解锁。而线程2访问到B语句时,又把lockb锁死,这样线程1无法访问语句C,挂起,开始等待锁lockb打开。两个线程由于互相等待对方解锁,所以都在等待,从而导致锁解不开,程序像死了一样。