一、多线程编程
多线程编程是一种系统编程,实现多任务多用户操作
多任务指的是:多进程编程、多线程编程、协程编程
进程(Process):操作系统的基本单位,可以直接申请内存空间,独立的,不影响其他进程,特点是稳定,效率较低
线程(Thread):依赖于进程,可以独立运行,更轻量,会影响其他线程,从而导致整个进程崩溃,特点是不稳定,效率较高
协程(Coroutime):是轻量级的线程
发展历史从B语言-->C语言(高级语言之父)
Unix操作系统:所有操作系统之父,第一款多任务多用户操作系统
注意:在多核CPU诞生之前,所有的多任务操作都是伪多任务!!
并发:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行。即多个任务抢占少量资源,交替执行不同任务的方式就是并发(大量用户抢占少量的资源)。
并行:指在同一时刻,有多条指令在多个处理器上同时执行,所以无论从微观还是从宏观来看,二者都是一起执行的
并发和并行的相同点:
并发和并行的目标都是最大化CPU的使用率,将cpu的性能充分压榨出来。并发和并行的不同点:
(1)并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在(2)并行要求程序能够同时执行多个操作,而并发只是要求程序“看着像是”同时执行多个操作,其实是交替执行。
时间片:每一个任务每次在CPU中占有的时间
- Thread.currentThread()获取当前线程对象
- Thread.currentThread().getName();获取当前线程对象的名
优先级别时间调度:开机、网络是基层,后才能运行其他需要开机联网才能运行的软件
注意:操作系统使用底层的多任务使用的就是时间片轮换机制,配合优先级别调度
5 1 4 3 2
优先级别越高越先执行,5先执行,执行后减1,有两个4,他俩互相抢
二、多进程编程
多线程编程就是抢占资源
Java自身提供了四种创建多线程的方案:
1.继承Thread
2.实现Runnable接口
3.实现Callable接口
4.线程池实现多线程
(1)继承Thread类:因为Thread类描述线程事物,具备线程应该有功能。
start() | 线程编程的启动方法 |
getId() | 获取进程的ID |
getName() | 获取当前进程的名称 |
getPriority() | 优先级别 |
getState() | 线程的状态(创建、就绪、运行、等待、销毁) |
setDamemo() | 将当前线程设置成守护线程 |
interrupt() | 线程抛出一个中断信号 |
isAlive() | 判断线程是否活着 |
join() | 使得子线程运行完后主线程才开始运行(阻塞当前线程) |
sleep() | 让线程停几秒钟再运行(进入休眠状态) |
currentThread.setName("主函数名称") | 通过构造函数传参给线程起名字(子线程) |
yield() | 表示(暗示hint,不一定放弃)当前线程放弃执行一次 |
/**
* 通过继承Thread类实现多线程编程,重写run()方法,
* 调用start()方法才能实现抢占资源
*/
public class MyThread extends Thread {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 10000; i++) {
System.out.println("主线程运行了----" + i + "次");
}
}
@Override
public void run(){
for (int i = 0; i < 10000; i++) {
System.out.println("一个子线程运行了-----" + i + "次");
}
}
}
那为什么不直接创建Thread类的对象呢?
Thread t1 = new Thread(); t1.start();//这样做没有错
但是该start调用的是Thread类中的run方法,而这个run方法没有做什么事 情,更重要的是这个run方法中并没有定义我们需要让线程执行的代码。
创建线程的目的是什么?
是为了建立单独的执行路径,让多部分代码实现同时执行。也就是说线程创建并执行需要给定的代 码(线程的任务)。对于之前所讲的主线程,它的任务定义在main函数中。自定义线程需要执行的任务都 定义在run方法中。
Thread类中的run方法内部的任务并不是我们所需要,只有重写这个run方法,既然Thread类已经定义了线程任务的位置,只要在位置中定义任务代码即可。所以进行了重写run方法动作。
Thread.currentThread().getName()//获取线程的名字
(2)实现Callable接口(接口式编程)
使用Callable接口实现的多线程对象,最后能够得到线程方法返回的结果
FutureTask(V) implements RunnableFuture
RunnableFuture ectends Runnable,Future
说明FutureTask(V)是RunnableFuture的子类
RunnableFuture是Runnable,Future的子类
总的来说:FutureTask(V)是Runnable的子类,
new Thread(futuretask).start();
代码示例:
public class MyThread3 implements Callable {
public static void main(String[] args) {
MyThread3 mt = new MyThread3();
FutureTask futureTask = new FutureTask(mt);
new Thread(futureTask).start();
try {
Integer result = (Integer) futureTask.get();
System.out.println("线程方法返回的结果" + result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
for (int i = 0; i < 10000; i++) {
System.out.println(Thread.currentThread().getName() + "主线程运行了----" + i + "次");
}
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 10000; i++) {
sum += i;
System.out.println(Thread.currentThread().getName() + "一个子线程运行了-----" + i + "次");
}
System.out.println(sum);
return sum;
}
}
继承Thread和Runnable的区别:
- 继承Thread的线程对象之间不能共享数据
- 实现Runnable线程对象,如果使用不同的Thread对象启动同一个Runnable线程对象,则会共享数据,这种现象会导致线程安全问题(又可能)
(3)实现 Runnable接口创建线程
创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方 法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。 怎么还要实现Runnable接口,Runable是啥玩意呢?Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。 类必须定义一个称为 run 的无参数方法。
总结: 创建线程的第二种方式:实现Runnable接口。
1. 定义类实现Runnable接口。
2. 覆盖接口中的run方法。
3. 创建Thread类的对象。
4. 将Runnable接口的子类对象作为参数传递给Thread类的构造函数。
5. 调用Thread类的start方法开启线程。
代码示例:
/**
* 通过实现Runnable接口,重写run()方法,因为Thread实现了Runnable接口,
* 所以可以通过创建Thread调用start()方法实现抢占资源的多线程
*/
public class MyThread2 implements Runnable {
public static void main(String[] args) {
MyThread2 myThread1 = new MyThread2();
MyThread2 myThread2 = new MyThread2();
new Thread(myThread1).start();
new Thread(myThread2).start();
for (int i = 0; i < 10000; i++) {
System.out.println("主线程运行了----" + i + "次");
}
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("一个子线程运行了-----" + i + "次");
}
}
}
三、线程安全问题线程安全问题本质上就是错误数据变化问题
(1)问题产生的原因
1. 线程任务中在操作共享的数据。
2. 线程任务操作共享数据的代码有多条(运算有多个)。
(2)如何解决线程安全问题
通过加锁解决
同步锁:synchronized 关键字
该锁有三种使用方式:
1.同步块
2.将方法加锁,加到方法上,方法就是锁
3.将静态方法加锁,当前静态方法所处的类就是锁
锁加的范围越小效率越高!!!
代码示例:
/**
* 解决线程安全问题的方法就是加锁
* 1.给代码块加锁
* 2.给方法加锁
* 3.给静态方法加锁,静态方法所在的这个类的字节码文件
*
* synchronized关键字就是同步锁
* 该锁必须使用一个对象作为钥匙
*/
public class MyThread5 extends Thread {
private static int count;
//创建一个对象
// Object obj = new Object();
@Override
public void run() {
test();
}
public synchronized static void test(){
for (int i = 0; i < 10000; i++) {
//++ --是非原子性代码
//这个锁是个对象,所以需要创建一个对象放进括号中
synchronized (MyThread5.class){
count++;
}
}
System.out.println(Thread.currentThread().getName() + "运行了"+ "count=" + count);
}
//两个线程同时共享一个进程,出现线程安全问题,即错误的数据问题,两个线程进行抢占的过程中将数据搞错啦
public static void main(String[] args) {
MyThread4 myThread = new MyThread4();
new Thread(myThread).start();
new Thread(myThread).start();
}
}