wordpress主题 建站,北京网站建设有限公司,微信信息流广告案例,godaddy wordpress 备多线程-初阶
1. 认识线程
1.1 概念
1) 线程是什么 多个线程组成了一个进程#xff0c;线程好比是一跟光纤中的一个玻璃丝#xff0c;进程是整根光纤。 一个进程中的线程共享这个进程中的资源#xff08;内存、硬盘#xff09;
2) 为什么需要线程
单核CPU发展出现瓶颈…多线程-初阶
1. 认识线程
1.1 概念
1) 线程是什么 多个线程组成了一个进程线程好比是一跟光纤中的一个玻璃丝进程是整根光纤。 一个进程中的线程共享这个进程中的资源内存、硬盘
2) 为什么需要线程
单核CPU发展出现瓶颈想要再提高算力只能增加CPU个数并发编程就是利用多核CPU的绝佳方式. 使用进程也可以实现并发编程只是进程重量大,创建销毁消耗资源多, 所以更好的方式是使用线程进行并发编程. 3) 线程和进程的区别
线程包含于进程每个进程至少有一个线程, 即main线程(main thread)进程之间互不干扰, 但是线程之间耦合度高(一个线程出现问题, 其他线程也会崩溃)进程是系统分配资源的最小单位, 线程是系统调度的最小单位
4) Java中线程 和 操作系统线程 的关系
Java中线程是对于操作系统线程的封装和抽象. 1.2 第一个多线程程序
public class Main {public static void main(String[] args) {Runnable r1 new Runnable() {Overridepublic void run() {while (true) {System.out.println(hello thread1.);try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};Thread t1 new Thread(r1);Runnable r2 new Runnable() {Overridepublic void run() {while (true) {System.out.println(hello thread2.);try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};Thread t2 new Thread(r2);t1.start();t2.start();}
}运行结果:
hello thread1.
hello thread2.
hello thread1.
hello thread2.
hello thread1.
hello thread2.
hello thread2.
hello thread1.
hello thread1.
hello thread2.1.3 创建线程
1) 继承Thread
class MyThread extends Thread{public void run() {System.out.println(继承Thread得到);}
}
public class Demo1 {public static void main(String[] args) {MyThread t1 new MyThread();t1.start();}
}运行结果:
继承Thread得到2) 实现Runnable接口
class MyRunnable implements Runnable {Overridepublic void run() {System.out.println(实现Runnable接口得到);}
}
public class Demo2 {public static void main(String[] args) {MyRunnable r1 new MyRunnable();Thread t1 new Thread(r1);t1.start();}
}运行结果:
实现Runnable接口得到继承Thread和实现Runnable接口的this指代的对象不同, 前者直接指代这个线程, 后者指代接口, 想要指代线程需要使用Thread.currentThread(). 4) 使用匿名内部类
public class Demo3 {public static void main(String[] args) {Thread t1 new Thread(new Runnable() {Overridepublic void run() {System.out.println(使用Thread匿名内部类直接传参new Runnable接口得到);}});Thread t2 new Thread() {public void run() {System.out.println(使用Thread匿名内部类直接重写run方法得到);}};t1.start();t2.start();}
}
运行结果:
使用Thread匿名内部类直接传参new Runnable接口得到
使用Thread匿名内部类直接重写run方法得到5) 使用lamda表达式
public class Demo4 {public static void main(String[] args) {Thread t new Thread(()- {// 不需要重写runlamda表达式就相当于是run方法System.out.println(lamda表达式创建得到);});t.start();}
}
运行结果:
lamda表达式创建得到1.4 使用多线程编程可以增加程序的运行速度 但是可能导致程序线程不安全, 需要合理加锁. 2. Thread类常见方法
2.1 Thread常见构造方法
构造方法名说明Thread()普通构造(仅分配空间)Thread(Runnable)根据所给的run()构造对象Thread(String)为将构造出的线程进行命名Thread(Runnable, String)根据run()创建对象并命名 命名主要是为了方便调试. 2.2 Thread常见属性
方法名作用start()创建线程并运行getId()返回线程的Id(这个Id不同于操作系统未进程分配的Id,也不是PCB中的Id,仅仅是JVM分配的Id)getName()返回线程名字getPriority()返回优先级getState()返回线程目前的状态(NEW, RUNNABLE, WAITING, TIMED_WAITINGBLOCKEDTERMINATED)isDaemon()判断是否为后台进程(后台进程不决定一个线程的存亡,只有前台进程才决定)isAlive()判断是否存活isInterrupted()判断是否被中断
2.3 让一个Thread跑起来
使用start()方法即可使其开始运行. 之前写过的run方法, 只是为这个线程规定要怎么做, 只有start方法才能启动线程. 2.3.1 start 和 run 的区别 start会调用系统api进行创建线程 run只是一个普通的方法告诉线程的执行逻辑不会创建线程 2.4 中断一个线程
有两种方式:
设置一个记号, 线程A和线程B共享这个记号, 两个线程约定一个在其为true时工作, 一个在其为false时工作, 此时如果在A中对于这个记号进行更改, 那就能够使得B停止工作.
public class Demo5 {// 设置共同变量public static boolean flag true;public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(()-{System.out.println(t1);// 在第一个线程执行完后暂停3秒try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}// 3秒后设置共同变量为falseflag false;});Thread t2 new Thread(()-{while (flag true) {System.out.println(t2);}});t1.start();// 在第二个线程执行前暂停2秒让t1线程运行2秒Thread.sleep(2000);// 意味着t2只能执行1秒t2.start();}
}
运行结果:
t1
(等待3秒)
t2
t2
...
t2
t2在3秒后,t1线程将共享变量修改为false, 所以t2被中断. 调用interrupt()进行通知
public class Demo6 {public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(()-{while (!Thread.interrupted()) {System.out.println(t1尚未被中断);try {Thread.sleep(100);} catch (InterruptedException e) {System.out.println(t1收到中断信号);throw new RuntimeException(e);}break;}});System.out.println(t1.getState());t1.start();System.out.println(t1.getState());// 暂停2秒后进行中断Thread.sleep(1);t1.interrupt();}
}
运行结果:
NEW
RUNNABLE
t1尚未被中断
t1收到中断信号
Exception in thread Thread-0 java.lang.RuntimeException: java.lang.InterruptedException: sleep interruptedat Demo6.lambda$main$0(Demo6.java:10)at java.lang.Thread.run(Thread.java:748)
Caused by: java.lang.InterruptedException: sleep interruptedat java.lang.Thread.sleep(Native Method)at Demo6.lambda$main$0(Demo6.java:7)... 1 moreinterrupted() 和 currentThread().isInterrupted() 截然不同:
方法名说明interrupted()查看当前线程是否被中断, 清除标记为falsecurrentThread().isInterrupted()查看当前线程是否中断,仅作判断, 不清除标记
演示: public class Demo7 {public static void main(String[] args) {Thread t1 new Thread(()-{for (int i 0; i 10; i) {//System.out.println(Thread.interrupted());System.out.println(Thread.currentThread().isInterrupted());}});t1.start();t1.interrupt();}
}运行结果:
true
true
true
true
true
true
true
true
true
true
//(这种方法不清除中断标记, 仅作判断)public class Demo7 {public static void main(String[] args) {Thread t1 new Thread(()-{for (int i 0; i 10; i) {System.out.println(Thread.interrupted());}});t1.start();t1.interrupt();}
}运行结果:
true
false
false
false
false
false
false
false
false
//(这种方法清除中断标记, 恢复为未被中断状态)2.5 等待一个线程
线程执行有先后顺序的时候**(线程A的执行需要依赖于线程B的执行结果), **那就需要使用join()方法, 这个方法能够保护当前的线程执行完毕后,其他线程才会去执行.
// Press Shift twice to open the Search Everywhere dialog and type show whitespaces,
// then press Enter. You can now see whitespace characters in your code.
public class Main {public static int count 0;// 1public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(()- {System.out.println(t11);for (int i 0; i 50000; i) {count;}System.out.println(t11);});Thread t2 new Thread(()- {System.out.println(t21);for (int i 0; i 50000; i) {count;}System.out.println(t21);});t1.start();t1.join();t2.start();Thread.sleep(100);System.out.println(count);}
}在都对count进行五万次的操作中可以不加锁也能使得count得到预期值的方法就是让t2在t1执行结束之后才启动这样两个线程都能完成自己的任务得到预期count。
2.5.1 方法中不能够加public、static等修饰词 访问局部变量的过程对象- 方法-局部变量。访问局部变量就已经有了访问权限的设定了。由此加修饰符也成了摆设。 对应static来说因为static只能修饰成员变量和成员方法在局部变量中用static修饰又不能直接被类调用。 2.6 获取当前线程的引用
使用 Thread.currentThread();进行获取。
2.7 休眠线程
使用 Thread.sleep(long mills)实现。 在线程内部需要捕获异常在方法中使用需要抛出异常。
Thread t2 new Thread(()- {System.out.println(t21);for (int i 0; i 50000; i) {count;}// 捕获异常try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}
}, t2);// 抛出异常
public static void main(String[] args) throws InterruptedException {3. 线程的状态
3.1 线程的所有状态
NEW: 安排了工作, 还未开始行动
RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
BLOCKED: 这几个都表示排队等着其他事情
WAITING: 这几个都表示排队等着其他事情
TIMED_WAITING: 这几个都表示排队等着其他事情
TERMINATED: 工作完成了 使用isAlive()方法可以观察线程是否存活。 使用yield()方法会使线程重新排队。 4. 线程不安全
4.1 线程不安全发生的时机
在两个线程进行对于同一个变量进行修改的时候会出现线程不安全的问题。
4.2 线程不安全发生的原因 指令非原子性 即使是“”这个操作仅有一条语句也是由3条微指令构成的 从内存中读出count到寄存器 在寄存器中完成 后的值放进寄存器。 在多个线程的这三个操作如果相互穿插进行那么就可能会读入”脏值“。 内存可见性 内存可见性一个线程对共享变量值的修改能够及时地被其他线程看到. 对于多次重复的读入同一个数据编译器会对其进行优化直接在寄存器中使用这份数据的拷贝值不再从内存中进行读取对这个变量的修改操作也都是在这个拷贝值身上完成在这个线程使用完此变量后才会将最终值写进内存。 这种方式对于单线程来说是一种优化简便了数据的读取操作但是对于多线程来说如果在线程A频繁修改变量count的同时
线程B需要对count进行修改那么就会读到“脏值”。
4.2.1 解决内存可见性问题
使用volatile关键字忽略编译器对其的优化。
5. synchronized 关键字——解决线程不安全问题
synchronized 会将其所在的代码块进行加锁。 5.1 synchronized 特性
1 互斥 如果说一个代码块相当于是一间房那么一个synchronized就相当于是给这个房间进行上锁其他人想进去必须要等到里面的人把锁打开两个人进行争夺房间的使用权的过程也称为“锁竞争”。 锁的作用就是让不同的线程拥有同一个对象的锁的时候只有执行顺序靠前的线程能够正常运行后面的线程需要等待前面的线程释放锁以后才能继续正常运行。
2刷新内存
底层实现
synchronized的底层是使用操作系统的mutex lock实现的.
synchronized工作过程本质上是通过获取一个安全的空间来进行保证操作原子性的 获得互斥锁 从主内存拷贝变量的最新副本到工作的内存 3. 执行代码 将更改后的共享变量的值刷新到主内存 释放互斥锁
3) 可重入性
public static final Object locker new Object();synchronized (locker) {synchronized (locker) {}
}在对于一个对象上同一把锁两次的时候理论上来说会产生“死锁”现象。 因为一个第二把锁所在的代码块执行的前提是第一把锁释放但是第一把锁释放的条件是后序的代码块执行完形成闭环造成“死锁”。 死锁的成因
1互斥使用同一把锁的不同线程同一时间只有一个能够运行
2不可抢占后面的线程只能等前面的将锁释放后才能运行
3循环等待在A阻塞等待B释放锁的时候B在等待A释放锁
4请求保持一个线程尝试获取多把锁线程A在已经被锁1加上的情况下获取一个已经被占用的锁2那么锁1不会被释放 1和2都是锁的基本特性3和4是代码结构当同时满足以上四点的时候才会发生死锁。 5.2 synchronized 使用示例
1给普通方法上锁
synchronized public void method1() {}2给静态方法上锁
synchronized public static void method1() {}3给代码块上锁
给当前对象上锁
//3
public void method2() {synchronized (this) {}
}给类对象上锁
//4
public void method3() {synchronized (Demo2.class) {}
}其中3和4等价。
5.3 Java 标准库中的线程安全类
不安全的ArrayList 、LinkedList、 HashMap、 TreeMap、 HashSet、 TreeSet、 StringBuilder
安全的Vector (不推荐使用)、 HashTable (不推荐使用) 、ConcurrentHashMap、 StringBuffer
6. volatile关键字
6.1 volatile能够保证内存可见性 内存可见性一个线程对共享变量值的修改能够及时地被其他线程看到. import java.util.Scanner;// volatile的作用
public class Demo3 {//public volatile static int isQuit 0;public static int isQuit 0;public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(()- {while (isQuit 0) {}System.out.println(t1退出);});t1.start();Thread.sleep(1000);Thread t2 new Thread(()- {System.out.println(请输入);Scanner scanner new Scanner(System.in);isQuit scanner.nextInt();});t2.start();Thread.sleep(1000);System.out.println(t1.getState());}
}由于编译器的优化, t2对于isQuit变量进行修改并不影响t1线程中看到的isQuit变量是0, 这就叫做内存不可见.
但是如果加上volatile, 那么编译器会保证内存的可见性, 放弃优化.(所以会将代码的运行效率降低)
volatile的工作过程:
将内存中的数据放进寄存器线程对于数据进行修改将数据写回内存
如果是读取:
读取最新值进入工作内存从工作内存中读取volatile变量的副本 6.2 volatile不能保证操作的原子性
volatile虽然一次性将数据读取到工作内存, 待其写完后又放回主内存, 但是在写的过程中, 如果其他线程也对同一个变量进行写入, 这将是合法的, 并且存在线程安全问题.
// 线程安全问题
public class Main {// 加上volatile并不能够得到预期的count值public static volatile int count 0;// 1public static void main(String[] args) throws InterruptedException {Thread t1 new Thread(()- {for (int i 0; i 50000; i) {count;}try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}});Thread t2 new Thread(()- {for (int i 0; i 50000; i) {count;}}, t2);t1.start();t2.start();Thread.sleep(1000);System.out.println(count);}
}结果:
75377
6.3 但是synchronized可以保证内存可见性和原子性