以前对多线程已经总结过:
【java高级】简说多线程(上)
【java高级】简说多线程(下)
现在又翻看多线程,对其理解的更系统了,现总结如下。
线程的操作 |
(1)线程的生命周期
- 新建
当程序使用new关键字创建了一个线程之后,该线程就处于新建状态。此时它和其它的java对象一样,仅仅由jvm为其分配内存,并初始化其属性的值。
- 就绪
jvm为其创建方法调用栈和程序计数器,此时的线程表示自己可以运行了,但具体什么时候运行,取决于jvm里的线程调度器的调度。
- 运行
我们知道,cpu用于执行线程,即使有多个线程,cpu也只能一次执行一个。线程获得资源的方式有两种:共用式和抢占式。目前操作系统大多采用抢占式,因为其效率更高。
- 阻塞
线程进入阻塞状态的情况:
a、调用sleep()方法主动放弃所占用的处理器资源
b、试图获得一个锁,但此时这个锁正在被其它线程所持有(synchronized、lock)
c、线程在等待某个通知(notify)
d、调用了一个阻塞式IO方法,在改方法返回前,该线程被阻塞
需要注意的是,程序从阻塞状态只能进入就绪状态,而不能进入运行状态。这个在生命周期图中也能看出来。
- 死亡
线程就像人的生命,出生和死亡都只能有一次。当一个线程死亡后,无法再用start()方法重启它。
(2)创建和启动
线程的创建有两种方式:继承Thread类;实现Runnable接口。推荐采用后者。
线程的启动是调用start()方法。
//通过实现Runnable接口来创建线程类
public class Thread implements Runnable
{
private int i;
public void run(){
for(;i<100;i++)
{
sout(Thread.currentThread().getName()+""+i);
}
}
public static void main(String[] args){
for(int i=0;i<100;i++)
{
sout(Thread.currentThread().getName()+""+i);
if(i==20){
Thread t=new Thread();
new Thread(t,"新线程1").start();
new Thread(t,"新线程2").start();
}
}
}
}
(3)线程的控制
- join()方法
作用:让一个线程等待另一个线程执行完后再往下执行。
public class JoinThread extends Thread
{
public Thread(String name)
{
super(name);
}
public void run(){
for(int i=0;i<100;i++)
{
sout(getName()+""+i);
}
}
public static void main(String[] args) throws Exception{
//启动子线程
new JoinThread("新线程").start();
for(int i=0;i<100;i++)
{
if(i==20){
JoinThread jt=new JoinThread("被Join的线程");
jt.start();
//main线程必须等jt线程执行结束后才会向下执行
jt.join();
}
sout(Thread.currentThread().getName()+""+i);
}
}
}
- sleep()、yield()方法
sleep()用于让正在执行的线程暂停一段时间,并进入阻塞状态。
//让当前线程暂停1s
Thread.sleep(1000);
yield()用于让正在执行的线程暂停,进入就绪状态。和sleep()的区别是,sleep()就睡死过去了。yield()就像让线程又回到起始点,和其它线程一起竞争cpu的资源,它还是有可能竞争上的。
- 改变优先级
每个线程执行时都有一定的优先级,优先级高的线程会获得更多的执行机会。注意,是更多的机会,更大的概率,不是肯定会竞争到资源。
//设置优先级,范围是1~10,10是优先级最高的
setPriority(int newPriority);
//返回指定线程的优先级
getPriority();
- 后台线程
后台线程是在后台运行的,为其它线程提供服务的线程。它的特点是如果所有的前台线程都死亡,后台线程会自动死亡。jvm的垃圾回收线程就是典型的后台线程。
//设置成后台线程,需要在线程启动之前设置
setDaemon(true)
//判断线程是否为后台线程
isDaemon();
同步 |
让线程之间进行同步执行,可以使用锁。线程同步就像上厕所,总得一个一个来,你不起来,别人没发上。同样,当你来了发现有人在占用厕所,那你就需要等待。对应到线程上就是:
a、进厕所,锁上门——获取锁
b、解决个人问题——修改
c、解决完出门,把锁打开——释放锁
(1)Synchronized
//同步代码块
synchronized(account)
{}
//同步方法,用synchronized修饰某个方法
public synchronized void draw()
{}
(2)Lock
使用Lock可以显示定义同步锁对象来实现同步。其有一个实现类ReentrantLock(可重复锁)。
class x
{
//定义锁对象
private final ReentrantLock Lock=new ReentrantLock();
//……
//定义需要保证线程安全的方法
public void m()
{
lock.lock();
try{
//需要保证线程安全的代码
//……
}
//使用finally来释放锁
finally{
lock.unlock();
}
}
}