1.方法一:直接使用Thread
//创建线程对象
Thread t = new Thread(){
public void run(){
//要执行的任务代码
}
};
//线程t启动
t.start();
例如:
Thread t1 = new Thread(){
@Override
public void run(){
System.out.println("hello");
}
};
t1.start();
2.方法二:使用Runnable配合Thread
- 把[线程]和[任务](要执行的代码)分开
- Runnable可运行的任务(线程要执行的代码)
Runnable runnable = new Runnable(){
public void run(){
//要执行的任务
}
};
//创建线程对象
Thread t = new Thread(runnable);
//启动线程
t.start();
3.查看源码分析一下以上两种方法的底层实现:
进入到Thread.class中查看到Thread.class的构造器,开始跟踪执行过程->
Thread()->init(.....)->发现了target被赋给了成员变量->找到了target被Thread自带的run()方法使用.
如果target对象不为空则调用target的run()方法.否则用Thread自己的run方法.如果没有targer对象则继承Thread.class的子类会重写父类的run()方法.然后调用子类的run()方法.
//Thread和Runnable共同驱动
public void test1(){
Runnable runnable = new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("aj");
}
};
Thread t = new Thread(runnable);
t.start();
}
//线程Thread驱动
public void test2() {
Thread t2 = new Thread() {
@Override
public void run() {
System.out.println("lalalala");
}
};
//启动线程
t2.start();
}
将来线程运行时,不管采用Thread 后者是 Thread和Runnable 最后运行的都是Thread的run()方法.
4.Thread 和Runnable之间的关系;
方法1是把线程和任务直接合并在了一起,方法2shi把线程和任务分开了.
使用Runnable更容易与线程池等高级API进行配合.
使用Runnable让任务类脱离了Thread继承体系,更加的灵活.其实Java中更加推荐组合而不是继承.
Java中只能单继承,但是支持多实现(接口).
5.方法3,FutureTask配合Thread
FutureTast能够接收Callable类型的参数,用来处理有返回结果的情况.
//创建任务对象
FutureTask<Integer> task3 = new FutureTask<>(()->{
System.out.println("hello);
return 100;
});
//参数1 是任务对象,参数2 是线程名称,推荐
new Thread(task3,"t3").start();
//主线程阻塞,同步等待 task执行完毕的结果
Integer result = task.get();
System.out.print("输出的结果是:"+result);
public static void main(String[] args) {
FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("running");
Thread.sleep(1000);//休息一秒
return 100;
}
});
//构造Thread对象
Thread t = new Thread(task);
t.start();
try {
System.out.println(task.get());//等待t线程 结果返回..
} catch (InterruptedException | ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
6.关于Thread的run()方法
我们在创建线程的时候为什么不直接通过 Thread对象实例调用run()方法呢?代码如下:
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
System.out.println("running...");
}
};
t1.run();//由main线程来进行完成的.
t1.start();//使得t1线程开始启动,并由t1线程执行.
}
结果发现:通过 t1.run() 执行的run()方法,是由main线程来进行驱动的.仅仅是main线程一个人在驱动.
而通过 t1.start()来调用,是由t1线程来执行的.这个才真正的启动了多线程并发.
7.sleep和yield
- sleep
- 调用sleep是让当前线程从Running进入Timed Waitting状态.
- 其他线程可以使用interrupt方法来打断正在睡眠的线程,此时sleep方法会抛出interruptedException异常.
- 睡眠结束后的线程未必会立刻得到执行,而是转到了 Runnable(就绪态)等待CPU调度.
- 推荐使用TimeUnit的sleep代替Thread的sleeo,可以获得更好的可读性.
- yield
- 调用yield是让当前线程从Running进入Runnable状态,.然后调度执行其他相同优先级的线程,如果这时没有通优先级的线程,那么不能保证让当前线程暂停的效果.
- 具体的实现有赖于不同的OS调度.
7.1sleep实现案例--防止CPU100%的占用
要是不添加Thread.sleep(50);对系统的CPU的占用了将会高达98%.(在单核处理器下)
while(true) {
try {
Thread.sleep(50);
}catch(InterruptedException e){
e.printStackTrace();
}
}
- 可以用wait或条件变量达到类似的效果.
- 不同的是,后两种都需要加锁,并且需要响应的唤醒操作,一般适用于要进行同步的场景.
- sleep适用于无需锁同步的场景.
8.线程优先级
- 线程优先级会提示线程调度机优先调度该线程.但是它也仅仅是一个提示,调度机可以听也可以不听.
- 如果CPU比较忙,那么优先级高的线程就会获得更多的时间片,但cpu闲时,优先级几乎没有作用.
8.1不使用Thread.yield();
public static void main(String[] args) {
//线程1
Thread t1= new Thread() {
public void run() {
int count=0;
while(true) {
System.out.println("----->1 "+count++);
}
}
};
//线程2
Thread t2= new Thread() {
public void run() {
int count=0;
while(true) {
System.out.println(" ----->2 "+count++);
}
}
};
//准备启动线程
t1.start();
t2.start();
}
控制台输出结果: 发现两个线程执行的频率基本上是均等的.
8.2线程2使用Thread.yield();
显示结果如下:线程2被调度的频率明显低于线程1
8.3设置优先级setPriority();
给线程1设为Min 线程2设置位Max.
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
在Thread.class中有3个优先级的常量,分别为如下3个:(数值越大,优先级越高).
执行结果:优先级高的线程2执行频率大于优先级低的线程1.
9.join()方法
public static int r = 0;
public static void main(String[] args) throws InterruptedException {
test1();
}
public static void test1() throws InterruptedException{
System.out.println("开始...");
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("线程1 开始");
try {
sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1 结束");
r = 10;
}
};
t1.start();
//t1.join();
System.out.println("结果为:"+r);
System.out.println("结束");
}
不调用t1.join();说出的结果是:r=0;
分析:因为main线程执行时,并没有等t1线程执行完,直接输出变量r的值.此时的r为0;
怎么解决?:
在t1线程启动之后,使用t1.join();等待t1线程运行结束后,在继续执行当前main线程的代码.
join();的作用就是:等待线程运行结束.
public static int r1 = 0;
public static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test2();
}
public static void test2() throws InterruptedException{
System.out.println("开始...");
//线程t1
Thread t1 = new Thread() {
@Override
public void run() {
System.out.println("线程1 开始");
try {
sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1 结束");
r1 = 10;
}
};
//线程t2
Thread t2 = new Thread() {
@Override
public void run() {
System.out.println("线程2 开始");
try {
sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2 结束");
r2 = 20;
}
};
t1.start();
t2.start();
long start = System.currentTimeMillis();
t1.join();
System.out.println("t1 join end");
t2.join();
System.out.println("t2 join end");
long end = System.currentTimeMillis();
System.out.println("r1="+r1+" r2="+r2+" 耗时="+(end-start));
}
因为使用了join();所以主线程会等待t1,t2线程运行结束,r1,r2会分别被t1,t2赋值.因此输出的值是:r1=10,r2=20; 耗时:2秒
10.打断sleep,wait,join的线程
打断sleep线程时,会清空打断状态,.(即将打断标记置为false)
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1") {
public void run() {
System.out.println("sleep...");
try {
Thread.sleep(5000);//wait,join
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//开启线程
t1.start();
Thread.sleep(1000);
System.out.println("interrupt");
//打断线程
t1.interrupt();
//当sleep,wait,join被打断后,会清楚打断标记.
System.out.println("打断标记:"+t1.isInterrupted());
}
输出结果:
:
打断正常运行的线程:
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread("t1") {
public void run() {
while(true) {
boolean interrupted = Thread.currentThread().isInterrupted();
if(interrupted) {
System.out.println("线程被打断退出循环");
break;
}
}
}
};
//开启线程
t1.start();
//主线程睡眠,等待一下t1线程.
Thread.sleep(1000);
//打断线程
t1.interrupt();
//当sleep,wait,join被打断后,会清楚打断标记.
System.out.println("打断标记:"+t1.isInterrupted());
}
输出结果:
11.interrupt的多线程模式---两阶段终止模式
在一个线程T1中如果优雅的终止线程T2?----优雅即:给T2线程一个料理后事的机会.
11.1介绍下两种错误的思路.
使用线程对象的stop()方法停止线程.
stop方法会真正杀死线程,如果这时的线程锁住了共享资源,那么他被杀死后就没有机会释放锁了,那么其他的线程就永远获取不到锁了.
使用System.exit(int)方法停止线程
目的仅仅是停止一个线程,这种做法会将整个程序都停止了.
11.1实现代码
public class Test10 {
public static void main(String[] args) throws InterruptedException {
TwoPhaseTermination t1 = new TwoPhaseTermination();
t1.start();
Thread.sleep(4000);
t1.stop();
}
}
class TwoPhaseTermination{
private Thread monitor;
//启动监控线程
public void start() {
monitor = new Thread() {
public void run() {
while(true) {
Thread current=Thread.currentThread();
if(current.isInterrupted()) {
System.out.println("料理后置...");
break;
}
try {
Thread.sleep(1000);//情况1
System.out.println("执行监控记录");//情况2
} catch (InterruptedException e) {
e.printStackTrace();
//重新设置打断标记.只要是因为:sleep被打断会将打断标记置为false;使得该线程无法及时正常退出.
current.interrupt();
}
}
}
};
monitor.start();
}
//停止监控线程
public void stop() {
//打断监控线程
monitor.interrupt();
}
}
情况1:线程在睡眠中被打断,会进入catch块中进行处理,因为sleep被打断后会将打断标记置为false.这会使得线程无法及时正常终止
情况2:正常情况下被打断,线程及时退出..
注意:isInterrupted()和interrupt()的区别.
11.2park()方法
打断park线程,不会清空打印状态,但是如果此时打断标记为ture,park方法就会失效.
11.3不推荐的方法
这些方法已经过时,容易破坏同步代码块,造成线程死锁,
12.主线程和守护线程(后台线程)
在默认情况下,Java进程需要等待所有的线程都运行结束,才会结束.有一种特殊的线程叫做守护线程,只要其他的非守护线程运行结束了,及时守护线程的代码没有执行完毕,也会强制结束.
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
public void run() {
while(true) {
if(Thread.currentThread().isInterrupted())
break;
}
System.out.println("Thread 1 end");
}
};
//将t1线程设置为后台进程,只要非后台进程结束.那么后台进程无论运行到何种程度,一律终止.
t1.setDaemon(true);
t1.start();
//主线程代码
Thread.sleep(1000);
System.out.println("main end");
}
将t1线程设置为守护线程,当"main end"输出语句结束后,会立刻杀死t1线程.发现t1线程中的"Thread 1 end"语句并没有打印出来.
其实垃圾回收器就是一种守护线程.
Tomcat中的Accept和Poller线程都是守护线程,所以,Tomcat接收到shutdown命令后,不会等待它们处理完当前请求.