目录
进程:
跑起来的程序,如果频繁的创建/销毁进程,成本比较高
线程:
一个线程就是一个“执行流”,每个线程都可以按照顺序执行自己的代码,多个线程可以“同时”执行多份代码
!!线程创建、销毁、调度比进程快,因为进程包括线程,进程只有创建第一个线程才需要申请资源,进程里面可以有多个线程(可以把进程看作工厂,线程是流水线)
线程和进程的区别:
1.进程是包括线程的,每一个进程里面至少包括一个线程,即主线程
2.进程和进程之间不共享内存空间,同一个进程的线程共享内存空间
3.进程是系统分配资源的最小单位,线程是系统调度的最小单位
4.进程之间是相互独立的,一个进程挂了,不影响其他进程,同一个进程的一个线程挂了,可能会影响其他线程
创建线程:
方法一:继承Thread类
//继承Thread类创建线程类
class MyThread extends Thread{
@Override
public void run() {
System.out.println("线程");
}
}
//创建MyThread的实例
MyThread myThread=new MyThread();
//启动线程
myThread.start();
方法二:实现Runnable接口
//实现Runnable接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程");
}
//创建Thread实例,Runnable对象作为参数
Thread t1=new Thread(new MyRunnable());
//启动线程
t1.start();
方法三:匿名内部类创建 Runnable 子类对象
Thread t2=new Thread(new Runnable(){
@Override
public void run() {
System.out.println("dyy");
}
});
t2.start();
方法四:lambda表达式创建 Runnable 子类对象
Thread t3=new Thread(() -> {//只有一个参数可以省略,这里省略了new Runnable
@Override
public void run() {
System.out.println("dyy");
}
});
t3.start();
Thread类的其他方法:
name 函数是给线程起个名字,不影响线程
Thread类的属性:
1.是否后台线程(isDaemon()):true表示后台线程,后台线程不阻止Java进程结束,后台线程没结束,Java线程该结束还是会结束;false为前台线程,前台线程会阻止Java进程结束,要前台线程全部执行完,进程才结束。!!创建的线程默认是前台线程
2.是否存活(sAlive():判断线程是否存活,如果人口方法执行完,线程就没了,此时返回值是false
中断一个线程:
定义:即让一个线程停下来,从本质来说,中断一个线程方法就一种,即让入口方法执行完毕
中断线程的两种方式:
1.给线程设置一个结束标志位
public static void main(String[] args) {
Thread t=new Thread(()->{
while (true){
System.out.println("hello t");
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
t.start();
}
!!上述代码进入死循环导致启动人口方法无法执行完毕,自然无法结束线程
public static boolean isQuit;//设置标志位
public static void main(String[] args) {
Thread t=new Thread(()->{
while (!isQuit){
System.out.println("hello t");
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
t.start();
try{
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();
}
isQuit=true;
}
执行结果:
2.: 使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定 义标志位.
public static void main(String[] args) {
Thread t=new Thread(()->{
while (!Thread.currentThread().isInterrupted() ){
//currentThread()获取当前线程的实例,这里即t,isInterrupted()是t自带的标志位
System.out.println("hello t");
try {
Thread.sleep(1000);//执行会清空标志位
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
t.start();
try{
Thread.sleep(3000);
}catch (InterruptedException e){
e.printStackTrace();//抛出异常之后会继续执行
}
t.interrupt();//打断sleep
}
执行结果:
等待一个线程-join():
定义:我们需要等一个线程完成它的工作后,再进行自己的下一步工作(例如,张三只有等李四转 账成功,才决定是否存钱)这个方法就明确等待线程的结束。
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(()->{
System.out.println("ttt");
});
t.start();
t.join();//执行的时候如果t线程还没有结束,main线程就会阻塞等待
System.out.println("main main main");
}
public void join(long millis)---指定了最大等待时间,不会死等
线程的状态:
NEW: 安排了工作, 还未开始行动
RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
BLOCKED: 这几个都表示排队等着其他事情
WAITING: 这几个都表示排队等着其他事情
TIMED_WAITING: 这几个都表示排队等着其他事情
TERMINATED: 工作完成了
线程安全:
不安全原因:1.线程的无序调度,导致了执行顺序不确定(抢占式执行)
2.多个线程修改同一个变量
3.修改操作不是原子的(单个cpu不可拆分)
4.内存可见性(编译器对代码进行优化,产生了误判)
(内存数据读取到cpu寄存器只执行一次)
5.指令重排序
解决方法:
1.加锁(synchronized)(适用多个线程写的场景)
进入 synchronized 修饰的代码块, 相当于 加锁
退出 synchronized 修饰的代码块, 相当于 解锁
!!两个线程对同一个对象加锁,就会触发锁竞争(一个线程先拿到锁,另一个阻塞等待)
!!加锁与join的不同:
join让两个线程完整的串行,加锁是让两个线程小部分串行,但大部分还是并发的
synchronized修饰方法相当于this为锁对象
public synchronized void add(){
count++;
}
synchronized修饰静态方法相当于类对象为锁对象(对象名.class)
public synchronized static void add(){
count++;
}
synchronized修饰明确指定的对象
public void add(){
synchronized(this){
count++;
}
}
2.volatile(适用一个线程读一个线程写的场景)
作用:被volatile修饰的变量,编译器会禁止优化,能够保证每次都是从内存重新读取数据,可以解决内存可见性和指令重排序的问题,但不保证原子性
3.wait和notify
作用:解决线程的无序调度,导致了执行顺序不确定问题,让线程有序执行
wait使当前执行代码的线程进行等待. (把线程放到等待队列中)(因为条件不满足)
释放当前的锁(去取钱,发现取钱机没钱,就先出来,让别人先进去)
其他线程拿锁(可能进去取钱或者存钱,取钱取钱机没钱的话也要先出来)
其他线程调用该对象的 notify 方法.(等别人存了钱,里面有钱了就会释放锁)
被唤醒, 重新尝试获取这个锁.(就可以进去取钱了)
notifyAll()方法
定义:notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程.
(比如有三个线程都调用等待操作,notify只能随机唤醒一个,用notifyAll()可以把三个线程全部唤醒,然后三个线程重新竞争锁)
wait 和 sleep 的对比
相同点:都可以让线程放弃执行一段时间
不同点:
目的不同:wait是为了控制线程的顺序执行,而sleep只是单纯的让线程休眠
使用不同:wait 需要搭配 synchronized 使用. sleep 不需要.
wait 是 Object 的方法 sleep 是 Thread 的静态方法.