线程:线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
简单理解:应用软件中互相独立,可以同时运行的功能
应用场景:
- 软件的耗时操作
- 拷贝,迁移文件
- 加载大量的 资源文件
作用:提高效率,在等 待的时间中,去执行别的需求
并发:在同一时刻,有多个指令在单个cpu上交替执行
:比如你在打游戏时,手在莫键盘,又时不时吃东西
并行:在同一时刻,有多个指令在多个cpu上同时执行
多个cpu的理解:是电脑的x核x线程,在计算机之中,随机的切换
多线程的实现:
1.自己定义一个类继承Thread
创建新执行线程有两种方法。一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的run方法接下来以分配并启动该子类的实例
实现继承
//重写run方法,将要实现的代码写在run中
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println("hello word");
}
}
//在测试类中,实现多进程。
}
进行测试
public class Test\_1 {
public static void main(String\[] args) {
//创建继承子类的对象
code\_1 t1=new code\_1();
// ti.run() 不能直接调用方法,这样就是调用了一个构造方法了,不是多线程了
//开启线程
t1.start();
}
**}**
打印出来100结果。
可见,test测试类中,实现了多线程。这里我们在添加一个t2进程,来更直观看到多线程的运行
//重写run方法,将要实现的代码写在run中
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println(getName()+"hello word");
}
}
//在测试类中,实现多进程。
}
public class Test\_1 {
public static void main(String\[] args) {
//创建继承子类的对象
code\_1 t1=new code\_1();
code\_1 t2=new code\_1();
//为两个线程起名字
t1.setName("线程一");
t2.setName("线程二");
// ti.run() 不能直接调用方法,这样就是调用了一个构造方法了,不是多线程了
//开启线程
t1.start();
t2.start();
}
}
可以发现,控制台打印的结果中,线程一和线程二是随机出现的
2.实现Runable接口
创建线程的另一种方法是声明实现 Runnable 接口的类,该类然后实现 接口 法然后可以分配该类的实例,在创建 时作为个参数来传并启动。
@Override
public void run() {
//书写要执行的代码
for (int i = 0; i <100 ; i++) {
//获取当前线程的对象
Thread t=Thread.currentThread();
//这里不能直接getname。因为Myrun这个类,和Runable是接口实现关系,不是Thread继承。
//所以才有上面获取当前线程对象的的代码
System.out.println(t.getName()+"hello");
}
}
}
public class Test {
public static void main(String\[] args) {
//创造实现类的对象
//表达多线程要执行的任务
Myrun mr=new Myrun();
//创造线程对象
Thread t1=new Thread(mr);
Thread t2=new Thread(mr);
//给线程设置名字
t1.setName("线程一");
t2.setName("线程二");
//开启线程
t1.start();
t2.start();
}
}
可以看到,实现Runnable接口,相比于Thread继承,仅仅多了一步”创造线程的对象“,还是十分方便的。
利用Callable接口和Future接口方式实现
特点: 可以获取到多线程运行的结果
-
创建一个类MyCallable实现Callable接口2。重写cal1 (是有返回值的,表示多线程运行的结果 )
-
创建MyCallable的对象 (表示多线程要执行的任务)
-
创建FutureTask的对象 (作用管理多线程运行的结果)
-
创建Thread类的对象,并启动 (表示线程)
创造三个变量分成:执行的任务 --多线程运行的结果---创造线程的对象
三个变量就可以调用该对应的方法来实现多线程
三种实现多线程的特点
总体分为两大类:可以获取结果(第三种),不能获取结果(前面两种)
多线程中构造方法的使用
setname/getname:
为了方便,继承Thread类的demo省略此处
这里实现一个测试类
demo1 t2=new demo1();
//开启 线程
t1.start(); //Thread-0 这是他的setname/getname的默认名字
// -x 默认从0开始
t2.start();
t2.setName("线程二");
Thread.currentThread();//当前获取的对像
String name= t1.getName();
System.out.println(name);//main
我们执行上面这段代码时,给线程二使用了setName定义了名字。线程一并未进行定义。
程序执行发现,当我们获取线程一和线程二的对象时,线程一返回了“Thread-0”这个默认名字
线程二的名字依旧是我们定义的”线程二“。
除了set/getName的构造方法,我们话可以如下这么写
这里我们可以在类的构造方法中,直接为它取名
demo1 t3=new demo3(“线程一”)
但前提是,需要在子类中,用super使用父类的构造方法。才能够实现。
getProiority/setProiority 优先级:
- 分为了十档,最小是一,最大是十。
- 先来看看默认的执行规律:可以发现,优先级都是5
public class Test {
public static void main(String\[] args) {
//创造线程要执行的参数对象
demo mr=new demo();
//创建线程对象
Thread t1=new Thread(mr);
Thread t2=new Thread(mr);
System.out.println(t1.getPriority());//5
System.out.println(t2.getPriority());//5
}
}
然后我们设置一下线程的优先级
t1.setPriority(1);
t2.setPriority(10);
//启动线程
t1.start();
t2.start();
-
将程序运行,我们会看到,线程二先执行完毕的概率总是大于线程一的。但并不是
-
完全是线程二执行完毕先每次。
-
所以,我们可以知道,线程的优先级别:是一个概率问题,并不是绝对谁先谁后
守护线程:setDaemon()
这里先创建两个Thread继续类。来观察守护线程的过程
测试类中执行一下
public class Test {
public static void main(String\[] args) {
//获取线程的任务对象
Thread1 t1=new Thread1();
Thread2 t2=new Thread2();
//名字
t1.setName("女神");
t2.setName("备胎");
//设置守护线程----将t2设置为守护线程
t2.setDaemon(true);
//启动线程
t1.start();
t2.start();
}
}
可以看到,当"女神"线程执行完了后,“备胎”线程,并没有执行完50次。而是慢慢的停止
所以得出守护线程的一个特点是:当非守护线程停止时,守护线程执行的内容,也会慢慢停止
这里可以设想一个场景:我们在qq和他人聊天时,常常会传输一些文件给别人。这时,我们可以把聊天当作为非守护线程
发送文件当作守护线程。当电脑突然没电时,聊天自动结束了,此时文件的传输,它会缓缓的停下了
最后退出。也就是等非守护线程执行完毕后,守护线程才会慢慢结束
插入线程和礼让线程 :
细节:表示可能的让出cpu线程执行权,和优先级有点相似。
它会使得结果更加均匀
public static void join();插入线程/插队线程
t1.join();//表示把t1这个线程插入到当前线程之前
//当前线程:正在运行的线程