1.线程的定义与创建
1.1线程的定义
每一个任务称作一个线程, 它是程序执行的最小单位;线程又叫做轻量级进程,可以把一个进程的资源分配和执行调度分开;线程是CPU调度的最小基本单位,各个线程既可以共享进程资源,又可以独立调度;各个线程拥有自己的栈空间
1.2线程与进程的区别
1)线程是CPU调度的最小基本单位;进程是资源分配的最小单位
2)进程有独立的地址空间,一个进程崩溃后在保护模式下不会对其他进程产生影响;线程没有独立的地址空间,只是一个进程中的不同执行路径,有自己的堆栈和局部变量,一个线程死掉就等于整个进程死掉。多进程的程序比多线程的程序健壮。
3)进程在执行过程中拥有独立的内存单元,而多个线程共享内存,多线程的程序运行效率更高,资源利用率高。
4)一个程序至少要有一个进程,一个进程至少有一个线程。考虑到安全性和稳定性的要求选择多进程;不同任务间需要大量共享数据或频繁通信时,需要频繁创建销毁时选择多线程。
1.3线程的创建方式
1)继承Thread类,重写run()方法
步骤:
(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程
class Mythread extends Thread{
@Override
public void run() {
.......
}
}
public class ThreadTest01 {
public static void main(String[] args) {
Thread mythread = new Mythread();//创建线程对象
mythread.start();//启动线程
}
}
2)实现Runnable接口,重写run()方法
步骤:
(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
(3)调用线程对象的start()方法来启动该线程
class MyRunnable implements Runnable{
@Override
public void run() {
......
}
}
public class ThreadTest02 {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());//线程对象、
thread.start();
}
}
3)实现Callable接口,重写call()方法
步骤:
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "看电视";
}
}
public class ThreadTest03 {
public static void main(String[] args) {
MyCallable myCallable = new MyCallable();
FutureTask<String> future = new FutureTask<>(myCallable);
Thread thread = new Thread(future);
thread.start();
try {
System.out.println(future.get());//get()拿到call()结果
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
注:
Callable和Runnable接口的区别
实现Runnable接口重写run()方法,无返回值,不会抛出异常;创建Runnable实例,依此实例作为Thread的target来创建Thread对象,主线程必须等待run方法运行完成拿到结果
实现Callable接口重写call()方法,有返回值,返回值类型与泛型参数类型相同,且可以抛出异常;创建Callable实现类的实例,使用FutureTask类来包装Callable对象,将FutureTask实例对象作为Thread()的target来创建Thread对象,启动线程后,需要调用FutureTask类下的get()方法获取返回值
2.线程的生命周期
2.1线程的六状态介绍
2.1.1新建状态(NEW)
创建后尚未启动的线程:Thread thread1 = new Thread();
2.1.2就绪状态(RUNNABLE)
Runnable包括操作系统线程状态中的Ready和Running。处于就绪状态的线程有可能正在等待着CPU分配执行时间,有可能正在执行。
2.1.3阻塞状态(BLOCKED)
处于阻塞状态的线程等待监视器锁(monitor lock)进入同步代码块/方法( synchronized block/method)或者调用Object.wait方法后重新进入同步代码块/方法
2.1.4等待状态(WAITING)
处于等待状态的进程不会被分配CPU执行时间,等待被其他线程显示的唤醒。
线程处于等待状态是由于调用了没有超时时间的Object.wait(),或者没有超时时间的join()方法,或者LockSupport.park()方法。
2.1.5睡眠状态(TIMED_WAITING)
具有指定等待时间的等待线程的线程状态。
线程处于睡眠状态是由于调用了Thread.sleep(),或者有超时时间的Object.wait(),或者有超时时间的join()方法,或者LockSupport.parkUntil()方法,或者LockSupport.parkNanos()方法。
2.1.6终止状态(TERMINATED))
线程执行结束。
源码:
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}
2.2线程状态间的转换
3.线程中的常用方法
3.1start():线程启动
调用start()方法后,线程由新建状态转为Runnable就绪状态。
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* 将当前线程添加到线程组,线程组可以对线程进行统一的控制和管理
*/
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
3.2sleep():线程休眠
sleep()方法使当前线程指定毫秒级休眠,和wait()方法不同,sleep()方法不会放弃monitor lock(监视器锁)的使用权,调用sleep()方法会抛出异常
sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
JDK1.5以后引入TimeOut枚举类型,对sleep()方法对其进行了封装 TimeOut.DAYS/HOURS/MINUTES/SECOND/MICROSECONDS/MILLISECONDS/NANOSECONDS.sleep(),省去时间换算问题。
//均为静态方法 调用方式Thread.sleep()
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos) throws InterruptedException {
........
}
3.3yield():
向调度程序提示当前线程愿意放弃它当前使用的处理器。
调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。调用yield方法让线程重回就绪状态。
A.yield(),会提醒调度器线程A愿意放弃本次的cpu资源,如果cpu资源不紧张,处理器有可能会忽略这种提示
public static native void yield();
sleep()和yield()方法的异同:
不同点:
①调用sleep()方法会抛出异常(可中断方法);调用yield()方法不会抛出异常(不可中断方法)
②调用sleep()方法会当前运行中的线程睡眠一段时间,将线程的执行权让给其它的线程,这段时间的长短是可以设置的;调用yield()方法使当前线程让出CPU执行权,但让出的时间是不可设置的
③调用sleep()方法进入睡眠状态;调用yield()方法进入就绪状态
相同点:
①sleep()方法和yield()方法都是Thread类方法
②sleep()方法和yield()方法都不会放弃锁
3.4join():
当前线程threadA里调用其它线程threadB的join方法,当前线程A进入WAITING/TIMED_WAITING状态,当前线程A不会释放已经持有的对象锁。线程B执行完毕或者millis时间到,当前线程进入就绪状态。join()方法可以控制线程的执行顺序。
public final synchronized void join(long millis) throws InterruptedException
public final synchronized void join(long millis, int nanos) throws InterruptedException
public final void join() throws InterruptedException
3.5实现线程中断的方法
a)interrupt():将java线程中的中断状态位置为true。线程A调用sleep()/join()/wait()(能抛出InterruptedException,是可中断方法),这些方法会使当前进入阻塞状态(操作系统层面),线程B调用被阻塞线程A的interrupt()方法会打断当前的阻塞状态,同时抛出异常(java.lang.InterruptedException)。对于正常运行的线程,调用interrupt()方法只是把线程A的状态改为interruptted,但是不会影响线程A的继续执行。
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0(); // Just to set the interrupt flag
b.interrupt(this);
return;
}
}
interrupt0();
}
b)isInterrupted():判断中断状态位是否是true
public boolean isInterrupted() {
return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
c)interrupted():静态方法。判断中断状态位是否是true;判断为true后擦除
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
3.6.wait/notify/notifyAll
wait()是Object的方法,放弃cpu,也放弃锁
obj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入等待队列。依靠notify()/notifyAll()唤醒或者wait(long timeout) timeout时间到自动唤醒。
obj.notify()唤醒在此对象监视器上等待的单个线程,选择是任意性的。notifyAll()唤醒在此对象监视器上等待的所有线程。
4.线程的优先级
private int priority;
public final static int MIN_PRIORITY = 1; //最小优先级1
public final static int NORM_PRIORITY = 5;//默认优先级5
public final static int MAX_PRIORITY = 10;//最大优先级10
public final void setPriority(int newPriority){...}//设置优先级
public final int getPriority() {...}//获取优先级
优先级增加线程抢到CPU执行权的概率。线程优先级具有继承性。a线程设置优先级,然后启动b线程,b线程的优先级和a线程的优先级是一样的。
例如:
public static void main(String[] args) {
Thread.currentThread().setPriority(10);
Thread thread = new Thread(){
@Override
public void run() {
super.run();
}
};
thread.start();
System.out.println(thread.getPriority());
System.out.println(Thread.currentThread().getPriority());
}
//结果:10 10
5、线程上下文切换
因为以下原因导致CPU不在执行当前的线程,转而执行另一个线程的代码
1、线程的CPU时间片用完了
2、垃圾回收
3、有更高优先级的线程需要运行
4、线程自己调用了sllep、yield、wait、join、park、synchronized、lock等方法
当线程的上下文切换发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java中对应的概念就是程序计数器,他的作用是记录下一条JVM指令的执行地址,是线程私有的。上下文切换频繁会影响性能。