1、定义线程的方法:
1.1 继承Thread类
1.2 实现Runnable接口
2、线程的生命周期
New(新建):当一个线程对象被创建后,线程就处于新建状态。
在新建状态中的线程对象从严格意义上看还只是一个普通的对象,它还不是一个独立的线程。
处于新建状态中的线程被调用start方法后就会进入准备状态。
从新建状态中只能进入准备状态,并且不能从其他状态进行新建状态。
新建状态是线程生命周期的第一个状态。
Runnable(准备):处于新建状态中的线程被调用start方法就会进入准备状态。
处于准备状态下的线程随时都可能被系统选择进入运行状态,从而执行线程。
可能同时有多个线程处于准备状态,对于哪一个线程将进入运行状态是不确定的。
线程从新建状态进入到准备状态后是不可能再进入新建状态的。
在等待/阻塞状态中的线程被解除等待和阻塞后将不直接进入运行状态,而是首先进入准备状态,让系统来选择哪一个线程进入运行状态。
Running(运行):处于准备状态中的线程一旦被系统选中,使线程获取了CPU时间,就会进入运行状态。在运行状态中将执行线程类run方法中的程序语句。
线程进入运行状态后也不是一下执行结束的,线程在运行状态下随时都可能被调度程序调度回准备状态。
在运行状态下还可以让线程进入到等待/阻塞状态。
在通常的单核CPU中,在同一时刻只有一个线程处于运行状态的。在多核的CPU中,就可能两个线程或者更多的线程同时处于运行状态,这也是多核CPU运行速度快的原因
Blocked(等待、阻塞):在Java中定义了许多线程调度的方法,包括睡眠、阻塞、挂起和等待,使用这些方法都会将处于运行状态的线程调度到等待/阻塞状态。
处于等待/阻塞状态的线程被解除后,不会立即回到运行状态,而是首先进入准备状态,等待系统的调度。
Dead(死亡):当线程中的run方法执行结束后,或者程序发生异常终止运行后,线程会进入死亡状态。
处于死亡状态的线程不能再使用start方法启动线程,但是这不代表处于死亡状态的线程不能再被使用,它也是可以再被使用的,只是将被作为普通的类来使用。
3、线程的调度
通过系统自动调度,线程的执行顺序是没有保障的。
在Java中定义了一些线程调度的方法,使用这些方法在一定程序上对线程进行调度,使用这些方法只是给线程一个建议,具体是否能够成功,也是没有保障的。
线程调度的方法有几个,包括睡眠方法、设置优先级、让步方法等
3.1 睡眠方法
当线程处于运行状态时,调用sleep睡眠方法将使线程从运行状态进入等待/阻塞状态,从而使程序停止运行。
sleep睡眠方法是具有一个时间参数的,当经过这么长时间后,线程将进入准备状态,等待系统的调度。
从而可以看出,当线程调用睡眠方法后,要想回到运行状态,需要的时间要比指定的睡眠时间长。
sleep方法只是给线程一个调度的建议,是否调度成功是不能确定的
sleep方法的基本语法:
public static void sleep(long millis);(毫秒)
public static void sleep(long millis,int nanos);(毫秒,纳秒)
3.2设置优先级
不同的线程可以具有不同的优先级,优先级高的线程就会占用更多的CPU资源和被执行概率。
Java中的优先级是采用从1到10来表示的,数字越大表示优先级越高。如果没有为线程设置优先级,则线程的优先级为5,这也是线程的默认值。
当需要对线程的优先级进行设置时,可以通过调用setPriority方法来设置。setPriority方法的语法:
public final void setPriority(int i);(0<=i<=10或者Thread.MAX_PRIORITY,Thread.NORM_PRIORITY,Thread.MIN_PRIORITY)
3.3让步方法
3.3.1 yield让步方法
yield让步方法是让线程让出当前CPU,而将CPU让给哪一个线程是不确定的,由系统来进行选择。使用yield让步方法的线程将从运行状态进入到准备状态。
yield让步操作是可能不成功的。因为在线程中使用yield方法,使该线程进入准备状态。但是系统是有可能再次选择该线程,使该线程进入运行状态的。
yield让步方法的基本语法:public static void yield(); 静态方法与对象无关。
3.3.2 join让步方法
使用join让步方法,可以将当前线程的CPU资源让步给指定的线程。
join让步方法的语法格式:
public final void join(); 指定的线程执行完成后再执行其他线程
public final void join(long mills);在参数的时间内执行让步给的执行线程
public final void join(long millis,int nanos);在参数的时间内执行让步给的执行线程
与对象有关,所以可以让步给指定线程。
4、加锁
多个线程同时对同一对象的状态做操作时可能会出现相互倾轧现象,造成不准确,于是引入锁的概念,对代码块加锁后可以保证不受并发访问的困扰。
4.1 ReentrantLock类加解锁
ReentrantLock lock = new ReentrantLock();
lock.lock();
......
lock.unlock();
4.2 Synchonzied关键字加锁
在方法申明中添加Synchonzied关键字即可,例如:
private synchonized void lock();
4.3 读写锁
ReentrantReadWriteLock lock2 = new ReentrantReadWriteLock();
Lock readLock = lock2.readLock();
Lock writeLock = lock2.writeLock();
加读锁(给访问者)
readLock.lock();
......
readLock.unlock();
加写锁(给修改者)
writeLock.lock();
......
writeLock.unlock();
5、线程生命周期图示
6、代码使用例子:
public class ThreadTest01 {
/**
* 继承Thread类的方式来定义线程
* @author Denny
*
*/
class Thread01 extends Thread{
@Override
public void run() {
for(int i =0;i<10000000;i++){}
ReentrantLock lock = new ReentrantLock();
lock.lock();
System.out.println("线程1运行中[继承Thread类]");
lock.unlock();
}
}
/**
* 实现Runnable接口的方式定义线程
* @author Denny
*
*/
class Thread02 implements Runnable{
public void run() {
for(int i = 0;i<10000000;i++){}
System.out.println("线程2运行中[实现Runnable接口]");
}
}
/**
* 线程使用
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
//对象声明及调用[继承Thread类]
Thread01 tr = new ThreadTest01().new Thread01();
tr.start();
System.out.println("线程开始等待");
Thread.sleep(1000*2);
System.out.println("线程结束等待");
//对象声明及调用[实现Runnable接口]
Thread02 temp = new ThreadTest01().new Thread02();
Thread tr2 = new Thread(temp);
tr2.start();
System.out.println("线程开始让步");
tr2.join(1000*2);
System.out.println("线程结束让步");
}
}