1.进程和线程
进程是程序的一次动态执行过程,它经历了从代码加载、执行到执行结束的完整过程,这个过程也是进程本身从产出、发展到最终消亡的过程。如在一个操作系统中,一个进程相当于整个应用程序。
线程是比进程更小的执行单位,线程是进程内部单一的一个顺序控制流。所谓多线程是指一个进程在执行过程中可以产生多个线程,这些线程同时存在、同时运行,形成多条执行线索。一个进程可能包含了多个同时执行的线程。
线程和进程的主要区别:(1)线程是比进程更小的执行单位;(2)每个进程都有自己专用的内存区域(包括代码和数据);而线程却共享内存单元,通过共享的内存单元来实现数据交换、实时通讯与必要的同步操作;
2.线程的介绍
线程是指程序的运行流程,多线程就是一个程序可以同时运行多个代码段,使程序运行的效率更高。比如,有些程序需要作一些费的向网络请求数据的操作,这个操作就可以交给另一个线程去处理,而主线程作本来要作的界面处理。多线程机制就是多个线程都持有自己的代码块,而这些代码块的执行需要有系统时间片的分配才能运行,而系统时间片在什么时候分配在哪片代码块上是有自己的一套规则的,这个我们不需要了解。
进程的两种实现方式:
(1)通过继承Thread实现多线程:下面有一个代码例子:
public class ThradDemo1 {
public static void main(String[] args) {
TestThread t = new TestThread();
t.start();
while(true){
System.out.println("mainThread.");
}
}
}
class TestThread extends Thread{
public void run() {
while(true){
System.out.println("TestThread.");
}
}
}
输出结果:
mainThread.
mainThread.
mainThread.
mainThread.
mainThread.
TestThread.
TestThread.
TestThread.
TestThread.
输出的结果很多,这里只是截取部分的内容;从中可见,mainThread和TestThread各轮流输出一部分,其实他们都在各自不同的线程上输出,所以能交易执行自己的代码块,所以交替输出自己的内容。
(2)通过实现Runnable借口实现多线程
public class ThreadDemo2 {
public static void main(String[] args) {
TestThread t = new TestThread();
new Thread(t).start();
while(true){
System.out.println("mainThread.");
}
}
}
class TestThread implements Runnable{
public void run() {
while(true){
System.out.println("TestThread.");
}
}
}
这个方式需要注意的是要启动Thread的start()方法启动多线程时,必须带进实现Runnable的TestThread实例。
(3)两种多线程实现方法的区别,实现Runnable借口相对于继承Thread类有如下优势:
(a)适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码、数据有效分离,较好地
体现了面向对象的设计思想;
(b)可以避免由于Java的单继承性带来的局限,开发中常碰到这样的情况:当要将已经继承了一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么就只能用实现Runnable借口的方式了。
(c)增强了程序的健壮性,代码能够被多个线程共享,代码与数据独立的。当多个线程的执行代码来自同一个类的实例时,即称它们共享相同的代码,多个线程可以操作相同的数据,与它们的代码无关。当共享访问相同的对象时,即共享相同的数据。当线程被构造时,需要的代码和数据通过一个对象作为构造函数实参传递过去,这个对象就是一个实现了Runnable借口的类的实例。
3.线程的状态
每个java程序都具有一个缺省的主线程,对于java应用程序,一般在main()方法上执行的程序就是主线程。要实现多线程,需要在主线程创建新的线程对象。任何一个线程都具有五种状态:即创建、就绪、运行、阻塞和终止。
(1)新建状态:在程序中用构造方法创建了一个线程对象后,新的进程对象便处于新建状态,此时它已经有了相应的内存空闲和其他资源,但还处于不可运行状态;新建一个线程可用该方法:Thread t = new Thread();
(2)就绪状态:当新建的线程对象调用start()方法后,就可以启动线程,启动线程后该线程就处于就绪状态;此时,线程进入线程队列,等待CPU时间片与其对接,如果对接了一个线程,该线程就启动线程的run()方法;
(3)运行状态:当排在线程队列中的线程获得CPU时间片服务时,该线程就处于运行状态了,此时系统会调用该线程的run()方法,
run()方法定义了该线程的功能和方法;
(4)阻塞状态:
一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入输出操作时,将让出CPU并暂时中止自己的执行,进入堵塞状态。在可执行状态下,如果调用sleep()、suspend()、wait()等方法,线程都将进入堵塞状态。堵塞时,线程不能进入排队队列,只有当引起堵塞的原因被消除后,线程才可以转入就绪状态。
(5)死亡状态:线程调用stop()方法时或run()方法执行结束后,线程即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。
4.线程的强制运行
当线程调用了join()方法后,系统就会强制该线程执行完所有操作,然后再让出时间片给其他线程。该方法还有join(long millis)和join(long millis,int nanos)两个重载方法,它们的作用是指定合并时间,前者精确到毫秒,后者精确到纳秒,意思是调用join()方法的线程强制执行的时间,等给定参数的时间一到,就还原回之前的状态,各自的线程同样又轮流得到时间片的执行;实例代码如下:
public class ThreadDemo3 {
public static void main(String[] args) {
TestThread t = new TestThread();
Thread tt = new Thread(t);
tt.start();
for(int i=0; i<20; i++){
if(i==5){
try {
tt.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("mainThread..."+i);
}
}
}
class TestThread implements Runnable{
public void run() {
for(int i=0; i<20; i++){
System.out.println("TestThread..."+i);
}
}
}
TestThread...0
mainThread...0
mainThread...1
mainThread...2
mainThread...3
mainThread...4
TestThread...1
TestThread...2
TestThread...3
TestThread...4
TestThread...5
TestThread...6
TestThread...7
TestThread...8
TestThread...9
TestThread...10
TestThread...11
TestThread...12
TestThread...13
TestThread...14
TestThread...15
TestThread...16
TestThread...17
TestThread...18
TestThread...19
mainThread...5
mainThread...6
mainThread...7
输出结果只是截取一部分,可以看出;在i=5之前两个线程在轮流执行,而当i=5后,系统就强制让test线程执行完所有操作,也就是输出到TestThread...19时,test线程全部执行完毕,然后剩下主线程了。主线程就把之前没执行完的操作执行完,也就是继续输出mainThread...5后面的所有字符串。
5. 线程同步
以下代码模拟买票的过程,现有5张车票,四个柜台可以同时购买,卖完为止。而且票也被标为票1,票2…… 哪个柜台卖了哪张票都有显示出来。
public class ThreadDemo2 {
public static void main(String[] args) {
TestThread t = new TestThread();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class TestThread implements Runnable{
int tickets = 5;
public void run() {
while(true){
if(tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售出票"+tickets--);
}
}
}
}
输出结果:
Thread-2售出票4
Thread-0售出票5
Thread-3售出票3
Thread-1售出票2
Thread-0售出票1
Thread-2售出票0
Thread-3售出票-1
Thread-1售出票-2
很明显不符合实际情况,票只有5张,现在却卖出了8张,还有票号为负数的情况。这是怎么回事呢?原因是主线程开辟了四个线程,四个线程都访问了同一个代码块,都是执行了run()方法里面的代码。试想一下,当tickets=1且线程1执行到if(tickets>0)这个语句时,线程1因sleep()阻塞;程序跳到另一个线程3,这是tickets依然为1,线程3同样执行到if(tickets>0)往下又产生阻塞又跳到另一个线程……而当线程1阻塞接触且时间片交给它时,程序往下执行,这时tickets就减为0了,依次类推,程序回到线程3时tickets就为-1了。出现这种情况的原因是资源数据访问不同步引起的,这时线程不安全的行为。解决的办法就是让每个线程在执行完整个while里面所有代码后,才能让出时间片让其他线程去执行。while代码块就像是个独木桥,每次只能有一个人通过。要实现这个功能,java为我们准备了语法:
…
synchronized(对象)
{
需要同步的代码 ;
}
…访问控制符 synchronized 返回值类型方法名称(参数)
{
…. ;
}
其中每个对象都默认拥有一把锁(该锁不用任何代码实现就能拥有),当某一线程调用了含synchronized的代码块或方法时,该线程就获得了该对象的锁,然后就可执行synchronized内的代码,而这个时候该对象的所有synchronized代码块或方法都会被锁定,其他线程都不可以访问,除非它们获得了该对象的锁。
public class ThreadDemo2 {
public static void main(String[] args) {
TestThread t = new TestThread();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
new Thread(t).start();
}
}
class TestThread implements Runnable{
int tickets = 20;
public void run() {
while(true){
synchronized (this) {
if(tickets>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售出票"+tickets--);
}
}
}
}
}
输出结果:
Thread-0售出票20
Thread-0售出票19
Thread-0售出票18
Thread-3售出票17
Thread-2售出票16
Thread-2售出票15
Thread-1售出票14
Thread-1售出票13
Thread-2售出票12
Thread-2售出票11
Thread-2售出票10
Thread-2售出票9
Thread-3售出票8
Thread-3售出票7
Thread-0售出票6
Thread-0售出票5
Thread-0售出票4
Thread-0售出票3
Thread-0售出票2
Thread-0售出票1
6.线程的通讯
Java提供了3个非常重要的方法来巧妙地解决线程间的通信问题。这3个方法分别是:wait()、notify()和notifyAll()。它们都是Object类的最终方法,因此每一个类都默认拥有它们。虽然所有的类都默认拥有这3个方法,但是只有在synchronized关键字作用的范围内,并且是同一个同步问题中搭配使用这3个方法时才有实际的意义。其中,调用wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行态退出,进入等待队列,直到被再次唤醒再从退出时的那个位置进去程序。而调用notify()方法可以唤醒等待队列中第一个等待同一共享资源的线程,并使该线程退出等待队列,进入可运行态。调用notifyAll()方法可以使所有正在等待队列中等待同一共享资源的线程从等待状态退出,进入可运行状态,此时,优先级最高的那个线程最先执行。
public class ThreadCommunication {
public static void main(String[] args) {
P q = new P();
new Thread(new Producer(q)).start();
new Thread(new Comsumer(q)).start();
}
}
class Producer implements Runnable{
P p = null;
public Producer(P p){
this.p = p;
}
@Override
public void run() {
int i=0;
while(true){
if(i==0){
p.set("李四","男");
}
else{
p.set("张三","女");
}
i=(i+1)%2;
}
}
}
class Comsumer implements Runnable{
P p = null;
public Comsumer(P p){
this.p = p;
}
@Override
public void run() {
while(true){
p.get();
}
}
}
class P{
String name = "张三";
String sex = "女";
boolean isSet = false;
public synchronized void set(String name, String sex){
if(isSet){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
this.name = name;
this.sex = sex;
this.isSet = true;
notify();
}
public synchronized void get(){
if(!isSet){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(this.name+"-->"+this.sex);
isSet = false;
notify();
}
}
输出结果:
张三-->女
李四-->男
张三-->女
李四-->男
张三-->女
李四-->男
张三-->女
李四-->男
张三-->女
李四-->男
张三-->女
李四-->男
张三-->女
李四-->男
张三-->女
李四-->男
张三-->女
李四-->男
张三-->女
李四-->男
……