目录
了解多线程:
并发&&并行
并发:在同一时刻,有多个指令在多个cpu上同时执行
并行:在同一时刻,有多个指令在单个cpu上交替执行
并发可以理解为多个小伙伴同时一起干不同的事儿,并行可以理解为多个小伙伴一起完成同一件事
进程&&线程
进程:是指正在运行的程序
- 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的单独单位
- 动态性:进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的
- 并发性:任何进程都可以同其它进程一起并发执行
线程:是进程的单个顺序控制流,是一条执行路径
- 单线程:一个进程如果只有一条执行路径,则称为单线程程序
- 多线程:一个进程如果有多条执行路径,则称为多线程程序
实现多线程的方式
方式一:继承Thread类
- 首先写一个类来继承Thread类,然后重写run方法来执行线程将要执行的功能,重写Thread的空参和有参是为了在main方法里设置线程的名字,当然线程也有默认的名字,在这设置一下自己喜欢的名字
public class MyThread extends Thread {
public MyThread() {
}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
System.out.println(getName() + i + "线程开始了");
}
}
}
- 然后创建一个main方法,来测试线程
public class Demo01 {
public static void main(String[] args) {
MyThread mt1 = new MyThread("可乐");
MyThread mt2 = new MyThread("猫儿");
mt1.start();
mt2.start();
// mt1.run();
// mt2.run();
}
}
- 最后看控制台输出,第一幅图是start方法输出,第二幅图是调用run方法输出(注释掉的)
- 由上图此可见线程是交替执行的,如果小伙伴第一次没有交替,那么多运行几遍即可(针对调用start方法)
- 两个小问题:
- 为什么要重写run()方法?
因为run()方法是用来封装被线程执行的代码 - run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程;然后由JVM调用此线程的run()方法
- 为什么要重写run()方法?
方式二:实现Runnable接口
实现Runnable接口前先了解Thread的构造方法:
Thread(Runnable target) 分配一个新的Thread对象
Thread(Runnable target,String name) 分配一个新的Thread对象,并指定线程名称
- 首先定义一个类来实现Runnable接口,然后重写里面的run()方法
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + i + "线程开始了");
}
}
}
- 然后创建一个main方法,来测试线程
public class Demo01 {
public static void main(String[] args) {
//创建MyRunnable 的对象
MyRunnable mr = new MyRunnable();
创建Thread类的对象,把MyRunnable对象作为构造方法的参数
Thread t1 = new Thread(mr,"可乐");
Thread t2 = new Thread(mr,"猫儿");
//启动线程
t1.start();
t2.start();
}
}
- 最后看一下控制台的输出
由此可见,方式二也可以实现多线程
方式三:实现Callable接口
同样,在实现方式三之前先了解一下Callable接口里的方法
V call() 计算结果,如果无法计算结果,则抛出一个异常
FutureTask(Callable<V> callable) 创建一个FutureTask,一旦运行就执行给定的Callable
V get() 如有必要,等待计算完成,然后获取其结果
- 首先定义一个MyCallable类,来实现Callable接口,然后重写Callable中的call()方法
public class MyCallable implements Callable {
@Override
public String call() throws Exception {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "跟女孩表白"+ i );
}
//返回值就表示线程运行完毕之后的结果
return "答应";
}
}
- 然后编写一个main方法,进行测试
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//线程开启之后需要执行里面的call方法
MyCallable mt = new MyCallable();
//可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
FutureTask<String> ft = new FutureTask(mt);
//创建线程对象
Thread t1 = new Thread(ft,"可乐");
//启动线程
t1.start();
System.out.println(ft.get());
}
}
- 最后看控制台输出
以上三种方式都实现了多线程
三种实现方式的对比
- 实现Runnable、Callable接口
- 好处: 扩展性强,实现该接口的同时还可以继承其他的类
- 缺点: 编程相对复杂,不能直接使用Thread类中的方法
- 继承Thread类
- 好处: 编程比较简单,可以直接使用Thread类中的方法
-
缺点: 可以扩展性较差,不能再继承其他的类
设置和获取线程名称
void setName(String name) 将此线程的名称更改为等于参数name
String getName() 返回此线程的名称
Thread currentThread() 返回对当前正在执行的线程对象的引用
public class MyThread extends Thread {
public MyThread() {}
public MyThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+":"+i);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
//void setName(String name):将此线程的名称更改为等于参数 name
my1.setName("可乐");
my2.setName("猫儿");
//Thread(String name)
MyThread my1 = new MyThread("可乐");
MyThread my2 = new MyThread("猫儿");
my1.start();
my2.start();
//static Thread currentThread() 返回对当前正在执行的线程对象的引用
System.out.println(Thread.currentThread().getName());
}
}
线程休眠
static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数
线程优先级
- 线程调度
- 两种调度方式
-
分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
-
抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
-
-
Java使用的是抢占式调度模型
-
随机性
假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的
-
优先级相关方法
final int getPriority() 返回此线程的优先级
final void setPriority(int newPriority) 更改此线程的优先级线程默认优先级是5;线程优 先级的范围是:1-10
- 两种调度方式
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
return "线程执行完毕了";
}
}
public class Demo {
public static void main(String[] args) {
//优先级: 1 - 10 默认值:5
MyCallable mc = new MyCallable();
FutureTask<String> ft = new FutureTask<>(mc);
Thread t1 = new Thread(ft);
t1.setName("飞机");
t1.setPriority(10);
//System.out.println(t1.getPriority());//5
t1.start();
MyCallable mc2 = new MyCallable();
FutureTask<String> ft2 = new FutureTask<>(mc2);
Thread t2 = new Thread(ft2);
t2.setName("坦克");
t2.setPriority(1);
//System.out.println(t2.getPriority());//5
t2.start();
}
}
守护线程
void setDaemon(boolean on)
将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出
代码演示:
public class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "---" + i);
}
}
}
public class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "---" + i);
}
}
}
public class Demo {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.setName("女神");
t2.setName("备胎");
//把第二个线程设置为守护线程
//当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
t2.setDaemon(true);
t1.start();
t2.start();
}
}
小节:
- 堆内存唯一的,每一个线程都有自己的线程栈
- 每一个线程在使用堆里面的变量的时候,都会先拷贝一份到变量的副本中
- 在线程中,每一次使用是从变量的副本中获取的
同时也存在一个问题:就是在一个线程访问共享数据的时候,改变了共享数据的值,那么别的线程可能会获取不到最新的值,那么只要在共享数据前面加个Volatile修饰符就行,作用是强制线程在每次使用的时候,都会看一下共享区域最新的值,或者加入synchronized同步代码块