多线程
创建线程的三种方式:
1. 继承Thread类,并且重写run方法
Thread类中的run方法不是抽象方法,Thread类也不抽象类
示例如下:
class MyThread extends Thread{
@Override
public void run() {
System.out.println(1);
}
}
public class Ch01 {
public static void main(String[] args) {
System.out.println(4);
MyThred myThred = new MyThred();
myThred.start();
//普通的对象调用方法
// myThred.run();
System.out.println(2);
System.out.println(3);
//输出 4 2 3 1
}
}
当MyThread继承了Thread类之后,它就是一个独立的线程,要让线程启动,调用线程的start方法
2. 实现Runnable接口
示例如下:
class MyThread2 implements Runnable{
@Override
public void run() {
System.out.println(2);
}
}
public class Ch02 {
public static void main(String[] args) {
System.out.println(1);
MyThread2 myThread2 = new MyThread2();
Thread t = new Thread(myThread2);
t.start();
System.out.println(3);
System.out.println(4);
//输出 1 3 4 2
}
}
当MyThread2成为了Runnable接口的实现类之后,它就是一个独立的线程,要让线程启动,调用线程的start方法
Runnable接口如何调用start方法?
- 使用Thread类的带参构造器引入这个Runnable接口的实现类的对象,然后通过实例化对象调用
3. 实现Callable接口
Callable接口支持泛型的返回值
示例如下:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class MyThread3 implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println(2);
return "call方法的返回值";
}
}
public class Ch04 {
public static void main(String[] args) {
System.out.println(1);
FutureTask<String> futureTask = new FutureTask<>(new MyThread3());
new Thread(futureTask).start();
System.out.println(3);
System.out.println(4);
//输出1 3 4 2
}
}
当MyThread3成为了Callable接口的实现类之后,它就是一个独立的线程,要让线程启动,调用线程的start方法
Callable接口如何调用start方法?
- 将Callable接口实现类的对象引入到FutureTask构造器中,创建FutureTask的对象
- 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法。
使用箭头函数实现创建线程的第一种方法
箭头函数又称Lambda表达式
示例如下:
public class Ch03 {
public static void main(String[] args) {
System.out.println(1);
new Thread(() -> System.out.println(2)).start();
try {
//暂停当前线程(主方法)的执行
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(3);
System.out.println(4);
//输出1 2 3 4
}
}
守护线程
****Java提供了两件线程:守护程序线程和用户线程
- 守护线程,是指在程序运行时 在后台提供一种通用服务的线程,这种线程并不属于程序中不可或缺的部分。
- 守护线程为用户线程提供服务,仅在用户线程运行时才需要
- 守护线程对后台支持任务非常有用
如:垃圾回收器。 大多数JVM线程都是守护线程 - 任何线程继承创建它的线程守护进程状态。由于主线程是用户线程,因此在main方法内启动的任何线程默认都是守护线程
****守护线程和用户线程的区别:用户线程和守护线程几乎一样,唯一的不同之处在于如果用户线程已经全部退出运行,只剩下守护线程存在了,JVM也就退出了
线程的生命周期
NEW:已创建,线程未被start()调用执行
RUNNABLE:线程正在JVM中被执行,等待来自操作系统的调度
BLOCKED:阻塞。因为某些原因不能立即执行需要挂起等待
WAITING:无限期等待。Object类,如果没有唤醒,则一直等待
TIMED_WAITING:有限期等待,线程等待一个指定的时间
TERMINATED:终止线程的状态,线程已经执行完毕
等待和阻塞的区别
- 阻塞是因为外部原因,需要等待
- 等待一般都是主动调用方法,发起主动的等待。等待时还可以传入参数来确定等待时间
join方法
join方法阻塞调用此方法的线程,直到线程t完成,此线程再继续
示例如下:
- 不使用join()方法的情况:
public static void main(String[] args){
System.out.println("MainThread run start.");
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threadA run start.");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("threadA run finished.");
}
});
threadA.start();
System.out.println("MainThread join before");
System.out.println("MainThread run finished.");
}
运行结果如下:
MainThread run start.
threadA run start.
MainThread join before
MainThread run finished.
threadA run finished.
- 使用了join()方法的情况:
public static void main(String[] args){
System.out.println("MainThread run start.");
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("threadA run start.");
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("threadA run finished.");
}
});
threadA.start();
System.out.println("MainThread join before");
try {
threadA.join(); //调用join()
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("MainThread run finished.");
}
运行结果如下:
MainThread run start.
threadA run start.
MainThread join before
threadA run finished.
MainThread run finished.
对子线程threadA使用了join()方法之后,我们发现主线程会等待子线程执行完成之后才往后执行
CPU多核缓存结构
- 物理内存:硬盘内存(固态硬盘,尽量不要选择混合硬盘)
- CPU缓存为了提高程序运行的性能,现在CPU在很多方面对程序进行优化
- CPU处理速度最快,内存次之,硬盘速度最低
- 在CPU处理内存数据时,如果内存运行速度太慢,就会拖累CPU的速度
- 为了解决这样的问题,CPU设计了多级缓存策略
- CPU分为三级缓存,每个CPU都有L1,L2缓存,但是L3缓存是多核公用的
- CPU查找数据时,CPU->L1->L2->L3->内存->硬盘
- 进一步优化,CPU每次读取一个数据,读取的时与它相邻的64个字节的数据【缓存行】
MESI协议
- 修改态,此缓存被动过,内容与主内存中不同,为此缓存专有
- 专有态,此缓存与主内存一致,但是其他CPU没有
- 共享态,此缓存与主内存一致,其他的缓存也有
- 无效态,此缓存无效,需要从主内存中重新读取
【指令重排】
四条指令,四个人在四张纸上写下【恭喜发财】各一个字
java内存模型-JMM
尽量做到硬件和操作系统之间到达一致的访问效果
线程的可见性
thread线程一直在高速读取缓存器中的isOver,不能感知主线程已经把isOber,这就是线程的可见性的问题
怎么解决?
volatile能够强制改变变量的读写直接在内存中操作
线程的争抢
解决线程争抢的问题最好的方法就是【加锁】
synchronized同步锁
当一个方法加上了synchronized修饰,这个方法就叫做同步方法
线程安全的实现方法
- 数据不可变,一切不可变的对象一定是线程安全的,对象的方法的实现方法的调用者,不需要再进行任何的线程安全的保障措施。比如final关键字修饰的基本数据类型,字符串只要一个不可变的对象被正确的创建出来,那外部的可见转态永远都不会改变
- 互斥同步。加锁 【悲观锁】
- 非阻塞同步【无锁编程】,自旋。我们会用cas来实现这种非阻塞同步
- 无同步方案。多个线程需要共享数据,但是这个鞋数据又可以在单独的线程中计算,而得出结果
我们可以把共享数据的可见范围限制在一个线程之内,这样就无需同步了。把共享的数据拿过来,我用我的,你用你的,从而保证线程安全。ThreadLocal
斗地主发牌案例
public class Ticket implements Runnable {
private static final Object lock = new Object();
private static Integer count = 100;
String name;
public Ticket(String name) {
this.name = name;
}
@Override
public void run() {
while (Ticket.count > 0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
System.out.println(name + "出票一张,还剩:" + Ticket.count-- + "张");
}
}
}
public static void main(String[] args) {
Thread one = new Thread(new Ticket("一号窗口"));
Thread two = new Thread(new Ticket("二号窗口"));
one.start();
two.start();
}
}