Java多线程
事先声明,本帖主要是记录跟学Bilibili课程狂神Java公开课的学习过程笔记;产生的代码和图片等部分或全部来源于视频教学内容以及网上搜索到的公开资料;需要传送的点击:
【狂神说Java】多线程详解
本系列贴主要是上传之前学习狂神老师Java公开课的学习过程笔记,笔记有不完善的地方后续会慢慢进行完善上传,见谅
1、概念区分
进程:执行程序的一次执行过程
线程:CPU可以调度和执行的最小单位(重点记忆)
程序:指令和数据的有序集合
2、线程的创建方式(三种)
1、Thread class
继承Thread类(重点)
将类声明为Thread的子类,这个子类需要重写run类的方法,然后进行分配并启动子类的实例
package Demo01.Thread01;
//继承thread类,重写run方法
public class TestThread extends Thread{
@Override
public void run(){
for (int i = 0; i < 100; i++) {
System.out.println("run()"+i);
}
}
public static void main(String[] args) {
//创建线程对象
TestThread t = new TestThread();
t.start();
for (int i = 0; i < 2000; i++) {
System.out.println("main()"+i);
}
}
}
2、Runnable接口
实现Runnable接口(重点)
声明实现类Runnable接口,重写run方法,分配类的实例
可以避免单继承的局限性,方便同一个对象被多个线程使用
package Demo01.Thread01;
//创建线程方法2:实现runnaable接口
public class TestThread02 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("run() + "+ i );
}
}
public static void main(String[] args) {
//创建runnable接口i的实现类对象
TestThread02 t = new TestThread02();
//创建线程对象,通过线程对象来开启线程
new Thread(t).start();
for (int i = 0; i < 100; i++) {
System.out.println("main()" + i);
}
}
}
3、Callable接口(了解)
3、静态代理模式
真实对象和代理对象都要实现同一个接口
代理对象要代理真实角色
好处:
代理对象可以做很多真实对象做不了的事情
真实对象可以专注做自己的事情
package Demo01.Demo03;
//实验静态代理模式
public class TestProxy {
public static void main(String[] args) {
You you = new You();
Proxy proxy = new Proxy(you);
proxy.run();
}
interface Marry {
void run();
}
static class You implements Marry{
@Override
public void run() {
System.out.println("You run");
}
}
static class Proxy implements Marry{
private Marry target;
//constructor
public Proxy(Marry target) {
this.target = target;
}
@Override
public void run() {
//在proxy里面可以定义代理函数需要实现的功能并调用,可以让YOU专心实现功能
this.target.run();
}
}
}
4、lambda表达式
5、线程状态

总共五个状态:
新生:Thread t = new Thread();线程对象一旦创建就进入到新生状态
就绪:调用start()方法,线程立即进入就绪状态
阻塞:调用sleep()、wait()或同步锁定时,线程进入阻塞状态
运行:进入运行状态,线程才是真正执行线程体的代码块
dead(死亡):线程中断或者结束就进入此状态,不能再次调用
1、线程停止
不推荐使用jdk提供的 stop(),destory()方法
推荐线程自己停下来
方法:使用标志位进行终止变量,当flag = false,则终止线程
package Demo02;
//测试停止线程
public class Demo01 implements Runnable {
//设置一个标识符
private boolean flag = true;
@Override
public void run() {
int i =0;
while(flag){
System.out.println("==>run "+i++);
}
}
//设置一个公开的方法来改变标识符
public void stop(){
this.flag = false;
}
public static void main(String[] args) {
Demo01 demo01 = new Demo01();
new Thread(demo01).start();
for (int i = 0; i < 1000; i++) {
if(i == 900){
demo01.stop();
System.out.println("stop");
}
System.out.println("==>main"+i);
}
}
}
2、线程休眠
sleep(time) : 指定当前线程阻塞的毫秒数;
sleep存在异常 interrupttedException
sleep可以模拟网络延时、倒计时等
每个对象都有一个锁,sleep不会释放锁(详细概念在-线程同步 内)
3、线程礼让
yield
礼让线程,让当前正在执行的线程暂停
让线程从运行状态转为就绪状态
让cpu重新调度,!!!礼让不一定成功,看cpu心情
4、线程强制执行
join 合并线程,待此线程执行完成后,才可以执行其他线程
可以想象成插队
package Demo02;
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("VIP"+i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin t = new TestJoin();
Thread t1 = new Thread(t);
t1.start();
for (int i = 0; i < 1000; i++) {
if (i == 200) {
t1.join();//插队
}
System.out.println("main"+i);
}
}
}
6、观测线程状态
一般线程可处于一下状态之一:
NEW:尚未启动的线程处于此状态
RUNNABLE:在Java虚拟机中执行的线程处于此状态
BLOCKED:被阻塞等待的线程处于此状态
WAITING:正在等待另一个线程执行特定动作线程~
TIMED_WAITING:正在等待另一个线程执行动作达到指定时间的线程~
TERMINATED:已退出的线程~
7、线程的优先级 priority
java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行
线程的优先级用数字表示,范围从1~10
Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY = 10
Thread.NORM_PRIORITY = 5
使用以下方式改变或获取优先级:
getPriority(); setPriority(int xxx);
!!!优先级的设定建议在start()调度之前
8、守护线程
线程分为用户线程和守护线程
虚拟机必须确保用户线程执行完毕
虚拟机不用等待守护线程执行完毕(即,用户线程结束后不会因为守护线程还在执行而程序继续执行)
eg:后台记录操作日志、监控回收、垃圾回收等待…
9、线程同步
多个线程操作同一个资源
并发:同一个对象被多个线程同时操作
线程同步就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用
锁机制 synchronized
由于同一进程的多个线程共享同一块存储空间,这带来了访问冲突问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制
一个线程获得对象的排它锁,独自资源,其他线程必须等待,使用后释放锁
问题:(主要就是导致性能、效率降低;安全性和效率二选一)
一个线程持有锁会导致其他所有需要此锁的线程挂起
在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题
如果优先级高的线程等待一个低优先级的线程释放锁,会导致优先级倒置
10、三大不安全案例
1、买票
多个人一起买票
package Demo03;
//不安全的买票
public class unsafeTest01 implements Runnable{
private int tickets = 10;
boolean flag = true;//外部停止方式
@Override
public void run() {
while(true){
try {
buy();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
private void buy() throws InterruptedException {
if (tickets <= 0) {
flag = false;
return;
}
//模拟延时,放大问题的发生性
Thread.sleep(100);
System.out.println(Thread.currentThread().getName() +"拿到"+tickets--);
}
public static void main(String[] args) {
unsafeTest01 t = new unsafeTest01();
new Thread(t,"黄牛党").start();
new Thread(t,"牛逼的你们").start();
new Thread(t,"厉害的我").start();
}
}
2、两个人一起去银行取钱
3、多个线程排队写入列表
package Demo03;
import java.util.ArrayList;
import java.util.List;
//线程排队
public class UnsafeTest03 {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
list.add(Thread.currentThread().getName());
}).start();
}
try{
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(list.size());
}
}
11、同步方法
synchronized
同步块
12、copyOnWriteArrayList
13、死锁
1、概念
多个线程各自占有一些共享资源,并且互相等待其他线程占用的资源才能运行,而导致两个或者多个线程都在等待对方释放资源,都停止执行的情况
2、产生死锁的四个必要条件
14、Lock锁
15、生产者消费者问题
16、管程法
17、信号灯法
18、线程池