多线程
-
线程是独立的执行路径
-
默认存在main()线程【用户线程】和gc()【守护线程】线程
-
main()线程是程序入口,执行整个程序
-
一个进程中开辟多个线程,由调度器执行调度
-
多个线程对同一份资源进行操作时会存在资源抢夺
创建线程对象的方法
1. 继承Thread类
- 自定义线程类,然后继承Thread
- 重写Thread中的run()方法
- 在主线程main中实例化线程对象
- 调用start()开启线程
2.实现Runable接口【推荐】
- 自定义线程类,实现Runable接口
- 重写Runable接口中的run()方法
- 主线程中实例化线程对象,然后实例化Thead类
扩展
引入一个jar包之后,必须添加到library才能使用
3.Lambda表达式
()->{}:
{}:接口实现类的方法体
():接口中方法的参数
4.线程的五大状态
new 创建状态
start 就绪状态,等待cpu来调度
运行状态
阻塞状态:sleep,wait
死亡状态:线程正常执行完
线程对象的方法:
设置线程优先级:setPriority(int new Priority)
public class TestPriority {
public static void main(String[] args) {
Myriority myriority = new Myriority();
Thread t1 = new Thread(myriority,"T1");
Thread t2 = new Thread(myriority,"T2");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
}
class Myriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"被执行了=="+Thread.currentThread().getPriority());
}
}
线程休眠:sleep(),休眠的线程进入阻塞状态
public class TestSleep {
public static void main(String[] args) {
tenDown();
System.out.println("System.currentTimeMillis()==="+System.currentTimeMillis());
Date startTime = new Date(System.currentTimeMillis());
System.out.println("start=="+startTime);
while (true){
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void tenDown(){
int num = 10;
while (true){
try {
Thread.sleep(1000);
if(num <= 0){
break;
}else {
System.out.println("倒计时"+num--+"秒");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程礼让:yield():线程礼让不一定成功,A线程礼让B线程,A从cpu出来和B同一起点
public class ThreadYield implements Runnable{
public static void main(String[] args) {
ThreadYield threadYield = new ThreadYield();
new Thread(threadYield,"小明").start();
new Thread(threadYield,"小红").start();
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"开始执行");
Thread.yield();//线程礼让
System.out.println(Thread.currentThread().getName()+"停止执行");
}
}
线程插队: join():优先执行全部的插队线程
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("VIP线程来插队了"+i);
}
}
public static void main(String[] args) throws Exception{
Thread thread = new Thread(new TestJoin());
for (int i = 0; i < 400; i++) {
System.out.println("主线程在排队"+i);
if(i==100){
thread.start();
thread.join();
}
}
}
}
线程状态:state()
线程优先级
线程的优先级用数字表示,范围1-10,线程优先级高也不一定先执行
- Thread.MIN_PRIORITY=1 最小优先级
- Thread.MAX_PRIORITY=10 最大优先级,
- 先设置优先级,再启动
- 优先级的高低表示线程调度的概率高低,存在性能倒置问题
守护线程(daemon)
线程分为:用户线程和守护线程
虚拟机必须确保用户线程执行完毕:main()
虚拟机不用确保守护线程执行完毕:gc() 垃圾回收 ,监控内存,
设置线程为守护线程:Thread.setDaemon(true);//默认值false,是用户线程
public class TestDemon {
public static void main(String[] args) {
God god = new God();
You you = new You();
Thread thread = new Thread(god);
thread.setDaemon(true);
thread.start();
new Thread(you).start();
}
}
class God implements Runnable{
@Override
public void run() {
while (true){
System.out.println("守护线程");
}
}
}
class You implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36; i++) {
System.out.println("用户线程");
}
System.out.println("bye~");
}
}
线程同步(重点+难点)
多个线程操作同一个资源,又叫并发,本质也是排队
例如,抢票,取钱,微信抢红包,ArrayList添加数据
线程同步形成条件:队列+锁
优点:安全但损失性能
关键字:synchronized
多个线程操作同一对象并改变数据,就需要线程同步,其他对象进入对象等待池形成队列
队列和锁🔒
ArrayList线程不安全
原因:多条线程同一瞬间操作同一位置,线程的内存都是各自的,互不影响
同步方法
- 方法加了synchronized关键字就叫同步方法
- 对方法进行synchronized,保证安全
- 每个线程对象都有一把锁,synchronized获得线程对象的锁,才能执行(比如每个人上厕所,synchronized相当于厕所的门,上厕所的那个人带了锁给了门,才能上厕所)
- 一旦执行,对象独占该锁🔒
需要修改的方法才需要synchronized
同步块
操作同一共享资源,使用同步块,比如,银行卡取钱
public void run(){
synchronized(account){
线程体逻辑
}
}
死锁🔒
概念:多个线程各自占有共享资源,互相等待对方释放资源而都停止的情形
发生条件:某一同步块同时拥有:两个以上对象的锁(比如,厕所门本来是一个人带着锁进去锁门上厕所就是正常运行,而死锁是厕所有人好几个人的锁,我在等你去上厕所,同时你也在等我去上厕所)
避免发生条件:
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞,对以获得的资源保持不放
- 不剥夺条件:进程以获得资源,在未使用完之前,不强行剥夺
- 循环等待条件:若干进程循环等待
Lock🔒
- synchronized是隐式同步锁
- Lock是显示同步锁
Lock类似于Synchronized,有相同的并发性
Lock的实现类是:ReentrantLock(可重用锁)
加锁:lock.lock();
解锁:lock.unlock();
使用步骤:
- 定义可重用锁🔒:
- 在线程体中调用lock()
- 在finally块中释放可重用锁Lock🔒
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();
线程体保证线程安全的代码
} finally {
lock.unlock();
}
}
}
Synchronized🔒和Lock🔒对比
- Lock是显式锁(手动开启,关闭),synchronized是隐式锁,出作用域自动释放
- Lock只有代码块锁,Synchronized有代码块锁+方法锁
- Lock锁,JVM花费较少时间调度线程,性能更好,有更好的扩展性
生产者消费者问题
线程之间如何协作,通信 :wait() , notify()
生产者线程和消费者线程