需要掌握的Java多线程基本技能

Thread

实现线程的方式主要有两种,一种是继承Thread类,另一种是实现Runnable接口。Thread类本身也是实现了Runnable接口的。

继承Thread,并且要重写run方法。

public class MyThread extends Thread{
    @Override
    public void run(){
        super.run();
        System.out.println("MyThread");
    }
}

public class Run{
    public static void main(String[] args){
        MyThread mythread = new MyThread();
        mythread.start();
        System.out.println("stop")
    }
}

Thread.java类中的start()方法用于通知线程规划器此线程准备就绪,等待调用线程对象的run方法。但是如果主线程调用run方法就会变成顺序执行,也就是同步执行。

注意点:

  • 线程执行具有随机性
  • 线程启动顺序与start()方法执行顺序无关
  • 使用Thread创建线程我们就不能再继承父类了,出于java单继承的特点。
  • 同时要注意start()方法只能调用一次,如果调用多次会出现IllegalThreadStateException异常

Runnable

如果已存在父类的类,可以直接实现Runnable接口来进行多线程的操作。

public class MyRunnable implements Runnable{
	@Override
	public void run() {
		System.out.println("running");
	}
}

public class RunnableTest {
	public static void main(String[] args) {
		MyRunnable mr = new MyRunnable();
		Thread t = new Thread(mr);
		t.start();
		System.out.println("Stop");
	}
}

以上两个类完成了一个多线程启动的Demo,那么我们深入分析一下这个MyRunnable类是如何启动的。

首先看Thread类构造器:

1、分配一个新的Thread对象
Thread( );
2、支持传递一个Runnable接口的对象,由于Thread类也实现了Runnable,所以此构造器也可以传入一个Thread类对象。
Thread(Runnable target);
3、name为线程的名子。也可以通过setName方法设置。如果不设置线程的名子,线程就使用默认的线程名:Thread-N,N是线程建立的顺序,是一个不重复的正整数。
Thread(String name);
Thread(Runnable target, String name);
4、ThreadGroup是当前建立的线程所属的线程组。如果不指定线程组,所有的线程都被加到一个默认的线程组中。
Thread(ThreadGroup group, Runnable target);
Thread(ThreadGroup group, String name);
Thread(ThreadGroup group, Runnable target, String name);
5、stackSize是线程栈的大小,这个值一般是CPU页面的整数倍。如x86的页面大小是4KB。在x86平台下,默认的线程栈大小是12KB。 
Thread(ThreadGroup group, Runnable target, String name, long stackSize)

当创建完Thread对象后调用start方法即可开辟线程执行Thread对象中的run方法。

实例变量与线程安全

自定义线程中的实例变量与其他线程之间存在共享和私有之分,共享和私有也是线程之间交互的很重要的技术点。举一个最简单的例子展示线程变量的私有性。

public class MyRunnable implements Runnable{
	private int a = 0;
	@Override
	public void run() {
		a++;
		System.out.println("a="+a);
	}
}

public class RunnableTest {
	public static void main(String[] args) {
		MyRunnable mr = new MyRunnable();
		Thread t1 = new Thread(mr);
		Thread t2 = new Thread(mr);
		Thread t3 = new Thread(mr);
		t1.start();
		t2.start();
		t3.start();
		System.out.println("Stop");
	}
}

打印结果:
Stop
a=3
a=3
a=3

从结果中可以看出同一个a在每个线程中都是一个私有的变量,并不会受其他线程影响。那么再举一个变量共享的例子:

public class RunnableTest {
	public static int a = 10;
	public static void main(String[] args) {
		MyRunnable mr = new MyRunnable();
		Thread t1 = new Thread(mr,"t1");
		Thread t2 = new Thread(mr,"t2");
		Thread t3 = new Thread(mr,"t3");
		t1.start();
		t2.start();
		t3.start();
		System.out.println("Stop");
	}
}

public class MyRunnable implements Runnable{
	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + "a="+--RunnableTest.a);
	}
}

运行结果
Stop
t1a=9
t2a=8
t3a=8

我们可以注意,在多线程操作共享变量的时候会产生一些与期望结果有误的数据,这也称之为非线程安全。大致分析一下原因,由于t2和t3同时读取a的时候得到的结果是9,所以两者同时减一变成了8。

这个时候我们只需要做一些微调:

public class MyRunnable implements Runnable{
	@Override
	public synchronized void run() {
			System.out.println(Thread.currentThread().getName() + "a="+--RunnableTest.a);
	}
}

输出结果:
Stop
t1a=9
t2a=8
t3a=7

增加synchronized关键字即可保证输出结果符合预期。

synchronized可以在任何对象和方法上增加锁,加锁的地方又称之为互斥区或临界区。如果一个线程得到锁,就会执行代码,其他线程只能不断尝试获得锁,直到拿到为止。

currentThread()方法

看过上述代码一定会发现这个方法Thread.currentThread()。该方法可以返回代码正在被哪个线程调用的信息

通过currentThread()方法返回值或者直接观察对象实例,都可以对线程做如下众多操作

关于Thread我们应该掌握的简单技术点:

  • 线程的启动:start()
  • 线程状态: isAlive() 线程未启动、线程执行完为false;线程启动、线程执行中为true。
  • 如何使线程暂停:sleep() 在指定的毫秒数内让线程休眠;例如:Thread.currentThread().sleep(500);
  • 如何获取线程唯一标识:getId() 取得线程的唯一标识

上述方法都是线程的简单应用,可以自己尝试不多做阐述,下面分析一些很重要的技术点

  • 如何使线程停止
  • 线程的优先级

停止线程

停止一个线程意味着线程处理完毕任务之前停掉正在做的操作,也就是放弃当前操作。看起来似乎很简单,但是必须做好防范工作才能达到预期目标。Thread.stop()方法可以停掉一个线程但是最好不用他因为他不安全,并且已经被废弃。

停止线程的操作使用Thread.interrupt()方法 注意!!!这个方法并不会终止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。

在java中有三种方式终止线程:

  1. 使用退出标志,使线程正常退出,也就是当run方法执行完成后线程终止
  2. 使用stop方法强制终止,但是不推荐因为它会产生不可预料的后果。
  3. 使用interrupt()方法中断线程

下面重点分析一下interrupt()方法。

停不掉的interrupt

interrupt仅仅会对线程终止打一个标记,并不是真的停止线程

判断线程是否已经中断有两种方式:

  • interrupted()测试当前线程是否已经中断。如果两次调用中断检查方法且第一次为true,则第一次会清除中断状态以至于第二次为false
  • isInterrupted()测试当前线程是否已经中断。如果是中断状态则不清除中断标记,所以第二次调用该方法还会是true。

异常法中断

interrupted()方法得到中断信号为true后,使用throw new InterruptedException();方式抛出异常,启动线程的主类接收异常,做进一步处理。

沉睡停止法

当一个线程正在sleep,这个时候调用interrupt()方法会抛出异常InterruptedException:sleep interrupted

暴力停止法

直接在thread对象调用stop方法,调用时会抛出ThreadDeath异常,通常无需显式捕捉该异常

return停止法

interrupted()方法调用后得到中断信号为true后直接return返回。

 

线程优先级

使用setPriority()方法来设置线程的优先级

线程的优先级总共有10级(1-10)如果设置优先级的时候值小于1或者大于10,会抛出异常。

分配优先级的目的是让优先级高的线程可以分到更多的CPU时间

注意:优先级具有随机性,优先级较高的线程并不一定每一次都先执行完

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值