Java多线程笔记【二】

2.2 Java线程的生命周期

线程状态,也称为线程的生命周期,代表着线程再代码运行中的状态

Thread.State

2.2.1 六种状态

  • NEW:刚刚新建的Thread的对象,但还未调用start()启动线程时,
  • RUNNABLE: 线程对象创建后,其他线程(比如 main 线程)调用了该对象的 start 方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 cpu 的使用权。
  • BLOCKED: 线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspendwait等方法都可以导致线程阻塞
  • WAITING:等待另外一个线程执行特定的动作((通知或中断))
  • TIMED_WAITING:该状态不同于WAITING,它可以在指定的时间后自行返回。
  • TERMINATED:表示该线程已经执行完毕,如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。

在这里插入图片描述

2.3 线程的同步

2.3.1 线程的安全问题

public class TicketMain {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();

        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t1);
        Thread thread3 = new Thread(t1);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();

    }
}


public class Ticket implements Runnable{
    private int ticketCount = 100;
    @Override
    public void run() {
        while (true){
            if (ticketCount == 0){
                break;

            }else{
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "买票,票还有" + ticketCount + "张" );
                ticketCount--;
            }
        }
    }
}

安全问题:

  • 相同得票出现了多次
  • 出现了负数票

注:当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行。导致共享数据的错误。

共享数据:多个线程共同操作的变量:比如上面代码的票数 ticketCount

2.3.2 同步代码块

锁多条语句操作共享数据,可以实现同步代码块实现

synchronized(同步监视器){
	多条语句操作共享数据的代码
}

注:

  • 默认情况是打开的,只要一个线程进去执行代码了,锁就会关闭。当线程执行完毕出来,锁才会自动打开

  • 同步监视器(锁)可以是任何对象。因为任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。但是要求多个线程要共用同一把锁

  • Runnable可以考虑用this当作同步监视器

  • 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this),同步代码块(指定需谨慎)

同步监视器的执行过程:

  • 第一个线程访问,锁定同步监视器,执行其中的代码
  • 第二个线程访问,发现同步监视器被锁定,无法访问
  • 第一个线程访问完毕,解锁同步监视器
  • 第二个线程访问,发现同步监视器没有锁,锁定同步加速器并访问

好处和弊端

  • 好处:解决了多线程的数据安全问题
  • 弊端:当线程很大时,因为每个线程都会去判断同步上的锁吗,这是很耗费资源的,无形中会降低程序的运行效率
public class TicketMain {
    public static void main(String[] args) {
        Ticket t1 = new Ticket();

        Thread thread1 = new Thread(t1);
        Thread thread2 = new Thread(t1);
        Thread thread3 = new Thread(t1);

        thread1.setName("窗口1");
        thread2.setName("窗口2");
        thread3.setName("窗口3");

        thread1.start();
        thread2.start();
        thread3.start();

    }
}
public class Ticket implements Runnable{
    private int ticketCount = 100;
    private Object obj = new Object();
    @Override
    public void run() {
        while (true){
            synchronized (obj){ // 多个线程必须使用同一把锁
                if (ticketCount == 0){
                    break;
                }else{
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "买票,票还有" + ticketCount + "张" );
                    ticketCount--;
                }
            }
        }
    }
}

2.3.3 同步方法

把synchronized关键字加到方法是,同步方法的锁对象是this

修饰符 synchronized 返回值类型 方法名(方法参数){}

同步代码块和同步方法的区别

  • 同步代码块可以锁住指定代码,同步方法是锁住方法中所有代码
  • 同步代码块可以指定锁对象,同步方法不能指定锁对象
  • 一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方法共用同一把锁(this)
public class Demo {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();

        Thread t1 = new Thread(mr);
        Thread t2 = new Thread(mr);

        t1.setName("窗口一");
        t2.setName("窗口二");

        t1.start();
        t2.start();

    }
}

class MyRunnable implements Runnable {
    private int ticketCount = 100;
    @Override
    public void run() {
        while (true){
            synchronizedMethod();
            if(ticketCount == 0){
                break;
            }

        }
    }

    private synchronized void synchronizedMethod() {
        if (ticketCount > 0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张");
            ticketCount--;

        }
    }
}
public class Demo {
    public static void main(String[] args) {
        // 创建线程对象
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        t1.start();
        t2.start();

    }
}

class MyThread extends Thread {
    private static int ticketCount = 100;
    @Override
    public void run() {
        while (true){
            synchronizedMethod();
            if(ticketCount == 0){
                break;
            }
        }
    }
    // 继承Thread 用静态方法
    private static synchronized void synchronizedMethod() {
        if (ticketCount > 0){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ticketCount--;
            System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticketCount + "张");
        }
    }
}

2.3.4 死锁

线程的死锁是指由于俩个或多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前进执行,**主要是因为锁的嵌套 **

产生死锁的必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用
  2. 请求与保存条件:一个进程因请求资源而阻塞时,对已获得得资源保持不放
  3. 不剥夺条件:进程以获得得资源吗,在未使用之前,不能强行剥夺
  4. 循环等待条件:若干进程之间形成一种头尾相接循环等待资源关系

只有破坏上面任意一个条件就可以避免死锁得发送

2.3.5 Lock(锁)

JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当。

java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。

ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。

  • private final ReentrantLock lock = new ReentrantLock():可以传入参数 boolean ,如果为true 则按线程进入的顺序,如果false则随机抽取线程进入
package lock;

import java.util.concurrent.locks.ReentrantLock;

public class TestLock {

    public static void main(String[] args) {
        TestLock2 testLock2 = new TestLock2();

        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }

}


class TestLock2 implements Runnable{

    int ticketNums = 100;

    // 定义lock锁
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            lock.lock();
            try {
                if (ticketNums > 0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                }else{
                    break;
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                // 解锁
                lock.unlock();
            }
        }
    }
}

注意:如果同步代码有异常,要将unlock()写入finally语句块,确保锁会得到释放

2.3.5.1 synchronized Lock 的对比
  1. Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
  2. Lock只有代码块锁,synchronized有代码块锁和方法锁
  3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
  4. Lock -> 同步代码块(已经进入了方法体,分配了相应资源) -> 同步方法(在方法体之外)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猩空中的猩⭐

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值