----------- android培训、java培训、java学习型技术博客、期待与您交流!------------
黑马程序员——44,多线程
一:概述----》
在了解多线程之前,需要先了解进程。
进程,正在执行的程序,每一个进程的执行至少有一个执行顺序,这个顺序就是执行路径,也称为控制单元,也就是所谓的线程。
举一个例子,程序执行时在堆内建立对象,这是一个线程,而虚拟机有一个不定时清空对内垃圾的机制,这也是一个线程。
多线程最为常见的例子也是网络分批同时下载,这里的同时其实是CPU快速切换执行程序才让我们视觉上感觉是同时进行的,真正意义上的同时执行是不可能的。而我们通常会认为执行程序主函数的线程是主线程。
线程也是对象,也可以用类来描述。
二:建立线程的方法----》
建立线程的第一种方法:
1,定义一个类继承了Thread类
2,覆盖Thread类中的run方法
3,建立该类对象,调用start方法,就会启动线程,内部调用run方法
特别注意:如果建立该类对象,然后用该类对象直接调用run方法,并不会开启线程。程序执行到这一步会先调用run方法执行完run方法里面的代码之后,再往下执行,这就依然只有一条主线程在执行代码而已。
由于CPU会不断的切换执行,所以不同线程的执行结果交错排列的,比如说,主线程有循环打印,线程里面也有一个循环打印,那么主线程开启子线程之后,两个线程打印的结果加错排列的,而且每一次编译运行的结果都是不一样的。
class Xc extends Thread //定义一个类继承了Thread类
{
public void run()//覆盖Thread类里面的run方法
//这里的run方法仅仅是用来封装需要运行的代码
{
//run方法内的代码…
}
}
class Duoxiancheng
{
public static void main(String[] args)
{
Xc b= new Xc();//建立一个线程
b.start();//调用线程的start方法
//b.run();/*这句话仅仅是一般的调用对象方法,不算是调用线程,虚拟机执行到这行也只是会把run方法执行完了之后再去执行下面的循环,这就没有调用线程!*/
//主线程执行的其他代码…
}
}
每一次运行结果都是不一样的,多个线程都在争夺CPU的执行权,哪个线程抢到就执行哪个线程。在某一时刻,最多只能够有一个线程在执行(多核情况例外),一般对于一个程序来说,哪怕主线程执行完了,但是子线程还没有执行完,还是会把子线程执行下去的。
多线程的运行状态介绍:被创建,运行,冻结,消亡,阻塞(临时)状态
阻塞状态:具备运行资格却没有执行权
冻结:放弃执行资格
1,先是被创建,然后通过start方法达到运行状态
2,在运行状态时也有可能通过stop或者run方法,结束而消亡
3,程序在运行状态时可能因为CPU的切换而需要临时等待,此时会处于阻塞状态。
4,程序运行时也可能通过sleep(时间)形成冻结状态,冻结时间到了之后可以恢复运行状态;运行到冻结状态之间的变换除了通过sleep关键字达成之外,还可以通过wait方法达到冻结状态,再通过notify方法恢复到运行状态。
5,不过,有时候从冻结状态恢复之后由于CPU切换到其他线程而导致处于临时状态。
在线程中比较常见的还有Thread.currentThread().getName()获取当前线程对象的名字。
线程名字系统会默认从0开始排序,格式是:Thread-数字。
如果直接用System.out.println(Thread.currendThread)来打印,打印出来的格式如下:Thread[Thread-数字,数字,线程组]
其中中间的数字是表示优先级,一般默认是5,线程组表示该线程是由哪个线程开启的就是属于哪个线程组的。
对于同一个线程,不能连续调用多次start方法,这样会导致编译出问题,既然一条线程已经调用start方法,已经属于开启线程了,就没有必要再一次开启线程了。
但是我们总结下来会发现,第一种建立线程方式很不方便,因为如果一个类已经有了自己的父类,那么就没有办法再让其继承Thread类了,毕竟对于java类不支持多继承。
那么我们就需要第二种建立线程的方式,这也是最常用的方式:
1,定义一个类实现Runnable接口
2,覆盖Runnable接口中的run方法
3,建立该类对象
4,建立Thread类的线程对象,其构造函数接收自定义类的对象
5,调用线程的start方法开启线程
class Shoupiao implements Runnable //定义一个类实现Runable接口
{
public void run()//覆盖Runnable接口的run方法
{
//自定义内容…
}
}
Class Xiancheng5
{
public static void main(String[] args)
{
Shoupiao a=new Shoupiao();//这个不是线程
//建立线程
Thread b1=new Thread(a);
Thread b2=new Thread(a);
Thread b3=new Thread(a);
Thread b4=new Thread(a);
//通过Thread类建立线程对象
//将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
b1.start();
b2.start();
b3.start();
b4.start();
//调用Thread类的start方法开启线程并调用Runnable接口子类的run方法
}
}
但是,多线程有一个安全隐患,那就是多条语句操作同一个共享数据,一个线程对代码还没有执行完另外一条线程又参与进来更改了共享数据,就容易产生错误。
三:同步----》
解决的方式是加锁synchronized,用同步块来解决:
synchronized(对象)
{
//需要同步的代码…
}
以下是多线程的安全隐患的解决方式
class Shoupiao implements Runnable
{
private int piao=200;
public void run()
{
while (true)//特别说明:因为 while(true)这句话的关系,最后要按Ctrl+c按键才停下虚拟机,否则会耗费很多资源)
{
synchronized (Shoupiao.class)// Shoupiao.class这个字节码文件就相当于锁,只有拥有所的线程才可以进来执行同步代码块,执行完之后再释放锁,后面一个线程才有机会得到锁进来执行同步代码块
//没有锁的线程绝对不能执行同步代码块
//虽然这种办法解决了安全隐患,但是每一个线程进来都要做一个锁的判断,如果线程过多会消耗资源,这也是一个弊端
{
if( piao>0 )
{
try{Thread.sleep(12); }
catch (Exception e) {}
System.out.println(Thread.currentThread().getName()+"————票数"+piao-- );
}
}
}
}
}
class Xiancheng7
{
public static void main(String[] args)
{
Shoupiao a=new Shoupiao();
//开启四条线程
new Thread(a) start();
new Thread(a) start();
new Thread(a) start();
new Thread(a) start();
}
}
使用同步块防止安全隐患,有两个前提:1,两个或两种以上的线程2,多线程用的是同一个锁。
既然有同步块,对应的有同步函数:
class A
{
int sum;
public synchronized void add(int aa,int bb)
//这里加了synchronized表示这是一个同步函数
//只有一个线程进来执行完下面的同步代码块之后下一个线程才可以进来执行
{
sum=aa+bb;
System.out.println("两个加起来的结果是"+sum);
}
}
同步函数用的锁是this,如果函数被static修饰的话,函数内部就不可以用this,那么此时对于静态函数,同步所需要的锁是该函数所在的类的字节码文件,书写的格式是:类名.class
另外,多线程与之前学的懒汉式单例设计模式有关,这里再复习一次。
class Adx
{
private Adx(){} //禁止外部建立该类对象
private static final Adx a=null;//内部建立一个该类引用型变量
public static Adx method()//公共方法提供外部访问
{
if(a==null)
{
synchronized(Adx.class)
{
if( a==null )
{
a=newAdx() ;//需要用的时候才初始化
}
}
}
return a;
}
}
死锁情况:锁里面还嵌套着锁。
//多线程中的死锁状况
//死锁:同步中嵌套这同步
//以下是一个简单的死锁例子:
class Suo//单独定义一个类来存放锁
{
static Object a=new Object();//a锁
static Object b=new Object();//b锁
//这里为了方便调用直接就修饰静态,可以用类名调用更加方便
}
class Shoupiao implements Runnable
{
private int piao=500;
private Boolean f;
Shoupiao(Boolean f)
{
this.f=f;
}
public void run()
{
if(f)
{
//锁Suo.a里面嵌套着锁Suo.b
synchronized (Suo.a)
{
synchronized (Suo.b)
{
if( piao>0 )
{
try{Thread.sleep(12); }catch (Exception e) {}
System.out.println(Thread.currentThread().getName()+"————true票数"+piao-- );
}
}
}
}
else
{
//锁Suo.b嵌套着Suo.a
synchronized (Suo.b)
{
synchronized (Suo.a)
{
if( piao>0 )
{
try{Thread.sleep(12); }catch (Exceptione) {}
System.out.println(Thread.currentThread().getName()+"————false票数"+piao-- );
}
}
}
}
}
}
class Xiancheng10
{
public static void main(String[]args)
{
//建立线程
Thread b1=new Thread(new Shoupiao(true));
Thread b2=new Thread(new Shoupiao(false));
//开启线程
b1.start();
b2.start();
}
}
//在这种情况下,b1线程与b2线程就会被死锁了,两个都没有办法执行下去
四:等待唤醒机制----》
例如有一堆数据(名字对应年龄),一条线程负责不断写入(更新),另外一条线程不断读取打印,那么结果很有可能是名字和年龄对应不上,因为多线程同时操作的原因,写入线程写入新的名字,但是还没有写入新年龄的时候CPU就切换到读取线程打印出来了,那么打印出来的同一个人,名字是新写入的,年龄却是旧的数据,这样就出错了。
直接用例题来解释:
等待唤醒机制,简单的说法就是用一个标示(一般是Boolean型的变量),true标记输入线程操作,而false标记输出线程操作。
输入线程操作时候,输出线程冻结状态;输出线程操作时候,输入线程冻结状态。
这样来保证输出结果的准确性。
这就需要用到wait()和notify() 或者是notifyAll()
class Person
{
String name;
String sex;
static Object obj=new Object();
boolean bs=true;//设定一个标示
}
class Shuru/*输入*/ implements Runnable
{
private Person p;
Shuru( Person p )
{
this.p=p;//接收一个Person类的对象,就指向该对象,该写法更加方便
}
public void run()
{
int x= 0;
while(true)
//特别说明:因为 while (true)这句话的关系,最后要按Ctrl+c按键才停下虚拟机,否则会耗费很多资源)
{
synchronized(Person.obj)//用的锁是obj
{
if(!p.bs)
{
try{Person.obj.wait();}catch(Exception e){}
}
//wait()方法可以冻结线程,该方法是定义在Object类的
/*
这里为什么不直接用wait()呢?
因为wait()方法会抛出一个异常所以要用try...catch
为什么这里还要在wait前面加上Person.obj这个锁呢?
因为这种写法:锁.wait(); -----表示持有该锁的线程要陷入沉睡。
也是因为这个原因,wait()方法必须在同步中才可以用,而notify()
和notifyAll()也是因为以上原因在同步中才可以用。
其写法也是: 锁.notify();或者是 锁.notifyAll();
*/
if(x==0)
{
p.name="张三";
p.sex="男性";
}
else
{
p.name="李四";
p.sex="女性";
}
x=(x+1)%2;
Person.obj.notify();
/*
陷入沉睡的线程都会被扔进线程池
notify()方法功能是唤醒在在线程池中头一个沉睡的线程
而notifyAll()方法则是唤醒所有沉睡的线程
*/
p.bs=false;
}
}
}
}
class Shuchu/*输出*/ implements Runnable
{
private Person p;
Shuchu( Person p )
{
this.p=p;
}
public void run()
{
while(true)
{
synchronized(Person.obj)//用的锁是obj
{
if(p.bs)
{
try{Person.obj.wait();} catch(Exception e){}
}
System.out.println(p.name+"-----"+p.sex);
Person.obj.notify();
p.bs=true;
}
}
}
}
class Xctx3
{
public static void main(String[] args)//主线程
{
Person p=new Person();
//建立线程
Shuru b1=new Shuru(p);
Shuchu b2=new Shuchu(p);
Thread c1=new Thread(b1);
Thread c2=new Thread(b2);
//开启线程
c1.start();
c2.start();
}
}
/*
以上程序编译运行结果是:
张三-----男性
李四-----女性
张三-----男性
李四-----女性
张三-----男性
李四-----女性
张三-----男性
李四-----女性
张三-----男性
李四-----女性
张三-----男性
李四-----女性
张三-----男性
李四-----女性
*/
随着jdk1.5版本的升级,一些新的功能也被加入,更加的方便使用者。
就拿多线程通信的生产者消费者例子来说,有多个生产者生产食品,有多个消费者消费食品,每生产一个食品就消费一个食品。
//另外一个是jdk1.5版本升级后的情况:Lock类定义在java.util.concurrent.locks包中
import java.util.concurrent.locks.*;
//这个导入的是后面需要用到的类
class Xctx6
{
public static void main(String[] args)//主函数
{
Shipin a=new Shipin();
Shengchangzhe b1=new Shengchangzhe(a);
Xiaofeizhe b2=new Xiaofeizhe(a);
//建立线程
Thread t1=new Thread(b1);
Thread t2=new Thread(b1);
Thread t3=new Thread(b2);
Thread t4=new Thread(b2);
//开启线程
t1.start();
t2.start();
t3.start();
t4.start();
System.out.println("HelloWorld!");
}
}
class Shipin/*食品类*/
{
private int sp=0;
private boolean f=true;
private Lock lock=new ReentrantLock();//将锁作为一个对象了
private Condition pro=lock.newCondition();
private Condition con=lock.newCondition();
/*
Condition将Object监视器(锁)的方法(wait,notify,notifyAll)分解成不同的对象,
这是为了便于与Lock组合使用。
Lock代替synchronized方法和语句的使用(同步函数和同步代码块)
Condition替代了Object监视器(锁)的方法的使用。
Condition实例是绑定在锁上的,一个Lock实例要获得Condition实例就要调用newCondition方法。
*/
public void shengchang() throws InterruptedException
{
lock.lock();//上锁
try
{
while(f==false)
pro.await();//生产者线程陷入冻结
//这个句式会抛出一个InterruptedException异常
sp++;
System.out.println("生产者"+Thread.currentThread().getName()+"----"+sp);
f=false;
con.signal();//仅仅唤醒消费者线程
}
finally
{
lock.unlock();//释放锁
}
/*
这里是所以要使用try...finally句型是因为确保
一定要执行 lock.unlock();也是因为前面的pro.await();会向外抛出
一个异常,如果没有这个句型程序就会跳出去而没有
执行lock.unlock();这样的话线程就没有释放锁
*/
}
public void xiaofei() throws InterruptedException
{
lock.lock();
try
{
while(f==true)
con.await();//消费者线程陷入冻结
System.out.println("消费者"+Thread.currentThread().getName()+"----"+sp);
f=true;
pro.signal();//唤醒生产者线程
}
finally
{
lock.unlock();
}
}
}
class Shengchangzhe /*生产类*/ implements Runnable
{
private Shipin a ;
Shengchangzhe(Shipin a)
{
this.a=a;
}
public void run()
{
while(true)
{
try{a.shengchang();}
catch(Exception e){}
}
}
}
class Xiaofeizhe /*消费类*/ implements Runnable
{
private Shipin a ;
Xiaofeizhe(Shipin a)
{
this.a=a;
}
public void run()
{
while(true)
{
try{a.xiaofei();}
catch(Exception e){}
}
}
}
个人对前面的例子做了一个改进:有生产者,检查者,消费者;先要由生产者生产食品,然后由检查者检测食品,最后由消费者消化食品。
import java.util.concurrent.locks.*;
class Xctx7
{
public static void main(String[] args)//主函数
{
Shipin a=new Shipin();
Shengchangzhe b1=new Shengchangzhe(a);
Xiaofeizhe b2=new Xiaofeizhe(a);
Jiancezhe b3=new Jiancezhe(a);
//建立线程
Thread t1=new Thread(b1);
Thread t2=new Thread(b1);
Thread t3=new Thread(b2);
Thread t4=new Thread(b2);
Thread t5=new Thread(b3);
Thread t6=new Thread(b3);
//开启线程
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
System.out.println("HelloWorld!");
}
}
class Shipin/*食品类*/
{
private int sp=0;
private int f=1;
private Lock lock=new ReentrantLock();//将锁作为一个对象了
private Condition pro=lock.newCondition();
private Condition con=lock.newCondition();
private Condition jc =lock.newCondition();
public void shengchang() throws InterruptedException //生产方法
{
lock.lock();
try
{
while(f!=1)
{
pro.await();//生产者线程陷入冻结
}
sp++;
System.out.println("生产者"+Thread.currentThread().getName()+"----"+sp);
f=2;
jc.signal();//唤醒检测者线程
}
finally
{
lock.unlock();//释放锁
}
}
public void xiaofei() throws InterruptedException //消费方法
{
lock.lock();
try
{
while(f!=3)
{
con.await();//消费者线程陷入冻结
}
System.out.println("消费者"+Thread.currentThread().getName()+"----------"+sp);
f=1;
pro.signal();//唤醒生产者线程
}
finally
{
lock.unlock();
}
}
public void jiance() throws InterruptedException //检测方法
{
lock.lock();
try
{
while(f!=2)
{
jc.await();//检测者线程陷入冻结
}
System.out.println("检测者"+Thread.currentThread().getName()+"-------"+sp);
f=3;
con.signal();//唤醒消费者线程
}
finally
{
lock.unlock();
}
}
}
class Shengchangzhe /*生产者类*/implements Runnable
{
privateShipin a ;
Shengchangzhe(Shipin a)
{
this.a=a;
}
public void run()
{
while(true)
{
try{a.shengchang();}//调用生产方法
catch(Exception e){}
}
}
}
class Xiaofeizhe /*消费者类*/ implements Runnable
{
private Shipin a ;
Xiaofeizhe(Shipin a)
{
this.a=a;
}
public void run()
{
while(true)
{
try{a.xiaofei();}//调用消费方法
catch(Exception e){}
}
}
}
class Jiancezhe /*检测者类*/ implements Runnable
{
private Shipin a ;
Jiancezhe(Shipin a)
{
this.a=a;
}
public void run()
{
while(true)
{
try{a.jiance();}//调用检测方法
catch(Exception e){}
}
}
}
如果所有线程都冻结了,主线程执行完的时候,这些子线程还是会处于冻结状态,没有结束,如何处理这个问题?
可以调用Thread类中的interrupt方法强制唤醒
/*
当程序里面有同步时程序可能停不下来
以下也提供了解决的办法
*/
class Xctx9
{
public static void main(String[] args)//主函数
{
Shipin a=new Shipin();
int sp=0;//计数器
Shengchangzhe b=new Shengchangzhe(a);
//建立线程
Thread t1=new Thread(b);
Thread t2=new Thread(b);
//开启线程
t1.start();
t2.start();
while(true)
{
if(sp==30)//主线程的while循环30次时唤醒沉睡的子线程
{
//a.fgai();//控制循环
t1.interrupt();//强制唤醒沉睡中的t1线程
t2.interrupt();//强制唤醒沉睡中的t2线程
break;
}
sp++;
System.out.println("main--"+Thread.currentThread().getName()+"----"+sp);
}
System.out.println("HelloWorld!");
}
}
class Shipin //食品类
{
private int sp=0;
private int f=1;//标示f,由于个人习惯,这里用的是int类型的
public void fgai()
{
f++;
}
public synchronized void shengchang( )//存在同步了
{
while(f==1)//当f为1的时候才执行
{
try{this.wait();}
catch(Exception e)
{
System.out.println("有异常");
f++;
}
/*
这种写法的话就会导致不管是哪一个线程得到锁都会被冻结
哪怕是主线程执行完了,这些副线程也还会被冻结,挂在这里不动
为了解决这个问题,主线程调用interrupt()方法,该方法会强制唤醒线程往下执行
第二步在catch块里面加上f++;强制被唤醒的线程执行到这里就把标示位停掉
run方法就不能被调用了,线程停止了。注意下面的编译运行结果。
*/
sp++;
System.out.println("生产者"+Thread.currentThread().getName()+"----"+sp);
}
}
}
class Shengchangzhe /*生产者类*/implements Runnable
{
private Shipin a ;
Shengchangzhe(Shipin a)
{
this.a=a;
}
public void run()
{
a.shengchang();
}
}
/*
以上代码编译运行结果:
main--main----1
main--main----2
main--main----3
main--main----4
main--main----5
main--main----6
main--main----7
main--main----8
main--main----9
main--main----10
main--main----11
main--main----12
main--main----13
main--main----14
main--main----15
main--main----16
main--main----17
main--main----18
main--main----19
main--main----20
main--main----21
main--main----22
main--main----23
main--main----24
main--main----25
main--main----26
main--main----27
main--main----28
main--main----29
main--main----30
Hello World!
有异常
生产者Thread-0----1
有异常
生产者Thread-1----2
*/
但是这种方式设置计数器控制子线程的停止很麻烦,所以可以用Thread类中的public final void setDaemon(boolean on)方法设定为守护线程,所谓的守护线程就是后台线程。
那个线程调用setDeamon(true)方法,那个线程就被设置为后台线程,一般设置好后台线程之后再启动线程。
与后台线程对应的非后台线程就是所谓的前台线程,当程序中的所有前台线程执行完之后,剩下的后台线程都会被强制停止。通过这种方式,这样以上的例子就可以不用设置计数器来控制线程停止了。
Thread类中还有一些常用的字段:
public static final int MAX_PRIORITY //最高优先级10
public static final int MIN_PRIORITY//最低优先级1
public static final int NORM_PRIORITY//默认优先级5
以上三者配合可以更改线程优先级的方法public final void setPriority(int newPriority)使用的,值得注意的是优先级高的线程代表获得CPU执行权的频率会增加,但是不意味着一定会得到CPU执行权。
Thread类中还有一些常用的方法:
public final void join()throws InterruptedException//等待该线程执行完
public String toString()//返回线程的字符串表示形式,包括线程名称、优先级和线程组
public static void yield()//暂停当前正在执行的线程对象,并执行其他线程,注意这里是暂停,并不是冻结!通过是直接用类名调用该方法,表示减缓线程的运行,尽可能达到每个线程都有运行的效果。
----------- android培训、java培训、java学习型技术博客、期待与您交流!------------