Java多线程系列(一)—多线程基础
线程是CPU调度的最小单元,单核时代多线程可以在IO密集型操作时有更高的效率,在如今这个多核为主的时代,多线程更能有效的发挥多核的优势,充分利用CPU资源;
个人主页:tuzhenyu’s page
原文地址:Java多线程系列(一)—多线程基础
1. 线程基础
(1) 线程和进程
进程是系统资源分配的基本单位,线程是CPU资源调度的最小单元,一个进程下可以有多个线程;
每个进程都有各自独立的地址空间,所有进程共享父进程的地址空间;
进程上下文切换的开销远远大于线程的上下文切换
(2) 线程的创建
调用Java程序入口的public void static main(){}的是一个由JVM默认创建的名称叫做main的线程
继承Tread类创建线程,线程的启动需要通过Thread.start()调用,会通过本独方法的调用让操作系分配CPU资源,JVM中的Thread对象只是线程的外壳;
Public class MyThread extends Tread{
@override
pulic void run(){
super.run(); //继承Thread类的run()方法
}
}
Public class Run {
public static void main(){
MyThread myThread = new MyThread();
myThread.start();
}
}
- 实现Runnabale接口创建线程,实现Runnable的实例化对象作为参数传入Thread中设置到一个名为”target”的属性上,Thread默认的Run是调用这个target的run()方法来完成;
Public class MyRunnable implements Runnable{
pulic void run(){
System.out.println("hello world")
}
}
Public class Run {
public static void main(){
MyRunnable myRunnable = new MyRunnable();
MyThread myThread = new MyThread(myRunnable);
myThread.start();
}
}
通过Thead创建线程和通过Runable创建线程区别
通过Thead创建线程需要继承Thread类重写run()方法,线程运行时直接执行继承类的run方法;
通过Runnable创建线程需要实现Runnable接口,并将实现类当做参数注入到Thread类的target中,线程运行时会运行Thread类的run()方法查看对应target是否为空,如果不为空则调用target的run()方法;
@Override
public void run() {
if (target != null) {
target.run();
}
}
- Thread实现只能继承一个类,Runnable接口可以实现多个接口,在需要多继承的情境下使用Runnable接口实现
(3) 线程的状态
NEW(新建状态):new了一个新的线程对象,还未调用start()
Runnable(可运行状态):包含Ready就绪状态和Running运行状态
就绪状态:其他线程调用了该对象的start()方法,等待获取CPU的使用权
运行状态:获取了CPU的使用权,执行run()程序
Block(阻塞状态):通常是因为等待锁造成线程挂起,JVM会把该线程置为阻塞状态
Waiting(等待状态):执行Object.wait()方法后所处的等待状态,需要其他线程通知或者中断
Time_Waiting(超时等待状态):执行Thread.sleep(n)线程睡眠挂起,进入超时等待状态;
Terminated(终止态):执行完run()方法后线程的状态;
(4) 线程的优先级
Java中优先级分为1~10等级,默认main的等级为5
线程优先级具有继承性,若B的优先级未明确设置,A线程启动B线程,则B线程的优先级与A是一样的
2. 线程常用方法
(1) Thread类的常用方法
静态方法
- Thread.currentThread()方法:获取当前线程
Thread.currentThread().getName() // 获取线程名
Thread.currentThread().getId() // 获取线程唯一标识
- sleep():让该正在执行的线程休眠指定毫秒数
this.currentThread().sleap(2000) //休眠2秒
- yield()线程让步
Thread.yield(); //当前线程进行让步,放弃CPU资源后重新竞争,如果竞争失败则从运行态度转换为阻塞态;
- intercepted(),isIntercepted()线程停止判断
实例方法
start():启动线程,进入就绪状态等待获取CPU资源
run():mian线程调用Thread.run()方法,代码还是同步顺序执行
isAlive()方法:判断当前的线程是否处于活动状态(就绪状态,运行状态)
myThread.isAlive() //判断线程是否处于存活状态
- intercept()线程停止
myThread.intercept(); //将myThread线程标记为停止态
- join()线程等待
myThread.join(); //当前线程等待myThread线程执行完后再执行
(2) 线程的停止
线程停止的方法:
当run()方法完成后线程终止
使用Thread.stop()方法强行终止当前线程
使用Thread.interrupt()方法中断当前线程
interrupt()+interruptted+break
interrupt()+interruptted+return
interrupt()+interruptted+throw Exception
sleep()和wait()方法+interrupt()抛出InterruptedException异常
class MyThread extends Thread {
@Override
public void run() {
System.out.println("run:"+ System.currentTimeMillis());
super.run();
for (int i=0;i<200;i++){
if(this.interruptted){
break;
}
System.out.println("i:"+i);
}
}
}
public class ThreadStopTest {
public static void main(String[] args) throws InterruptedException{
MyThread myThread = new MyThread();
myThread.start();
System.out.println("start:"+System.currentTimeMillis());
Thread.sleep(2000); //main主线程休眠,让出CPU使用权给myThread线程,不然会执行完main再让出使用权
System.out.println("sleep:"+System.currentTimeMillis());
myThread.interrupt();
System.out.println("interrupt:"+System.currentTimeMillis());
}
}
判断线程停止状态:
this.interrupted():测试当前线程是否已经是中断状态,执行后清除状态标志
this.isInterrupted():测试当先线程对象是否已经中断,但不清除状态标志
suspend()和resume():可能会出现独占公共同步对象,也会出现因为暂停造成的不同步现象
(3) 线程的让步
线程的让步可以通过yield(),sleep(0),sleep(1)实现
sleep(0),sleep(1)和yelid()实现线程让步区别
sleep(0)线程进入就绪状态,但是只允许优先级更高的线程使用CPU,如果没有合适的线程该线程会重新获取到时间片
sleep(1)线程睡眠1ms后进入就绪状态,各个线程公平抢占CPU
yelid()线程让出CPU进入就绪状态,并且和其他线程公平竞争CPU
线程让步Thread.yield()是通过本地方法(native)实现的
(4) 线程的等待
- Thread.join()的使用:一个线程A执行了thread.join()表式线程A要等待线程thread运行结束后才能从thread.join()中返回继续执行后面的代码。
//全部开启线程后执行线程等待
public static void main(String[] args) throws Exception{
Thread[] threads = new Thread[5];
for (int i=0;i<5;i++){
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId()+" is run");
}
});
}
for (int i=0;i<5;i++){
threads[i].start();
}
for (int i=0;i<5;i++){
threads[i].join();
}
System.out.println("finish");
}
线程等待join()方法实现原理
join()方法加有对象锁,锁的对象就是被等待的线程;在方法内循环判断被等待线程是否存活,如果存活则调用wait()方法进入阻塞状态等待被通知;
当被等待线程结束时会做清理工作,其中包括一项就是通知唤醒等待该线程对象锁的线程;因此join()方法是通过对象锁实现的;
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
(4) 线程的wait/notify通信机制
class Thread1 extends Thread{
private Object object;
public Thread1(Object object){
this.object = object;
}
@Override
public void run() {
try{
synchronized (object){
System.out.println("begin to wait:"+Thread.currentThread().getName());
object.wait();
System.out.println("wait end:"+Thread.currentThread().getName());
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
class Thread2 extends Thread{
private Object object;
public Thread2(Object object){
this.object = object;
}
@Override
public void run() {
synchronized (object){
System.out.println("begin to notify:"+Thread.currentThread().getName());
object.notify();
System.out.println("notify end:"+Thread.currentThread().getName());
}
}
}
public class WaitNotifyTest {
public static void main(String[] args) throws InterruptedException{
Object o = new Object();
Thread1 thread1 = new Thread1(o);
thread1.start();
Thread.sleep(2000);
Thread2 thread2 = new Thread2(o);
thread2.start();
}
}
wait()和notify()必须在同步块中调用,wait()和notify()必须持有同一对象锁,这样才能wait()释放对象锁后notify()持有该对象锁;wait()应该先持有对象锁,执行wait()方法后释放锁,其他线程竞争该对象锁;
调用wait()的线程将进入Waiting状态,只有等待其他线程通知或者被中断才返回;wait()方法底层调用wait(0)表示一直等待直到被其他线程唤醒;
wait(long)方法:等待某一时间内是否有线程进行唤醒,超出这个时间会自动唤醒,如果未竞争得到CPU资源则进入阻塞Blocked状态;
notify()方法用来通知wait状态的线程,如果有多个持有相同对象锁的线程,随机挑选一个发出通知notify
执行完notify()方法之后,当前线程不会马上释放该对象锁,要执行完synchronized代码块内的程序后才能释放
notifyAll()方法:唤醒所有处于当前对象锁的wait状态的线程
总结
线程的方法一般是通过调用本地方法将具体操作交由操作系统执行,线程让步join()方法的实现是通过对象锁;