一、线程的创建(有四种方式)
1.继承Thread类
创建一个线程的第一个方式是创建一个新的类,该类继承 Thread 类,然后创建一个该类的实例。继承类必须重写 run() 方法,该方法是新线程的入口点。它也必须调用 start() 方法才能进入就绪态(Thread类的对象调用start()方法,则自动执行重写的run()方法,Thread类的线程都用这个方法来调用run()方法)。
public class MyThread extends Thread {
public MyThread() {
}
public void run() {
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread()+":"+i);
}
}
public static void main(String[] args) {
MyThread mThread1=new MyThread();
MyThread mThread2=new MyThread();
MyThread myThread3=new MyThread();
mThread1.start();
mThread2.start();
myThread3.start();
}
}可以看出,上面的start方法会自动进去就绪态,在进去运行态后会自动调用run方法。同时需要注意的是,继承自Thread的自带的start方法可以被改写,但是无论怎么改写,哪怕改写的方法里面没有写调用run()方法,start方法仍然会调用run()方法。看下面的例子,下面的例子重写了start方法,虽然没有显示地写调用run()方法,但是运行时run()还是会被调用。所以这里重写start()方法类似于调用前增强的概念,就是自己改写的start()方法会在执行完后,自动完成原本的start()方法要干的事情——调用run()。
class ThreadDemo extends Thread {
private Thread t;
private String threadName;
ThreadDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// 让线程睡眠一会
Thread.sleep(50);
}
}catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (t == null) {
t = new Thread (this, threadName);
t.start ();
}
}
}
public class TestThread {
public static void main(String args[]) {
ThreadDemo T1 = new ThreadDemo( "Thread-1");
T1.start();
ThreadDemo T2 = new ThreadDemo( "Thread-2");
T2.start();
}
}补充:Thread类方法
1 public void start()使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
2 public void run()如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
3 public final void setName(String name)改变线程名称,使之与参数 name 相同。
4 public final void setPriority(int priority) 更改线程的优先级。
5 public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。
6 public final void join(long millisec)等待该线程终止的时间最长为 millis 毫秒。
7 public void interrupt()中断线程。
8 public final boolean isAlive()测试线程是否处于活动状态。
测试线程是否处于活动状态。 上述方法是被Thread对象调用的。
下面的方法是Thread类的静态方法。
1 public static void yield()暂停当前正在执行的线程对象,并执行其他线程。
2 public static void sleep(long millisec)在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
3 public static boolean holdsLock(Object x)当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
4 public static Thread currentThread()返回对当前正在执行的线程对象的引用。
5 public static void dumpStack()将当前线程的堆栈跟踪打印至标准错误流。
2.实现Runnable接口
创建一个线程,第二个方法是创建一个实现 Runnable 接口的类。这也是最简单的创建线程的方法,也是推荐的方法(因为实现了Runnable接口后,还可以继承其他的类,但是继承Thread类的方法会让类无法再继承其他的类)。需要注意的是,Thread类也是实现Runnable接口的,同时具体写时,要先写一个类实现Runnable接口,也是重写run()方法,然后要把实现了Runnable接口的类作为Thread类的构造函数的形参传递给Thread类,再用这个Thread类来调用Start()方法。
需要注意的是,构建完Runnable类的实现类的实例对象后,要把这个实例作为参数来创建Thread对象,这个对象才是真正的线程对象,前面的所有工作都是为了创建这个对象。
public class MyThread implements Runnable{
public static int count=20;
public void run() {//也是重写run方法
while(count>0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-当前剩余票数:"+count--);
}
}
public static void main(String[] args) {
MyThread Thread1=new MyThread();//生成实现Runnable类的对象
//将实现Runnable接口的类的对象和线程名作为参数传给Thread
//创建真正的线程对象Thread实例
Thread mThread1=new Thread(Thread1,"线程1");
Thread mThread2=new Thread(Thread1,"线程2");
Thread mThread3=new Thread(Thread1,"线程3");
//通过Thread的start()方法来运行线程
mThread1.start();
mThread2.start();
myThread3.start();
}3.使用Callable和FutureTask创建线程
(1)创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
(2)使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
(3)使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyThread implements Callable<String> {
private int count = 20;
@Override
public String call() throws Exception {
for (int i = count; i > 0; i--) {
// Thread.yield();
System.out.println(Thread.currentThread().getName()+"当前票数:" + i);
}
return "sale out";
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
//创建Callable对象
Callable<String> callable =new MyThread();
//创建FutureTask类包装Callable对象
FutureTask <String>futureTask=new FutureTask<>(callable);
//使用FutureTask对象来生成Thread类对象
Thread mThread=new Thread(futureTask);
Thread mThread2=new Thread(futureTask);
Thread mThread3=new Thread(futureTask);
mThread.start();
mThread2.start();
mThread3.start();
System.out.println(futureTask.get());
}4.通过线程池启动多线程
在线程池一节详解
二、线程的睡眠和暂停
线程的暂停和停止之前有stop,resume和suspend关键字,不过现在已经启用,因为stop会导致线程不安全,suspend会导致死锁等问题。
1.线程的睡眠(静态方法Thread.sleep())
Thread类提供了静态方法Thread.sleep(a),来让进程睡眠a毫秒后再运行。
public class MyYieldStudy {
public static void main(String[] args) {
long start = System.currentTimeMillis();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println(end-start + "毫秒过去了");
}Thread.sleep()注意点:
- sleep()不会释放锁,线程在sleep时仍然控制着锁,不释放锁;但是会释放它所占用的CPU资源。
- sleep()必须要捕捉异常,写sleep()必须要写try…catch
- 线程的暂停(静态方法Thread.yield())
Thread类提供了静态方法Thread.yield(),用来暂停当前的线程,去运行其他的线程(其他鲜线程只能是其他的任务,不能是和yield线程相同的任务,因为yield没有交出锁)。
yield()与sleep类似,只是不能指定暂停的具体时间,而且只能让同等优先级的有执行机会
public class YiledTest implements Runnable {
private String name;
public YiledTest(String name) {
= name;
}
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(name + ":" + i);
if ( i == 5) {
System.out.println(name + ":" + i +"......yield.............");
Thread.yield();//在i=5的时候,暂停去运行其他线程,然后再回来
}
}
}Thread.yield()注意点:和Threa.sleep()一样,Thread.yield()不会释放锁,但会释放占用的CPU资源。
只有Object.wait()会释放锁资源。
- 线程的终止
线程的run()运行完,会自动终止。如果想要终止运行中的线程,有以下两种方法:
(1)使用标记位,来使线程终止:
下面的方法,使用flag作为线程的循环标记位,在main()函数中手动设置flag的值来终止线程。
public class Mythread9 implements Runnable {
//设置标志位
private volatile boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag) {
try {
Thread.sleep(1000);
i++;
System.out.println("线程:" + Thread.currentThread().getName() + "第" + i + "次执行");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
public class StopTest1 {
public static void main(String[] args) throws InterruptedException {
Mythread9 mythread9=new Mythread9();
Thread thread=new Thread(mythread9,"子线程");
thread.start();
Thread.sleep(2000);
mythread9.setFlag(false);//设置标志位,终止线程
}结果:
线程:子线程第1次执行
线程:子线程第2次执行
(2)使用Thread类的实例方法interupt()来终止线程
调用线程类的interrupt方法,其本质只是设置该线程的中断标志,将中断标志设置为true,并根据线程状态决定是否抛出异常。因此,通过interrupted方法真正实现线程的中断原理是:开发人员根据中断标志的具体值,来决定如何退出线程。
实际上,调用interrupt()后,这个线程的中断标志位会改变,使用isinterrupted可以检测到这个标志位,那么就是用isinterrupted()来当标志位,检测到就退出线程。(这么处理是一种退出方式,也可以检测到标志位后怎么怎么滴都可以)。
需要注意的是,interrupt()函数是另一个线程调用,来终端这个线程。比如在线程2中调用thread1.interrupt(),那么线程1就会被中断。一般示例代码都是在main()主线程中调用thread1.interrupt()来中断线程1.
public class MyThread10 implements Runnable {
private boolean flag = true;
@Override
public void run() {
int i = 1;
while (flag) {
try {
Thread.sleep(1000);
boolean bool = Thread.currentThread().isInterrupted();
if (bool) {//调用interrupt后,bool会变成true;
System.out.println("非阻塞情况下执行该操作。。。线程状态" + bool);
break;
}
System.out.println("第"+i+"次执行,线程名称为:"+Thread.currentThread().getName());
i++;
} catch (InterruptedException e) {//捕获interrupted异常
System.out.println("退出了");
boolean bool = Thread.currentThread().isInterrupted();
System.out.println(bool);
return;
}
}
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public class StopTest1 {
public static void main(String[] args) throws InterruptedException {
MyThread10 mythread=new MyThread10();
Thread thread=new Thread(mythread,"子线程");
thread.start();
Thread.sleep(1000);
//方法interrupt()只是给受阻塞的线程发出一个中断信号,这样受阻线程就得以退出阻塞的状态。
thread.interrupt();
}
}结果
第1次执行,线程名称为:子线程
退出了
false
















