JAVA多线程

本文详细介绍了JAVA多线程的创建方式,包括继承Thread类和实现Runnable接口,并讲解了线程的生命周期、线程控制(如join、sleep、yield)、线程安全(同步代码块、同步方法)以及生产者消费者模式。此外,还讨论了JMM(Java内存存储模型)和volatile关键字的作用,最后提到了公平锁与非公平锁的概念及其应用场景。
摘要由CSDN通过智能技术生成

JAVA多线程

一、线程创建

在java中,线程用Thread类表示,所有的线程对象都是Thread类或子类的对象。
要想开发一个线程类,有两种方式:
1、继承Thread类,重写run方法。
首先开发我们的线程类,我们暂且称为这个类为A,类要继承Thread类。
重写Thread类的run方法。run方法就是你的线程要完成的功能。
创建A的实例,调用start方法启动这个线程。
注意这里不要调用run方法,如果调用run方法,就相当于run是一个普通方法,而不是线程了。一定通过Thread类的start方法来启动线程。

实例代码:
public class MyFirstThread extends Thread {
   
	public int i;
	@Override
	public void run() {
   
		while (i++ < 100) {
   
			System.out.println(this.getName() + ":i=" + i);
		}
	}

	public static void main(String[] args) {
   
		MyFirstThread thread1 = new MyFirstThread();
		MyFirstThread thread2 = new MyFirstThread();
		thread1.start();
		thread2.start();
	}
}

在这个代码中,我们开发了一个类MyFirstThread,并继承了Thread类,重写了run方法。
在run方法中,我们循环100次打印int类型的i。这里的this.getName是得到当前线程的名字。在main方法中,我们创建了线程对象的两个实例thread1和thread2,并分别调用它们的start方法启动这两个线程。
部分运行结果如下:

Thread-0:i=23
Thread-1:i=2
Thread-0:i=24
Thread-1:i=3
Thread-0:i=25
Thread-1:i=4
Thread-0:i=26
Thread-1:i=5

2、实现Runnable接口,实现run方法。
这里和前面的用法一样。实现Runnable接口,实现run方法。只是在调用的时候,需要将实现Runnable接口的类对象置入Thread类中,作为Thread的target来启动。

这里需要注意,Runnable对象是Thread对象的target,Runnable中的run方法是作为线程的执行体。最重仍然使用Thread类负责执行target的run方法。

查看Thread类的源代码:

public
class Thread implements Runnable {
   
/* What will be run. */
    private Runnable target;
    @Override
    public void run() {
   
        if (target != null) {
   
            target.run();
        }
}

可以看到Thread类和Runnable接口之间的关系。

示例代码:
public class MySecondThread implements Runnable{
   

	public int i;
	@Override
	public void run() {
   
		// TODO Auto-generated method stub
		while (i++ < 100) {
   
			//下面的代码编译失败,Runnable没有getName方法
		//	System.out.println(this.getName() + ":i=" + i);
			System.out.println(Thread.currentThread().getName() + ":i=" + i);
		}
	}
	public static void main(String[] args) {
   
		MySecondThread thread1 = new MySecondThread();
		MySecondThread thread2 = new MySecondThread();
		//下面两行代码编译失败,Runnable没有start方法
//		thread1.start();
//		thread2.start();
		Thread t1 = new Thread(thread1);
		Thread t2 = new Thread(thread2);
		t1.start();
		t2.start();
	}
}

3 线程的生命周期
当线程被创建并且启动后,它经历了5种状态:新建、就绪、运行、阻塞和死亡状态。当线程在运行的时候,不能一直占有CPU时间片,CPU会在多个线程之间进行调度,线程的状态也会多次切换于阻塞和运行状态。
当线程对象被创建出来是进入了新建状态,当调用了start方法后,线程进入就绪状态。这里可能读者的理解是线程start后进入运行状态,其实线程内部还是依赖JVM的调度,当调用了start方法后,JVM会认为这个线程可以执行,至于什么时候执行取决于JVM的内部调度。
如果就绪状态的线程获取了CPU,那么这个线程处于运行状态,当这个线程运行时,不会一直霸占CPU,线程在执行的过程中会被在CPU上调度下来,以便其他线程能够获取执行机会。
线程进入阻塞状态的情况:
线程调用一个阻塞方法,方法返回前该线程一直阻塞。
线程调用sleep方法进入阻塞。
线程尝试获取同步监视器,但该同步监视器被其他线程持有。
线程调用了suspend方法挂起。
线程解除阻塞,重新进入就绪状态的情况:
调用的阻塞方法返回。
调用的sleep到期。
线程成功获取同步监视器。
被suspend的方法挂起的线程被调用了resume方法恢复。
线程的死亡状态就是线程的结束,线程结束的情况有如下几种:
run方法执行完成
线程抛出异常
直接调用线程的stop方法结束线程

判断线程是否死亡可以使用isAlive方法,当线程处于就绪、运行和阻塞三种状态的时候返回true,否则返回false。另外,不能对已经死亡的线程重新调用start方法重新启动。
另外,线程的suspend方法和stop方法非常容易导致死锁,一般不推荐使用。
本小节中提到的一些名词,如同步监视器,在后面章节会有详细讲解。

二、 线程控制

1、 join线程
当一个线程需要等待另一个线程完毕再执行的话,可以使用Thread的join方法。
假设线程A和线程B,在A执行时调用了B的join方法,A将被阻塞,一直等到B线程执行完毕后,A线程继续执行,就好像排队加塞。

示例代码:
public class TestJoinThread {
   
	public static void main(String[] args) {
   
		MainThread mt = new MainThread();
		mt.start();
	}
}

class MainThread extends Thread {
   
	public void run() {
   
		// TODO Auto-generated method stub
		for (int i = 0; i < 50; i++) {
   
			if (i == 30) {
   
				JoinThread jt = new JoinThread();
				jt.setName("join线程");
				jt.start();
				try {
   
					jt.join();
				} catch (InterruptedException e) {
   
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
			System.out.println(this.getName() + " " + i);
		}
	}
}

class JoinThread extends Thread {
   
	@Override
	public void run() {
   
		// TODO Auto-generated method stub
		for (int i = 0; i < 50; i++) {
   
			System.out.println(this.getName() + " " + i);
		}
	}
}

运行结果:

Thread-0 27
Thread-0 28
Thread-0 29
join线程 0
join线程 1
join线程 2
…
join线程 46
join线程 47
join线程 48
join线程 49
Thread-0 30
Thread-0 31
Thread-0 32
Thread-0 33
…
Thread-0 48
Thread-0 49

可以看到Main线程正常运行,调用了另外一个线程JoinThread的join方法,那么Main线程等待JoinThread执行完毕后再执行。

2、 线程睡眠
如果让线程休息一会,我们可以使用Thread的静态方法sleep(long millis)。这个方法的意思是:在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
sleep方法会让线程休息指定的毫秒数,在sleep的时候,不会放弃执行权力,等待休息时间到了,马上获取执行机会。也就是说,在这个线程sleep的时候,其他线程不会获得CPU的执行权力。

示例代码:
public class TestThreadSleep {
   
	public static void main(String[] args) {
   
		for (int i = 0; i < 50; i++) {
   
			System.out.println(i);
			try {
   
				Thread.sleep(1000);
			} catch (InterruptedException e) {
   
				e.printStackTrace();
			}
		}
	}
}

程序将每隔一秒打印一下变量i的值。

3、 线程让步
线程让步和线程睡眠差不多,但是有一个重要的区别。是让当前线程暂停执行,会放弃cpu的执行权力。也就是说,会让操作系统的线程调度器重新调度一下,其他的线程都有可能获取到执行的权力。
我们知道,每一个时间点只有一个线程可以执行,而其他的线程都是竞争cpu的执行权力,然而调用正在执行线程的让步方法后,所有的线程就会一拥而上,一起竞争cpu的执行权力。

线程让步的方法是Thread的yield静态方法。

3.1 线程优先级
每个线程执行时都具备一个优先级,优先级越高越容易获得执行机会,优先级越低获得的执行机会越少。默认情况下,如果不设置,线程都具备普通优先级。
另外,并不一定是拥有优先级高的线程和有用优先级低的线程在竞争时,优先级高的就一定能获取执行机会。
Thread类的setPriority(int newPriority)和getPriority()方法能够设置和查看线程的优先级。
其中,setPriority方法的参数是一个int整数,范围是1-10,更经常的,我们使用Thread类的三个静态变量来设置:
static int MAX_PRIORITY
线程可以具有的最高优先级。 值是10
static int MIN_PRIORITY
线程可以具有的最低优先级。 值是1
static int NORM_PRIORITY
分配给线程的默认优先级。 值是5

示例代码:
public class Test {
   
	public static void main(String[] args) {
   
		Thread t1 = new MyThread1();
		Thread t2 = new Thread(new MyRunnable());
		t1.setPriority(Thread.MAX_PRIORITY);
		t2.setPriority(Thread.MIN_PRIORITY);
		t2.start();
		t1.start();
	}
}

class MyThread1 extends Thread {
   
	public void run(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值