java-多线程编程<三>
1.进程
每个独立进行的程序称为进程,即“正在进行的程序”,进程都有自己独立的内存空间,
如果某个进程去访问其他进程的内存空间,则有可能是病毒来的,操作系统的多任务
其实是cpu以非常小的时间间隔交替执行多个程序,给人同时进行多个程序的感觉。
2.线程
1.线程是轻量级的进程。
2.线程没有独立的内存空间。
3.线程是由进程产生,寄生于进程。
4.一个进程可以有多个线程(就是我们所说的多线程编程)
3.线程的状态
1.新建状态(new):新创建了一个线程对象。
2.就绪状态(Runnable):对象创建后,其他线程调用该对象的start( )方法。
该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
3.运行状态(Running):就绪状态的线程获取CPU,执行程序代码
4.阻塞状态(Blocked):线程因为某种原因放弃了CPU的使用权,暂时停止运行。
直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况三种。
a.等待阻塞:运行的线程执行wait( )方法,JVM把该线程放入等待池中
b.同步阻塞:运行的线程在获取对象的同步锁时,如该同步锁被其他线程占用,则JVM把该线程放入锁池中
c.其他阻塞: 运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。
当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5.死亡状态(Dead):线程执行完了,或因异常退出run()方法。结束生命周期。
4.线程使用
a.继承Thread 类, 重写run函数,一个线程只能启动一个线程,无论调用多少次start方法,结果只有一个线程。
public class java_thread extends Thread{ public static void main(String args[]) {
(new java_thread()).run();
System.out.println("main thread run ");
}
public synchronized void run()
{ System.out.println("sub thread run "); }
}
b.实现Runnable接口,重写run函数
public class java_thread implements Runnable{ public static void main(String args[]) {
(new Thread(new java_thread())).start();
System.out.println("main thread run ");
}
public void run()
{ System.out.println("sub thread run "); }
}
c.直接在函数体使用
void java_thread()
{
Thread t = new Thread(new Runnable(){
public void run(){
mSoundPoolMap.put(index, mSoundPool.load(filePath, index));
getThis().LoadMediaComplete();
}});
t.start();
}
4.1 (Thread,Runnable的代理模式)
实现Runnable 接口优势:
a.解决Java单一继承的限制
b.适合多个代码相同的程序出处理同一资源
c.增强程序的健壮性,代码可以被多个线程共享,代码与数据独立。
继承Thread类优势:
a.多线程同步。
b.可以将线程类抽象出来,当需要使用抽象工厂模式设计时。
在函数体使用优势:
a.无需继承Thread或实现Runnable,缩小作用域。
5.后台线程与前台进程
前台线程是为完成一项任务而存在的,后台线程却是服务性的,
当一个一个程序的所有用户线程(前台线程和主线程)都已经停止运行的时候(因为错误、异常还是正常结束),
jvm都将退出,作为后台服务而存在的后台线程也将退出,尽管这期间将有一点时间的延迟。
一般后台线程用于处理时间较短的任务,如在一个Web服务器中可以利用后台线程来处理客户端发过来的请求信息
而前台线程一般用于处理需要长时间等待的任务,如在Web服务器中的监听客户端请求的程序,或是定时对某些系
统资源进行扫描的程序。
两者关系:
前台线程与后台线程,在调用start 方法前调用setDaemon (true)方法则该线程变成后台线程,
如果不调用setDaemon (true)方法,则默认是前台线程,在java程序中,只要有一个前台线程
还没结束,则这个java程序进程不会结束,直到最后一个前台线程结束,Java程序进程才结束,
但是当java程序进程中,没有任何前台线程,只有后台线程,则该进程立即结束。
6.联合线程与join方法
某线程调用join方法后,它将合并到当前其调用join方法所在线程中。
由原本的两个线程合并为“一个线程”,按顺序的执行下去。
main方法中:
1 Threadtest tt =new Threadtest();
2 Thread t=new Thread (tt);
3 t.start();
4 while (true)
5 {
6 if (i==100)
7 {
8 try {
9 t.join();
10 } catch (InterruptedException e) {
11 // TODO Auto-generated catch block
12 e.printStackTrace();
13 }
14 }
15 System.out.println("main thread runing"+i++);
16 }
7.线程同步:
使用同步代码块:synchronized (Object) { 同步代码 }
标志位(锁旗标): 任意类型的对象(监视器)都有一个标志位,标志位为0或1,初始默认是1。
但执行synchronized (Object) 语句后,该标志位为0,直到执行完同步代码后,该对象的标志位才变回1,
其他线程在执行synchronized (Object) 之前会检查对象的标志位,当发现为0时,代表有线程正在执行
同步代码,这时它们会进入到一个等待线程池中,处于阻塞状态,CPU不是人为控制的,CPU其实会
切换到其他线程,但是发现它们处于阻塞状态,只好回到原来running的线程继续执行。
同步的缺点:系统要不停的对同步监视器检查,造成了额外的开销,导致了为了安全性而做出了运行速度的牺牲。
8.同步函数:
与同步代码块作用一样,在函数名前加synchronized 修饰。在一个类中如果有多个synchronized 修饰的方法,
则可以再多个线程中实现同步。当某个线程执行某对象中的某个synchronized 修饰的方法时,其他的线程无法
执行这对象中所有synchronized 修饰的方法,直到该线程执行完同步的方法,其他线程才可以去执行。
注意:同步函数使用的监视器对象是this,即本身。
9.代码块与函数之间的同步
只要它们的监视器是同一个对象即可以实现了,因为同步函数使用的监视器是this,所以代码块与函数的监视器应该是this才可以。
1 public class testsychronized { 2 public static void main(String[] args) { //启动一个线程tt 3 testThread tt=new testThread(); 4 new Thread(tt).start(); //主线程main睡眠1000毫秒 5 try { 6 Thread.sleep(1000); 7 } catch (InterruptedException e) { 8 // TODO Auto-generated catch block 9 e.printStackTrace(); 10 } //改变tt的标志位为1,交给另一个进程启动 11 tt.flag=1; 12 new Thread(tt).start(); 13 }} 14 class testThread implements Runnable 15 { //默认标志位为0,100张票 16 int flag=0; 17 private int ticket=100; 18 public void run() { 19 if (flag==0) 20 { 21 while(true) 22 { 23 try { 24 Thread.sleep(100); 25 } catch (InterruptedException e) { 26 // TODO Auto-generated catch block 27 e.printStackTrace(); 28 } //调用同步代码块,显示票数并减一 29 synchronized(this) 30 { 31 if (ticket>0) 32 System.out.println("block ( )"+ticket--); 33 }}} 34 else //标志位为1时,调用同步方法show 35 { 36 while(true) 37 { 38 try { 39 Thread.sleep(1000); 40 } catch (InterruptedException e) { 41 // TODO Auto-generated catch block 42 e.printStackTrace(); 43 } 44 show(); 45 } }} //同步方法show,显示剩余票数,并减一 46 public synchronized void show () 47 { 48 if (ticket>0) 49 System.out.println("show ( )"+ticket--); 50 } 51 }
10.死锁问题
当一个线程进入了对象x的监视器,而另一个线程已经进入了对象y的监视器,
如果进入x的线程还想进入到对象y的监视,就会被阻隔,接着进入y的线程想进入对象x的监视器的话,这就造成死锁问题。
11.线程间的通信
如果有两个线程,一个线程是对数据进行修改,而另一个线程是对线程进行读取。达到同步的情况,
应该是添加一个数据后,立即读取该数据,以这样的方式不停的执行下去。
当两线程之间不同步时,则可能发生以下情况
a.只修改数据的部分内容,这时切换到另一个线程读取它的数据,将会是一部分是原来的数据,另一部分是已修改的数据。
b.已经进行数据多次修改,另一个线程才开始读取,那只能读到最后一次的数据。也可能对数据重复读取多次后,才进行数据的修改
1 //生产者线程 class Producer implements Runnable 2 { 3 Q q=null; //初始化它的生产对象 4 public Producer (Q q) 5 { 6 this.q=q ; 7 } 8 public void run() { 9 while(true) 10 { 11 try { 12 Thread.sleep(100); 13 } catch (InterruptedException e) { 14 // TODO Auto-generated catch block 15 e.printStackTrace(); 16 } //如果标志位为0,则添加对象 17 if(q.flag==0) 18 q.addSome(); 19 }}} //消费者线程 20 class Comsuer implements Runnable 21 { 22 Q q=null ; //初始化它的消费对象 23 public Comsuer (Q q) 24 { 25 this.q=q ; 26 } 27 public void run() { 28 while(true) 29 { 30 try { 31 Thread.sleep(100); 32 } catch (InterruptedException e) { 33 // TODO Auto-generated catch block 34 e.printStackTrace(); 35 } //标志位为1时,读取消费对象 36 if(q.flag==1) 37 q.readSome(); 38 }}} 39 //将对数据Q的同步操作方法写入到Q中,因为同步的对象时Q, 40 //那直接将同步的方法写入Q中,再用生产者和消费者调用它的方法即可 41 class Q 42 { 43 String name ; 44 String sex ; //默认标志位为0 45 int flag =0; 46 int i=0; //同步添加方法,只能允许添加一男或一女 47 public synchronized void addSome () 48 { //添加一男 49 if (i==0) 50 { 51 name="陈奕迅"; 52 sex="男性"; 53 System.out.println("添加"+name+" "+sex); 54 flag=1; 55 } 56 else if(i==1) 57 { 58 name="王菲"; 59 sex="女性"; 60 System.out.println("添加"+name+" "+sex); 61 flag=1; 62 } //改变下一次,添加为一女 63 i=(i+1)%2; 64 } 65 public synchronized void readSome () 66 { 67 System.out.println(name+" "+sex); 68 flag=0; 69 } 70 }
12.wait,notify,notifyAll方法
这三个方法来自Object类中,所以任何的类都可以调用这三个方法,但只能在synchronized 方法中调用
wait:释放cpu执行权,释放锁即放弃监视器,直到其他线程进入同一个监视器并调用notify方法为止.
调用wait方法的线程处于等待被唤醒状态,而不是等待拿锁的状态。
notify:唤醒同一个监视器中调用wait的第一个线程。
notifyAll:唤醒同一个监视器中调用wait的所有线程,具有最高优先级的线程首先被唤醒并执行。
Thread.yield() 方法:线程让步,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
13.线程安全问题原因
a.多个线程访问出现延迟。
b.线程随机性
14.同步的前提
a.同步需要两个或者两个以上的线程。
b.多个线程使用的是同一个锁。
15.停止线程
a.定义循环结束标记
因为线程运行代码一般都是循环,只要控制了循环即可。
b.使用interrupt(中断)方法。
该方法是结束线程的冻结状态,使线程回到运行状态中来。
注:stop方法已经过时不再使用。
16.wait(),sleep()有什么区别?
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
常见线程名词解释
主线程:JVM调用程序mian()所产生的线程。
当前线程:这个是容易混淆的概念。一般指通过Thread.currentThread()来获取的进程。
后台线程:指为其他线程提供服务的线程,也称为守护线程。JVM的垃圾回收线程就是一个后台线程。
前台线程:是指接受后台线程服务的线程,其实前台后台线程是联系在一起,就像傀儡和幕后操纵者一样的关系。傀儡是前台线程、幕后操纵者是后台线程。由前台线程创建的线程默认也是前台线程。可以通过isDaemon()和setDaemon()方法来判断和设置一个线程是否为后台线程。