①.创建子类,继承自Thread.重写Thread类中的run()方法,在new的时候newThread的子类,在run()中写在县城中具体实现的代码,描述了这个线程内部要执行哪些代码.在代码中,并非定义了子类,一写run方法,线程就创建出来,线程的具体实现,需要调用start方法,线程才被创建出来。调用start之前,系统中并没有创建出线程。
class MyThread extends Thread{
@Override
public void run() {
System.out.println("hello thread");
}
}
public class Demo1 {
public static void main(String[] args) {
Thread t = new MyThread();
t.start();
}
}
如果在一个循环中不加任何限制,这个循环的速度非常非常快,在线程中可以加上一个sleep操作,来强制让这个线程休眠一段时间,这个休眠操作,就是强制让线程进入阻塞状态,单位是ms,相当于一秒之内这个线程不会到cpu上执行
class MyThread1 extends Thread{
@Override
public void run() {
while (true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
一个进程中至少会有一个线程.在一个java进程中,也是至少会有一个调用main方法的线程(系统自生成),,此时t线程和main线程,就是并发执行的关系(此处的并发=并行+并发),现在两个线程都是打印一条休眠一秒,当一秒时间到了之后.系统先唤醒哪个线程的顺序是随机的.对于操作系统来说,内部对于线程之间的调度顺序,在宏观上也可以认为是随机的~~~(抢占式执行)
class MyThread1 extends Thread{
@Override
public void run() {
while (true){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo2 {
public static void main(String[] args) {
Thread t = new MyThread1();
t.start();
while (true){
System.out.println("hello world");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
②.创建一个类,实现Runnable接口,再创建Runnable实例传给Thread
Runnable就是在描述一个"任务",通过Runnable来描述一个任务,进一步的再把描述好的任务交给Thread实例
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("hello thread");
}
}
public class Demo3 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
③.写法三/写法四:就是上述两个写法的翻版,使用了匿名内部类,继承自Thread类,同时重写run方法,同时在new出这个匿名内部类的实例
public class Demo4 {
public static void main(String[] args) {
Thread t = new Thread(){//匿名内部类
@Override
public void run(){
System.out.println("hello thread");
}
};
t.start();
}
}
public class Demo5 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello thread!");
}
});
t.start();
}
}
new的Runnable,针对这个创建的匿名内部类,同时new出的Runnable实例传给Thread的构造方法
④.第五种写法是第四种写法的延伸,使用lambda表达式(使用lambda代替了Runnable而已~~)
public class Demo6 {
public static void main(String[] args) {
Thread t = new Thread(() ->{
System.out.println("hello thread");
});
t.start();
}
}
①.是否后台线程(isDaemon)
*具体代码案例参考Demo7
如果线程是后台线程,就不影响进程退出;如果线程不是后台线程(前台线程),就会影响到进程退出。创建的t1和t2默认都是前台的线程,即使main方法执行完毕,进程也不能退出,得等t1和t2都执行完,整个进程才能退出;如果t1和t2是后台线程,此时如果main执行完毕,整个进程就直接退出,t1和t2就被强行终止了~~
②.给线程(thread对象)起一个名字
Thread(String name)
仅仅只是影响程序猿调试,可以借助一些工具看到每个线程以及名字,很容易在调试中对线程做出区分~~~
public class Demo8 {
public static void main(String[] args) {
Thread t1 = new Thread(() ->{
while (true){
System.out.println("hello thread1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"ThreadT1");
t1.start();
Thread t2 = new Thread(() ->{
while (true){
System.out.println("hello thread2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"ThreadT2");
t2.start();
}
}
③.线程是否存活(isAlive),操作系统中对应的线程是否正在运行
Thread t对象的生命周期和内核中对应的线程,生命周期并不完全一致~~
创建出t对象之后,在调用start之前,系统中是没有对应线程的~~
在run方法执行完毕之后,系统中的线程就销毁了,但是t这个对象可能还存在~~
通过isAlive就能判定当前系统的线程的运行情况
如果调用start之后,run执行完之前,isAlive就返回True;
如果调用start之前,run执行完之后,isAlive就返回False。
①.start(决定了系统是不是真的创建出线程)
t.run(); ---> run单纯的只是一个普通的方法,描述了任务的内容
run方法只是一个普通的方法,在main线程里调用run,并没有创建新的线程
t.start(); --->start则是一个特殊的方法,内部会在系统中创建线程
②.中断线程~~让一个线程停下来~~
线程停下来的关键,是要让线程对应的run方法执行完~~(还有一个特殊的,是main这个线程,对于main来说,得是main方法执行完,线程就完了)
(一)、可以手动的设置一个标志位,来控制线程是否要执行结束~~~
public class Demo9 {
private static boolean isQuit = false;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (!isQuit){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
//只要把isQuit设为True,此时这个循环就退出了,进一步的run就执行完了,再进一步就是线程执行结束了
try {
Thread.sleep(5000);
}catch (InterruptedException e){
e.printStackTrace();
}
//main线程在此时执行isQuit = true的操作,此时t线程的while循环条件不成立,那么t线程就终止了
isQuit = true;
System.out.println("终止T线程");
}
}
(二)、更好的做法,使用Thread中内置的一个标志位来进行判定~~
可以通过:Thread.interrupted() 这是一个静态的方法;Thread.currentThread().isInterrupted() 这是实例方法,其中currentThread能够获取到当前线程的实例。
public class Demo10 {
public static void main(String[] args) {
Thread t = new Thread(() ->{
while (!Thread.currentThread().isInterrupted()){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//在主线程中,调用interrupted方法,来中断这个线程
//t.interrupt的意思就是t线程被中断!!!
t.interrupt();
}
}
t.interrupt();调用这个方法,可能产生两种情况:①.如果t线程是处在就绪状态,就是设置线程的标志位为true;②.如果t线程处在阻塞状态(sleep休眠了),就会触发一个InterruptException~~
此处的中断,是希望能够立即产生效果的~~如果线程以及是阻塞状态下,此时设置标志位就不能起到及时唤醒的效果~~
调用这个interrupt方法,就会让sleep触发一个异常,从而导致线程从阻塞状态被唤醒~当下的代码一旦触发了异常之后,就会进入了catch语句,在catch语句中,就单纯的只是打了一个日志。
while (!Thread.currentThread().isInterrupted()){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
//当触发异常的时候,立即就退出循环
break;
}
}
Thread.interrupted() 这个方法判定的标志位是Thread的static成员(一个程序中只有一个标志位)。
Thread.currentThread().isinterrupted()这个方法判定的标志位是Thread的普通成员,每个实例都有自己的标志位。
③. 线程等待
多个线程之间的调度顺序不确定的~~
线程等待就是一种控制线程执行顺序的手段~~此处的线程等待,主要是控制线程结束的先后顺序~
join~
调用join的时候,哪个线程调用的join,哪个线程就会阻塞等待~~得到对应的线程执行完毕为止(对应线程的run执行完)
通过线程等待,就是在控制让t先结束,main后结束
public class Demo11 {
public static void main(String[] args) {
Thread t = new Thread(() ->{
for (int i = 0; i < 5; i++) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
//在主线程中就可以使用一个等待操作,等待t线程执行结束
try {
t.join();//首先,调用这个方法的线程是main线程,针对t这个线程对象调用的,此时就是让main等待t~~~~
} catch (InterruptedException e) {
e.printStackTrace();
}
//调用join之后,main线程就会进入阻塞状态(暂时无法再cpu上执行)
//等t线程执行完之后,才会执行下面的main线程中的代码
System.out.println("t执行完了,main才执行");
}
}
join操作默认情况下,是死等(不合理),join提供了另外一个版本,就是可以执行等待时间,最常等待时间,如果等不到,就不等了去执行要执行的代码。
public class Demo12 {
public static void main(String[] args) {
Thread t = new Thread(() ->{
for (int i = 0; i < 10; i++) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t.start();
try {
t.join(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("等不及了,不等了");
}
}
④.获取当前线程的引用
Thread.currentThread()就能够获取到当前线程的引用;哪个线程调用的这个currentThread,就获取到的哪个线程的实例~~~
public class Demo13 {
public static void main(String[] args) {
Thread t = new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
t.start();
//这个操作是在main线程中使用,拿到的就是main这个线程的实例
System.out.println(Thread.currentThread().getName());
}
}
⑤.线程休眠
sleep~~~
如果一个进程有多个线程,此时每个线程都有一个PCB,一个进程对应的就是一组PCB了~~
PCB上有一个字段tgroupid,这个id其实就相当于进程的id,同一个进程中的若干个线程的tgroupid是相同的!!!
如果某个线程调用了sleep方法,这个PCB就会进入到阻塞队列~~
操作系统调度线程的时候,就只是从就绪队列中挑选合适的PCB到CPU上运行,阻塞队列里的PCB就只能干等着~~
当睡眠时间到了,系统就会把刚才这个PCB从阻塞队列挪回就绪队列(休眠结束)
public class Demo7 {
private static final long count = 10_0000_0000;
public static void serial(){
//记录程序执行时间
long begin = System.currentTimeMillis();
long a = 0;
for (int i = 0; i < count; i++) {
a++;
}
long b = 0;
for (int i = 0; i < count; i++) {
b++;
}
//记录结束时间
long end = System.currentTimeMillis();
System.out.println("串行消耗的时间:" + (end - begin) + "ms");
}
public static void concurrency() throws InterruptedException {
long start = System.currentTimeMillis();
Thread t1 = new Thread(() ->{
long a = 0;
for (int i = 0; i < count; i++) {
a++;
}
});
t1.start();
Thread t2 = new Thread(() ->{
long b = 0;
for (int i = 0; i < count; i++) {
b++;
}
});
t2.start();
t1.join();
t2.join();
long end = System.currentTimeMillis();
System.out.println("并行消耗的时间:"+ (end - start)+"ms");
}
public static void main(String[] args) throws InterruptedException {
serial();
concurrency();
}
}
serial代表串行执行,concurrency代表了多线程并行执行。
并发执行相比于串行执行效率提升了将近50%。
main和t1、t2是并发执行的关系,此处t1和t2还没执行完呢,这里就开始记录结束时间了。因此,main线程需要等待t1、t2执行完毕后再记录结束时间。使用t1.join和t2.join,join效果就是等待线程结束,t1.join就是让main线程等待t1执行结束,t2.join让main线程等待t2结束。
创建线程本身也会消耗时间,当数据量小时,串行执行效率就会高于并发执行!