Java线程/多线程-锁-并发

1.Java线程

1.1 进程

进程:进程是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。

1.2 什么是线程

线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。
如果每个请求都创建一个线程去处理,那么服务器的资源很快就会被耗尽,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
线程分类:
线程:进程的执行单元,执行路径
单线程:一个应用程序只有一条执行路径
多线程:一个应用程序有多条执行路径
多进程的意义?—— 提高CPU的使用率
多线程的意义? —— 提高应用程序的使用率

1.3 进程和线程的区别是什么?

进程是执行着的应用程序,而线程是进程内部的一个执行序列。
一个进程可以有多个线程。线程又叫做轻量级进程。
线程与进程的区别归纳:
a.地址空间和其它资源:进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
b.通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
c.调度和切换:线程上下文切换比进程上下文切换要快得多。
d.在多线程OS中,进程不是一个可执行的实体。

1.3 什么是多线程

多线程:在同一个时间段内可以执行多个任务,提高了CPU的使用率。

1.3 线程的创建-继承Thread类

一:Thread类
1、创建一个Thread类,或者一个Thread子类的对象
2、Thread是一个线程类,位于java.lang包下
在这里插入图片描述
3、Thread类的常用方法
在这里插入图片描述
二:创建线程
1、通过继承Thread类的方式创建线程类,重写run()方法。
2、线程的运行是随机的。
3、代码如下

package com.imooc.thread1;

class MyThread extends Thread{
    public MyThread(String name){
        super(name);
    }

    @Override
    public void run(){
        for (int i=1;i<=20;i++){
            System.out.println(getName()+"正在运行:"+i);
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        //两个线程演示,多线程效果需要创建多个对象而不是一个对象多次调用start()方法
        MyThread tm1 = new MyThread("主线程1");
        MyThread tm2 = new MyThread("主线程2");
        //启动线程,线程不能多次启动
        tm1.start();
        //启动线程,线程不能多次启动
        tm2.start();
    }
}

1.4 线程的创建-实现Runnable接口(该方式应用更广泛)

一:创建一个实现Runnable接口的类的对象
1、只有一个方法run();
2、Runnable是Java中用以实现线程的接口
3、任何实现线程功能的类都必须实现接口

二:代码测试
1、Runnable的run方法可以被多个线程共享,就Thread类的实例
2、代码

package com.imooc.runnable;
class PrintRunnable implements Runnable{
    //测试2:
    //int i = 1;
    //Runnable的run方法可以被多个线程共享,就Thread类的实例
    @Override
    public void run() {
        //获取当前正在运行的线程名称
        String name = Thread.currentThread().getName();
        int i = 1;
        while (i<=20){
            System.out.println(name+"子线程正在运行:"+(i++));
        }

    }
}

public class Test {
    public static void main(String[] args) {
        //定义runnable实现类的对象
        PrintRunnable pr1 = new PrintRunnable();
        //创建线程类对象
        Thread thread1 = new Thread(pr1);
        //启动线程
        thread1.start();

        PrintRunnable pr2 = new PrintRunnable();
        //测试2:替换为pr1进行测试
        Thread thread2 = new Thread(pr2);
        thread2.start();
    }
}

1.5 线程的状态和生命周期

**一:线程的5个状态 **
1、新建( new ):新创建了一个线程对象。
2、可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu的使用权 。

  • 线程什么时候运行是由CPU决定的,只有当该线程获取CPU的使用权的使用,它才能运行。
    3、正在运行( running ):可运行状态( runnable )的线程获得了cpu时间片( timeslice ) ,执行程序代码。
    4、阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu 时间片轮转 到运行( running )状态。阻塞的情况分三种:
  • 等待阻塞:运行( running )的线程执行 o . wait ()方法, JVM 会把该线程放 入等待队列( waitting queue )中。
  • 同步阻塞:运行( running )的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。
  • 其他阻塞: 运行( running )的线程执行 Thread . sleep ( long ms )或 t . join ()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。 当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。
    5、死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。

二:线程的生命周期 - 时间片轮转方式让线程轮流执行
人的生命:出生-成长(生病导致死亡/生病医治成功重新正常成长)-结束
在这里插入图片描述
注意:stop()方法不建议使用了。

三:sleep方法的使用
1、Thread类的方法–线程休眠
public static void sleep(long millis)
2、作用:在指定的毫秒数内让正在执行的线程休眠(暂停执行)
3、参数为休眠的时间,单位是毫秒
4、代码

package com.imooc.runnable;

class MyThread1 implements Runnable{
    @Override
    public void run() {
        for (int i=1;i<=30;i++){
            System.out.println(Thread.currentThread().getName()+"执行第"+i+"次!");
            try{
                //休眠这么长的时间,重新变成可运行的状态,等待获取CPU的使用权
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
public class SleepDemo {
    public static void main(String[] args) {
        //定义runnable实现类的对象
        MyThread1 mt = new MyThread1();
        //创建线程类对象
        Thread thread = new Thread(mt);
        //启动线程
        thread.start();

        Thread thread1 = new Thread(mt);
        thread1.start();
    }
}

四:join方法的使用
1、Thread类的方法
public final void join();//该方法不能被重写
作用:等待调用该方法的线程结束后才能执行(其实是一种抢占资源的方式)
public final void join(long millis);//该方法不能被重写
作用:等待该线程终止的最长时间为millis毫秒
//join()方法抢占资源
//join()方法不指定时间,其它线程会一直等待调用join方法的线程执行完毕以后,其它线程才能执行
//thread.join();
//join()方法指定时间,在join方法的时间有效期过来以后,不管调用join方法的线程是否执行完毕,其它线程都会来执行
2、 代码

package com.imooc.runnable;

class MyThread implements Runnable{
    @Override
    public void run() {
        for (int i=1;i<=500;i++){
            System.out.println(Thread.currentThread().getName()+"子线程正在执行第"+i+"次");
        }
    }
}
public class JoinDemo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        Thread thread = new Thread(mt);
        thread.start();
        //测试:
        // 没有join方法,主线程和子线程随机执行,谁获取到CPU使用权就谁先执行
        try {
            //join()方法抢占资源
            //join()方法不指定时间,其它线程会一直等待调用join方法的线程执行完毕以后,其它线程才能执行
            //thread.join();
            //join()方法指定时间,在join方法的时间有效期过来以后,不管调用join方法的线程是否执行完毕,其它线程都会来执行
            thread.join(1);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        for (int i=1;i<=15;i++){
            System.out.println("主线程运行第"+i+"次");
        }
        System.out.println("主线程运行结束");
    }
}

1.6 线程的优先级

1、Java为线程类提供了10个优先级
2、优先级可以用整数1-10表示,超过范围会抛出异常
3、主线程默认优先级为5
4、优先级常量

  • MAX_PRIORITY:线程的最高优先级10
  • MIN_PRIORITY:线程的最低优先级1
  • NORM_PRIORITY:线程的默认优先级5
    5、优先级相关的方法
    public int getPriority():获取线程优先级的方法
    public void setPriority(int newProirity):设置线程优先级的方法
    6、优先级的结论:线程的优先级的设置与操作系统的环境,以及CPU的调用方式都是有关系的,不能完全保证优先级高的线程一定先执行
    7、代码
package com.imooc.runnable;

class MyThread2 extends Thread{
    private String name;
    public MyThread2(String name){
        this.name = name;
    }

    @Override
    public void run() {
       for (int i=1;i<=50;i++){
           System.out.println("线程"+name+"正则运行"+i);
       }
    }
}
public class ProirityDemo {
    public static void main(String[] args) {
        //获取主线程的优先级
        int mainProirity = Thread.currentThread().getPriority();
        //System.out.println("主线程的优先级为:"+mainProirity);
        MyThread2 mt1 = new MyThread2("子线程1");
        MyThread2 mt2 = new MyThread2("子线程2");
        mt1.setPriority(Thread.MAX_PRIORITY);
        mt2.setPriority(Thread.MIN_PRIORITY);
        mt2.start();
        mt1.start();
        //System.out.println("子线程1的优先级为:"+mt1.getPriority());
    }
}

1.7 线程同步(线程互斥)

一:多线程运行的问题
1、各个线程是通过竞争CPU时间而获得运行机会的
2、各个线程什么时候得到CPU时间,占用多久,是不可预测的
3、一个正在运行着的线程在什么地方被暂停是不确定的

二:银行存取款的问题
1、为什么会出现存/取款线程不一致的原因?
就是因为代码在执行到一半的时候被打断,就会造成数据没有进行及时的更新。

存款线程在执行过程是随机进行终止的,如为更改账户余额成功就终止;存款线程执行sleep方法,线程终止
取款操作线程就执行,发现余额还是1000,并没有被修改(+100);取款线程执行sleep方法,线程终止
存款线程获得CPU使用权继续执行,修改账户余额=1100;存款线程执行完成后
取款线程继续修改账户余额,把之前获取的余额(800)写入账户。

三:同步 - synchronized
1、为了保证在存款或取款的时候,不允许其它线程对账户余额进行操作
2、需要将Bank对象进行锁定
3、使用同步关键字synchronized实现
同步关键字可以确保共享对象(银行类对象/账户),共享对象在同一时刻只能被一个线程访问
4、synchronized关键字用在

  • 成员方法
  • 静态方法
  • 语句块
  public synchronized void saveAccount(){}
  public static synchronized void saveAccount(){}
  synchronized(obj){......}

四:代码实现-可以对不加synchronized和加synchronized的代码进行测试
1、Bank类

package com.imooc.bank;

public class Bank {
    private String account;//账户
    private  int balance;//账号余额

    public Bank(String account, int balance) {
        this.account = account;
        this.balance = balance;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public int getBalance() {
        return balance;
    }

    public void setBalance(int balance) {
        this.balance = balance;
    }

    @Override
    public String toString() {
        return "Bank{" +
                "account='" + account + '\'' +
                ", balance=" + balance +
                '}';
    }

    //存款
    //存款线程在执行过程是随机进行终止的,如为更改账户余额成功就终止
    public synchronized void saveAccount(){
        //可以在不同的位置处添加sleep方法
        //获取当前账户余额
        int balance = getBalance();
        // 线程终止
        try{
            Thread.sleep(1000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        //修改余额,存100元
        balance +=100;
        //修改账户余额
        setBalance(balance);
        //输出存款后的账户余额
        System.out.println("存款后的账户余额为:"+balance);
    }

    //取款
    public void drawAccount(){
        //this-->Bank类
        synchronized(this){
            //可以在不同的位置处添加sleep方法
            int balance = getBalance();
            balance = balance - 200;
            //存款线程终止,取款操作线程就执行,发现余额还是1000,并没有被修改(+100)
            // 线程终止
            try{
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            //修改账户余额
            setBalance(balance);
            System.out.println("取款后的账户余额:"+balance);
        }
    }
}

2、存款-SaveAccount

package com.imooc.bank;

//存款
public class SaveAccount implements Runnable{
    Bank bank;
    public SaveAccount(Bank bank){
        this.bank = bank;
    }
    @Override
    public void run() {
        bank.saveAccount();
    }
}

3、取款-DrawAccount

package com.imooc.bank;

//取款
public class DrawAccount implements Runnable {
    Bank bank;
    public DrawAccount(Bank bank){
        this.bank = bank;
    }
    @Override
    public void run() {
        bank.drawAccount();
    }
}

4、测试-Test

package com.imooc.bank;

/**
 * 银行存取款操作
 */
public class Test {
    public static void main(String[] args) {
        //创建账户,给定余额为1000
        Bank bank = new Bank("1001", 1000);
        //创建线程对象
        SaveAccount sa = new SaveAccount(bank);
        DrawAccount da = new DrawAccount(bank);
        Thread save = new Thread(sa);
        Thread draw = new Thread(da);
        save.start();
        draw.start();
        try{
            draw.join();
            save.join();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(bank);
    }
}

四:问题:账户余额不够了怎么办?
解决:等待存入足够的钱之后进行取款操作

1.8 线程间的通信(同步与死锁)

一:问题:账户余额不够了怎么办?
解决:等待存入足够的钱之后进行取款操作
在这里插入图片描述
二:线程间的通信
1、wait()方法:中断方法的执行,使线程等待

  • 等待其实是处于线程阻塞状态
  • 死锁状态:生产者线程在等待消费,而消费者线程也同样在等待生产,那么这两个线程互相等待永远都不 可能继续执行,这时就处于死锁是状态

2、notify()方法:唤醒处于等待的某一个线程,使其结束等待
3、notifyAll()方法:唤醒所有处于等待的线程,使它们结束等待

三:代码
1、队列类-Queue

package com.imooc.queue;

public class Queue {
    private int n;
    //标志 false没有数据 true有数据
    boolean flag = false;

    //消费
    public synchronized int getN() {
        //没有数据,消费线程等待,生产线程获取CPU使用权,执行生产操作
        if (!flag){
            try{
                wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("消费:"+n);
        //消费完毕,容器中没有数据
        flag = false;
        //不唤醒线程可能会发生死锁,程序不能继续执行下去
        notifyAll();
        return n;
    }

    //生产
    public synchronized void setN(int n) {
        if (flag){
            try{
                wait();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
        System.out.println("生产:"+n);
        this.n = n;
        //生产完毕,容器中已经有数据
        flag = true;
        //不唤醒线程可能会发生死锁,程序不能继续执行下去
        notifyAll();
    }
}

2、生产者

package com.imooc.queue;

//生产者
public class Producer implements Runnable{
    Queue queue;
    public Producer(Queue queue){
        this.queue = queue;
    }

    @Override
    public void run() {
       int i = 0;
       while (true){
          queue.setN(i++);
          try {
              Thread.sleep(1000);
          }catch (InterruptedException e){
              e.printStackTrace();
          }
       }
    }
}

3、消费者

package com.imooc.queue;

//消费者
public class Consumer implements Runnable{
    Queue queue;
    public  Consumer(Queue queue){
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true){
            queue.getN();
            try{
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

4、测试类

package com.imooc.queue;

/**
 * 测试
 */
public class Test {
    //需求:生产一个消费一个
    //不能出现2次生产1次消费;生产1次消费多次
    public static void main(String[] args) {
        Queue queue = new Queue();
        new Thread(new Producer(queue)).start();
        new Thread(new Consumer(queue)).start();
    }
}

1.9 同步方法和同步代码块的区别是什么?

同步方法默认用this或者当前类class对象作为锁;
同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;
同步方法使用关键字 synchronized修饰方法,而同步代码块主要是修饰需要进行同步的代码,用 synchronized(object){代码内容} 进行修饰;

1.20 在监视器(Monitor)内部,是如何做线程同步的?程序应该做哪种级别的同步?

监视器和锁在Java虚拟机中是一块使用的。监视器监视一块同步代码块,确保一次只有一个线程执行同步代码块。每一个监视器都和一个对象引用相关联。线程在获取锁之前不允许执行同步代码。

2. Java锁

2.1 什么是锁?

2.2 什么是死锁(deadlock)?

所谓死锁是指多个进程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。
死锁产生的4个必要条件:

  • 互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某 资源仅为一个进程- 所占有。此时若有其他进程请求该资源,则请求进程只能等待。
  • 不剥夺条件: 进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
  • 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
  • 循环等待条件: 存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。

2.3 如何确保N个线程可以访问N个资源同时又不导致死锁?

使用多线程的时候,一种非常简单的避免死锁的方式就是:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。
因此,如果所有的线程都是以同样的顺序加锁和释放锁,就不会出现死锁了。

3.并发

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值