回到顶部

多线程

线程

概念

process和Thread

  • 程序是一个静态的概念
  • 进程是程序的一次执行,是一个动态的概念。是资源分配的单位
  • 通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位

线程创建

三种创建方式

  1. Thread class ====》继承Thread类(重点)
  2. runnable 接口====》 实现runnable接口(重点)
  3. callable接口====》实现callable接口(了解)

继承Thread类(重点)

  • 自定义线程继承Thread类
  • 重现run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
package deom01;

//创建线程方式一:继承Thread类,重写run方法,调用start开启线程
//总结:注意,线程开启不一定立即执行,由cpu调度执行
public class TestThread01 extends Thread{
    @Override
    public void run() {
        //run方法
        for(int i = 0; i<20; i++){
            System.out.println("我在看代码---->"+i);
        }
    }

    public static void main(String[] args) {
        //创建一个线程对象
        TestThread01 thread01 = new TestThread01();
        //调用start方法开启线程
        thread01.start();
        //主方法,main线程
        for(int i = 0; i<20; i++){
            System.out.println("我在学习多线程---->"+i);
        }
    }
}

多线程网图下载

package deom01;

import org.apache.commons.io.FileUtils;

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

//练习Thread02,实现多线程同步下载图片
public class TestThread02 extends Thread{
    private String url; //网络图片地址
    private String name;//保存的文件名

    public TestThread02(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @Override
    public void run(){
        WebDownload webDownload = new WebDownload();
        webDownload.downloader(url,name);
        System.out.println("下载了文件名为"+name);
    }

    public static void main(String[] args) {
        TestThread02 testThread01 = new TestThread02("https://img1.baidu.com/it/u=1821367333,2020547634&fm=26&fmt=auto&gp=0.jpg","2.jpg");
        TestThread02 testThread02 = new TestThread02("https://img1.baidu.com/it/u=1821367333,2020547634&fm=26&fmt=auto&gp=0.jpg","2.jpg");
        TestThread02 testThread03 = new TestThread02("https://img1.baidu.com/it/u=1821367333,2020547634&fm=26&fmt=auto&gp=0.jpg","2.jpg");

        testThread01.start();
        testThread02.start();
        testThread03.start();
    }
}


//下载器
class WebDownload{
    //下载方法
    public void downloader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,download失败");
        }
    }
}

结果

实现Runnable接口(重点)

  • 定义MyRunnable类实现Runnable接口
  • 实现run()方法,编写线程执行体
  • 创建线程对象,调用start()方法启动线程
package deom01;

//创建线程02:实现runnable接口,重写run()方法,执行线程丢入runnable接口实现类.
public class TestThread03 implements Runnable{
    @Override
    public void run() {
        //run方法
        for(int i = 0; i<20; i++){
            System.out.println("我在看代码---->"+i);
        }
    }

    public static void main(String[] args) {
        //创建一个runnable接口的实现类对象
        TestThread03 testThread03 = new TestThread03();
        //创建线程对象,通过线程对象来开启我们的线程,代理
        Thread thread = new Thread(testThread03);
        //调用start方法开启线程
        thread.start();//new Thread(testThread03).start;
        //主方法,main线程
        for(int i = 0; i<20; i++){
            System.out.println("我在学习多线程---->"+i);
        }
    }
}

实现Callable接口(了解)

package dome02;

import deom01.TestThread02;
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 TestCallable implements Callable<Boolean> {
    private String url; //网络图片地址
    private String name;//保存的文件名

    public TestCallable(String url, String name) {
        this.url = url;
        this.name = name;
    }

    @Override
    public Boolean call(){
        WebDownload webDownload = new WebDownload();
        webDownload.downloader(url,name);
        System.out.println("下载了文件名为"+name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable testThread01 = new TestCallable("https://img1.baidu.com/it/u=1821367333,2020547634&fm=26&fmt=auto&gp=0.jpg","1.jpg");
        TestCallable testThread02 = new TestCallable("https://img1.baidu.com/it/u=1821367333,2020547634&fm=26&fmt=auto&gp=0.jpg","2.jpg");
        TestCallable testThread03 = new TestCallable("https://img1.baidu.com/it/u=1821367333,2020547634&fm=26&fmt=auto&gp=0.jpg","3.jpg");
        //创建执行服务:
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> future1 = ser.submit(testThread01);
        Future<Boolean> future2 = ser.submit(testThread02);
        Future<Boolean> future3 = ser.submit(testThread03);
        //获取结果
        boolean r1 = future1.get();
        boolean r2 = future2.get();
        boolean r3 = future3.get();
        //关闭服务
        ser.shutdownNow();
    }
}
//下载器
class WebDownload{
    //下载方法
    public void downloader(String url,String name){
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO异常,download失败");
        }
    }
}
//好处:1. 可以定义返回值 2.可以抛出异常 

小结

继承Thread类

  1. 子类继承Thread类具备多线程能力

  2. 启动线程:子类对象. start()

  3. 不建议使用:避免OOP单继承局限性

实现Runnable接口

  1. 实现接口Runnable具有多线程能力
  2. 启动线程:传入目标对象+Thread对象.start()
  3. 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

并发问题

例子

package deom01;

//多个线程同时操作同一个对象
//买火车票例子

//多个线程操作同一个资源,线程不安全,数据紊乱
public class TestThread04 implements Runnable{
    private int ticketNum = 10;
    @Override
    public void run() {
        while (true){
            if (ticketNum<=0){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNum--+"票");
        }
    }

    public static void main(String[] args) {
        TestThread04 ticket = new TestThread04();
        //创建3个线程
        new Thread(ticket,"小明").start();
        new Thread(ticket,"老师").start();
        new Thread(ticket,"黄牛").start();
    }
}

龟兔赛跑

package deom01;

//模拟龟兔赛跑
public class Race implements Runnable{
    //胜利者
    private static String winner;
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++){
            //模拟兔子休息
            if (Thread.currentThread().getName().equals("兔子")&&i%10==0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //判断比赛是否结束
            boolean flag = gameOver(i);
            //如果比赛结束,就停止程序
            if (flag){
                break;
            }
            System.out.println(Thread.currentThread().getName()+"跑了"+i+"步");
        }
    }
    //判断是否完成比赛
    private boolean gameOver(int step){
        //判断是否有胜利者
        if (winner!=null){//已经有胜利者了
            return true;
        }{
            if (step>=100){
                winner=Thread.currentThread().getName();
                System.out.println("winner is "+winner);
                return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Race race = new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"乌龟").start();
    }
}

静态代理

好处:

  • 代理对象可以做很多真实对象不能完成的事情
  • 真实对象专注做自己的事情
//真实对象和代理对象都要实现同一个接口
//代理对象要代理真实角色
package proxystatic;

public class StacticProxy {
    public static void main(String[] args) {
        WeddingCompany weddingCompany = new WeddingCompany(new You());
        weddingCompany.HappyMarry();
    }
}

interface Marry{
    //
    void HappyMarry();
}
//真实角色
class You implements Marry{
    @Override
    public void HappyMarry() {
        System.out.println("秦要结婚了");
    }
}
//代理角色
class WeddingCompany implements Marry{
    //代理谁===>真实目标角色
    private Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void HappyMarry() {
        before();
        this.target.HappyMarry(); //这就是真实对象
        after();
    }

    private void after() {
        System.out.println("结婚后");
    }

    private void before() {
        System.out.println("结婚前");
    }
}

image-20210903130553611

Lamda表达式

为什么要使用lambda表达式

  • 避免匿名内部类定义过多

  • 可以让你的代码看起来很简洁

  • 去掉了一堆没有意义的代码,只留下核心的逻辑。
    理解Functional Interface(函数式接口)是学习Java8 lambda表达式的关键所在。

    (params)-> expression [ 表达式 ]
    (params)-> statement  [ 语句 ]
    (params)-> { statements }
    
    1. 函数式接口的定义:
      • 任何接口,如果只包含唯一一个抽象方法,那么它就是一个函数式接口。

        public interface Runnable {
                public abstract void run();
        }
        
      • 对于函数式接口,我们可以通过lambda表达式来创建该接口的对象。

package lamdaShow;

/*
* 推到lamda表达式
* */
public class TestLamda {
    //3.静态内部类
    static class Like2 implements ILike{
        @Override
        public void Lambda() {
            System.out.println("I like Lamda2");
        }
    }
    public static void main(String[] args) {
        ILike like = new Like();
        like.Lambda();

        like = new Like2();
        like.Lambda();
        //4.局部内部类
        class Like3 implements ILike{
            @Override
            public void Lambda() {
                System.out.println("I like Lamda3");
            }
        }
        like = new Like3();
        like.Lambda();
        //5.匿名内部类,没有类的名称,必须借助接口或者父类
        like = new ILike() {
            @Override
            public void Lambda() {
                System.out.println("I like Lambda内部类");
            }
        };
        like.Lambda();

        //6.用lambda简化
        like = ()-> {
            System.out.println("I like Lambda5");
        };
        like.Lambda();
    }
}
//1.定义一个函数式接口
interface ILike{
    void Lambda();
}
//2. 实现类
class Like implements ILike{
    @Override
    public void Lambda() {
        System.out.println("I like Lamda");
    }
}
//带参数的lambda
ILoove love = (int a)->{
    System.out.println("I love Lamda-->"+a);
}
//简化一:参数类型
love = (a)->{
     System.out.println("I love Lamda-->"+a);
}
//简化2:简化括号
love=a->{
    System.out.println("I love Lamda-->"+a);
}
//简化三:花括号 如果代码多行,不能简化
love =a->System.out.println("I love Lamda-->"+a);

总结:

  1. lamdba表达式只能有一行代码的情况,才能简化成一行,如果有多行,那么要用花括号包裹
  2. 前提是接口为函数式接口
  3. 多个参数也可以去掉参数类型,要去就全都去了,必须加上括号

线程的状态

方法 说明
setPriority(int newPriority) 更改线程的优先级
static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠
void join() 等待该线程终止
static void yield() 暂停当前正在执行的线程对象,并执行其他线程
void interrupt() 中断线程,别用这个方式
boolean isAlive() 测试线程是否处于活动状态

停止线程

不推荐使用JDK提供的 stop()、destroy()方法。【已废弃】

  1. 推荐线程自己停止下来
  2. 建议使用一个标志位进行终止变量
  • 当flag=false,则终止线程运行。
package state;

//测试stop
//1.建议线程正常停止--->利用次数,不建议死循环
//2.建议使用标志位-->设置一个标志位
//3.不要使用stop或者destroy等过时或者JDK不推荐使用的方法
public class TestStop implements Runnable{
    //1.设置一个标志位
    private boolean flag = true;
    @Override
    public void run() {
        int i = 0;
        while (flag){
            System.out.println("run....Thread" + i++);
        }
    }
    //2.设计一个公开的方法停止线程,转换标志位
    public void stop(){
        this.flag=false;
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();

        new Thread(testStop).start();
        for (int i = 0;i < 1000;i++){
            System.out.println("main"+i);
            if (i == 900){
                //调用stop方法切换标志位,让线程停止
                testStop.stop();
                System.out.println("线程该停止了");
            }
        }
    }
}

线程休眠

  • sleep (时间) 指定当前线程阻塞的毫秒数;
  • sleep存在异常InterruptedException;
  • sleep时间达到后线程进入就绪状态;
  • sleep可以模拟网络延时,倒计时等。
  • 每一个对象都有一个锁,sleep不会释放锁;
  1. 模拟网络延时

    package state;
    //模拟网络延时:放大问题的发生性
    public class TestSleep implements Runnable{
        private int ticketNum = 10;
        @Override
        public void run() {
            while (true){
                if (ticketNum<=0){
                    break;
                }
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNum--+"票");
            }
        }
    
        public static void main(String[] args) {
            TestSleep ticket = new TestSleep();
            //创建3个线程
            new Thread(ticket,"小明").start();
            new Thread(ticket,"老师").start();
            new Thread(ticket,"黄牛").start();
        }
    }
    
    
  2. 模拟倒计时

    package state;
    
    //模拟倒计时
    public class TestSleep02 {
    
        public static void tenDown() throws InterruptedException {
            int num = 10;
            while (true){
                Thread.sleep(1000);
                System.out.println(num--);
                if (num<=0){
                    break;
                }
            }
        }
    
        public static void main(String[] args) {
            try {
                tenDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    //获取当前时间
     Date date = new Date(System.currentTimeMillis());//获取当前时间
            while (true){
                try {
                    Thread.sleep(1000);
                    System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
                    date = new Date(System.currentTimeMillis());//更新当前时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    

线程礼让

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让cpu重新调度,礼让不一定成功!看CPU心情
package state;


//测试礼让进程,礼让不一定成功
public class TestYield {
    public static void main(String[] args) {
        MyYield yield = new MyYield();

        new Thread(yield,"a").start();
        new Thread(yield,"b").start();
    }
}
class MyYield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程开始");
        Thread.yield();//礼让
        System.out.println(Thread.currentThread().getName()+"线程结束");
    }
}
结果
a线程开始
b线程开始
a线程结束
b线程结束

Join

  • Join合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞
  • 可以想象成插队
package state;

//测试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 InterruptedException {
        //启动线程
        TestJoin join = new TestJoin();
        Thread thread = new Thread(join);
        thread.start();
        //主线程
        for (int i = 0; i < 500; i++) {
            if(i==200){
                thread.join();//插队
            }
            System.out.println("main"+i);
        }
    }
}

线程优先级

  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度是按照优先级决定应该调度哪个线程来执行。

  • 线程的优先级用数字表示,范围从1~10.

    • Thread.MIN_PRIORITY = 1;

    • Thread.MAX_PRIORITY = 10;

    • Thread.NORM_PRIORITY = 5;

  • 使用以下方式改变或获取优先级
    • getPriority() . setPriority(int xxx)
package state;

//测试线程优先级
public class TestPriority {
    public static void main(String[] args) {
        //主线程默认优先级
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
        MYPriority myPriority = new MYPriority();
        Thread thread1 = new Thread(myPriority);
        Thread thread2 = new Thread(myPriority);
        Thread thread3 = new Thread(myPriority);
        Thread thread4 = new Thread(myPriority);
        //先设置优先级,再启动
        thread1.start();

        thread2.setPriority(1);
        thread2.start();

        thread3.setPriority(4);
        thread3.start();

        thread4.setPriority(Thread.MAX_PRIORITY);//10
        thread4.start();
    }
}
class MYPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
    }
}

优先级低只是意味着获得调度的概率低.并不是优先级低就不会被调用了.这都是看CPU的调度

守护(daemon)线程

  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 如,后台记录操作日志,监控内存,垃圾回收等待..
package state;

//测试守护线程
public class TestDaemon {
    public static void main(String[] args) {
        God god = new God();
        You you = new You();

        Thread thread = new Thread(god);
        thread.setDaemon(true);  //默认false表示用户线程,正常都是用户线程

        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 < 36500; i++) {
            System.out.println("你开心的活着");
        }
        System.out.println("===goodbye world=====");
    }
}

线程同步

并发 : 同一个对象被多个线程同时操作

package syn;

//不安全的买票
//线程不安全
public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station = new BuyTicket();
        new Thread(station,"苦逼的你").start();
        new Thread(station,"牛逼的你").start();
        new Thread(station,"黄牛").start();
    }
}

class BuyTicket implements Runnable{
    //票
    private int ticketNum=10;
    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--);
    }
}

同步方法

由于我们可以通过 private 关键字来保证数据对象只能被方法访问 , 所以我们只需要针对方法提出一套机制 , 这套机制就是 synchronized 关键字 , 它包括两种用法 :
synchronized 方法 和synchronized 块 .

同步方法 :  public synchronized void method(int args) {}

synchronized方法控制对 “对象” 的访问 , 每个对象对应一把锁 , 每个synchronized方法都必须获得调用该方法的对象的锁才能执行 , 否则线程会阻塞 , 方法一旦执行 , 就独占该锁 , 直到该方法返回才释放锁 , 后面被阻塞的线程才能获得这个锁 , 继续执行

缺陷 : 若将一个大的方法申明为synchronized 将会影响效率

synchronized方法默认锁类本身

synchronized代码块锁的是想锁的数据

synchronized(被锁的数据){}//锁的对象是变化的量,需要增删改的对象

CopyOnWriteArrayList

package syn;

import java.util.concurrent.CopyOnWriteArrayList;

//测试JUC安全类型的集合
public class TestJUC {
    public static void main(String[] args){
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
        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());
    }
}

死锁

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

package lock;

//多个线程互相抱着对方需要的资源,形成僵持 死锁
public class DeadLock {
    public static void main(String[] args) {
        MakeUp g1 = new MakeUp(0,"灰姑娘");
        MakeUp g2 = new MakeUp(1,"白公主");
        g1.start();
        g2.start();
    }
}

//口红
class Lipstick{

}
//镜子
class Mirror{

}

class MakeUp extends Thread{
    //需要的资源,只有一份
    static Lipstick lipstick = new Lipstick();
    static  Mirror mirror = new Mirror();
    int choice;//选择
    String girlName;//化妆的人

    MakeUp(int choice,String girlName){
        this.choice=choice;
        this.girlName=girlName;
    }
    @Override
    public void run() {
        //化妆
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //互相持有对方的锁,就是需要拿到对方的资源
    private void makeup() throws InterruptedException {
        if(choice==0){
            synchronized (lipstick){ //获得口红的锁
                System.out.println(this.getName()+"获得口红的锁");
                Thread.sleep(1000);

                synchronized (mirror){//一秒钟后,想要获得镜子
                    System.out.println(this.getName()+"获得镜子的锁");
                }
            }
        }else {
            synchronized (mirror) {//一秒钟后,想要获得镜子
                System.out.println(this.getName() + "获得镜子的锁");
                Thread.sleep(2000);
                synchronized (lipstick) { //获得口红的锁
                    System.out.println(this.getName() + "获得口红的锁");

                }
            }
        }
    }
}
//解决 将叠加获得锁打开
package lock;

//多个线程互相抱着对方需要的资源,形成僵持
public class DeadLock {
    public static void main(String[] args) {
        MakeUp g1 = new MakeUp(0,"灰姑娘");
        MakeUp g2 = new MakeUp(1,"白公主");
        g1.start();
        g2.start();
    }
}

//口红
class Lipstick{

}
//镜子
class Mirror{

}

class MakeUp extends Thread{
    //需要的资源,只有一份
    static Lipstick lipstick = new Lipstick();
    static  Mirror mirror = new Mirror();
    int choice;//选择
    String girlName;//化妆的人

    MakeUp(int choice,String girlName){
        this.choice=choice;
        this.girlName=girlName;
    }
    @Override
    public void run() {
        //化妆
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    //互相持有对方的锁,就是需要拿到对方的资源
    private void makeup() throws InterruptedException {
        if(choice==0){
            synchronized (lipstick){ //获得口红的锁
                System.out.println(this.getName()+"获得口红的锁");
                Thread.sleep(1000);
            }
            synchronized (mirror){//一秒钟后,想要获得镜子
                System.out.println(this.getName()+"获得镜子的锁");
            }
        }else {
            synchronized (mirror) {//一秒钟后,想要获得镜子
                System.out.println(this.getName() + "获得镜子的锁");
                Thread.sleep(2000);
            }
            synchronized (lipstick) { //获得口红的锁
                System.out.println(this.getName() + "获得口红的锁");

            }
        }
    }
}

死锁避免方法

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

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

Lock

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

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

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

image-20210904152838247

package lock;

import java.util.concurrent.locks.ReentrantLock;

//测试lock锁
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 ticketNum = 10;

    //定义lock锁
    private final ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){

            try {
                lock.lock();//加锁
                if (ticketNum>0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNum--);
                }else {
                    break;
                }
            }finally {
                    //解锁
                    lock.unlock();
            }

        }
    }
}

synchronized 与 Lock 的对比

  • Lock是显式锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放
  • Lock只有代码块锁,synchronized有代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展
    性(提供更多的子类)
  • 优先使用顺序:
    • Lock > 同步代码块(已经进入了方法体,分配了相应资源)> 同步方法(在方法体之外)

线程通信

方法名 作用
wait() 表示线程一直等待 , 直到其他线程通知 , 与sleep不同 , 会释放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait()方法的线程 , 优先级别高的线程优先调度

注意

  • 均是Object类的方法 , 都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException

管程法

package communication;

//测试:生产者消费者模型-->利用缓冲区解决:管程法
//生产者,消费者,产品,缓冲区
public class TestPC {
    public static void main(String[] args) {
        SynContainer synContainer = new SynContainer();

        new Producer(synContainer).start();
        new Consumer(synContainer).start();
    }
}
//生产者
class Producer extends Thread{
    SynContainer container;
    public Producer(SynContainer container){
        this.container = container;
    }
    //生产
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("生产了"+i+"只鸡");
            container.push(new Chicken(i));
        }
    }
}
//消费者
class Consumer extends Thread{
    SynContainer container;
    public Consumer(SynContainer container){
        this.container = container;
    }
    //消费

    @Override
    public void run() {
        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 SynContainer{
    //容器大小
    Chicken[] chickens=new Chicken[10];
    //容器计数器
    int count = 0;
    //生产者放入
    public synchronized void push(Chicken chicken){
        //如果容器满了,等待消费者消费
        if (count == chickens.length){
            //通知消费者消费,生产者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.notify();
        //如果没有满,我们就把鸡放入
        chickens[count++] = chicken;

        //可以通知消费者消费
    }

    //消费者消费
    public synchronized Chicken pop(){
        //判断是否能消费
        if (count==0){
            //等待生产者生成,消费者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //如果可以消费
        this.notify();
        count--;
        Chicken chicken = chickens[count];
        //吃完了.通知生产者生产
        return chicken;
    }
}

信号灯法

package communication;

//测试生产者消费者问题2:信号灯法,标志位解决
public class TestPC2 {
    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();
        new Watcher(tv).start();
    }
}
//生产者-->演员
class  Player extends Thread{
    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 Watcher extends Thread{
    TV tv;
    public Watcher(TV tv) {
        this.tv = tv;
    }

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

//产品-->节目
class TV {
    //演员表演时,观众等待 T
    //观众观看时,演员等待 F
    String voice;//表演的节目
    boolean flag = true;
    //表演
    public synchronized void play(String voice){
        if (!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("表演了"+voice);
        //通知观众观看
        this.notifyAll();//通知唤醒
        this.voice = voice;
        this.flag = !this.flag;
    }
    //观看
    public synchronized void watch(){
        if (flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观看了"+voice);
        //通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

线程池

  • JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors

  • ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor

    • void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
    • Future submit(Callable task):执行任务,有返回值,一般又来执行Callable
    • void shutdown() :关闭连接池
  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

posted @ 2021-09-04 17:42  cungen  阅读(21)  评论(0编辑  收藏  举报