线程:一个顺序的单一的程序执行流程就是一个线程。代码一句一句的有先后顺序的执行。
多线程:多个单一顺序执行的流程并发运行。造成"感官上同时运行"的效果。
并发:
多个线程实际运行是走走停停的。线程调度程序会将CPU运行时间划分为若干个时间片段并尽可能均匀的分配给每个线程,拿到时间片的线程被CPU执行这段时间。当超时后线程调度程序会再次分配一个时间片段给一个线程使得CPU执行它。如此反复。由于CPU执行时间在纳秒级别,我们感觉不到切换线程运行的过程。所以微观上走走停停,宏观上感觉一起运行的现象成为并发运行!
用途:
- - 当出现多个代码片段执行顺序有冲突时,希望它们各干各的时就应当放在不同线程上"同时"运行
- - 一个线程可以运行,但是多个线程可以更快时,可以使用多线程运行
线程生命周期
“线程”指两件不同的事情:
- java.lang.Thread类的一个实例;
- 线程的执行。
- 使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。
- 一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具有变量和方法,生死于堆上。
- Java中,每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行着。
- 一个Java应用总是从main()方法开始运行,mian()方法运行在一个线程内,它被称为主线程。
- 一旦创建一个新的线程,就产生一个新的调用栈。
- 线程总体分两类:用户线程和守候线程。
- 当所有用户线程执行完毕的时候,JVM自动关闭。但是守候线程却不独立于JVM,守候线程一般是由操作系统或者用户自己创建的。
方式一:继承Thread并重写run方法。定义一个线程类,重写run方法,在其中定义线程要执行的任务(希望和其他线程并发执行的任务)。
【注】启动该线程要调用该线程的start方法,而不是run方法!!!
public class ThreadDemo1 {
public static void main(String[] args) {
//创建两个线程
Thread t1 = new MyThread1();
Thread t2 = new MyThread2();
/*
启动线程,注意:不要调用run方法!!
线程调用完start方法后会纳入到系统的线程调度器程序中被统一管理。
线程调度器会分配时间片段给线程,使得CPU执行该线程这段时间,用完后
线程调度器会再分配一个时间片段给一个线程,如此反复,使得多个线程
都有机会执行一会,做到走走停停,并发运行。
线程第一次被分配到时间后会执行它的run方法开始工作。
*/
t1.start();
t2.start();
}
}
class MyThread1 extends Thread{
public void run(){
for (int i=0;i<1000;i ){
System.out.println("hello姐~");
}
}
}
class MyThread2 extends Thread{
public void run(){
for (int i=0;i<1000;i ){
System.out.println("来了~老弟!");
}
}
}
优点: 在于结构简单,便于匿名内部类形式创建。
缺点:
- - :直接继承线程,会导致不能在继承其他类去复用方法,这在实际开发中是非常不便的。
- - :定义线程的同时重写了run方法,会导致线程与线程任务绑定在了一起,不利于线程的重用。
方式二:实现Runnable接口单独定义线程任务
public class ThreadDemo2 {
public static void main(String[] args) {
//实例化任务
Runnable r1 = new MyRunnable1();
Runnable r2 = new MyRunnable2();
//创建线程并指派任务
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
//Runnable可以使用lambda表达式创建
Runnable r2 = ()->{
for(int i=0;i<1000;i ){
System.out.println("我是查水表的!");
}
};
t1.start();
t2.start();
}
}
class MyRunnable1 implements Runnable{
public void run() {
for (int i=0;i<1000;i ){
System.out.println("你是谁啊?");
}
}
}
class MyRunnable2 implements Runnable{
public void run() {
for (int i=0;i<1000;i ){
System.out.println("开门!查水表的!");
}
}
}
/*Thread提供了一个静态方法:
* static Thread currentThread()
* 获取执行该方法的线程。
*/
public class CurrentThreadDemo {
public static void main(String[] args) {
/*
重要的API:ThreadLocal,它可以使得我们在一个线程上跨越多个
方法时共享数据使用,其内部要用到currentThread方法来辨别线程。
如spring的事物控制就是靠ThreadLocal实现的。
*/
Thread main = Thread.currentThread();//获取执行main方法的线程(主线程)
System.out.println("线程:" main);
dosome();//主线程执行dosome方法
}
public static void dosome(){
Thread t = Thread.currentThread();//获取执行dosome方法的线程
System.out.println("执行dosome方法的线程是:" t);
}
}
多线程实现客户端连接
常见问题:
- 线程的名字,一个运行中的线程总是有名字的,名字有两个来源,一个是虚拟机自己给的名字,一个是你自己的定的名字。在没有指定线程名字的情况下,虚拟机总会为线程指定名字,并且主线程的名字总是mian,非主线程的名字不确定。
- 线程都可以设置名字,也可以获取线程的名字,连主线程也不例外。
- 获取当前线程的对象的方法是:Thread.currentThread();
- 在上面的代码中,只能保证:每个线程都将启动,每个线程都将运行直到完成。一系列线程以某种顺序启动并不意味着将按该顺序执行。对于任何一组启动的线程来说,调度程序不能保证其执行次序,持续时间也无法保证。
- 当线程目标run()方法结束时该线程完成。
- 一旦线程启动,它就永远不能再重新启动。只有一个新的线程可以被启动,并且只能一次。一个可运行的线程或死线程可以被重新启动。
- 线程的调度是JVM的一部分,在一个CPU的机器上上,实际上一次只能运行一个线程。一次只有一个线程栈执行。JVM线程调度程序决定实际运行哪个处于可运行状态的线程。众多可运行线程中的某一个会被选中做为当前线程。可运行线程被选择运行的顺序是没有保障的。
- 尽管通常采用队列形式,但这是没有保障的。队列形式是指当一个线程完成“一轮”时,它移到可运行队列的尾部等待,直到它最终排队到该队列的前端为止,它才能被再次选中。事实上,我们把它称为可运行池而不是一个可运行队列,目的是帮助认识线程并不都是以某种有保障的顺序排列唱呢个一个队列的事实。
- 尽管我们没有无法控制线程调度程序,但可以通过别的方式来影响线程调度的方式。
学习记录,如有侵权联系删除。
,