一、Thread类(其实也是应用了Runnable接口)和Runnable接口(只有一个run方法,应用该类必须重写run方法)
一般我们定义一个线程类,可以继承Thread类或者应用Runnable接口,不管是继承哪个,都要重写run方法。
比如我们定义一个线程类:
public class Threadtest implements Runnable
{
@Override
void run()
{
//写你需要这个进程进行的操作
}
}
然后在主方法里面生成线程对象并调用start()方法运行:
public static void main(String args[]){
new Thread(new Threadtest()).start();
}
一般来说线程有以下几种状态:
新生(New):这个状态下我们通过new关键字生成了新的线程Thread对象,为它分配了内存空间,但是这个线程并不是存活状态;
就绪(Runnable):线程对象调用start()方法,线程进入runnable状态,等待分配CPU资源。这个时候线程是存活的;
运行(Running):获取CPU资源后,线程开始进行,run()方法被执行,直到线程结束,或者因为资源耗尽等原因被中断或挂起;
阻塞(Blocked):线程调用join()、sleep()和wait()等方法而暂处阻塞状态;
死亡(Dead):线程结束,或者因为异常而被迫中断或异常退出,生命周期结束。
三、sleep方法和wait方法
3.1、sleep方法(只释放CPU,不释放锁),比如一个变量i,好几个线程都要用到,线程A调用sleep方法只释放了CPU,但是A没有释放i的锁,而那几个线程又刚好需要i达到一定条件才能继续运行,所以线程A就算释放了CPU也没用。
3.2、wait方法(释放CPU和锁),比如有两个进程,进程A调用wait方法之后,进程释放CPU和锁,要等待线程B调用notify方法或notifyAll方法来通知A,你可以来抢夺资源了,但是A不一定就能抢得到。
四、synchronized同步标志
使用场合:比如这里有个资源j,好几个线程都要用到,我们把它叫做竞争资源(竞态条件),对这样的共享资源我们就要进行同步操作,以免一个线程对这个变量的操作被另一个线程覆盖了。通常有两种使用方法:
1、public/private/protected 函数返回类型 synchronized 函数名(形参)
{
//函数内容
}
例如:
public void synchronized FuncName()
{
//do something
}
2、public/private/protected 函数返回类型 函数名(形参)
{
synchronized(this)
{
//需要进行同步操作的函数内容
}
//其他操作
}
下面来个经典的面试题:给个变量j给你,叫你写个两个函数(线程)一个对它进行累加操作,一个对它进行递减操作。然后在主函数里产生四个线程,两个进行累加,两个进行递减。
View Code
五、Lock方法和unLock方法
jdk5.0版本及之前只有synchronized,后来就有了Lock方法,synchronized能做的事情Lock方法都能做,而且锁的获取和释放更加明确,因为编程人员需要明确给出释放锁的时机,是更严谨的机制。
这个的用法也比较简单,我们先给出下面的类定义,方便解释:
View Code
上面的代码没编译运行过,因为直接在文本文档里面敲的,不过也就是那个意思。你要对一个竞态资源进行写操作的时候,先获得锁,这样其他进程没办法对它进行访问,操作完成之后释放锁。
为了有个确定能运行的例子,去抄了别人写的可以运行的测试代码过来:
View Code
如果是多个线程读取,一个线程进行写的话,只需要给进行写操作的进程锁即可,读取操作那些线程一般是不需要锁的。