1.创建多线程
1)继承Thread类
第一步:继承Thread创建一个线程类
第二步:创建Thread实例
第三部:调用start方法
2)实现Runnable接口
第一步:创建一个实现Runable接口类
第二步:创建Thread实例类,调用构造方法将Runnable方法作为参数
第三部:调用start方法
3.使用匿名内部类的方法
4.使用lambda表达式(最常用的方法)
2.多线程的方法
1)start是启动一个线程
2)使用thread.interrupt来中断线程 如果线程因为sleep join wait而引起阻塞则以InterruptedExpection异常的形式来通知,catch里面可以加一个break来跳出线程
三种情况:1.我正在做一件是别人又给我分配一件事,我就当没听见继续做我手头里的事。
2.我听见了给他说待会我就去。
3.我立马放下我手里的事去做这件事。
3)join方法 在main方法中调用join方法效果就是让main线程阻塞等待,等到t线程执行完了,main线程才能运行。join(long )有带参数版本的不是一直死等而是到了设定时间就不阻塞了。
4)sleep方法,sleep指的是线程休眠,调用了sleep这个方法就会被移动到阻塞队里中。等待设定的时间结束了就会进入到就绪队列中但并不会进入就开始运行
3.线程安全
导致线程不安全的原因:
1)抢占式执行(线程不安全的罪魁祸首)。
2)多个线程修改同一个变量。
3)修改操作不是原子的。
4)内存的可见性也会引起线程安全。
5)指令的重排序。
针对线程不安全实行加锁的方法Java代码中用synchronized关键字来进行加锁
加锁操作指的是A和B同时追一个女生,A追到这个女生后A就对这个女生加了一个锁,B就无法再追这个女生了需要进入等待状态,等他们俩分手了B才能继续追这个女生。
4.synchronized
互斥性:一个进程加锁之后另一个进程在向他加锁就会进入阻塞状态。
刷新内存:就是A、B、C三个进程,A先加锁,B、C后面也尝试加锁进入阻塞状态,A释放锁后,B与C之间并不遵循先来后到原则,而是抢占式的,谁先抢到谁先加锁。
可重入:synchronized在使用时对用一个线程加锁并不会出现死锁的状态。不可重入指的是,一个线程被加了两把锁,第二把锁进入阻塞状态,但是需要第一把锁释放了之后,第二把锁才能进行加锁操作,但又需要线程来进行解锁,这时候线程就躺平了从而出现死锁状态。
synchronized使用方法:
1)直接修饰方法
2)修饰静态方法
3)修改代码块(明确指定锁那个对象)
5.volatile
volatile保证了内存的可见性,针对一个线程进行修改操作,一个进程进行读操作他是可行的,但是针对两个进程都进行修改操作那么他将无法使用
volatile只保证了线程的可见性并没有保证线程的原子性
volatile禁止了编译器的优化,避免了直接读取CPU寄存器上的数据,而是直接从内存中读数据。
6.wait和notify方法
wait表示释放了线程的锁但要进入等待状态等待notify的通知才能进行接下来的动作
wait必须放在synchronized中使用否则会报错。
7.wait和sleep的区别
wait要和synchronized搭配使用而sleep不需要
wait是Object的方法,而sleep是Thread的静态方法。