------- android培训、java培训、期待与您交流! ----------
咖啡没有牛奶,依然是咖啡,但是缺少了甜美,正是因为有了多线程,Java才变得如此完美,我认为多线程是Java最迷人的特点之一。
首先要了解什么是线程呢?那要从进程说起,进程是一个正在执行中的程序。每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。而线程就是进程中的一个独立的控制单元,线程在控制着进程的执行。需要注意的是一个进程中至少有一个线程。可以看出,进程是包含线程的,就好比于进程是一个负责人,要完成一项工程,他就吩咐他下面的工人,也就是线程去做不同的事,线程是服务于包含它的进程的。在我们的计算机上,当然不止运行着一个进程,所以线程更是不计其数,我们设想,如果Java只能做到完成当前线程,再去执行下一个线程,那这一个进程执行完,要等long long time......所以多线程的出现,解决的这个问题,大大提高的系统运行的效率。java允许多个线程同时执行,当然,这个“同时”只是对于人的主观感觉来说的。对于单核机来说,cpu只有一个核心,所以他的执行权在一瞬间只能在一个线程手中。cpu就是在这很多个线程中快速的进程切换,给我一种并发执行的感觉,切换的速度之快,可以用微妙甚至纳秒来衡量,人们根本感觉不出来,只是感觉到多个线程在同时运行。
那么,如何自己创建一个线程呢?Java给我们提供了两种方法。
第一种:继承Thread类
1.子类覆盖父类中的run方法,将线程运行的代码存放在run中。
2.建立子类对象的同时线程也被创建。
3.通过调用start方法开启线程。任何类凡是继承了Thread类,它便成为一个线程类,包含了线程的特性,可以独立运行。首先要说明的一点是,继承Thread的类要覆盖run方法,这个run方法就是存放线程要执行代码的函数,你想要线程完成什么任务,就必须将代码写在run方法中。就好比主函数main,它是程序的起始位置,虚拟机就会找main函数中的代码来执行。在对线程类进行实例化的时候,建立了这个类的对象,同时线程也被创建,也就是这个对象指向了这个线程。在调用线程是,我们需要开启线程,通过对象调用start方法,只有调用了这个方法以后,线程才真的开始出去运行状态了,严谨点说,就是处于争夺cpu执行权的状态。
第二种:实现Runnable接口
1.子类覆盖接口中的run方法。
2.通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。
3.Thread类对象调用start方法开启线程。通过实现方法来实现多线程,将自定义的run方法存放于实现Runnable接口的类中,这点和继承方法相似,但是在创建线程时不同,是通过Thread类创建线程,通过Thread类中带参数的构造方法,将Ruunable接口的子类对象传递给Thread类的构造函数,然后通过Thread类的对象,调用start方法来开启线程。
例:Ticket t=new Ticket();//这里Ticket类实现Runnable接口.
Thread thread=new Thread(t);
thread.start();
这里有一个问题:为什么要将Runnable接口的子类对象传递给Thread的构造函数?
直接上答案:因为,自定义的run方法所属的对象是Runnable接口的子类对象。所以要让线程去执行指定对象的run方法。就必须明确该run方法所属对象。也就是说线程要执行的代码存放在Runnable接口的子类对象中,要执行它,就必须告诉线程它在哪。
实现方法的好处:避免了单继承的局限性。
因为一个类只能有一个直接父类,如果继承了Thread类,就没办法去继承别的类,影响扩展性。所以,在定义线程时,建议使用实现方式。
两种方式的区别:
继承Thread:线程代码存放Thread子类run方法中。
实现Runnable,线程代码存在接口的子类的run方法。
线程是包括四种状态的,有的参考书上分为了五种,在这里以毕老师说的为准:直接上图
具体还包含一个特殊的状态:就绪。具备了执行资格,但是还没有获取资源。
以上只是简单介绍了线程的基本情况,下面说一下线程的安全问题。
用哲学的思想来说,世上没有完美的事物。换作程序员的思想就是,bug无处不在。多线程再美好,也会有问题,而且是安全问题。对出现的问题不做阐述,先说一下原因吧:
导致安全问题的出现的原因:
1.多个线程访问出现延迟。
2.线程随机性 。
翻译成白话文就是:好多好多线程在争夺仅有的cpu执行权,很激烈~谁抢到谁就能执行,至于能执行多久,那就是cpu说的算了,可以想象,竞争很激烈,场面很混乱,因为根本不知道下一个执行的线程会是哪个,所以产生问题的几率是存在的,就好比买彩票,中奖的几率微乎其微,也还有好多人中大奖,难以预料的事情往往是最可怕的~!
说一下解决的办法,就是同步:
1.同步代码块:
格式:
synchronized(对象)
{
需要同步的代码;
}2.同步函数:
格式:
在函数上加上synchronized修饰符即可。同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。需要说明的是,同步函数的那个对象,也就是锁是this。
同步的前提:
1.同步需要两个或者两个以上的线程。
2.多个线程使用的是同一个锁。
未满足这两个条件,不能称其为同步。
同步的弊端:
当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形 中会降低程序的运行效率。
最后说一下线程间通信,在操作共享数据时,线程间需要通过沟通获得对方状态来决定自己对共享数据的操作,用我们的话说就是:商量着来好办事!
通过wait()-线程等待,notify()-唤醒一个线程,notifyAll()-唤醒全部线程,用来操作线程实现线程间通信,啥解释都不好使,直接上代码:
/* 需求:创建两个线程,一个写入数据,一个读出数据 */ class Resources { private String name; private String sex; private boolean flag=false; public synchronized void set(String name,String sex) { if (flag) { try { this.wait(); } catch (Exception e) { } } this.name=name; this.sex=sex; flag=true; this.notify(); } public synchronized void out() { if (!flag) { try { this.wait(); } catch (Exception e) { } } System.out.println(name+"...."+sex); flag=false; this.notify(); } } class Input implements Runnable { private Resources r; Input(Resources r) { this.r=r; } public void run() { int x=0; while (true) { if (x==0) r.set("LEO","man"); else r.set("刘","男"); x=(x+1)%2; } } } class Output implements Runnable { private Resources r; Output(Resources r) { this.r=r; } public void run() { while (true) { r.out(); } } } class InOutPutDemo { public static void main(String[] args) { Resources r=new Resources(); new Thread(new Input(r)).start(); new Thread(new Output(r)).start(); } }
搞定收工!另外说一句,感觉自己写的技术blog很不严肃啊,不知道这种风格能不能被接受~~!