------- android培训、java培训、期待与您交流! ----------
一个正在运行完整的Java应用程序实例就是一个进程,在这个应用程序中会保护许多小的执行单元,那么每一个执行单元或控制单元就是一个线程。一个应用程序必须至少有一个线程(主线程)。
一、线程的创建
Java中线程的创建方式有两种:1、继承Thread类实现run方法。通过例子说明创建及启动线程的步骤:
class MyThread extends Thread //1、继承Thread类
{
public MyThread(){}
public void run()//2、实现run方法
{
DoWork();//将要执行的功能
}
public void DoWork(){}
}
class MyThreadDemo
{
public static void main(String[] args)
{
MyThread thread=new MyThread();//3、创建线程子类的对象
thread.start();//4、利用start()函数启动线程,并调用run方法
}
}
注意:该创建方式的一个弊端:因为要创建某线程必须要继承Thread类,而很多情况下一个功能类很可能会继承其他类,Java只支持单继承,那么这个时候就无法再继承Thread类。为了解决这一问题,Java中提供了另外一种创建线程方式,通过实现接口来扩展功能。
2、由于第一中创建线程方式的局限性,第二种方式应运而生——通过实现Runnable接口复写run方法来实现线程的创建。其具体步骤:
class MyFun implements Runnable //1、实现Runnable接口,扩展功能
{
public MyFun(){}
public void run()//2、实现run方法
{
DoWork();//将要执行的功能
}
public void DoWork(){}
}
class MyThreadDemo
{
public static void main(String[] args)
{
MyFun fun=new MyFun();//3、创建MyFun类的对象(注意它不是线程对象)
Thread thread=new Thread(fun);//创建线程对象,并将fun引用作为参数传递给线程的构造函数,与线程建立联系
thread.start();//4、利用start()函数启动线程,并调用run方法
}
}
第二种方式只是对该类进行额外的功能扩展,java就提供了一个接口Runnable并定义了run方法,其实run方法的定义就是为了存储多线程要运行的代码。由于实现Runnable接口可以避免单继承的局限性,所以,通常创建线程都用第二种方式。
二、线程的几种状态。
线程主要有:被创建、运行、冻结(睡眠和等待)、消亡及阻塞等五种状态。
1、线程被创建后通过调用start方法使该线程具有资源的访问资格,同时具备执行权后线程进入运行状态。
2、如果start后该线程拥有了执行资格但是还不具备访问资源的执行权,此时该线程处于临时阻塞状态,当其他线程交出执行权且自己获得执行权后才进入运行状态。
3、对于运行中的线程,可以通过sleep和wait方式将线程进行冻结,对于sleep方式当睡眠时间到,线程自动进入运行状态。而wait方式只能等待别的线程调用notify或notifyAll才能唤醒进入运行状态。各状态之间转换的过程如下图所示:
三、多线程的问题,通常为满足一定的需求,会需要多个线程对同一个资源进行处理,比如一个数据库的写入操作、运算、读取操作等,可能会需要两个以上的线程对这样一个数据库进行处理,那么在线程运行期间会出现数据访问或写入错乱的问题,这就是多线程的安全问题。故未来解决线程安全问题,Java就提供了线程同步机制。
class MulThreaDemo extends Thread
{
private int count=0;
public MulThreaDemo(){}
public void run()
{
for(int i=0;i<10;i++)
{
count += 10;
System.out.println(Thread.currentThread().getName()+"----"+this.count);
Thread.sleep(20);
}
}
}
class Test
{
MulThreaDemo m1=new MulThreaDemo();
MulThreaDemo m2=new MulThreaDemo();
MulThreaDemo m3=new MulThreaDemo();
m1.start();
m2.start();
m3.start();
}
运行程序我们将会看到m1、m2、m3线程的打印结果是错乱排列的,我本意是要先打印m1结果,然后是m2、m3,这就是由于多个线程同事共享同一个资源造成的,那么,为了解决这个现象,可以利用关键字synchronized来标记同步代码块或同步函数。这个关键字就告诉jvm当前线程拥有访问权,在该线程结束前其他线程不能访问该段代码或函数,此刻其他线程处于等待状态位于线程池中,当线程1执行完后,放弃执行权,那么线程池中等待的线程顺序获得执行权,执行代码。
synchronized的使用方式有两种:同步代码块和同步函数。修饰一个代码块时,synchronized需要指定同步对象,而且保证多线程的同步对象为同一个对象。特殊情况:对于在静态方法内声明同步代码块的地方,在指定同步对象的时候要注意,因为静态方法是在加载类的时候就存在,先于对象引用存在,此时不能再指定所属对象为同步对象,而是要指定为该类的字节码文件对象。
第二种就是同步函数,直接在返回值前添加关键字synchronized(不能放置于返回值的后面)即可。注意同步函数使用的锁对象是this,而同步代码块可以使用任意对象为锁对象。特殊情况是静态方法为同步函数的情况,锁对象不再是this而是该类的字节码文件对象。
eg:
class SynTest
{
public SynTest(){}
//普通方法中指定同步代码块
public void method1()
{
synchronized(this){
//要同步的代码
}
}
//注意是静态方法中同步代码块
public static void method2()
{
synchronized(SynTest.class)
{
//需要同步的代码块
}
}
//普通同步函数
public synchronized void method3(){}
//静态的同步函数
public static synchronized void method4(){}
}
注意:判断线程同步的两个条件:1、必须有两个或两个以上的线程运行。2、多线程必须保证使用的是同一个同步锁(同步对象)。