day11线程.docx
《day11线程.docx》由会员分享,可在线阅读,更多相关《day11线程.docx(33页珍藏版)》请在冰豆网上搜索。
day11线程
1线程的概述
进程:
正在运行的程序,负责了这个程序的内存空间分配,代表了内存中的执行区域。
线程:
就是在一个进程中负责一个执行路径。
多线程:
就是在一个进程中多个执行路径同时执行。
图上的一键优化与垃圾清除同时在运行,在一个进程中同时在执行了多个任务。
假象:
电脑上的程序同时在运行。
“多任务”操作系统能同时运行多个进程(程序)——但实际是由于CPU分时机制的作用,使每个进程都能循环获得自己的CPU时间片。
但由于轮换速度非常快,使得所有程序好象是在“同时”运行一样。
多线程的好处:
1.解决了一个进程里面可以同时运行多个任务(执行路径)。
2.提供资源的利用率,而不是提供效率。
多线程的弊端:
1.降低了一个进程里面的线程的执行频率。
2.对线程进行管理要求额外的CPU开销。
线程的使用会给系统带来上下文切换的额外负担。
3.公有变量的同时读或写。
当多个线程需要对公有变量进行写操作时,后一个线程往往会修改掉前一个线程存放的数据,发生线程安全问题。
4.线程的死锁。
即较长时间的等待或资源竞争以及死锁等多线程症状。
2创建线程的方式
2.1创建线程的方式一
1.继承Thread类
getName()是获取线程的名字。
执行后的效果:
问题:
先按照顺序运行完了张三,然后接着再按照顺序运行完李四,我们想要的效果是张三和李四做资源的争夺战,也就是先是张三然后李四,没有顺序的执行。
这就证明多线程没有起到效果。
2.需要复写run方法,把要执行的任务放在run方法中。
运行效果:
问题:
先按照顺序运行完了张三,然后接着再按照顺序运行完李四,我们想要的效果是张三和李四做资源的争夺战,也就是先是张三然后李四,没有顺序的执行。
这就证明多线程没有起到效果。
3.调用start()方法启动线程
效果:
达到了我们预期的效果。
线程的使用细节:
1.线程的启动使用父类的start()方法
2.如果线程对象直接调用run(),那么JVN不会当作线程来运行,会认为是普通的方法调用。
3.线程的启动只能由一次,否则抛出异常
4.可以直接创建Thread类的对象并启动该线程,但是如果没有重写run(),什么也不执行。
5.匿名内部类的线程实现方式
2.2线程的状态
创建:
新创建了一个线程对象。
可运行:
线程对象创建后,其他线程调用了该对象的start()方法。
该状态的线程位于可运行线程池中,变得可运行,等待获取cpu的执行权。
运行:
就绪状态的线程获取了CPU执行权,执行程序代码。
阻临时塞:
阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。
直到线程进入就绪状态,才有机会转到运行状态。
死亡:
线程执行完它的任务时。
2.3常见线程的方法
Thread(Stringname)初始化线程的名字
getName()返回线程的名字
setName(Stringname)设置线程对象名
sleep()线程睡眠指定的毫秒数。
getPriority()返回当前线程对象的优先级默认线程的优先级是5
setPriority(intnewPriority)设置线程的优先级虽然设置了线程的优先级,但是具体的实现取决于底层的操作系统的实现(最大的优先级是10,最小的1,默认是5)。
currentThread()返回CPU正在执行的线程的对象
classThreadDemo1extendsThread
{
publicThreadDemo1(){
}
publicThreadDemo1(Stringname){
super(name);
}
publicvoidrun(){
inti=0;
while(i<30){
i++;
System.out.println(this.getName()+""+":
i="+i);
System.out.println(Thread.currentThread().getName()+""+":
i="+i);
System.out.println(Thread.currentThread()==this);
System.out.println("getId()"+""+":
id="+super.getId());
System.out.println("getPriority()"+""+":
Priority="+super.getPriority());
}
}
}
classDemo3
{
publicstaticvoidmain(String[]args)
{
ThreadDemo1th1=newThreadDemo1("线程1");
ThreadDemo1th2=newThreadDemo1("线程2");
//设置线程名
th1.setName("th1");
th2.setName("th2");
//设置线程优先级1~10
th1.setPriority(10);
th2.setPriority(7);
//查看SUN定义的线程优先级范围
System.out.println("max:
"+Thread.MAX_PRIORITY);
System.out.println("min:
"+Thread.MIN_PRIORITY);
System.out.println("nor:
"+Thread.NORM_PRIORITY);
th1.start();
th2.start();
System.out.println("HelloWorld!
");
}
}
练习:
模拟卖票
存在问题:
这时候启动了四个线程,那么tickets是一个成员变量,也就是在一个线程对象中都维护了属于自己的tickets属性,那么就总共存在了四份。
解决方案一:
tickets使用staitc修饰,使每个线程对象都是共享一份属性。
解决方案2:
编写一个类实现Runnable接口。
2.4创建线程的方式二
创建线程的第二种方式.使用Runnable接口.
该类中的代码就是对线程要执行的任务的定义.
1:
定义了实现Runnable接口
2:
重写Runnable接口中的run方法,就是将线程运行的代码放入在run方法中
3:
通过Thread类建立线程对象
4:
将Runnable接口的子类对象作为实际参数,传递给Thread类构造方法
5:
调用Thread类的start方法开启线程,并调用Runable接口子类run方法
为什么要将Runnable接口的子类对象传递给Thread的构造函数,因为自定义的run方法所属对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法
packagecn.itcast.gz.runnable;
publicclassDemo1{
publicstaticvoidmain(String[]args){
MyRunmy=newMyRun();
Threadt1=newThread(my);
t1.start();
for(inti=0;i<200;i++){
System.out.println("main:
"+i);
}
}
}
classMyRunimplementsRunnable{
publicvoidrun(){
for(inti=0;i<200;i++){
System.err.println("MyRun:
"+i);
}
}
}
理解Runnable:
Thread类可以理解为一个工人,而Runnable的实现类的对象就是这个工人的工作(通过构造方法传递).Runnable接口中只有一个方法run方法,该方法中定义的事会被新线程执行的代码.当我们把Runnable的子类对象传递给Thread的构造时,实际上就是让给Thread取得run方法,就是给了Thread一项任务.
买票例子使用Runnable接口实现
在上面的代码中故意照成线程执行完后,执行Thread.sleep(100),以让cpu让给别的线程,该方法会出现非运行时异常需要处理,这里必须进行try{}catch(){},因为子类不能比父类抛出更多的异常,接口定义中没有异常,实现类也不能抛出异常。
运行发现票号出现了负数,显示了同一张票被卖了4次的情况。
出现了同样的问题。
如何解决?
classMyTicketimplementsRunnable{
inttickets=100;
publicvoidrun(){
while(true){
if(tickets>0){
try{
Thread.sleep(100);
}catch(InterruptedExceptione){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"窗口@销售:
"
+tickets+"号票");
tickets--;
}else{
System.out.println("票已卖完。
。
。
");
break;
}
}
}
}
publicclassDemo6{
publicstaticvoidmain(String[]args){
MyTicketmt=newMyTicket();
Threadt1=newThread(mt);
Threadt2=newThread(mt);
Threadt3=newThread(mt);
Threadt4=newThread(mt);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
3锁对象
什么是锁对象?
每个java对象都有一个锁对象.而且只有一把钥匙.
如何创建锁对象:
可以使用this关键字作为锁对象,也可以使用所在类的字节码文件对应的Class对象作为锁对象
1.类名.class
2.对象.getClass()
Java中的每个对象都有一个内置锁,只有当对象具有同步方法代码时,内置锁才会起作用,当进入一个同步的非静态方法时,就会自动获得与类的当前实例(this)相关的锁,该类的代码就是正在执行的代码。
获得一个对象的锁也成为获取锁、锁定对象也可以称之为监视器来指我们正在获取的锁对象。
因为一个对象只有一个锁,所有如果一个线程获得了这个锁,其他线程就不能获得了,直到这个线程释放(或者返回)锁。
也就是说在锁释放之前,任何其他线程都不能进入同步代码(不可以进入该对象的任何同步方法)。
释放锁指的是持有该锁的线程退出同步方法,此时,其他线程可以进入该对象上的同步方法。
1:
只能同步方法(代码块),不能同步变量或者类
2:
每个对象只有一个锁
3:
不必同步类中的所有方法,类可以同时具有同步方法和非同步方法
4:
如果两个线程要执行一个类中的一个同步方法,并且他们使用的是了类的同一个实例(对象)来调用方法,那么一次只有一个线程能够执行该方法,另一个线程需要等待,直到第一个线程完成方法调用,总结就是:
一个线程获得了对象的锁,其他线程不可以进入该对象的同步方法。
5:
如果类同时具有同步方法和非同步方法,那么多个线程仍然可以访问该类的非同步方法。
同步会影响性能(甚至死锁),优先考虑同步代码块。
6:
如果线程进入sleep()睡眠状态,该线程会继续持有锁,不会释放。
4死锁
经典的“哲学家就餐问题”,5个哲学家吃中餐,坐在圆卓子旁。
每人有5根筷子(不是5双),每两个人中间放一根,哲学家时而思考,时而进餐。
每个人都需要一双筷子才能吃到东西,吃完后将筷子放回原处继续思考,如果每个人都立刻抓住自己左边的筷子,然后等待右边的筷子空出来,同时又不放下已经拿到的筷子,这样每个人都无法得到1双筷子,无法吃饭都会饿死,这种情况就会产生死锁:
每个人都拥有其他人需要的资源,同时又等待其他人拥有的资源,并且每个人在获得所有需要的资源之前都不会放弃已经拥有的资源。
当多个线程完成功能需要同时获取多个共享资源的时候可能会导致死锁。
1:
两个任务以相反的顺序申请两个锁,死锁就可能出现
2:
线程T1获得锁L1,线程T2获得锁L2,然后T1申请获得锁L2,同时T2申请获得锁L1,此时两个线程将要永久阻塞,死锁出现
如果一个类可能发生死锁,那么并不意味着每次都会发生死锁,只是表示有可能。
要避免程序中出现死锁。
例如,某个程序需要访问两个文件,当进程中的两个线程分别各锁住了一个文件,那它们都在等待对方解锁另一个文件,而这永远不会发生。
3:
要避免死锁
publicclassDeadLock{
publicstaticvoidmain(String[]args){
newThread(newRunnable(){//创建线程,代表中国人
publicvoidrun(){
synchronized("刀叉"){//中国人拿到了刀叉
System.out.println(Thread.currentThread().getName()
+":
你不给我筷子,我就不给你刀叉");
try{
Thread.sleep(10);
}catch(InterruptedExceptione){
e.printStackTrace();
}
synchronized("筷子"){
System.out.println(Thread.currentThread()
.getName()+":
给你刀叉");
}
}
}
},"中国人").start();
newThread(newRunnable(){//美国人
publicvoidrun(){
synchronized("筷子"){//美国人拿到了筷子
System.out.println(Thread.currentThread().getName()
+":
你先给我刀叉,我再给你筷子");
try{
Thread.sleep(10);
}catch(InterruptedExceptione){
e.printStackTrace();
}
synchronized("刀叉"){
System.out.println(Thread.currentThread()
.getName()+":
好吧,把筷子给你.");
}
}
}
},"美国人").start();
}
}
5线程的通讯
线程间通信其实就是多个线程在操作同一个资源,但操作动作不同
生产者消费者
如果有多个生产者和消费者,一定要使用while循环判断标记,然后在使用notifyAll唤醒,否者容易只用notify容易出现只唤醒本方线程情况,导致程序中的所有线程都在等待。
例如:
有一个数据存储空间,划分为两个部分,一部分存储人的姓名,一部分存储性别,我们开启一个线程,不停地想其中存储姓名和性别(生产者),开启另一个线程从数据存储空间中取出数据(消费者)。
由于是多线程的,就需要考虑,假如生产者刚向数据存储空间中添加了一个人名,还没有来得及添加性别,cpu就切换到了消费者的线程,消费者就会将这个人的姓名和上一个人的性别进行了输出。
还有一种情况是生产者生产了若干次数据,消费者才开始取数据,或者消费者取出数据后,没有等到消费者放入新的数据,消费者又重复的取出自己已经去过的数据。
publicclassDemo10{
publicstaticvoidmain(String[]args){
Personp=newPerson();
Producerpro=newProducer(p);
Consumercon=newConsumer(p);
Threadt1=newThread(pro,"生产者");
Threadt2=newThread(con,"消费者");
t1.start();
t2.start();
}
}
//使用Person作为数据存储空间
classPerson{
Stringname;
Stringgender;
}
//生产者
classProducerimplementsRunnable{
Personp;
publicProducer(){
}
publicProducer(Personp){
this.p=p;
}
@Override
publicvoidrun(){
inti=0;
while(true){
if(i%2==0){
p.name="jack";
p.gender="man";
}else{
p.name="小丽";
p.gender="女";
}
i++;
}
}
}
//消费者
classConsumerimplementsRunnable{
Personp;
publicConsumer(){
}
publicConsumer(Personp){
this.p=p;
}
@Override
publicvoidrun(){
while(true){
System.out.println("name:
"+p.name+"---gnder:
"+p.gender);
}
}
}
在上述代码中,Producer和Consumer类的内部都维护了一个Person类型的p成员变量,通过构造函数进行赋值,在man方法中创建了一个Person对象,将其同时传递给Producer和Consumer对象,所以Producer和Consumer访问的是同一个Person对象。
并启动了两个线程。
输出:
显然屏幕输出了小丽man这样的结果是出现了线程安全问题。
所以需要使用synchronized来解决该问题。
packagecn.itcast.gz.runnable;
publicclassDemo10{
publicstaticvoidmain(String[]args){
Personp=newPerson();
Producerpro=newProducer(p);
Consumercon=newConsumer(p);
Threadt1=newThread(pro,"生产者");
Threadt2=newThread(con,"消费者");
t1.start();
t2.start();
}
}
//使用Person作为数据存储空间
classPerson{
Stringname;
Stringgender;
}
//生产者
classProducerimplementsRunnable{
Personp;
publicProducer(){
}
publicProducer(Personp){
this.p=p;
}
@Override
publicvoidrun(){
inti=0;
while(true){
synchronized(p){
if(i%2==0){
p.name="jack";
p.gender="man";
}else{
p.name="小丽";
p.gender="女";
}
i++;
}
}
}
}
//消费者
classConsumerimplementsRunnable{
Personp;
publicConsumer(){
}
publicConsumer(Personp){
this.p=p;
}
@Override
publicvoidrun(){
while(true){
synchronized(p){
System.out.println("name:
"+p.name+"---gnder:
"+p.gender);
}
}
}
}
编译运行:
屏幕没有再输出jack–女或者小丽-man这种情况了。
说明我们解决了线程同步问题,但是仔细观察,生产者生产了若干次数据,消费者才开始取数据,或者消费者取出数据后,没有等到消费者放入新的数据,消费者又重复的取出自己已经去过的数据。
这个问题依然存在。
升级:
在Person类中添加两个方法,set和read方法并设置为synchronized的,让生产者和消费者调用这两个方法。
publicclassDemo10{
publicstaticvoidmain(String[]args){
Personp=newPerson();
Producerpro=newProducer(p);
Consumercon=newConsumer(p);
Threadt1=newThread(pro,"生产者");
Threadt2=newThread(con,"消费者");
t1.start();
t2.start();
}
}
//使用Person作为数据存储空间
cla