------- android培训、java培训、期待与您交流! ----------
一、初识多线程
不管在哪一种编程语言中,都要用到多线程的思想。比如在Android中,UI界面和各种上传、下载任务将涉及到多 线程,如果没有采用多线程的思想,那我们在使用列入拍照上传的时候,将看不到图形化的界面。
为什么要创建线程:
在JVM的多线程机制也体现得淋漓尽致,打开JVM,至少就有两个线程在“同时”运行:主线程和垃圾回收机制,他们在不同的线程中工作,互不干扰,就算是主线程结束了,JVM也不会结束,因为在只有只要还有线程在执行(线程是由操作系统完成的,不是JVM完成的),JVM是不会结束的。
多线程的优缺点:①优点:解决多部分代码块(任务)同时执行的问题
②缺点:虽然说多线程是在“同时”执行任务,可是那是给我们的感觉时“同时”,然而实际上多个 线程之间的的执行是不同步的(多线程的执行机制是在时间片在任务之间不停并无序地切换)。所以说如果同时有很多个线程在同时执行,线程获得时间片的机会就会越小,从而线程执行任务的额效率也就会越低。
二、创建多线程
单线程与多线程执行过程:
大概了解到什么是多线程,那就来学习如何创建线程,创建线程有两种方法:①、继承Thread类 ②、实现Runnable接口
①Thread类:用于描述接口的类
方法一:继承Thread类
创建一个类,并继承Thread类,并覆盖Thread类中的run()方法。实例如下:
public class Demo1 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Show show=new Show();
show.start();
}
}
class Show extends Thread{
Show(){
}
@Override
public void run() {
// TODO Auto-generated method stub
//使用多线程执行的代码块
super.run();
}
}
方法二:实现Runnable接口
创建一个,并让这个类实现Runnable接口中run()方法,实例如下:
public class Demo2 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Show2 show=new Show2();
Thread s=new Thread(show);
s.start();
}
}
class Show2 implements Runnable{
Show2(){}
@Override
public void run() {
// TODO Auto-generated method stub
//线程要执行的代码块
}
}
在这里,我们虽然实现了Runnable接口,但是实现的对象不是线程对象,所以我们必须创建一个线程Thread对象, 才能开启线程(Thread s=new Thread(show));
方法一和方法二的区别:
Thread类继承太繁冗。继承Thread类后,我们只需要覆盖run()方法,但是继承后我们将拥有Thread类所有的方法。
实现接口避免java单继承的局限性,将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象。只需实现自己想要的方法即可。
三、线程安全性问题
多个线程同时操作同一个对象,将会产生什么呢?
线程的生命周期:
下面是三个线程同时操作一个对象的例子:
public class Demo2 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Show2 show=new Show2();
Thread s1=new Thread(show);
Thread s2=new Thread(show);
Thread s3=new Thread(show);
s1.start();
s2.start();
s3.start();
}
}
class Show2 implements Runnable{
int num=100;
Show2(){}
@Override
public void run() {
while(num>0){
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+":"+num--);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
如果这是一个买票的系统,只有100张票,三个线程充当三个售票员,在售票过程则会出现售票为0或者-1,这显然是不符合实际需求的。
这就要提及到线程的安全性问题:多个线程同时操作一个共享数据且操作此共享数据的代码有多条,则会产生线程安全性问题。
解决方法:使用同步代码块或同步函数
同步代码块:使用关键字synchronized,格式如下:
synchronized(对象){ //操作共享数据的代码块 }
同步函数:在函数名使用关键字synchronized修饰函数,格式如下:
@Override
public synchronized void run() {
// TODO Auto-generated method stub
//使用多线程执行的代码块
super.run();
}
下面是多个线程操作共享资源的实例:
public class Demo2 {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
Show2 show=new Show2();
Show2 show2=new Show2();
Thread s1=new Thread(show);
Thread s2=new Thread(show);
Thread s3=new Thread(show);
s1.start();
s2.start();
s3.start();
}
}
class Show2 implements Runnable{
int num=100;
Show2(){}
@Override
public void run() {
synchronized(Show2.class){
while(num>0){
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName()+":"+num--);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
这样就会解决上面提到的问题,原理是:当一个线程得到执行权的时候,也会获得同步锁,执行同步代码块。当cpu切换到其他线程的时候,其他线程有执行资格和执行权,但是没有得到同步锁,所以操作不了同步代码块,只有等到执行同步代码块的线程释放同步锁后,其他线程才能操作同步代码块。这样就能避免在某一时刻有多个线程同时访问共享资源,从而解决了线程的安全性问题。
同步函数和同步代码块有着相似的结构和功能,不同的是同步代码块中同步锁是任意对象,而同步函数中同步锁只能是当前对象this。
所以在考虑线程安全时建议使用同步代码块。
四、线程死锁
在多线程执行的过程中,有时候会发生一种现象:那就是线程互相需要对方的同步锁,但是自己又不释放同步锁,导致双方无限等待对方资源,从而造成死锁现象
在多线程中,出现死锁现象时,很难发生是哪里发生了死锁,所以在使用多线程时,应该小心谨慎,避免发生死锁
下面是一个死锁的实例:
public class DealLock {
/**
* @param args
*/
public static void main(String[] args) {
Lock lock1=new Lock(true);
Lock lock2=new Lock(false);
//我们说过多线程安全发生的前提是多个线程同时操作同一个资源,
//这里我们new了两个对象资源,但是它传递的只是两个状态,出了true就是false
//就好像多线程中同步锁的释放和获取一样
Thread t1=new Thread(lock1);
Thread t2=new Thread(lock2);
t1.start();
t2.start();
}
}
class Lock implements Runnable{
private boolean Tag;
Lock(boolean Tag){
this.Tag=Tag;
}
public void run(){
if(Tag){
while(true){
synchronized(MyLock.obj1){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+"if");
synchronized(MyLock.obj2){
System.out.println(Thread.currentThread().getName()+":"+"if");
}
}
}
}
else{
while(true){
synchronized(MyLock.obj2){
System.out.println(Thread.currentThread().getName()+":"+"else");
synchronized(MyLock.obj1){
System.out.println(Thread.currentThread().getName()+":"+"else");
}
}
}
}
}
}
class MyLock{
public static final Object obj1=new Object();
public static final Object obj2=new Object();
}
四,关于多线程的一些小细节
①,区分正在运行的线程:在创建线程子类对象时就已经产生了线程名,所以使用Thread.currentThread().getName()就能得到当前正在执行的线程
②,改变线程名称,直接调用Thread类的构造方法super(name);
③,多线程虽然解决了同步问题,但是其执行效率低。
④,执行资格:可以被cpu处理,在处理队列中等待 执行权:正在被cpu处理
---------------------- Android+J2EE开发、Java培训、期待与您交流! ----------------------