Java多线程

Java多线程

进程与线程的关系:

  1. 进程可以单独看做一个程序;
  2. 一个进程里面可以有多个线程;
  3. 线程是最小的执行单元;
  4. 多个线程共用同一份资源;
  5. 「进程是系统资源分配和调度的基本单位」,它能并发执行较高系统资源的利用率.
  6. 「线程」是「比进程更小」的能独立运行的基本单位,创建、销毁、切换成本要小于进程,可以减少程序并发执行时的时间和空间开销,使得操作系统具有更好的并发性。

线程的创建

  1. 继承Thread类;
package com.zq.Demo01;

public class Thread01 {
    public static void main(String[] args) {
        //创建一个多线程
        Thread td = new MyThread();

        //调用start方法
        td.start();

        //主线程
        for (int i = 0; i < 20; i++) {
            System.out.println("我在敲代码" + i);
        }
    }
}

/*
继承多线程
 */
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("我在听歌" + i);
        }
    }
}
  1. 实现Runnable接口;
package com.zq.Demo01;

public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("正在用Runnable接口实现多线程" + i);
        }
    }
}
package com.zq.Demo01;

public class Thread02 {
    public static void main(String[] args) {
        //创建Runnable对象
        MyRunnable runnable = new MyRunnable();

        //通过new一个Thread类启动线程
        new Thread(runnable).start();

        //主线程
        for (int i = 0; i < 20; i++) {
            System.out.println("这是主线程" + i);
        }
    }
}
  1. 实现Callable接口;
package com.zq.demo02;

import java.util.concurrent.*;

public class TestCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable t1 = new MyCallable();
        MyCallable t2 = new MyCallable();
        MyCallable t3 = new MyCallable();

        //创建执行服务
        ExecutorService executorService = Executors.newFixedThreadPool(3);//放3个线程

        //提交执行
        Future<Boolean> r1 = executorService.submit(t1);
        Future<Boolean> r2 = executorService.submit(t2);
        Future<Boolean> r3 = executorService.submit(t3);

        //获取结果
        Boolean b1 = r1.get();
        Boolean b2 = r2.get();
        Boolean b3 = r3.get();
        System.out.println(b1);
        System.out.println(b2);
        System.out.println(b3);

        //关闭服务
        executorService.shutdown();
    }
}


class MyCallable implements Callable<Boolean>{

    @Override
    public Boolean call() throws Exception {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "跑了" + i + "步");
        }
        return true;
    }
}

线程安全问题(经典事例:抢火车票事例):

package com.zq.Demo01;

/**
 * 抢火车票例子
 */
public class Thread03 {
    public static void main(String[] args) {
        //模拟三个人抢票
        Tickets tickets = new Tickets();

        new Thread(tickets, "张三").start();
        new Thread(tickets, "李四").start();
        new Thread(tickets, "王五").start();
    }
}


class Tickets implements Runnable{

    //火车票票数
    private int ticketsNum = 10;

    @Override
    public void run() {
        while (true){
            if(ticketsNum <= 0)
                break;

            //延时0.1秒
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + "抢到了第" + (ticketsNum --) + "张票");
        }
    }
}

控制台输出:

王五抢到了第10张票
张三抢到了第9张票
李四抢到了第10张票
张三抢到了第8张票
王五抢到了第7张票
李四抢到了第6张票
王五抢到了第4张票
张三抢到了第5张票
李四抢到了第3张票
李四抢到了第2张票
张三抢到了第1张票
王五抢到了第0张票
李四抢到了第-1张票

案例:龟兔赛跑!

package com.zq.Demo01;

/**
 * 龟兔赛跑
 */
public class Thread04 {
    public static void main(String[] args) {
          Race race = new Race();

          //开始比赛(谁跑完100步谁就赢)
        new Thread(race, "兔子").start();
        new Thread(race, "乌龟").start();
    }
}


class Race implements Runnable{

    //胜利者
    private static String winner;

    //比赛是否结束
    private boolean flag;

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {

            //模拟兔子睡觉,每10步睡0.01毫秒
            if("兔子".equals(Thread.currentThread().getName()) && i % 10 == 0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            //判断是否比赛结束
            flag = isGameOver(i);
            if (flag){
                break;
            }

            System.out.println(Thread.currentThread().getName() + "跑了第" + i + "步");
        }
    }

    private boolean isGameOver(int steps){
        if(null != winner){
            return true;
        } else if (steps >= 100){
            winner = Thread.currentThread().getName();
            System.out.println(winner + "胜利了!");
            return true;
        }

        return false;
    }
}

静态代理

package com.zq.demo01;

/**
 * 静态代理总结:
 * 1.目标类和代理类必须实现同一接口
 * 2.代理对象能提目标对象完成目标对象不需要完成的步骤
 * 3.目标对象只需要专注做自己的事情就行
 */
public class StaticProxy {
    public static void main(String[] args) {
        //创建目标对象
        You you = new You();

        //创建代理对象
        Proxy proxy = new Proxy(you);

        //执行方法
        proxy.happyMarry();
    }
}

//接口
interface Marry{
    /**
     * 人间四大喜事
     * 1.久旱逢甘霖
     * 2.他乡遇故知
     * 3.洞房花烛夜
     * 4.金榜题名时
     */

    void happyMarry();
}

//真实目标类
class You implements Marry{
    @Override
    public void happyMarry() {
        System.out.println("我要结婚了!");
    }
}

//代理类
class Proxy implements Marry{

    private Marry marry;

    public Proxy(Marry marry){
        this.marry = marry;
    }

    @Override
    public void happyMarry() {
        before();
        marry.happyMarry();
        after();
    }

    private void before(){
        System.out.println("结婚之前,布置婚礼现场");
    }

    private void after(){
        System.out.println("结婚之后,收尾款");
    }
}

Lombda表达式

  1. lombda表达式只支持函数式接口;
  2. 什么是函数式接口?

任何接口,有且只有一个抽象方法,那么它就是一个函数式接口。

  1. 对于函数式接口,我们可以使用 lombda表达式来创建它的对象;

线程的五大状态

  1. 新建状态
  2. 就绪状态
  3. 运行状态
  4. 阻塞状态
  5. 死亡状态

怎么停止一个运行的线程

  1. jdk自带的stop、destroy方法,但不推荐使用【已废弃】;
  2. 使用一个标志(flag = false),当标志变成某种状态时,则不往下运行;
  3. 一般都会让线程自己运行完,自己停下来;

事例:

package com.zq.demo03;

public class StopThread implements Runnable{

    //标志,控制线程的停止
    private boolean flag = true;

    @Override
    public void run() {
        int i = 1;
        while (flag) {
            System.out.println(Thread.currentThread().getName() + "==>" + (i++));
        }
    }

    //自己定义线程停止的方法
    private void stop (){
        this.flag = false;
    }

    public static void main(String[] args) {
        StopThread stopThread = new StopThread();

        //启动线程
        new Thread(stopThread, "SubThread").start();

        //当主线程的i等于900的时候停止子线程
        for (int i = 0; i < 1000; i++) {
            System.out.println("Main==>" + i);

            if(i == 900){
                stopThread.stop();
                System.out.println("子线程停止");
            }
        }
    }
}

线程休眠(sleep)

  1. sleep需要捕获一个异常;
  2. sleep时间到达后,线程进入就绪状态,等待cpu的调度;
  3. 每一个对象都有一把锁,sleep不会释放锁;
  4. 模拟网络延时,放大问题的发生性;

线程的礼让(yield)

  1. 礼让线程,让当前正在执行的线程暂停,但不阻塞;
  2. 让线程从运行状态转为就绪状态;
  3. 让cpu重新调度,礼让不一定成功,看cpu心情!
  4. 礼让调用yield方法;

线程的插队(join)

  1. join合并线程,待此线程执行完成后,再执行其它线程,其它线程阻塞;

代码示例:

package com.zq.state;

//线程插队join测试
public class TestJoin implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() + "插队执行-->" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //插队线程
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin, "VIP线程");
        thread.start();

        //主线程
        for (int i = 0; i < 500; i++) {
            //插队
            if(i == 250)
                thread.join();
            System.out.println("main-->" + i);
        }
    }
}

线程的状态

    • NEW 尚未启动的线程处于此状态。
    • RUNNABLE 在Java虚拟机中执行的线程处于此状态。
    • BLOCKED 被阻塞等待监视器锁定的线程处于此状态。
    • WAITING 正在等待另一个线程执行特定动作的线程处于此状态。
    • TIMED_WAITING 正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
    • TERMINATED 已退出的线程处于此状态。

代码示例:

package com.zq.state;

public class TestState implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(i);
        }
        System.out.println("线程执行完毕!");
    }

    public static void main(String[] args) {
        //创建一个线程
        Thread thread = new Thread(new TestState());

        //获取线程的状态
        Thread.State state = thread.getState();
        System.out.println(state);//NEW

        //启动线程
        thread.start();
        state = thread.getState();
        System.out.println(state);

        //打印线程的其余状态
        while(state != Thread.State.TERMINATED){
            state = thread.getState();
            System.out.println(state);
        }
    }
}

线程的优先级

  1. 线程的优先级有1-10级;
  2. 优先级高的不一定先执行,但是cpu调度的概率要大;
  3. 设置优先级的方法是setPriority();
  4. *MIN_PRIORITY = 1;*
  5. *MAX_PRIORITY = 10;*
  6. *NORM_PRIORITY = 5;*

守护线程

  1. 线程分为用户线程守护线程;
  2. 虚拟机必须确保用户线程执行完毕;
  3. 虚拟机不用等待守护线程执行完毕;
  4. 如:后台操作日志,监控内存,垃圾回收等...
  5. 设置一个线程为守护线程setDaemon(true);

线程的同步机制

当多个线程操作同一个资源时可以用到同步机制

三大不安全案例:

  1. 不安全的抢票
package sync;

/**
 * 不安全的抢票
 */
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        //创建一个线程
        BuyTicket ticket = new BuyTicket();

        new Thread(ticket, "张三").start();
        new Thread(ticket, "李四").start();
        new Thread(ticket, "王五").start();
    }

}


class BuyTicket implements Runnable {

    //有10张票
    private int ticketNum = 10;
    //循环标志
    private boolean flag = true;

    @Override
    public void run() {
        //进行抢票
        while(flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //买票
    private void buy() throws InterruptedException {
        //模拟延时
        if(ticketNum <= 0){
            flag = false;
            return;
        }
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName() + "抢到第" + (ticketNum --) + "张票");
    }
}
  1. 不安全的取钱
package com.zq.sync;

import com.sun.deploy.util.BlackList;

public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account("印度阿三", 1000000);
        //模拟妻子和丈夫同时取钱
        ATM wife = new ATM(account, 800000, "妻子");
        ATM husband = new ATM(account, 500000, "丈夫");

        wife.start();
        husband.start();
    }
}


//账户
class Account{
    private String accountName; //账户名
    private double balance; //账户余额

    public Account(String accountName, double balance){
        this.accountName = accountName;
        this.balance = balance;
    }

    public String getAccountName(){
        return accountName;
    }

    public void setAccountName(String accountName){
        this.accountName = accountName;
    }

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

    public double getBalance(){
        return balance;
    }
}

//取款机
class ATM extends Thread{
    //账户
    private Account account;
    //取多少钱
    private double drawingMoney;
    //现在有多少钱
    private double nowMoney;

    public ATM(Account account, double drawingMoney, String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    @Override
    public void run() {
        //开始取钱
        if(account.getBalance() - drawingMoney < 0){
            System.out.println(this.getName() + "取钱失败,余额不足,当前账户余额为:" + account.getBalance());
            return;
        }
        //模拟延时
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //账户取钱后余额
        account.setBalance(account.getBalance() - drawingMoney);
        //取钱后拥有多少钱
        nowMoney += drawingMoney;

        System.out.println(this.getName() + "取钱成功,现在有" + nowMoney + ",当前账户余额为:" + account.getBalance());
    }
}

输出结果:

丈夫取钱成功,现在有500000.0,当前账户余额为:-300000.0
妻子取钱成功,现在有800000.0,当前账户余额为:-300000.0

不安全的集合:

package com.zq.sync;

import java.util.ArrayList;
import java.util.List;

//不安全的集合
public class UnsafeList {
    public static void main(String[] args){
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> list.add(Thread.currentThread().getName())).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

解决线程不安全,同步方法

  1. synchronized关键字对方法上锁或使用同步代码块synchronized(Object object){}
  2. 注意的是锁的对象一定要准确,对操作增、删、改的变量所存在的对象上锁。

例如在抢票的例子中,我们对buy()方法进行上锁,能有效避免出现票为0或-1的情况:

package com.zq.sync;

/**
 * 不安全的抢票
 */
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        //创建一个线程
        BuyTicket ticket = new BuyTicket();

        new Thread(ticket, "张三").start();
        new Thread(ticket, "李四").start();
        new Thread(ticket, "王五").start();
    }

}


class BuyTicket implements Runnable {

    //有10张票
    private int ticketNum = 10;
    //循环标志
    private boolean flag = true;

    @Override
    public void run() {
        //进行抢票
        while(flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //买票
    private synchronized void buy() throws InterruptedException {
        //模拟延时
        if(ticketNum <= 0){
            flag = false;
            return;
        }
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName() + "抢到第" + (ticketNum --) + "张票");
    }
}

然而我们在银行取钱案例中对ATM对象的run()方法进行上锁,多运行几次后还是出现了取钱成功,存款为负数的情况,这就是锁的对象不对:

package com.zq.sync;

import com.sun.deploy.util.BlackList;

public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account("印度阿三", 1000000);
        //模拟妻子和丈夫同时取钱
        ATM wife = new ATM(account, 800000, "妻子");
        ATM husband = new ATM(account, 500000, "丈夫");

        wife.start();
        husband.start();
    }
}


//账户
class Account{
    private String accountName; //账户名
    private double balance; //账户余额

    public Account(String accountName, double balance){
        this.accountName = accountName;
        this.balance = balance;
    }

    public String getAccountName(){
        return accountName;
    }

    public void setAccountName(String accountName){
        this.accountName = accountName;
    }

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

    public double getBalance(){
        return balance;
    }
}

//取款机
class ATM extends Thread{
    //账户
    private Account account;
    //取多少钱
    private double drawingMoney;
    //现在有多少钱
    private double nowMoney;

    public ATM(Account account, double drawingMoney, String name){
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    @Override
    public synchronized void run() {
            //开始取钱
            if (account.getBalance() - drawingMoney < 0) {
                System.out.println(this.getName() + "取钱失败,余额不足,当前账户余额为:" + account.getBalance());
                return;
            }
            //模拟延时
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //账户取钱后余额
            account.setBalance(account.getBalance() - drawingMoney);
            //取钱后拥有多少钱
            nowMoney += drawingMoney;

            System.out.println(this.getName() + "取钱成功,现在有" + nowMoney + ",当前账户余额为:" + account.getBalance());
    }
}

真正的解决办法:原因是我们多个用户在同时操作同一个账户里面的余额,所以使得账户余额的变量变得不安全了,所以我们真正要锁定对象是账户对象Account,解决办法:在操作时加上同步代码块synchronized(Account account){};

package com.zq.sync;

import com.sun.deploy.util.BlackList;

public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account("印度阿三", 1000000);
        //模拟妻子和丈夫同时取钱
        ATM wife = new ATM(account, 800000, "妻子");
        ATM husband = new ATM(account, 500000, "丈夫");

        wife.start();
        husband.start();
    }
}


//账户
class Account {
    private String accountName; //账户名
    private double balance; //账户余额

    public Account(String accountName, double balance) {
        this.accountName = accountName;
        this.balance = balance;
    }

    public String getAccountName() {
        return accountName;
    }

    public void setAccountName(String accountName) {
        this.accountName = accountName;
    }

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

    public double getBalance() {
        return balance;
    }
}

//取款机
class ATM extends Thread {
    //账户
    private Account account;
    //取多少钱
    private double drawingMoney;
    //现在有多少钱
    private double nowMoney;

    public ATM(Account account, double drawingMoney, String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }

    @Override
    public void run() {
        synchronized (account) {
            //开始取钱
            if (account.getBalance() - drawingMoney < 0) {
                System.out.println(this.getName() + "取钱失败,余额不足,当前账户余额为:" + account.getBalance());
                return;
            }
            //模拟延时
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //账户取钱后余额
            account.setBalance(account.getBalance() - drawingMoney);
            //取钱后拥有多少钱
            nowMoney += drawingMoney;

            System.out.println(this.getName() + "取钱成功,现在有" + nowMoney + ",当前账户余额为:" + account.getBalance());

        }
    }
}

这时我们发现多运行几次的情况下,再也没有出现过账户余额为负的情况:

妻子取钱成功,现在有800000.0,当前账户余额为:200000.0
丈夫取钱失败,余额不足,当前账户余额为:200000.0

JUC安全类型的集合:

package com.zq.sync;

import java.util.concurrent.CopyOnWriteArrayList;

//JUC安全集合
public class JUCTest {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(() -> list.add(Thread.currentThread().getName())).start();
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

死锁

  1. 什么是死锁?

死锁指的是多个线程各自占有一些共有资源,而又同时等待其它线程释放占有的资源才能往下运行,这种造成两个或两个以上的线程同时等待且不能往下运行的现象称之为死锁。死锁它不是异常,而是一种阻塞现象。

  1. 造成死锁的原因:

某一同步代码块同时拥有**”两个或两个以上对象的锁时“**就有可能发生死锁。

例如以下代码造成死锁:

package com.zq.lock;

//死锁:多个线程互相拥有对方需要的资源,形成僵持
public class DeadLock {
    public static void main(String[] args) {
        //同一份资源(口红和镜子)
        Lipstick lipstick = new Lipstick();
        Mirror mirror = new Mirror();

        /**
         * 1.两个不同的人(灰姑凉、白雪公主)
         * 2.不同的操作:灰姑凉先用口红后用镜子(ops = 0),白雪公主先用镜子后用口红(ops = 1)
         */
        new Makeup(lipstick, mirror, 0, "灰姑凉").start();
        new Makeup(lipstick, mirror, 1, "白雪公主").start();
    }
}

//口红
class Lipstick{

}

//镜子
class Mirror{

}

//化妆
class Makeup extends Thread{

    private Lipstick lipstick;//口红
    private Mirror mirror;//镜子
    private int ops; //操作,模拟两种不同的操作(0或1)

    public Makeup(Lipstick lipstick, Mirror mirror, int ops, String name){
        super(name);
        this.lipstick = lipstick;
        this.mirror = mirror;
        this.ops = ops;
    }

    @Override
    public void run() {
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //操作
    private void makeup() throws InterruptedException {
        if(ops == 0){
             synchronized (lipstick){
                 System.out.println(this.getName() + "拿到了口红!");
                 Thread.sleep(1000);

                 synchronized (mirror){  //1秒后需要拿到镜子
                     System.out.println(this.getName() + "拿到了镜子!");
                 }
             }
         } else if(ops == 1){
            synchronized (mirror){
                System.out.println(this.getName() + "拿到了镜子!");
                Thread.sleep(2000);

                synchronized (lipstick){  //2秒后需要拿到口红
                    System.out.println(this.getName() + "拿到了口红!");
                }
            }
         }
    }
}

结果输出:

灰姑凉拿到了口红!
白雪公主拿到了镜子!

解决办法:避免嵌套锁(同步代码块里含有同步代码块)

package com.zq.lock;

//死锁:多个线程互相拥有对方需要的资源,形成僵持
public class DeadLock {
    public static void main(String[] args) {
        //同一份资源(口红和镜子)
        Lipstick lipstick = new Lipstick();
        Mirror mirror = new Mirror();

        /**
         * 1.两个不同的人(灰姑凉、白雪公主)
         * 2.不同的操作:灰姑凉先用口红后用镜子(ops = 0),白雪公主先用镜子后用口红(ops = 1)
         */
        new Makeup(lipstick, mirror, 0, "灰姑凉").start();
        new Makeup(lipstick, mirror, 1, "白雪公主").start();
    }
}

//口红
class Lipstick{

}

//镜子
class Mirror{

}

//化妆
class Makeup extends Thread{

    private Lipstick lipstick;//口红
    private Mirror mirror;//镜子
    private int ops; //操作,模拟两种不同的操作(0或1)

    public Makeup(Lipstick lipstick, Mirror mirror, int ops, String name){
        super(name);
        this.lipstick = lipstick;
        this.mirror = mirror;
        this.ops = ops;
    }

    @Override
    public void run() {
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //操作
    private void makeup() throws InterruptedException {
        if(ops == 0){
             synchronized (lipstick){
                 System.out.println(this.getName() + "拿到了口红!");
                 Thread.sleep(1000);

             }
            synchronized (mirror){  //1秒后需要拿到镜子
                System.out.println(this.getName() + "拿到了镜子!");
            }
        } else if(ops == 1){
            synchronized (mirror){
                System.out.println(this.getName() + "拿到了镜子!");
                Thread.sleep(2000);

            }
            synchronized (lipstick){  //2秒后需要拿到口红
                System.out.println(this.getName() + "拿到了口红!");
            }
        }
    }
}

结果输出:

白雪公主拿到了镜子!
灰姑凉拿到了口红!
白雪公主拿到了口红!
灰姑凉拿到了镜子!

死锁产生的四个必要条件和避免方法:

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

解决办法:避免其中一个或多个条件都可避免死锁。

Lock锁

一般使用Lock的实现类ReentrantLock实现同步;

package com.zq.lock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//使用Lock同步
public class TestLock {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();

        //三个人同时抢票
        new Thread(ticket, "张三").start();
        new Thread(ticket, "李四").start();
        new Thread(ticket, "王五").start();
    }
}


class Ticket implements Runnable{

    //有10张票
    private int ticketNum = 10;
    //可重入锁
    private final Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {
                //加锁,一般写到try语句块里面
                lock.lock();
                if (ticketNum > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "抢到第" + (ticketNum--) + "张票");
                } else {
                    return;
                }
            } finally {
                //释放锁
                lock.unlock();
            }
        }
    }
}

线程协作(生产者与消费者)

1. 并发协作模型“生产者/消费者模型”-->管程法

  • 生产者:负责生产数据的模块(可能是方法、对象、线程、进程);
  • 消费者:负责处理数据的模块(可能是方法,对象,线程,进程);
  • 缓冲区:消费者不能直接使用生产者的数据,它们之间有个“缓冲区”;
  • 生产者将生产好的数据放入缓冲区,消费者从缓冲区拿走数据;

代码示例:

package com.zq.producer;

//测试生产者消费者模式,利用缓冲区解决:管程法
//需要生产者、消费者、产品、缓冲区
public class TestPC {
    public static void main(String[] args) {
        Container container = new Container();

        new Producer(container).start();
        new Customer(container).start();
    }
}

//生产者
class Producer extends Thread {
    private Container container;

    public Producer(Container container) {
        this.container = container;
    }

    //生产
    @Override
    public void run() {
        //生产100只鸡
        for (int i = 1; i <= 100; i++) {
            Chicken chicken = new Chicken(i);
            container.push(chicken);
            System.out.println("生产了第" + i + "只鸡!");
        }
    }
}

//消费者
class Customer extends Thread {
    private Container container;

    public Customer(Container container) {
        this.container = container;
    }

    //消费
    @Override
    public void run() {
        //消费100只鸡
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了第" + container.pop().id + "只鸡");
        }
    }
}

//产品
class Chicken {
    int id;

    public Chicken(int id) {
        this.id = id;
    }
}

//缓冲区
class Container {
    //需要一个容器大小
    private Chicken[] chickens = new Chicken[10];
    //产品计数器
    private int count = 0;

    //生产者生产产品
    public synchronized void push(Chicken chicken) {
        //如果容器满了,就等待消费者消费
        if (count == chickens.length) {
            //通知消费者消费,生产者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果没有满,则生产者继续生产
        chickens[count++] = chicken;

        //可以通知消费者消费了
        this.notifyAll();
    }

    //消费者进行消费
    public synchronized Chicken pop() {
        //判断是否能消费
        if (count == 0) {
            //等待生产者生产,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //如果可以消费
        Chicken chicken = chickens[--count];

        //消费完,通知生产者开始生产
        this.notifyAll();

        return chicken;
    }
}

2. 并发协作模型“生产者/消费者模式”-->信号灯法;

代码示例:

package com.zq.producer;

//测试生产者消费者问题:信号灯法,用标志位解决
public class TestPC02 {
    public static void main(String[] args) {
       TV tv = new TV();

       //演员和观众
        new Player(tv).start();
        new Watch(tv).start();
    }
}


//生产者-->演员
class Player extends Thread{

    private TV tv;

    public Player(TV tv){
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            if(i % 2 == 0)
                this.tv.play("快乐大本营节目!");
            else
                this.tv.play("抖音:记录美好生活节目!");
        }
    }
}

//消费者-->观众
class Watch extends Thread{

    private TV tv;

    public Watch(TV tv){
        this.tv = tv;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            this.tv.watch();
        }
    }
}

//产品-->节目
class TV{

    //节目
    String program;
    //标志(true为演员等待,观众观看,false为观众等待,演员生产节目
    private boolean flag;

    //生产者生产
    public synchronized void play(String program){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //生产节目
        this.program = program;
        System.out.println("演员表演了:" + this.program);
        //生产后可以通知观看者观看了
        this.notifyAll();
        //改变标志位
        this.flag = !this.flag;
    }

    //消费者消费
    public synchronized void watch(){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //观看节目
        System.out.println("观众观看了:" + this.program);
        //看完后通知生产者生产节目
        this.notifyAll();
        //改变标志位
        this.flag = !this.flag;
    }
}

*特别注意:*

  1. *wait():【使当前线程等待,并释放锁】、notify():【随机唤醒一个线程】、notifyAll():【唤醒所有线程】属于Object类的方法;*
  2. *wait()、notify()、notifyAll()只能在同步代码块或同步方法中使用;*
  3. *wait()与sleep()方法的区别*
  • wait()是Object类中的一个方法,而sleep()是Thread类的一个静态方法。
  • wait()只能在同步代码块中或同步方法中调用,而sleep()可以在任何地方调用。
  • wait()会释放锁,而sleep()不会释放锁。

使用线程池

  1. 问题:在并发情况下的线程,线程的频繁创建和销毁,对性能有很大的影响;
  2. 思路:提前创建多个线程放入线程池中,使用时从线程池中拿,不用时就放回线程池,可以避免线程的频繁创建和销毁,实现重复利用;
  3. 好处:
  • 提高响应速度(减少了创建线程的时间);
  • 降低了资源消耗(重复利用线程池中的线程,不需要重复创建);
  • 便于线程的管理(有很多方法);
  1. 线程池的API:ExecutorService和Executors;

代码示例:

package com.zq.pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestPool {
    public static void main(String[] args) {
        //创建线程池
        ExecutorService service = Executors.newFixedThreadPool(10);

        //启动线程
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());

        //关闭资源
        service.shutdown();
    }
}

class MyThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
end
  • 作者:AWhiteElephant(联系作者)
  • 发表时间:2022-03-13 17:17
  • 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 转载声明:如果是转载栈主转载的文章,请附上原文链接
  • 评论