2.1 什么是线程(What Is a Thread? )
举个栗子
public class HelloYB {
public static void main(String[] args) {
System.out.println("hello yellowbaby");
}
}
一个很简单的程序是吧~我们的程序里面都是由一条条的指令(instructions)构成的,这个程序里面只有一个指令,打印~当然,你的程序不可能就这一条指令..,但是原理是一样的,这些指令的执行路径就是一个线程。(The execution path of these instructions is a thread)
多线程的魅力就在于共享数据,多线程程序共享的是整个java堆,数据能够透明的被存在堆中的对象共享,当然每个线程也都有自己的本地存储空间(threads can transparently share access between any object in the heap,Eachthread still has its own space for local variables )
2.2 线程的创建(Creating a Thread)
书中的程序过于繁琐以及涉及了很多java swing的东西,我改编了一下,下面是个简略版,带有了创建线程的两个方式
package com.yellow.chapteOne;
/**
* 第一种方式,直接继承 Thread 类 重写 run 方法
* @author yellowbaby
*
*/
public class MyTread extends Thread{
@Override
public void run() {
new SwingTypeTester().show();
}
public static void main(String[] args) {
new MyTread().start();
}
}
这里是创建线程的第一种方式,直接继承 Thread ,重写 run 方法,当主线程调用 start方法的时候会执行 run 方法
package com.yellow.chapteOne;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class SwingTypeTester extends JFrame {
private JButton quitButton;//退出按钮
private JButton startButton;//开始按钮
private MyJLabel displayBorad;//数字显示的面板
public SwingTypeTester() {
init();
}
private void init() {
JPanel p = new JPanel();
startButton = new JButton("start");
quitButton = new JButton("quit");
displayBorad = new MyJLabel("0");
p.add(startButton);
p.add(quitButton);
add(p, BorderLayout.SOUTH);
add(displayBorad,BorderLayout.NORTH);
startButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {//点击开始按钮后启动另一个线程
Thread thread = new Thread(displayBorad);
thread.start();
}
});
quitButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
pack();
}
}
这是一个JFrame,有三个部件,两个button,一个Jlabel,点击按钮后我希望可以改变JLabel的值
package com.yellow.chapteOne;
import java.util.Random;
import javax.swing.JLabel;
/**
* 创建线程的第二种方式,实现Runnable接口,然后传入Thread类里面执行
* @author yellowbaby
*
*/
public class MyJLabel extends JLabel implements Runnable{
private static Random random = new Random();
public MyJLabel(String string) {
super(string);
}
@Override
public void run() {
while (true) {
setText(String.valueOf(random.nextInt()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
程序里面我一共创建了两枚线程,一枚是直接继承的Thread(用来启动界面),一枚是继承runnable接口(点击按钮后为了改变JLabel的值的),两种方式的优劣后面应该有详细的解释
2.3 线程的生命周期(The Lifecycle of a Thread)
1)线程的创建 (Creating a Thread)
一个Thread的实例就代表了一个线程,我们创建一个Thread的实例很明显就需要调用它的构造方法啦~让我们来瞄一眼它有哪些构造方法
public Thread() {//这是空的
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {//耳熟能详的接受一个runnable任务
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(ThreadGroup group, String name) {//我们可以设置名称,但是这个ThreadGroup 是干啥的捏?
init(group, null, name, 0);
}
我搜了一下这个ThreadGroup 顿时涨姿势了有没有
先看注释
/**
* A thread group represents a set of threads. In addition, a thread
* group can also include other thread groups. The thread groups form
* a tree in which every thread group except the initial thread group
* has a parent. (这是一个树形结构哟,而且除了根节点...都有父节点)
* <p>
* A thread is allowed to access information about its own thread
* group, but not to access information about its thread group's
* parent thread group or any other thread groups.
*
* @author unascribed
* @version 1.66, 03/13/08
* @since JDK1.0
*/
public
class ThreadGroup implements Thread.UncaughtExceptionHandler
然后给你们看个小例子
public class AllThread {
public static Thread[] getAllThread() {
ThreadGroup root = Thread.currentThread().getThreadGroup();//得到当前线程所在的ThreadGroup
ThreadGroup ttg = root;//因为ThreadGroups是树形结构的嘛,所以我们希望找到根节点是神马
while ((ttg = ttg.getParent()) != null)
root = ttg;
Thread[] tlist = new Thread[(int) (root.activeCount() * 1.2)];//目测一下大概有多少个线程,至于这个系数为毛是1.2,我应该和树形数学知识有关系.....
return java.util.Arrays.copyOf(tlist, root.enumerate(tlist, true));
}
public static void main(String[] args) {
Thread[] ts = getAllThread();
for (Thread t : ts) {//这样我们就可以知道所有在跑的线程的所有信息,还可以进行一些操作比如设置priority神马的
System.out.println(t.getId() + ": " + t.getName() + " "
+ t.getPriority() + " " + t.getThreadGroup() + " ");
}
}
}
这是打印结果,我们的一共跑了5个线程,好吧我已经忍不住去研究这5个线程的细节了
2: Reference Handler 10 java.lang.ThreadGroup[name=system,maxpri=10]
3: Finalizer 8 java.lang.ThreadGroup[name=system,maxpri=10]
4: Signal Dispatcher 9 java.lang.ThreadGroup[name=system,maxpri=10]
5: Attach Listener 5 java.lang.ThreadGroup[name=system,maxpri=10]
1: main 5 java.lang.ThreadGroup[name=main,maxpri=10]
Reference Handler JVM在创建main线程后就创建Reference Handler线程,其优先级最高,为10,它主要用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收问题。
Finalizer 这个线程也是在main线程之后创建的,其优先级为10,主要用于在垃圾收集前,调用对象的finalize()方法;
关于Finalizer线程的几点:
1)只有当开始一轮垃圾收集时,才会开始调用finalize()方法;因此并不是所有对象的finalize()方法都会被执行;
2)该线程也是daemon线程,因此如果虚拟机中没有其他非daemon线程,不管该线程有没有执行完finalize()方法,JVM也会退出;
3) JVM在垃圾收集时会将失去引用的对象包装成Finalizer对象(Reference的实现),并放入ReferenceQueue,由Finalizer线程来处理;最后将该Finalizer对象的引用置为null,由垃圾收集器来回收;4) JVM为什么要单独用一个线程来执行finalize()方法呢?如果JVM的垃圾收集线程自己来做,很有可能由于在finalize()方法中误操作导致GC线程停止或不可控,这对GC线程来说是一种灾难;
Attach Listener
Attach Listener线程是负责接收到外部的命令,而对该命令进行执行的并且吧结果返回给发送者。通常我们会用一些命令去要求jvm给我们一些反馈信息,如:java -version、jmap、jstack等等。如果该线程在jvm启动的时候没有初始化,那么,则会在用户第一次执行jvm命令时,得到启动。
Signal Dispatcher
前面我们提到Attach Listener线程的职责是接收外部jvm命令,当命令接收成功后,会交给signal dispather线程去进行分发到各个不同的模块处理命令,并且返回处理结果。signal dispather线程也是在第一次接收外部jvm命令时,进行初始化工作。
2.3.2 开始一个线程(Starting a Thread)
一个线程从被构造就已经存在了,但是没有执行任何代码,这个时候它处于一种等待状态,我们可以设置它的一些参数比如priority,name,daemon statue
当线程已经准备就绪,就可以调用start()方法了,这个方法最终会调用 run()方法,当start方法执行后,有两个线程在并行执行
当线程执行start后,这个线程就可以说是激活(alive)的了,我们可以用isAlive()来检测是否处于激活状态,如果返回是true,说明线程已经启动,并且在执行run方法了,如果返回的是false,说明还没有start或者已经结束了
看个例子说明 isAlive()方法
package com.yellow.chapteOne;
public class Test {
public static void main(String args[]) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("执行中");
}
});
System.out.println(thread.isAlive());//执行前 false
thread.start();
System.out.println(thread.isAlive());//执行的时候 true
thread.join();//等待线程结束
System.out.println(thread.isAlive());//结束后false
}
}
2.3.3 终止线程(Terminating a Thread)
正常情况,想要一个线程终止就是执行完run方法,还有办法就是抛出异常.....(run方法不能抛出check异常,但是能抛出unchecked异常),后面有两种终止线程的方式
2.3.4 线程暂停,挂起,恢复(Pausing, Suspending, and Resuming Threads)
我们熟悉能够暂停线程的方法就是sleep啦,但是sleep方法不算严格意义的thread suspension,真正的thread suspension是可以suspend其他线程以及resume其他线程的,而sleep方法只能对当前线程有用,真正意义的线程Suspending和Resuming的实现是用wait和notify方法,第三章里面会有详细说明
2.3.5 线程清除(Thread Cleanup)
既然线程也是一个对象,自然也会被垃圾收集器(garbage collect)管理,一般来说,当我们不再占用这个线程的引用的时候,这个thread就可以被回收了
一个继续持有引用的理由是我们的run方法还在继续执行,我们可以用isAlive方法来判断是否run还在执行,如果我们想强制等待某个线程执行完,可以使用join方法
具体扯扯join的用法,join的作用是等待一个线程挂掉!!
/**
* Waits for this thread to die.
*
* @exception InterruptedException if any thread has interrupted
* the current thread. The <i>interrupted status</i> of the
* current thread is cleared when this exception is thrown.
*/
public final void join() throws InterruptedException
看一个实例,我们吃饭前一定要洗手哦,一个吃饭线程,一个洗手线程,我们要求执行吃饭前洗手线程必须已经完成了,用join再合适不过了
package com.yellow.chapteOne;
public class Test {
public static void main(String args[]) throws InterruptedException {
final Thread washHandThread = new Thread(new Runnable() {//洗手线程
@Override
public void run() {
System.out.println("吃饭前要洗手哟~");
}
});
Thread eatThread = new Thread(new Runnable() {//吃饭线程
@Override
public void run() {
isWashFinished();
try {
washHandThread.join();// 当eat线程到这里时候会被阻塞,为了等待wash线程完成才会继续执行
} catch (InterruptedException e) {
e.printStackTrace();
}
isWashFinished();
System.out.println("可以吃饭了");
}
/**
* 如果洗手线程isAlive返回的true,说明洗手还没有完成,如果返回false,说明已经执行完了
*/
public void isWashFinished() {
if (washHandThread.isAlive()) {
System.out.println("还没有洗完呢");
} else {
System.out.println("洗完了");
}
}
});
eatThread.start();
washHandThread.start();
}
}
说明,当一个线程执行其他线程的join的时候,会一直被阻塞直到那个线程的run方法执行完毕,如果在那之前run方法以及执行完了,join方法会立刻返回
(但是你千万不能用join方法去判断run方法是否已经执行完,你应该用isAlive方法)
下面补充一下线程的状态:
一个线程的各种状态为:
1)新生态(newly created),刚刚被new出来的线程,还没有执行start方法
2)可执行状态(Runnable),这个时候已经启动了start方法,但是线程正在等待CPU轮转分配资源,从sleep,wait,这些方法里面回来的时候线程都是这个状态
3)执行状态(Running),线程已经得到了资源,开始正式运行了,有很多方法可以进入Runnable状态,但是只有一种方法能进入running状态,就是调度程序从runnable线程池里面选出一个线程运行
4)死亡(Dead),线程已经执行完run方法
5)阻塞(Blocked),资源被其他线程占据了
多线程的时候:
多线程比单线程多了几种状态,一个正在running的线程可以进入任何一种non-runnable的状态,但是non-runnable不能马上进入run,需要先到runnable状态,看看几种non-runnable状态:
1)睡眠(sleeping) :在这个状态,线程还是处于alive状态,只是不可执行而已(non-runnable)
2)等待被唤醒(waiting for notification):这个线程调用了wait方法,释放了自己的资源,等待别的线程用完资源然后唤醒自己(notify)
3)I/O阻塞(blocked for I/O):等待I/O资源
4)等待其他线程完成(blocked for join completion):调用了其他线程的join方法,所以阻塞了
5)死锁(blocked for lock acquisition):两个线程都在等同一个资源,而且都不肯放手,当然阻塞
2.4 两种方式停止线程(Two Approaches to Stopping a Thread)
先说重要的一种,使用interrupt方法终止线程,Interrupting a Thread
package com.yellow.chapteOne;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class SwingTypeTester extends JFrame {
private JButton stopButton;//停止按钮
private JButton startButton;//开始按钮
private MyJLabel displayBorad;//数字显示的面板
private Thread thread;
public SwingTypeTester() {
init();
}
private void init() {
JPanel p = new JPanel();
startButton = new JButton("start");
stopButton = new JButton("stop");
displayBorad = new MyJLabel("0");
p.add(startButton);
p.add(stopButton);
add(p, BorderLayout.SOUTH);
add(displayBorad,BorderLayout.NORTH);
startButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {//点击开始按钮后启动另一个线程
thread = new Thread(displayBorad);
thread.start();
}
});
stopButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
thread.interrupt();
}
});
pack();
}
public static void main(String[] args) {
new SwingTypeTester().show();
}
}
package com.yellow.chapteOne;
import java.util.Random;
import javax.swing.JLabel;
/**
* 创建线程的第二种方式,实现Runnable接口,然后传入Thread类里面执行
* @author yellowbaby
*
*/
public class MyJLabel extends JLabel implements Runnable{
private static Random random = new Random();
public MyJLabel(String string) {
super(string);
}
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
setText(String.valueOf(random.nextInt()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
return;
}
}
}
}
interrupt方法有两个作用
1)能让所有的阻塞方法(blocked method)抛出InterruptedException异常,阻塞方法也就是 sleep,wait,join这些方法,比如你sleep(一年),然后后悔了怎么办,就可以用interrupt方法
2)改变了线程的中断状态,仅仅是改变了中断状态而已,对非阻塞方法没有任何影响,也就是isInterrupted()方法的返回值
现在再来看我们的程序,我们点击stop后,调用了线程的interrupt方法,然后妥妥的isInterrupted()的值改变了,我们的程序停下来了,而且一旦执行sleep方法,我们就会抛出异常,然后return,线程马上停止
第二种终止线程的方法,使用标志变量 Setting a Flag
package com.yellow.chapteOne;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class SwingTypeTester extends JFrame {
private JButton stopButton;//停止按钮
private JButton startButton;//开始按钮
private MyJLabel displayBorad;//数字显示的面板
private Thread thread;
public SwingTypeTester() {
init();
}
private void init() {
JPanel p = new JPanel();
startButton = new JButton("start");
stopButton = new JButton("stop");
displayBorad = new MyJLabel("0");
p.add(startButton);
p.add(stopButton);
add(p, BorderLayout.SOUTH);
add(displayBorad,BorderLayout.NORTH);
startButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {//点击开始按钮后启动另一个线程
thread = new Thread(displayBorad);
displayBorad.setFlag(true);
thread.start();
}
});
stopButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
displayBorad.setFlag(false);
}
});
pack();
}
public static void main(String[] args) {
new SwingTypeTester().show();
}
}
package com.yellow.chapteOne;
import java.util.Random;
import javax.swing.JLabel;
/**
* 创建线程的第二种方式,实现Runnable接口,然后传入Thread类里面执行
* @author yellowbaby
*
*/
public class MyJLabel extends JLabel implements Runnable{
private boolean flag = true;
private static Random random = new Random();
public MyJLabel(String string) {
super(string);
}
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
while (flag) {
setText(String.valueOf(random.nextInt()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
return;
}
}
}
}
这种方式比较简单了,设置一个flag,点击stop后直接设为false,但是这种方法有延时,因为,如果正在执行sleep的时候,会一直执行完再推出,没有interrupt那么暴力,直接抛出异常然后返回
2.5 Runnable接口(The Runnable Interface)
前面我们提到了Rrunnable接口,和继承Thread类比,用Runnable能够让我们把task和执行task的Thread分开,避免多继承的局限,一个类可以继承多个接口而且还可以方便数据共享
2.6 线程和对象(Threads and Objects)
每个线程都是一个对象,可以被传递给其他任何对象执行,有时候你如果想知道执行当前代码的到底是个线程,使用Thread.currentThread()方法
转载于:https://blog.51cto.com/2925665/1313583