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中有三种方式终止线程:
- 使用退出标志,使线程正常退出,也就是当run方法执行完成后线程终止
- 使用stop方法强制终止,但是不推荐因为它会产生不可预料的后果。
- 使用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时间
注意:优先级具有随机性,优先级较高的线程并不一定每一次都先执行完