高并发系列文章
本文章来会议一下多线程中的基本操作,如有错误望指正。
这篇文章您将看到
一、创建线程
创建一个线程对象十分简单,只需要new Thread()就可以创建好一个线程对象。
然后调用start()方法即可开启线程。调用start()方法后线程会自动执行run()方
法。
注意:调用start()方法启动线程而不是直接调用run方法。
举例简单创建一个线程对象并调用:
public class ConcurrentTest {
public static void main(String[] args) {
//这里采用匿名内部类的写法
Thread thread = new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行了");
}
};
thread.setName("线程1");
thread.start(); //切记使用start()方法开启线程而不是直接选择调用run()方法。
}
}
接下来试着自定义线程
1.继承Thread
这种方式比较简单,我们只需要一个类去继承Thread类,然后重写run方法后开启线程即可。
注:我们知道java是只支持单继承,如果一个类继承了Thread类就会导致无法继承其它类,因此这种创建线程的方法不怎么使用。一般会采用第二种。
public class MyThread extends Thread {
//重写方法
@Override
public void run() {
System.out.println("可以执行");
}
public static void main(String[] args) {
new MyThread().start(); //启动线程
}
}
2.实现Runnable接口
//Thread有一个构造方法
public Thread(Runnable target);
这样的话我们就可以让一个类实现一个接口,然后让该类的实例作为Thread构造的一个参数即可
public class MyThread implements Runnable {
@Override
public void run() {
//Thread.currentThread():当前线程
//getName():获取当前线程的名字
System.out.println(Thread.currentThread().getName()+"正常执行");
}
public static void main(String[] args) {
//此时传递的name是线程的名字,也可以不传递name
new Thread(new MyThread(),"线程2").start();
}
}
二.中断线程(interrupt)
线程的终止即停掉线程
1.原始终止
Thread中的stop()方法可用于线程的终止,但是该方法已经不被推荐使用。stop()
方法太过于暴力了,它不管线程处于什么样的状态直接终止线程。类似于你的文档还
没有保存直接就给你断电了,是不是超级崩溃!正确的做法是在你断电前提醒我一下
以便我能即时的保存一下。
stop()方法代码演示:
--------------------------------------------------------------------
public class MyThread implements Runnable {
//sleep线程睡眠后期会详细解释
@Override
public void run() {
for (int i = 1;true;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程打印出"+i);
}
}
public static void main(String[] args) {
//创建线程
Thread thread = new Thread(new MyThread());
try {
//线程开启
thread.start();
//线程睡眠,确保主线程后执行
Thread.sleep(5000);
thread.stop();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//结果:打印到4时程序结束
再次提醒,该方法即将被废弃,不建议(不要)使用该方法结束线程。
2.中断线程
我们知道stop()方法的弊端后了解正确的中断线程方式应给是提前给线程打招呼,线程接收到信息后要不要终止自行决定(并不是接收中断请求就立马中断,那将和stop()方法就没有多大的差别了)
Thread提供的线程中断的三个方法(十分容易混淆)
void interrupt() //中断线程
static boolean interrupted() //测试当前线程是否已经中断
boolean isinterrupted() //测试线程是否已经中断
线程中断返回true,否则返回false
用法详细解释
interrupt()方法作为一个实例方法
通知目标线程中断(将线程的中断标志设置为true)
如果线程在调用Object类的wait()…方法或者目标线程的join(…)、sleep(…)方法时
会抛出InterruptedException,并将中断状态清除(中断状态设为false)!!!
isinterrupted()方法是一个实例方法,判断当前线程是否中断(查看中断标志true(中断)\false(未中断))
interrupted()方法是一个静态方法,也用来判断当前线程是否中断,但是和isinterrupted不同的是,它会清除中断状态(置为false)。
换句话说:中断状态可以由interrupted清除,连续调用两次interrupt方法会返回false.
切记:
Thread.interrupted()方法以及sleep发生异常抛出的interruptException异常都会清除中断标记。
//调用线程的interrupt()方法设置中断状态为true
if(Thread.interrupted()){ //此时的中断标记为true,但是之后会清除中断标记(置中断状态为false)
if(!Thread.interrupted()){
System.out.println("线程中断标记为false");
}
break;
}
代码演示:
//interrupt()中断线程演示
public class MyThread implements Runnable {
@Override
public void run() {
while (true){ //定义一个死循环,使用线程中断来结束循环。
System.out.println("正在执行");
if(Thread.interrupted()){ //判断该线程是否是中断线程
break;
}
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyThread());
try {
thread.start();
Thread.sleep(2000);
thread.interrupt();//线程中断,设置中断状态为true
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
使用变量的方式来中断线程
public class MyThread implements Runnable {
//使用volatile关键字可以做到变量的可见性,后续会详细的解释
static volatile boolean flag = false;
@Override
public void run() {
while (true){
System.out.println("正在执行");
if(flag){
break;
}
}
}
public static void main(String[] args) {
new Thread(new MyThread()).start();
try {
Thread.sleep(2000);//主线程睡眠确保另一个线程先执行
flag = true; //修改变量的值
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
注意:
使用变量的方式去中断线程,该变量需要volatile关键字修饰,具体原因和java内存模型有关,可以看一下我的另一篇博客Java内存模型
两种方式有什么区别:
如果一个线程中有sleep休眠的话,我们就需要使用interrupt中断线程。
调用interrupt()方法后,sleep会抛出interruptException异常。并且会清除中断标记。切记抛出异常后会清除中断标记。
三、线程等待与通知
线程等待wait/通知notify
线程的等待与通知是jdk中Object类中的方法,也就是说所有的对象都有这两个方法
线程的等待与通知
一个对象调用wait方法后,在其他线程调用该对象的notify或notifyAll方法之前
当前线程处于等待状态。简单来说在线程A中调用obj.wait()方法后,线程A就会停
止执行处于等待状态,在其他线程调用obj.notify()或obj.notifyAll()之后,
当前线程A才可以继续执行。
解释:
一个线程调用object.wait()方法后,该线程就会加入该对象的等待队列。该队列中
可能有多个线程。因为可能好多线程都需要该对象。如果该对象调用notify后,则会
随机!随机!随机!选择一个线程执行。这里是随机的,也就是说不是先进来的线程
就可以优先执行。而该对象还有一个方法是notifyAll方法,该方法会唤醒队列中的
所有线程。
wait与notify的工作机制:
对象的wait()和notify()不能随便使用,必须包含在synchronized语句块中,因为调用这两个方法之前必须先获取该对象独享的监视器(锁)。
流程:在调用该对象的wait()方法之前先获取该对象的监视器(锁),正常调用wait()方法后会释放掉该对象的监视器。原因有:①不会因为该线程的休眠而导致需要使用该对象的线程不能工作②后续执行notify()方法前也需要获取该对象的监视器。此后一个线程执行该对象的notify方法,随机唤醒一个等待线程。这里注意的是,唤醒的线程不是立即执行后续的代码,而是先去获取该对象在wait之前的那个监视器,获取到监视器后才执行后续的代码
代码演示:
//模拟等待线程
public class MyThreadA extends Thread {
private Object object;
public MyThreadA(Object object){
this.object = object;
}
@Override
public void run() {
while(true){
synchronized (object){
System.out.println(Thread.currentThread().getName()+"开始等待,线程暂停");
try {
object.wait();
System.out.println(Thread.currentThread().getName()+"继续执行");
break;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
-----------------------------------------------------------------
//模拟唤醒线程
public class MyThreadB extends Thread {
private Object object;
public MyThreadB(Object object){
this.object = object;
}
@Override
public void run() {
synchronized (object){
System.out.println(Thread.currentThread().getName()+"开始执行,两秒后唤醒");
try {
object.notify();
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
MyThreadA myThreadA = new MyThreadA(object);
myThreadA.setName("等待线程A");
MyThreadB myThreadB = new MyThreadB(object);
myThreadB.setName("唤醒线程B");
myThreadA.start();
Thread.sleep(100);
myThreadB.start();
}
}
控制台打印:
等待线程A开始等待,线程暂停
唤醒线程B开始执行,两秒后唤醒
....等待三秒
等待线程A继续执行
控制台打印信息证明:线程B调用notify()方法后线程A并不会立刻执行。而是要先获取该对象的锁
wait方法和sleep方法都会让线程处于暂停状态,两者的区别在于:
① wait方法可以被notify方法唤醒
② wait方法会主动释放对象的锁,而sleep不会主动释放锁
四、线程挂起与执行
线程挂起(suspend)和继续执行(resume) 了解
线程中的suspend和resume是成反对存在,该方法已过时。因为当一个线程调用
suspend方法时,该线程所占用的锁对象是不会释放的,直到该线程调用resume
方法。也就是说在该线程挂起的同时,其他需要该对象锁的线程是不能执行的。
这样一个系统是十分不安全的。因此该方法已不在推荐使用。
public class MyThreadA extends Thread {
private Object object;
public MyThreadA(Object object){
this.object = object;
}
@Override
public void run() {
synchronized (object){
this.suspend();
System.out.println(Thread.currentThread().getName()+"获取锁");
}
}
}
public class MyThreadB extends Thread {
private Object object;
public MyThreadB(Object object){
this.object = object;
}
@Override
public void run() {
System.out.println("尝试去获取锁");
synchronized (object){
System.out.println(Thread.currentThread().getName()+"获取锁");
}
}
}
public class Test {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
MyThreadA myThreadA = new MyThreadA(object);
myThreadA.setName("线程A");
MyThreadB myThreadB = new MyThreadB(object);
myThreadB.setName("线程B");
myThreadA.start();
myThreadB.start();
}
}
//我们发现当线程A获取锁对象后,挂起状态时,此时不会释放锁。
//导致线程B因不能获取锁对象而迟迟不能运行...
五、等待线程结束(join)和线程谦让(yield)
等待线程结束业务逻辑:
一个线程的输入依赖另一个或多个线程的输出,表明该线程需要依赖的线程执行结束之后才能继续执行。
jdk中提供的方法join就是解决这种逻辑的。
void join() //等待该线程终止
void join(long time) //等待该线程终止,等待时间最长为time毫秒
简单说:ThreadA需要在线程B结束后执行,就可以在线程A中调用ThreadB.join()方法。
举例:
public class MyThreadC implements Runnable {
static long start = 0;
@Override
public void run() {
try {
start = System.currentTimeMillis();
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyThreadC());
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("相差时间为"+(System.currentTimeMillis()-start)/1000);
}
}
控制台打印:相差时间为2
如果将thread.join() ==>替换为 thread.join(1000);//表明等待该线程终止的最长时间为1秒。1秒后主线程获取资源直接执行不在等待线程C的结束。
控制台打印:相差时间为1
线程的谦让yield
static void yield() //暂停当前线程,执行其它线程
线程yield()方法会让当前线程让出cpu,但是需要注意的是并不代表接下来该线程
不抢占cpu资源,它还是会和其他线程竞争cpu资源的。该方法经常使用在线程优先级低
不重要的线程过多的占用cpu资源。可以调用该方法yield();
说明
多线程的知识点比较碎,需要理解的比较多。多多揣摩一下。