多线程

多线程

1.进程与线程

  • 程序:程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
  • 进程:进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。
  • 线程:线程是CPU调度和执行的的单位。通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。

注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉。

2.线程的实现方式

  1. 继承Thread类实现多线程
package thread;


/**
 * 继承thread类实现多线程
 */
public class DemoTest1 extends Thread {

    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            System.out.println("thread线程。。。。。。。。。");
        }
    }


    public static void main(String[] args) {

        DemoTest1 demoTest1 = new DemoTest1();
        //通过start方法开启线程
        demoTest1.start();
        //demoTest1.run();

        for (int i = 0; i < 200; i++) {
            System.out.println("主线程。。。。。。。。。。。");
        }
        
    }

}

​ 执行run方法会按照顺序输出,执行start()方法开启多线程,程序会同时执行,交替输出。开启线程,不意味着线程就会执行,线程的执行由cpu进行调度。

package thread;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;

/**
 * 多线程实现网图下载
 */
public class DemoTest2 extends Thread {

    private String url;
    private String file;

    public DemoTest2(String url,String file){
        this.url = url;
        this.file = file;
    }

    @Override
    public void run(){
        webDownLoad(url,file);
        System.out.println("下载完成,文件名"+file);
    }

    public static void main(String[] args) {
        DemoTest2 t1 = new DemoTest2("https://t7.baidu.com/it/u=1595072465,3644073269&fm=193&f=GIF","1.gif");
        DemoTest2 t2 = new DemoTest2("https://t7.baidu.com/it/u=4198287529,2774471735&fm=193&f=GIF","2.gif");
        DemoTest2 t3 = new DemoTest2("https://t7.baidu.com/it/u=1956604245,3662848045&fm=193&f=GIF","3.gif");
        t1.start();
        t2.start();
        t3.start();
    }



    public void webDownLoad(String url,String file){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(file));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("下载网图出错!");
        }
    }


}

输出结果:
	下载完成,文件名3.gif
	下载完成,文件名1.gif
	下载完成,文件名2.gif



jar包版本2.6
  1. 实现Runnable接口
package thread;

/**
 * 线程的实现方式二:继承Runnable接口
 */
public class DemoTest3 implements Runnable{


    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("run方法。。。。。。");
        }
    }
    
    public static void main(String[] args) {
        //创建一个继承了Runnable接口的实体类
        DemoTest3 demoTest3 = new DemoTest3();
        //创建一个线程,传入实体类启动线程,代理
        new Thread(demoTest3).start();
        
        for (int i = 0; i < 2000; i++) {
            System.out.println("main方法。。。。。");
        }

    }
    
}

由于java的单继承的局限性,推荐使用Runnable接口。

  1. 实现Callable接口
package thread;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;

/**
 * 线程的实现方式二:继承Callable接口
 */
public class DemoTest5 implements Callable<Boolean> {

    private String url;
    private String file;


    public DemoTest5(String url, String file) {
        this.url = url;
        this.file = file;
    }

    @Override
    public Boolean call() throws Exception {
        webDownLoad(url,file);
        System.out.println("下载完成,文件名"+file);
        return true;
    }


    public static void main(String[] args) throws ExecutionException, InterruptedException {

        DemoTest5 t1 = new DemoTest5("https://t7.baidu.com/it/u=1595072465,3644073269&fm=193&f=GIF","1.gif");
        DemoTest5 t2 = new DemoTest5("https://t7.baidu.com/it/u=4198287529,2774471735&fm=193&f=GIF","2.gif");
        DemoTest5 t3 = new DemoTest5("https://t7.baidu.com/it/u=1956604245,3662848045&fm=193&f=GIF","3.gif");

        //创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> result1 = ser.submit(t1);
        Future<Boolean> result2 = ser.submit(t2);
        Future<Boolean> result3 = ser.submit(t3);

        //获取结果
        Boolean b1 = result1.get();
        Boolean b2 = result2.get();
        Boolean b3 = result3.get();

        //关闭服务
        ser.shutdownNow();
    }



    public void webDownLoad(String url,String file){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(file));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("下载网图出错!");
        }
    }


}

3.线程的并发问题

​ 多个线程去操作同一份资源时,产生的并发问题,抢票案例

package thread;

/**
 * 多个线程操作同一资源的并发问题
 */
public class DemoTest4 implements Runnable {

    int ticket = 10;
    
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(ticket<=0){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"拿到了第"+ticket--+"张票");
        }
    }

    public static void main(String[] args) {
        DemoTest4 demoTest4 = new DemoTest4();
        new Thread(demoTest4,"小红").start();
        new Thread(demoTest4,"小明").start();
        new Thread(demoTest4,"小蓝").start();

    }

}

结果:
    小蓝拿到了第9张票
    小明拿到了第10张票
    小红拿到了第10张票
    小红拿到了第6张票
    小明拿到了第7张票
    小蓝拿到了第8张票
    小明拿到了第5张票
    小红拿到了第5张票
    小蓝拿到了第5张票
    小蓝拿到了第4张票
    小明拿到了第2张票
    小红拿到了第3张票
    小红拿到了第1张票
    小明拿到了第1张票
    小蓝拿到了第1张票

4.静态代理

package thread;

/**
 * 静态代理
 * 真实对象和代理对象实现同一个接口
 * 代理对象要代理真实角色
 */
public class DemoTest6 {
    public static void main(String[] args) {
        MarryCompany marryCompany = new MarryCompany(new You());
        marryCompany.happyMarry();
        
        
        //线程的Thread类的实现原理,也是通过静态代理模式实现的
        //Thread类也实现了runnable接口
        new Thread(()-> System.out.println("Runnable 接口")).start();
        
    }

}

interface Marry{
    void happyMarry();
}

class You implements Marry{

    @Override
    public void happyMarry() {
        System.out.println("秦老师要结婚了,超开心!");
    }
}

class MarryCompany implements Marry{

    private You you;
    
    public MarryCompany(You you){
        this.you = you;
    }
    
    @Override
    public void happyMarry() {
        before();
        you.happyMarry();
        after();
    }

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

    public void after(){
        System.out.println("结婚之后,收分子钱");
    }
    
}

5.lambda表达式

package thread;

/**
 * lambda 表达式推导过程
 *
 * lambda表达式出现的作用,就是简化代码,必须是函数式接口,一个接口中只有一个抽象方法
 * 从最原始的 通过接口new出对象 --》 静态内部类 (同一个类中) --》局部内部类(同一个方法中) --》匿名内部类(通过接口或父类直接new) --》 JDK8 lambda表达式
 *
 *
 */
public class DemoTest7 {

    //静态内部类
    static class Like2 implements ILike{
        @Override
        public void lambda() {
            System.out.println("i like lambda2");
        }
    }


    public static void main(String[] args) {

        class Like3 implements ILike{
            @Override
            public void lambda() {
                System.out.println("i like lambda3");
            }
        }

        //原始
        ILike like = new Like();
        like.lambda();
        //静态内部类
        like = new Like2();
        like.lambda();
        //局部内部类
        like = new Like3();
        like.lambda();
        //匿名内部类
        like = new ILike() {
            @Override
            public void lambda() {
                System.out.println("i like lambda4");
            }
        };
        like.lambda();

        //lambda表达式
        like = () -> {System.out.println("i like lambda5");};
        like.lambda();


    }

}

interface ILike{
    void lambda();
}

class Like implements ILike{

    @Override
    public void lambda() {
        System.out.println("i like lambda");
    }
}

总结:

  1. 需要是函数式接口,即只有一个抽象方法的接口
  2. 参数列表类型可以不写,只有一个参数时可以不写()
  3. 方法体只有一行代码时,可以省略代码块{}

6.线程状态及API

1. 线程状态介绍

2.线程停止

package thread;

/**
 * 线程停止
 *
 * 官方不建议使用destory,stop等方法来停止线程
 * 此处使用标识符,来手动的停止线程
 */
public class DemoTest8 implements Runnable {

    private boolean flag = true;


    @Override
    public void run() {
        int i=0;
        while (flag){
            System.out.println("线程正在运行。。。。。"+i++);
        }
    }

    /**
     * 更改标识符
     */
    public  void changeFlag(){
        this.flag = false;
    }


    public static void main(String[] args) {
        DemoTest8 demoTest8 = new DemoTest8();

        //启动线程
        new Thread(demoTest8).start();

        for (int i = 0; i < 1000; i++) {
            System.out.println("main线程。。。。"+i);
            if(i==500){
                demoTest8.changeFlag();
            }
        }
    }
}

3.线程休眠

  • sleep(时间) 指定当前线程阻塞的毫秒数
  • sleep存在异常InterruptedException
  • sleep时间到达后线程进入就绪状态
  • sleep可以模拟网络延时(放大问题的发生性,如上面的抢票案例)、倒计时等
  • 每个对象都有一个锁,sleep不会释放锁
package thread;

/**
 * 线程休眠模拟倒计时
 */
public class DemoTest9 implements Runnable {
    
    public static void main(String[] args) {
        DemoTest9 demoTest9 = new DemoTest9();
        new Thread(demoTest9).start();
    }
    
    @Override
    public void run() {
        int i=10;
        while(true){
            System.out.println(i--);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(i<=0){
                break;
            }
        }
    }
}

4.线程礼让

  • Thread.yield()
  • 礼让不一定成功,看cpu调度

5.线程强制执行 (join)

/**
 * join 方法
 * 线程强制执行 类似于插队,会导致其他线程阻塞,不建议使用
 */
public class DemoTest11 implements Runnable {
    
    @Override
    public void run() {
        for (int i = 0; i < 200; i++) {
            System.out.println("join线程执行。。"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {

        DemoTest11 demoTest11 = new DemoTest11();
        Thread thread = new Thread(demoTest11);
        thread.start();

        for (int i = 0; i < 500; i++) {
            if(i==100){
                thread.join();
            }
            System.out.println("main线程。。。"+i);
        }

    }
    
}

6.观测线程状态

package thread;

/**
 * 观测线程状态 Thread.state
 */
public class DemoTest12 {

    public static void main(String[] args) {

        Thread thread = new Thread(() ->{

            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("00000");
            }

        });

        Thread.State state = thread.getState();
        System.out.println(state);

        thread.start();
        state = thread.getState();

        while (state!= Thread.State.TERMINATED){
            state = thread.getState();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(state);
        }
    }
}

7.线程的优先级

  • 优先级的设定建议在start()前
  • 默认优先级为5,最低为1,最高为10
  • 优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看cpu的调度
package thread;

/**
 * 线程优先级
 */
public class DemoTest13 {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName()+"---"+thread.getPriority());
        
        MyPripority myPripority= new MyPripority();
        Thread t1= new Thread(myPripority);
        Thread t2= new Thread(myPripority);
        Thread t3= new Thread(myPripority);
        Thread t4= new Thread(myPripority);

        t1.start();

        t2.setPriority(3);
        t2.start();

        t3.setPriority(6);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY);
        t4.start();

    }

}


class MyPripority implements Runnable{
    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(thread.getName()+"---"+thread.getPriority());
    }
}

8.守护线程

  • 线程分为用户线程和守护线程

  • 虚拟机必须确保用户线程执行完毕

  • 虚拟机不用等待守护线程执行完毕

  • 如,后台记录操作日志,监控内存,垃圾回收等待

package thread;

/**
 * 守护线程
 */
public class DemoTest14 {

    public static void main(String[] args) {

        God god = new God();
        Thread thread  = new Thread(god);

        //设置该线程为守护线程
        thread.setDaemon(true);
        thread.start();

        YouDamon you = new YouDamon();
        new Thread(you).start();

    }

}


class YouDamon implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("你一直快乐的活着");
        }
        System.out.println("=======GOODBYE,WORLD!======");
    }
}


class God implements Runnable{

    @Override
    public void run() {
        while (true){
            System.out.println("上帝守护着你");
        }
    }
}

7.并发问题及解决方式

1.三大线程不安全实例

package thread;

/**
 * 并发问题--抢票
 */
public class TestDemo16 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(ticket,"小明").start();
        new Thread(ticket,"小红").start();
        new Thread(ticket,"老师").start();
        new Thread(ticket,"小白").start();
        new Thread(ticket,"小绿").start();
        new Thread(ticket,"小紫").start();
    }
}

class Ticket implements Runnable{
    //总票数
    int ticketNum=10;
    boolean flag = true;
    @Override
    public void run() {
        while (flag){
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName()+"抢到了第"+ticketNum--+"张票");
            if(ticketNum<=0){
                flag = false;
                break;
            }
        }
    }
}


结果:老师抢到了第10张票
小绿抢到了第9张票
小红抢到了第8张票
小明抢到了第7张票
小白抢到了第5张票
小紫抢到了第6张票
小红抢到了第4张票
小明抢到了第4张票
小绿抢到了第4张票
老师抢到了第4张票
小紫抢到了第4张票
小白抢到了第4张票
小红抢到了第3张票
小白抢到了第1张票
小明抢到了第2张票
老师抢到了第3张票
小紫抢到了第3张票
小绿抢到了第3张票

package thread;

/**
 * 并发问题 ---- 取钱实例
 */
public class TestDemo15 {

    public static void main(String[] args) {

        Account account = new Account("结婚基金",100);

        DrawMoney you = new DrawMoney(account,50,"you");
        DrawMoney girl = new DrawMoney(account,100,"girl");
        you.start();
        girl.start();

    }
}


/**
 * 账户类
 */
class Account{

    /**
     * 账户名称
     */
    private String name;

    /**
     * 账户余额
     */
    private int accountMoney;

    public Account(String name, int accountMoney) {
        this.name = name;
        this.accountMoney = accountMoney;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAccountMoney() {
        return accountMoney;
    }

    public void setAccountMoney(int accountMoney) {
        this.accountMoney = accountMoney;
    }
}


/**
 * 银行取钱操作模拟
 */
class DrawMoney extends Thread{

    /**
     * 手里的余额
     */
    private int nowMoney;
    /**
     * 需要取的金额
     */
    private int drawMoney;

    /**
     * 银行卡账户信息
     */
    private Account account;


    public DrawMoney(Account account,int drawMoney,String name){
        super(name);
        this.account = account;
        this.drawMoney = drawMoney;

    }


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

            //判断卡内余额是否大于要取的金额
            if(account.getAccountMoney()-drawMoney<0){
                //this.getName == Thread.currentThread().getName()
                System.out.println("卡内余额剩余"+account.getAccountMoney()+",要取"+drawMoney+","+this.getName()+"无法取出!");
                return;
            }

            int accountMoney = account.getAccountMoney()-drawMoney;
            account.setAccountMoney(accountMoney);
            System.out.println(this.getName()+"取走了"+drawMoney);
            System.out.println("卡内剩余余额"+accountMoney);

            nowMoney = nowMoney+drawMoney;
            System.out.println(this.getName()+"手里的余额:"+nowMoney);

    }
}

结果:girl取走了100
卡内剩余余额0
you取走了50
girl手里的余额:100
卡内剩余余额50
you手里的余额:50

package thread;

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

/**
 * 并发问题 -- 线程不安全实例
 */
public class TestDemo17 {
    public static void main(String[] args) throws InterruptedException {
        List<String> list = new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }
        //由于多个线程可能会操作集合同一个位置,集合中就会有空值
        Thread.sleep(1000);

        System.out.println(list.size());
    }
}

结果:9999

2.synchronized关键字

​ 由于同一进程的多个线程共享同一块存储空间 , 在带来方便的同时,也带来了访问 冲突问题 , 为了保证数据在方法中被访问时的正确性 , 在访问时加入锁机制

synchronized , 当一个线程获得对象的排它锁 , 独占资源 , 其他线程必须等待 , 使用后释放锁即可。

​ 同时锁机制也可能带来以下问题:

1. 一个线程持有锁会导致其他所有需要此锁的线程挂起 
2. 在多线程竞争下 , 加锁 , 释放锁会导致比较多的上下文切换 和 调度延时,引 起性能问题 ; 
3. 如果一个优先级高的线程等待一个优先级低的线程释放锁 会导致优先级倒 置 , 引起性能问题 

通过同步方法、同步代码块来解决上面三个并发问题:

1.抢票案例

public synchronized void run() {}

结果:小明抢到了第10张票
小明抢到了第9张票
小明抢到了第8张票
小明抢到了第7张票
小明抢到了第6张票
小明抢到了第5张票
小明抢到了第4张票
小明抢到了第3张票
小明抢到了第2张票
小明抢到了第1张票

2.取钱案例

  
//通过同步代码块,锁定账户,取钱的操作放在同步代码块中执行
public void run(){
        synchronized (account){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            //判断卡内余额是否大于要取的金额
            if(account.getAccountMoney()-drawMoney<0){
                //this.getName == Thread.currentThread().getName()
                System.out.println("卡内余额剩余"+account.getAccountMoney()+",要取"+drawMoney+","+this.getName()+"无法取出!");
                return;
            }

            int accountMoney = account.getAccountMoney()-drawMoney;
            account.setAccountMoney(accountMoney);
            System.out.println(this.getName()+"取走了"+drawMoney);
            System.out.println("卡内剩余余额"+accountMoney);

            nowMoney = nowMoney+drawMoney;
            System.out.println(this.getName()+"手里的余额:"+nowMoney);
        }
    }

结果:you取走了50
卡内剩余余额50
you手里的余额:50
卡内余额剩余50,要取100,girl无法取出!
  1. 集合案例
//锁定集合对象 
new Thread(()->{
     synchronized (list){
         list.add(Thread.currentThread().getName());
     }
 }).start();

3.CopyOnWriteArrayList

CopyOnWriteArrayList是一个线程安全的集合,上述ArrayList案例,换成CopyOnWriteArrayList,不需要使用关键字

package thread.juc;

import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 线程安全的集合
 */
public class DemoTestJUC {
    public static void main(String[] args) throws InterruptedException {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();

        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }

        Thread.sleep(3000);
        System.out.println(list.size());
    }

}

4.死锁

多个线程各自占有一些共享资源 , 并且互相等待其他线程占有的资源才能运行 , 而 导致两个或者多个线程都在等待对方释放资源 , 都停止执行的情形 . 某一个同步块同时拥有 “ 两个以上对象的锁 ” 时 , 就可能会发生 “ 死锁 ” 的问题。

package thread.lock;

/**
 * 死锁
 */
public class DeadLock {

    public static void main(String[] args) {
        new EatFood("小明", 0).start();
        new EatFood("小红", 1).start();

    }

}

class EatFood extends Thread {

    //使用static保证资源只有一份
    static Cake cake = new Cake();
    static Sugar sugar = new Sugar();

    /**
     * 想吃零食的小朋友
     */
    private String name;

    /**
     * 想要先吃零食的种类
     */
    private int type;

    public EatFood(String name, int type) {
        this.name = name;
        this.type = type;
    }

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

    /**
     * 吃零食方法
     */
    private void eatFood() throws InterruptedException {
        if (type == 0) {
            synchronized (cake) {
                System.out.println(this.name + "获得的蛋糕的锁");
                Thread.sleep(1000);
                //1秒中后想获得糖的锁
                synchronized (sugar) {
                    System.out.println(this.name + "获得的糖的锁");
                }
            }
//            synchronized (sugar) {
//                System.out.println(this.name + "获得的糖的锁");
//            }
        } else {
            synchronized (sugar) {
                System.out.println(this.name + "获得的糖的锁");
                Thread.sleep(2000);
                synchronized (cake) {
                    System.out.println(this.name + "获得的蛋糕的锁");
                }
            }
//            synchronized (cake) {
//                System.out.println(this.name + "获得的蛋糕的锁");
//            }
        }
    }
}

/**
 * 蛋糕
 */
class Cake {
}

/**
 * 糖
 */
class Sugar {
}

死锁避免方法

产生死锁的四个必要条件:

  1. 互斥条件:一个资源每次只能被一个进程使用。

  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

  3. 不剥夺条件 : 进程已获得的资源,在末使用完之前,不能强行剥夺。

  4. 循环等待条件 : 若干进程之间形成一种头尾相接的循环等待资源关系。

上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件 就可以避免死锁发生

5.Lock(锁)

  • 从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock对象充当 java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。 锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开 始访问共享资源之前应先获得Lock对象
  • ReentrantLock (可重用锁)类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释 放锁。
package thread.lock;

import java.util.concurrent.locks.ReentrantLock;

public class TestReenLock {
    public static void main(String[] args) {
        TestLock testLock = new TestLock();
        new Thread(testLock).start();
        new Thread(testLock).start();
    }

}

class TestLock implements Runnable{

    int ticketNums = 10;
    private final ReentrantLock lock = new ReentrantLock();

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

synchronized和Lock锁的对比:

  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了 作用域自动释放

  • Lock只有代码块锁,synchronized有代码块锁和方法锁

  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展 性(提供更多的子类)

  • 优先使用顺序: Lock > 同步代码块(已经进入了方法体,分配了相应资源)> 同步方法(在方法体之外)

8.线程通信

应用场景:生产者和消费者问题

这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互依赖 ,互为条件。

  • 对于生产者 , 没有生产产品之前 , 要通知消费者等待 . 而生产了产品之后 , 又 需要马上通知消费者消费
  • 对于消费者 , 在消费之后 , 要通知生产者已经结束消费 , 需要生产新的产品 以供消费
  • 在生产者消费者问题中 , 仅有synchronized是不够的 ,synchronized 可阻止并发更新同一个共享资源,却不能用来实现不同线程之间的消息传递 (通信)

1.线程通信的解决方式一 (管程法)

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

  • 生产者 : 负责生产数据的模块 (可能是方法 , 对象 , 线程 , 进程) ;

  • 消费者 : 负责处理数据的模块 (可能是方法 , 对象 , 线程 , 进程) ;

  • 缓冲区 : 消费者不能直接使用生产者的数据 , 他们之间有个 “ 缓冲区

生产者将生产好的数据放入缓冲区 , 消费者从缓冲区拿出数据

package thread.communication;

/**
 * 线程通信 --> 管程法
 */
public class TestTubeSide {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();

        Producer producer = new Producer(buffer);
        Consumer consumer = new Consumer(buffer);

        new Thread(producer).start();
        new Thread(consumer).start();
    }

}


/**
 * 生产者
 */
class Producer implements Runnable{

    Buffer buffer;

    public Producer(Buffer buffer) {
        this.buffer = buffer;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            buffer.yieldProduct(new Product(i));
            System.out.println("生产者生产了第"+i+"个产品。");
        }
    }
}


/**
 * 消费者
 */
class Consumer implements Runnable{

    Buffer buffer;

    public Consumer(Buffer buffer) {
        this.buffer = buffer;
    }


    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("消费者消费了第"+buffer.pop().getId()+"个产品");
        }
    }
}

/**
 * 产品
 */
class Product{

    //产品编号
    private int id;

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}


/**
 * 缓冲区
 */
class Buffer{

    //定义缓冲区大小
    Product[] products = new Product[10];
    //产品数量计数
    int count = 0;


    /**
     * 生产方法
     */
    public synchronized void yieldProduct(Product product){

        //判断当前缓冲区是否已满
        if(count == products.length){
            //缓冲区已满,停止生产
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //生产一个产品
        products[count]=product;
        count++;

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


    /**
     * 消费方法
     */
    public synchronized Product pop(){
        //判断当前缓冲区是否有产品
        if(count == 0){
           //没有产品,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //有产品,消费者消费
        count--;
        Product product = products[count];


        //通知生产者生产
        this.notifyAll();
        return product;
    }
}



2.线程通信的解决方式二(信号灯法)

package thread.communication;

/**
 * 线程通信 -->信号灯法 通过标志位来决定哪个线程等待或运行
 */
public class TestSignalLamp {
    public static void main(String[] args) {
        Cup cup = new Cup();
        new MakeTea(cup).start();
        new DrinkTea(cup).start();

    }
}

/**
 * 制茶
 */
class MakeTea extends Thread{
    Cup cup;

    public MakeTea(Cup cup){
        this.cup =cup;
    }
    
    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            if(i%2 == 0){
               cup.makeTea("西湖龙井");
            }else{
                cup.makeTea("太平猴魁");
            }
        }
    }
}

/**
 * 喝茶
 */
class DrinkTea extends Thread{
    Cup cup;

    public DrinkTea(Cup cup){
        this.cup =cup;
    }

    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
           cup.drinkTea();
        }
    }
}

/**
 * 杯子
 */
class Cup{

    /**
     * 茶名
     */
    String teaName;

    /**
     * 标志位
     * T 进行制茶
     * F 进行喝茶
     */
    boolean flag = true;

    public synchronized void makeTea(String teaName){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("制作了"+teaName);
        this.notifyAll();
        this.teaName = teaName;
        this.flag = !this.flag;

    }

    public synchronized void drinkTea(){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notifyAll();
        System.out.println("喝了"+teaName);
        this.flag = !this.flag;

    }
}

9.线程池

​ 经常创建和销毁使用量特别大的资源,并发下的线程,对性能的影响很大,最好使用线程池,可以重复使用,优化性能。

package thread.communication;

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.shutdownNow();
    }
}

class MyThread extends Thread{

    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName());
    }
}
posted @ 2022-09-19 22:19  梁小白123  阅读(339)  评论(0)    收藏  举报