一、构造函数
1、默认空构造
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
//默认线程名字Thread-0,静态同步自增
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
2、Runnable子类
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
//线程调用start方法时,底层会调用run方法,如果重写则执行runnable实现的方法
@Override
public void run() {
if (target != null) {
target.run();
}
}
3、线程名
public Thread(String name) {
init(null, null, name, 0);
}
4、指定线程栈大小
/**
* ThreadGroup 如果没有设置默认是创建该线程的线程的TreadGroup(一般是main)
* 如果传入一个Runnable子类,就会调用其run方法。如果为null,将调用Tread自己的run();
* name 设置线程名字,可以不设置,默认从线程Thread-0开始(当然这个构造必须设置)
* stackSize 代表着该创建的线程占用的栈内存大小,栈内存可以不设置,默认为0,将会忽略;不同操作系统也有影响;线程栈越少可以创建的线程数量越多,越多则线程内方法调用深度越深。
* */
public Thread(ThreadGroup group, Runnable target, String name,long stackSize)
{
init(group, target, name, stackSize);
}
=====================================================
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
//需设置线程名
throw new NullPointerException("name cannot be null");
}
this.name = name;
//这个父线程就是帮你调用start方法的线程
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
if (g == null) {
//获取创建本线程的groupTread
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
二、线程组
public class ThreadConstruction {
public static void main(String[] args) {
Thread t1 = new Thread("t1");
ThreadGroup testGroup = new ThreadGroup("testGroup");
Thread t2 = new Thread(testGroup,"t2");
ThreadGroup currentGroup = Thread.currentThread().getThreadGroup();
System.out.println("当前线程的线程组是"+currentGroup);
System.out.println("t1 的线程组 和当前线程组关系"+ (t1.getThreadGroup() == currentGroup));
System.out.println("t1 的线程组 == t2 的"+ (t1.getThreadGroup() == t2.getThreadGroup()));
}
}
当前线程的线程组是java.lang.ThreadGroup[name=main,maxpri=10]
t1 的线程组 和当前线程组关系true
t1 的线程组 == t2 的false
main线程是由jvm创建的默认线程组;
构造线程时没有设置线程组,默认和父线程属于同一个线程组;和父线程的优先级一样。
三、 start方法
源码如下,Thread调用start方法,里面有个本地方法start0,jvm会调用run方法(也称为执行单元)。
public synchronized void start() {
//线程只能start一次,不然会IllegalThreadStateException异常
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
四、 守护线程
原理:都属于同一个线程组
特点:
1、如果非守护线程结束,则不管守护线程什么状态都将结束(包括守护线程里面子线程);默认为非守护线程。
2、设置tread.setDaemon(true);必须在调用start方法之前调用,不然会报 IllegalThreadStateException异常
作用:
1、比如做如下,网路连接心跳,设置心跳线程为守护线程,如果其他线程结束,心跳也没意义存在,所以跟着结束
2、做为后台线程,随着程序关闭或者jvm退出而关闭
3、垃圾回收线程,就是守护线程,不然jvm退出还在垃圾处理。
①外面守护线程,内线程非守护
public class HartDaemo {
public static void main(String[] args) throws InterruptedException {
Thread hartThread = new Thread() {
@Override
public void run() {
Thread innerThread = new Thread() {
@Override
public void run() {
while (true){
System.out.println("内部线程正常中...........");
}
}
};
innerThread.start();
while (true){
System.out.println("心跳正常中...........");
}
}
};
//设置为守护线程
hartThread.setDaemon(true);
hartThread.start();
Thread.sleep(1000);
System.out.println("main线程结束...........");
}
}
心跳正常中...........
心跳正常中...........
main线程结束...........
内部线程正常中...........
内部线程正常中...........
内部线程正常中...........
Process finished with exit code 0
由于main线程结束,守护线程和内部线程也随着结束
② 外部守护线程,内部守护
public class HartDaemo {
public static void main(String[] args) throws InterruptedException {
Thread hartThread = new Thread() {
@Override
public void run() {
Thread innerThread = new Thread() {
@Override
public void run() {
while (true){
System.out.println("内部线程正常中...........");
}
}
};
innerThread.setDaemon(true);
innerThread.start();
while (true){
System.out.println("心跳正常中...........");
}
}
};
//设置为守护线程
hartThread.setDaemon(true);
hartThread.start();
Thread.sleep(1000);
System.out.println("main线程结束...........");
}
}
心跳正常中...........
心跳正常中...........
main线程结束...........
内部线程正常中...........
内部线程正常中...........
内部线程正常中...........
Process finished with exit code 0
由于main线程结束,守护线程和内部线程也随着结束
五、优先级和id
public class PriorityDemo {
public static void main(String[] args) {
for (int i=1; i<100;i++){
Thread t1 = new Thread();
System.out.println("线程一:"+ t1.getId()+".....i="+ i);
t1.setPriority(Thread.MIN_PRIORITY);//1级最低
t1.start();
}
for (int i=1; i<100;i++){
Thread t2 = new Thread();
System.out.println("线程二:"+ t2.getId()+".....i="+ i);
t2.setPriority(Thread.MAX_PRIORITY);//10级最高
t2.start();
}
for (int i=1; i<100;i++){
Thread t3= new Thread();
System.out.println("线程三:"+ t3.getId()+".....i="+ i);
t3.setPriority(Thread.NORM_PRIORITY); //5级中间,默认优先级
t3.start();
}
}
}
控制台:随便显示几个看看就行了
线程一:97.....i=87
线程一:98.....i=88
线程二:206.....i=97
线程二:207.....i=98
线程二:208.....i=99
线程三:209.....i=1
线程三:210.....i=2
结论:并没有按照优先级执行,所以控制不住;只能说应该高优先级的优先于低优先级的运行
。如果想要实现优先效果,可以自己做一个队列。
源码
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
// 优先级控制在 1-10之间,数字越大,优先级越高
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
线程的优先级只能在1-10之间,如果指定的线程优先级大于线程组的优先级,那么将被设置为线程组的最大优先级
/**
* 线程优先级,具有不确定性
*/
public class ThreadPriority {
public static void main(String[] args) {
ThreadGroup group = new ThreadGroup("testGroup");
group.setMaxPriority(7);
Thread thread = new Thread(group, "testThread");
thread.setPriority(10);
System.out.println("设置后的testThread线程的优先级为"+thread.getPriority());
System.out.println("默认主线程的优先级为"+Thread.currentThread().getPriority());
}
}
设置后的testThread线程的优先级为7
默认主线程的优先级为5
线程的优先级默认和它的父线程优先级一致。
六、sleep() 方法
sleep 是一个静态方法,有两个重载方法,一个需要传入毫秒数,另外一个既需要毫秒数也需要纳秒数
public static void sleep(long millis, int nanos) throws InterruptedException
public static void sleep(long millis) throws InterruptedException
sleep方法会使当前线程进入指定毫秒数的休眠,暂停执行,虽然给定一个休眠时间但最终要以系统的定时器和调度器精度为准,需要注意此时线程不会放弃任何的关于“monitor”锁的所有权,每个线程之间进入睡眠互不影响
JDK1.5之后,通过TimeUnit 可以对sleep方法提供很好的封装,使用它可以省去时间单位的换算步骤,如需要线程休眠1个小时10分10秒10毫秒
TimeUnit.HOURS.sleep(1);
TimeUnit.MINUTES.sleep(10);
TimeUnit.SECONDS.sleep(10);
TimeUnit.MILLISECONDS.sleep(10);
七、yield( ) 方法
yield方法属于一种启发式的方法,提醒调度器我愿意放弃CPU当前执行权,CUP的资源不紧张时候,则会忽略这种提醒
调用yield方法可能使当前线程从Running 状态切换到Runnable 状态,一般这个方法不太常用。
关于yield 和 sleep
- sleep会导致当前线程暂停指定的时间,没有CPU时间的消耗,到指定时间之后会自动被系统唤醒
- yield 只是对CPU调度器的一个提示,如果CPU没有忽略这个提示,它会导致线程上下文的切换
- yield 会是Running 状态下的 Thread 进入 Runnable 状态(如果CPU调度器没有忽略这个提示的话)
- sleep 会使线程短暂进入block,会在给定的时间内释放CPU资源
- sleep几乎百分之百完成给定时间的休眠,而yield的提示并不能一定保证
- A线程sleep,B线程调用A线程的interrupt 会捕获到中断信号,而yield 则不会
六、 join( ) 方法
join 某个线程A,会使当前线程B进入等待,直到线程A结束生命周期,或者到达给定时间,那么在此期间B线程将处于Blocked状态,而不是A线程(放在thread.start();之后)
public final void join() throws InterruptedException
public final synchronized void join(long millis, int nanos) throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i = 1; i < 100; i++) {
System.out.println("一号同学:.....上几号i=" + i + "课程");
}
});
Thread t2 = new Thread(()->{
for (int i = 1; i < 100; i++) {
System.out.println("二号同学:.....上几号i=" + i + "课程");
}
});
t1.start();
t2.start();
//main线程阻塞,执行t1线程任务
t1.join();//t1.join(1000);设置超过1秒就不优先执行完这个了
t2.join();//等待t1和t2执行完,才执行main线程代码。
//Thread.currentThread().join(); main线程等待main线程结束,导致最后一直等待
System.out.println(" 放假。。。");
}
}
控制台:如果不加人上面两个join方法,导致直接没上完课,学生就放假了,不符合情理
七、interrupt
如下的方法(可中断方法)调用会使当前线程进入阻塞状态,而调用当前线程的interrupt方法,就可以中断阻塞
- Object 的 wait方法
- Object 的 wait(long) 方法
- Object 的 wait() 方法
- Thread 的 sleep(long) 方法
- Thread 的 sleep(long, int) 方法
- Thread 的 join 方法
- Thread 的 join(long) 方法
- Thread 的 join(long int) 方法
- InterruptibleChannel 的 IO 操作
- Selector 的 wakeup 方法
- 其他方法
上述方法都会是当前进程进入阻塞状态,若另外的一个线程调用被阻塞线程的interrupt 方法,则会打断这种阻塞,因此上面这种方法有时也称为可中断方法,记住 : 打断一个线程并不等于改线程的生命周期结束,仅仅是打断了当前线程的阻塞状态。
一旦线程在阻塞的情况下被打断,都会抛出一个InterruptedException 的异常,这个异常相当于一个信号一样通知当前线程被打断了
public class ThreadInterrupt {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
TimeUnit.MINUTES.sleep(10);
} catch (InterruptedException e) {
System.out.println("i am be interrupted");
}
});
thread.start();
TimeUnit.MILLISECONDS.sleep(2);
thread.interrupt();
}
}
通过创建一个线程,并企图睡眠10分钟,但大约在2毫秒之后就被主线程调用interrupt 方法打断,打印 “i am be interrupted”
如果一个线程调用interrupt方法,将被标记中断标志;如果是执行可中断方法被阻塞时,调用interrupt将会清除中断标志。
/**
* isInterrupted是Thread的成员方法,主要判断当前线程是否被中断,
* 该方法仅是对interrupt标识的一个判断,并不会影响标识发生任何改变。
*/
public class ThreadIsInterrupted {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
//这里不使用sleep()是因为它是一个可中断方法,会捕获到中断信号,从而干扰程序运行结果(sleep():false,false)
while (true) {
}
});
thread.setDaemon(true);
thread.start();
TimeUnit.MILLISECONDS.sleep(2);
System.out.printf("thread is interrupted? %s\n", thread.isInterrupted());
thread.interrupt();
System.out.printf("thread is interrupted? %s\n", thread.isInterrupted());
//sleep()会捕获中断信号,并且擦除interrupt标识,因此程序结果总是false
//可中断方法捕获到中断信号后,为了不影响当前线程中的其他方法的正常执行,将interrupt标识复位
System.out.println("============================================");
Thread thread1 = new Thread() {
@Override
public void run() {
while (true) {
try {
TimeUnit.MINUTES.sleep(1);
} catch (InterruptedException e) {
System.out.printf("Thread is interrupted? %s\n", isInterrupted());//调用这个方法不能用lambda
}
}
}
};
thread1.setDaemon(true);
thread1.start();
TimeUnit.MILLISECONDS.sleep(2);
System.out.printf("Thread is interrupted? %s\n", thread1.isInterrupted());
thread1.interrupt();
TimeUnit.MILLISECONDS.sleep(2);
System.out.printf("Thread is interrupted? %s\n", thread1.isInterrupted());
}
}
thread is interrupted? false
thread is interrupted? true
============================================
Thread is interrupted? false
Thread is interrupted? false
Thread is interrupted? false
isInterrupted 和 interrupted 区别
- isInterrupted是 Thread 的一个成员方法,主要判断当前线程是否被中断,该方法仅仅是对interrupt 标识的一个判断,并不会影响标识发生任何改变。
- interrupted 存在很大差别,需要注意的是可中断的方法捕获到中断信号后,也就是捕获到异常之后会擦除interrupt 标识,相当于标识复位
源码如下:区别就是interrupted会清除中断标志
public static boolean interrupted() {
return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
return isInterrupted(false);
}
对于前面有中断标志没有清除的情况,后面该线程有可中断方法,还将被中断
/**
* interrupted()是一个静态方法,用于判断当前线程是否被中断,但是和成员方法isInterrupted()还是有很大区别,
* 调用该方法会直接擦除线程的interrupt标识,需要注意的是,如果当前线程被打断了,第一次调用interrupted会返回true,
* 并且立即擦除线程的interrupt标识,第二次包括以后的调用永远返回false,除非在此期间线程再一次被打断
*/
public class ThreadInterrupted {
public static void main(String[] args) throws InterruptedException {
//如果一个线程在没有执行可中断方法之前就被打断,那么接下来执行可中断方法,会立即被中断。
//也就是说,如果一个线程设置了interrupt表示,那么接下来的可中断方法会立即被中断
System.out.println("============================================");
//判断当前线程是否被中断,这里用成员方法isInterrupted()也不会有影响
System.out.println("Main thread is interrupted? "+Thread.interrupted());
//中断当前线程
Thread.currentThread().interrupt();
//interrupted擦出了后面就看不到flag标志
//System.out.println("Main thread interrupted? "+Thread.interrupted());
//判断当前线程是否被中断,这里用了成员方法isInterrupted(),原因是interrupted()会立即擦除线程的interrupt标识
//导致程序运行结果不同(下面的sleep不会被打断)
System.out.println("Main thread isInterrupted? "+Thread.currentThread().isInterrupted());
//当前线程执行可中断方法
try {
TimeUnit.MINUTES.sleep(1);
}catch (InterruptedException e){
System.out.println("I will be interrupted still.");
}
}
}
============================================
Main thread is interrupted? false
Main thread isInterrupted? true
I will be interrupted still.
参考: 多线程 中断
八、关闭线程方式
/**
* 示例3 - 线程stop强制性中止,破坏线程安全的示例
*/
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
StopThread thread = new StopThread();
thread.start();
// 休眠1秒,确保i变量自增成功
Thread.sleep(1000);
// 暂停线程
thread.stop(); // 错误的终止
// thread.interrupt(); // 正确终止
while (thread.isAlive()) {
// 确保线程已经终止
} // 输出结果
thread.print();
}
}
public class StopThread extends Thread {
private int i = 0, j = 0;
@Override
public void run() {
synchronized (this) {
// 增加同步锁,确保线程安全
++i;
try {
// 休眠10秒,模拟耗时操作
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
++j;
}
}
/** * 打印i和j */
public void print() {
System.out.println("i=" + i + " j=" + j);
}
}
结果:i=1 j=0
stop强制终止线程,执行到一半终止有安全问题
由于stop方法存在诸多问题,不建议使用,强制终止线程并清除monitor锁信息,所以我们考虑使用如下方案:
1、使用标记判断进行线程关闭
2、使用中断加守护线程(如下案例)
先设置任务线程为守护线程,利用main线程控制执行线程中断来控制任务线程中断。
public class ThreadService {
//线程调度服务,用来控制文件拷贝任务
private Thread thread; //该线程用来调度资源拷贝线程的
private boolean flage = false;//拷贝线程结束改变状态为true
public void execute(Thread task)
{
//拷贝执行方法,task为拷贝线程
thread = new Thread(() -> {
task.setDaemon(true);//设置copy线程为守护线程
task.start();
try {
task.join();//怕copy线程还没启动就随非守护线程结束了
flage = true;
} catch (InterruptedException e) {
// e.printStackTrace();
}
});
thread.start(); //开启调度线程
}
public void shutdown(Long mills)
{
//关闭线程方法
Long logoTime = System.currentTimeMillis();
while(!flage){
//如果没有完成拷贝任务
if ((System.currentTimeMillis() - logoTime) > mills){
//查看是否超时
System.out.println("超时。。拷贝任务被强制结束");
thread.interrupt();//调度线程中断,非守护线程都结束了,守护线程也跟着结束
break;//跳出循环,main线程和thread线程
}else {
try {
thread.sleep(1);
} catch (InterruptedException e) {
System.out.println("执行线程被打断");
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
Long logoTime = System.currentTimeMillis();
ThreadService threadService = new ThreadService();
threadService.execute( new Thread(()->{
while(true){
//拷贝文件中,时间非常长
}
/* try {//5秒就搞定了拷贝
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}));
threadService.shutdown(10_000l);//10秒
System.out.println(System.currentTimeMillis() - logoTime);
}
}