本帖最后由 杨玲 于 2013-1-27 23:25 编辑
------- <a target="blank">android培训</a>、<a target="blank">java培训</a>、期待与您交流! ----------
要说线程首先需要知道进程.在计算机中,当CPU执行时一段代码时,会先把该段代码翻译成一条条的可执行指令,CPU一条一条的执行这些指令,
而计算机中肯定不止一段代码吧.那么CPU是如何区分每一段代码是哪一段程序的呢?
其实在程序执行之前,计算机会为每一个程序分配一段空间,用来存放该程序的代码和数据以及一些更具体的控制信息.而这些东西就被叫做进程,
说得更简单一点就是,进程就是一段包含了一个程序的代码,数据,状态等等信息的内存空间.而CPU就通过这些信息来判断这段代码是哪一个程序的,
它执行到哪,下一条指令是哪一条,执行到哪结束等等.我们可以简单的把进程理解为一个正在执行的程序.
而线程呢,它和进程的思想是一致的,它是在进程空间中分配的一段包含了一段代码,数据,状态等等信息的内存空间,而CPU其实就是根据进程中
的线程空间中的信息来判断要执行的代码的.我们可以简单的把线程理解为一条执行路径.
在一个进程中可以存在一个或多个这样的执行路径(线程),而这些线程的执行顺序取决与操作系统的调度算法.所以它们的执行顺序是不确定的
那么在java这样一个一切皆对象的世界里线程这个存在也就很自然的成为了一个对象:Thread.要在java程序中建立一个线程有两种方法,
方法1:继承Thread类.
需要以下几个步骤:
//1.创建一个类继承自Thread,并覆盖它的run方法.并把要让这个线程执行的代码写在该方法中
class MyThread extends Thread
{
//覆盖run方法
public void run()
{
/*
由于线程其实就是一段存储在进程空间中的代码.数据.状态等信息.所以在创建线程时自然要把这些代码和数据单独的放在一个地方,而这个地方
就是run方法内.所以在该方法中的代码就是要让该线程执行的代码
*/
}
}
//2.在要创建这个线程的程序中创建并启动这个线程.
class Demo
{
public static void main(String args[])
{
/*
创建并启动该线程,不能直接调用run方法,因为这样和调用一个普通的方法没区别.
必须要启动这个线程,而启动线程调用的就是Thread类中的start()方法.在该方法中
会调用创建线程的本地方法,启动一个线程并调用run方法.
*/
new MyThread().start();
}
}
这样一个最简单的线程就创建并启动了,需要说一点的是在这个程序中,其实还有另一个线程main(主线程),虽然它除了创建和启动一个线程外没有其它代码.
方法二: 实现Runnable接口.
由于java中只支持单继承,这也就意味着如果继承了Thread类就没办法继承别的类了,考虑到这一点,写java的那帮工程师们就想到了这样一个方法:把
用于存放线程要执行的代码的run方法提取出来封装成一个接口.别的类只用实现这个接口并覆盖run方法,并把它传递给Thread类就可以了,而这个接口就是
Runnable,这样就解决了java中只支持单继承的这个局限性.其实Thread类也是实现了Runnable接口的,而要想创建一个线程就可以让这个类实现Runnable接
口,并调用Thread类的一个需要实现Runnable接口的类为参数的构造函数就可以了.
具体如下:
class MyThread implements Runnable//实现Runnable接口 在这里就可以继承其它类了
{
//覆盖Runnable接口中的run方法
public void run()
{
//需要线程执行的代码
}
}
class Demo
{
public static void main(String args[])
{
//创建并启动线程.把MyThread对象的一个实例作为参数传递给Thread.
new Thread(new MyThread()).start();
}
}
关于线程的一些获得和设置方法可以从API文档中查到,这里就不去一一的试验了.这里要说的是另一个问题:线程间通信.
我想在两个线程间通信又该如何去做呢?
这里还是用毕老师讲的用引用传递的方式吧.
//创建一个要在多个线程间通信的类
public class Resource
{
//随便来点属性
}
class MyThread1 implements Runnable
{
//把这个资源类封装在内部,并在构造的时候把引用传递进来
private Resource res;
public MyThread1(Resource res)
{
this.res = res ;
}
//实现Runnable中的run方法
public void run()
{
}
}
class MyThread2 implements Runnable
{
//把这个资源类封装在内部,并在构造的时候把引用传递进来
private Resource res;
public MyThread2(Resource res)
{
this.res = res ;
}
//实现Runnable中的run方法
public void run()
{
}
}
class Demo
{
public static void main(String args[])
{
//创建一个资源类对象,并把它做为参数分别传入要共享这个资源的线程中.
Resource res = new Rresource();
new Thread(new MyThread1(res)).start();
new Thread(new MyThread1(res)).start();
}
}
不知道你们有没有发现,做了这么多的事,其实它的主要目的就是要实现内存的共享.所以要想实现线程间通信只要让多个线程都要操作的这些数据
通过任意的方式让多个线程都可以共享就行了.
好吧!多线程间通信的问题解决了,但是同时还带来了另一个问题:当一个线程在修改共享数据中的内容时,修改到一半另一个线程就去取数据等等这
样的问题,这就是多线程间的共享数据的安全问题.我们要相信做出java的这帮哥们不是SB,所以他们自然考虑到了这个问题,而他们给出的解决方案
就是同步:synchronized 修饰关键字.用该关键字修饰的块或函数在同一时间只允许一个线程调用或访问.换句话说就是一个线程在用别的线程就都
不能用.synchronized 关键字有以下两种用法:
语句块格式:synchronized(任意引用类型){需要同步的代码}
修饰函数格式: 访问权限 其它修饰关键字(可选) synchronized 返回值类型 函数名(参数列表){需要被同步的代码}
那么这个synchronized关键字是怎样达到同步这个效果的呢?
先看看语句块这里.它为什么需要(任意引用类型)这样的一个参数呢?
其实括号里面的这个任意引用类型是一个锁.而这个锁可以是任意引用类型的,当一个线程执行到用synchronized修饰的语句块的时候,它会先去获得
这个锁,如果这个锁被别的线程所持有,那么在别的线程没有释放这个锁时,它就只能等待别的线程释放了这个锁之后,它才能获得这个锁并执行被这个锁
所锁住的代码.而如果这个锁没有被别的线程持有,那么它就可以获得这个锁,并执行被这个锁所锁住的代码.(这里有一点疑惑的是,在底层是把这样一个锁
作为一个标识符存在的吗?)就这样实现了同步,从而保证了数据的完整性.
咦!那么用它来修饰函数的时候它的锁又是什么呢?在非静态函数中这个锁是this,而在静态函数中这个锁是这个函数所在类的类类型对象,也就是:类名.Class.
在同步中还需要注意几点,一个是当用synchronized修饰了一个方法或者代码块的时候因为每次都要去判断锁,所以速度有所下降,为了程序的效率别乱用.
另一个是使用同步的前提:必须是两个或两个以上的线程要同时操作被同步的数据,这多个线程使用的必须是同一个锁.必须保证同步中只能有一个线程访问.
等待唤醒机制:由于线程的执行顺序是不确定的,所以为了控制线程的执行顺序可以通过这样的一个机制,方法是调用wait,notify方法,这两个方法定义在Object中.
在前面的线程的定义中说到了线程是在进程空间中分配的一段包含了一段代码,数据,状态等等信息的内存空间,而要想理解等待唤醒机制就需要了解线程的状态.
线程的状态包括(Windows系统中的别的系统可能不同):运行,等待,就绪,备用,转换,终止.其中运行,就绪和备用表示该线程可以被立即执行,而等待,转换和终止表示
程序因为其种原因不能被立即执行.而调用wait方法实际上就是把线程状态设置为等待状态.让其不可以被立即执行,notify方法同理,就是改变线程状态为就绪态,
关于线程状态的更具体的内容可以看操作系统相关的书籍.
在JDK1.5中,Lock 接口把这个隐式的锁,显式的表示了出来,具体的可以看java.util.concurrent.locks包中的 Lock 接口.在该接口中可以返回一个绑定到该
锁的实例的Condition对象,这个对象主要就是打算替代Object中的wait,notify等方法的
以上就是我所了解的于多线程有关的所有内容!如果有什么不足或不对的地方还请指正一下!谢谢了!
|
|