线程是什么 一个线程就是一个 "执行流". 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 "同时" 执行 着多份代码。
为什么需要多线程
随着科学技术发展,需求变多·单核cpu并不能满足人们的需求,于是多核菜谱,多线程出现在大众视线中。
并发编程的方式也有多种 多进程也可以实现
1:创建线程比创建进程要快
2:销毁线程比销毁进程要快
3:调度线程比调度进程要快
进程与线程的区别
进程是包含线程的,一个进程中包含多个线程。
进程之间是不能共享空间,线程之间是可以共享的。
我们可以通过多个方法来实现线程的创建
(1)继承Thread来创建一个线程类
class mythread extends Thread
@override
public vois run(){
System.out.println("线程运行”);
}
①创建mythread实例
mythread t = new mythread();
②调用start方法 启动线程
t.start();
(2)实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("这里是线程运行的代码");
①创建Thread类,调用Thread方法将Runnable作为参数传入
Thread t = new Thread(new MyRunnable());
②调用start方法
t.start();
多线程优势----增加线程运行速率
public class ThreadAdvantage {
// 多线程并不一定就能提高速度,可以观察,count 不同,实际的运行效果也是不同的
private static final long count = 10_0000_0000;
public static void main(String[] args) throws InterruptedException {
// 使用并发方式
concurrency();
// 使用串行方式
serial();
}
private static void concurrency() throws InterruptedException {
long begin = System.nanoTime();
// 利用一个线程计算 a 的值
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int a = 0;
for (long i = 0; i < count; i++) {
a--;
}
}
});
thread.start();
// 主线程内计算 b 的值
int b = 0;
for (long i = 0; i < count; i++) {
b--;
}
// 等待 thread 线程运行结束
thread.join();
// 统计耗时
long end = System.nanoTime();
double ms = (end - begin) * 1.0 / 1000 / 1000;
System.out.printf("并发: %f 毫秒%n", ms);
}
private static void serial() {
// 全部在主线程内计算 a、b 的值
long begin = System.nanoTime();
int a = 0;
for (long i = 0; i < count; i++) {
a--;
}
int b = 0;
for (long i = 0; i < count; i++) {
方法 说明
Thread() 创建线程对象
Thread(Runnable target) 使用 Runnable 对象创建线程对象
Thread(String name) 创建线程对象,并命名
Thread(Runnable target, String name) 使用 Runnable 对象创建线程对象,并命名
【了解】Thread(ThreadGroup group,Runnable target)
线程可以被用来分组管理,分好的组即为线程组,这个目前我们了解即可
2. Thread 类及常见方法
Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。
用我们上面的例子来看,每个执行流,也需要有一个对象来描述,类似下图所示,而 Thread 类的对象
就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。
2.1 Thread 的常见构造方法
b--;
}
long end = System.nanoTime();
double ms = (end - begin) * 1.0 / 1000 / 1000;
System.out.printf("串行: %f 毫秒%n", ms);
}
}
结果是
并发: 399.651856 毫秒
串行: 720.616911 毫秒
阶段总结
方法 | 说明 |
Thread() | 创建线程对象 |
Thread( Runnable target) | 使用Runnable对象创建线程 |
Thread(String name) | 给线程对象赋名 |
Thread( Runnable target String name) | 使用Runnable对象创建线程 并命名 |
Thread常见属性
属性 | 获取方法() |
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台线程 | isDaemon() |
是否存活 | isAlive() |
是否中断 | isInterrupted() |
·ID 是线程的唯一标识,不同线程不会重复 。 ·名称是各种调试工具用到 。 ·状态表示线程当前所处的一个情况,下面我们会进一步说明 。 ·优先级高的线程理论上来说更容易被调度到 。 ·关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。 ·是否存活,即简单的理解,为 run 方法是否运行结束了。
JOIN方法
方法 说明 public void join() 等待线程结束 public void join(long millis) 等待线程结束,最多等 millis 毫秒 public void join(long millis, int nanos) 同理,但可以更高精度
方法 | 说明 |
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等milis毫秒 |
public void join(long milis,int nanos) | 可以更高精度 |
线程安全概念
如果多线程状态下代码是符合预期,在单线程运行这个程序是安全的。
①修改共享数据
②可见性
③代码顺序性
解决线程不安全方法
(1)synchronized (2)volatile
synchronized关键字特性
①互斥 :
synchronized会起到互斥效果。当线程执行到synchronized ,其他对象也执行到同一个synchronized会阻塞等待
②刷新内存:
从主内存拷贝变量的最新副本到工作内存 ,会将新的数据送回内存 释放阻塞锁。
③可重入:
synchronized对于同一线程来说,可以重复使用synchronized加锁,不会出现锁死状态。也方便代码过多不方便回头看时 可重复加锁。
volatile 关键字
①改变线程工作内存中volatile变量副本的值 ②将改变后的副本的值从工作内存刷新到主存
volatile 和 synchronized 区别
synchronized 能够保证①原子性②内存可见性, volatile 保证的是①内存可见 性。
由于在实际开发中线程抢占执行,每个线程执行顺序就保证不了所以就引出(wait notify)方法
这样就可以将程序的执行同当初设计保持一致。
完成这个协调工作, 主要涉及到三个方法
wait() / wait(long timeout): | 让当前线程进入等待状态. |
notify() / notifyAll(): | 唤醒在当前对象上等待的线程. |
wait()_方法
①使当前执行代码线程进入等待(将线程放入等待队列中)
②释放当前锁
③满足一定条件被唤醒,尝试重新获取锁
前提 wait也要搭配synchronized()使用,否则会抛出异常
wait结束条件
①当其方法调用该方法的notify()方法
②wait()方法的时间超时
③其他线程调用该线程interrupted()方法,使其抛出异常
notify()方法
旨在唤醒等待的线程
①notify()是要用在同一方法和同一块中,该方法用来通知等待该对象锁的线程对其发出notify通知,使其又机会获取到该对象的对象锁。
②如果有多个等待线程,会随机选择一个线程 具有随机性
③在执行notify()代码时,当前代码并不会立刻释放对象锁, 而是在完全执行完整个代码后释放对象锁。
内容先整理这样 还有很多知识还未理解 。人生路漫长,求知路同样。