Java多线程入门

一、创建线程的三种方法


1.1 继承Thread类

  1. 自定义线程类继承Thread类
  2. 重写**run() **方法,编写线程执行体
  3. 创建线程对象,调用start()方法启动线程
public class testThread1 extends Thread{
    @Override
    public void run(){
        for(int i = 0; i < 20; i++){
            System.out.println("我在看代码---" + i);
        }
    }

    public static void main(String[] args) {
        testThread1 testThread1 = new testThread1();
        testThread1.start();
        for(int i = 0; i < 200; i++){
            System.out.println("我在学习多线程---" + i);
        }
    }
}

练习--下载图片

public class testThread2 extends Thread{
    private String url;
    private String name;

    public testThread2(String url, String name){
        this.url = url;
        this.name = name;
    }
    //下载图片线程的执行体
    @Override
    public void run(){
        webDownloader webDownloader = new webDownloader();
        webDownloader.downloader(url, name);
        System.out.println("下载了文件名为:" + name);
    }

    public static void main(String[] args) {
        testThread2 t1 = new testThread2("https://upload-images.jianshu.io/upload_images/25994682-8a5932b369e3cc36.png?imageMogr2/auto-orient/strip|imageView2/2/format/webp", "1.jpg");
        testThread2 t2 = new testThread2("https://upload-images.jianshu.io/upload_images/25994682-a0c494eb408e4f66.png?imageMogr2/auto-orient/strip|imageView2/2/format/webp", "2.jpg");
        testThread2 t3 = new testThread2("https://upload-images.jianshu.io/upload_images/25994682-4304e6bbc1c519fd.png?imageMogr2/auto-orient/strip|imageView2/2/format/webp", "3.jpg");
        t1.start();
        t2.start();
        t3.start();
    }
}

//下载器
class webDownloader{
    //下载方法
    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异常,downloader方法出现问题");
        }
    }
}

1.2 实现Runnable接口

  1. 定义MyRunnable类实现Runnable接口
  2. 实现run()方法,编写线程执行体
  3. 创建线程对象,调用start()方法启动线程
//实现runnable接口、重写run方法、创建一个线程、执行线程需要丢入runnable接口实现类,调用start方法
public class TestTheard3 implements Runnable{

    @Override
    public void run() {
        for(int i = 0; i < 20; i++){
            System.out.println("我在run" + i);
        }
    }

    public static void main(String[] args) {
        TestTheard3 testTheard3 = new TestTheard3();
//        Thread thread = new Thread(testTheard3);
//        thread.start();
        new Thread(testTheard3).start();
        for(int i = 0; i < 100; i++){
            System.out.println("我在主函数" + i);
        }
    }
}

1.3 小结

继承Thread类

  1. 子类继承Thread类具备多线程能力
  2. 启动线程: 子类.start()
  3. 不建议使用,避免OOP单继承局限性

实现Runnable接口

  1. 实现Runnable接口,重写run方法
  2. 启动线程: new Thread(子类).start()
  3. 推荐使用,避免OOP单继承局限性,灵活方便,一个对象(实现Runnable接口的)可以被多个线程使用

1.4 实现Callable接口

  1. 实现Callable接口,需要返回值类型
  2. 重写call方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务: ExecutorService ser = Executors.newFixedThreadPool(1);
  5. 提交执行: Future<Boolean> result1 = ser.submit(t1);
  6. 获取结果: boolean r1 = result1.get();
  7. 关闭服务: ser.shutdownNow();
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(){
        webDownloader webDownloader = new webDownloader();
        webDownloader.downloader(url, name);
        System.out.println("下载了文件名为:" + name);
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable t1 = new TestCallable("https://upload-images.jianshu.io/upload_images/25994682-8a5932b369e3cc36.png?imageMogr2/auto-orient/strip|imageView2/2/format/webp", "1.jpg");
        TestCallable t2 = new TestCallable("https://upload-images.jianshu.io/upload_images/25994682-a0c494eb408e4f66.png?imageMogr2/auto-orient/strip|imageView2/2/format/webp", "2.jpg");
        TestCallable t3 = new TestCallable("https://upload-images.jianshu.io/upload_images/25994682-4304e6bbc1c519fd.png?imageMogr2/auto-orient/strip|imageView2/2/format/webp", "3.jpg");
        //创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //提交执行
        Future<Boolean> result1 = ser.submit(t1);
        Future<Boolean> result2 = ser.submit(t2);
        Future<Boolean> result3 = ser.submit(t3);
        //获取结果
        boolean r1 = result1.get();
        boolean r2 = result2.get();
        boolean r3 = result3.get();
        //关闭服务
        ser.shutdownNow();
    }
}

//下载器
class webDownloader{
    //下载方法
    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异常,downloader方法出现问题");
        }
    }
}
  1. Callable规定的方法是call(),而Runnable规定的方法是run().
  2. Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
  3. call()方法可抛出异常,而run()方法是不能抛出异常的。
  4. 运行Callable任务可拿到一个Future对象, Future表示异步计算的结果。
  5. 它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。
  6. 通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。
  7. Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。

二、静态代理


静态代理模式:
真实对象和代理对象都实现同一个接口
代理对象要代理真实角色,所以代理对象中有一个接口的对象,用以接受真实对象

  • 代理是为了不改变真实对象功能,给原对象以功能增强
  • 真实对象可以专注做自己的事情
public class StacticProxy {
    public static void main(String[] args) {
        WeddingCompany weddingCompany = new WeddingCompany(new You());
        weddingCompany.HappyMerry();
    }
}

interface Merry{
    void HappyMerry();
}
class You implements Merry{

    @Override
    public void HappyMerry() {
        System.out.println("要结婚了");
    }
}
class WeddingCompany implements Merry{

    private Merry target;

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

    @Override
    public void HappyMerry() {
        before();
        this.target.HappyMerry();
        after();
    }

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

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

可以用lambda表达式,通过线程来执行代理

public static void main(String[] args) {
        new Thread( () -> System.out.println("我爱你")).start();
        new WeddingCompany(new You()).HappyMerry();
    }

三、Lambda表达式


  • 是什么:实质属于函数式编程的概念
  • 干什么:避免匿名内部类定义过多、简洁、留下核心逻辑

函数式接口

  • 任何接口如果只包含一个抽象方法,那就是函数式接口

接口形式

  1. 实现一个函数式接口
  2. 实现类
  3. 静态内部类
  4. 把实现类放在方法里,局部内部类
  5. 匿名内部类,new 接口,要重写方法
  6. Lambda
  • 类名 对象名 = (参数类型 参数名) ->{方法体};
  • 类名 对象名 = (参数名) ->{方法体};
    如果有参数
  • 类名 对象名 = 参数名 ->{方法体};
    如果方法体只有一行
  • 类名 对象名 = 参数名 -> 方法体;
public class TestLambda1 {
    //3.静态内部类
    static class like2 implements ILike{

        @Override
        public void lambda() {
            System.out.println("I like Lambda2");
        }
    }
    public static void main(String[] args) {
        //接口实现
        ILike like = new Like();
        like.lambda();

        //3.静态内部类
        like = new like2();
        like.lambda();

        //4.局部内部类
        class like3 implements ILike{

            @Override
            public void lambda() {
                System.out.println("I like Lambda3");
            }
        }
        like = new like3();
        like.lambda();

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

        //6.Lambda简化
        like = ()->{
            System.out.println("I like Lambda5");
        };
    }
}
//1. 定义一个函数式接口
interface ILike{
    void lambda();
}
//2. 外部类实现接口
class Like implements ILike{

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

多线程的Runnable接口只有一个静态方法run(),用lambda表达式可以优化

四、线程状态

线程状态

线程状态

线程方法

1. 线程停止 stop (自定义)

  • 不推荐使用JDK自带的stop(), destroy()方法
  • 推荐线程自己停下来
  • 建议使用一个标志位进行终止变量
    当flag = false,则终止线程运行


    线程停止
public class TestStop implements Runnable{
    private boolean flag = true;
    @Override
    public void run() {
        int i = 0;
        while(flag){
            System.out.println("run....Thread" + i++);
        }
    }

    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){
                testStop.stop();
                System.out.println("线程该停止了");
            }
        }
    }
}

2. 线程休眠 sleep

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

    private int tickteNum = 10;

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

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

                System.out.println(Thread.currentThread().getName()+"得到了第 "+tickteNum--+" 票");
            }
        }
    }

    public static void main(String[] args) {
        TestSleep testSleep = new TestSleep();

        new Thread(testSleep,"小红").start();
        new Thread(testSleep,"小明").start();
        new Thread(testSleep,"小黄").start();
    }
}

3. 礼让yield

  • 礼让线程,让当前正在执行的线程暂停,但不阻塞
  • 将线程从运行状态转为就绪状态
  • 让CPU重新调度
public class TestYield {
    public static void main(String[] args) {
        MyYield myYield = new MyYield();

        new Thread(myYield,"小红").start();
        new Thread(myYield,"小名").start();
    }
}

class MyYield implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"线程在执行");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+"线程被暂停");
    }
}
  • 礼让不一定成功
  • 礼让线程,让当前正在执行的线程暂停,但不阻塞,将线程从运行状态转为就绪状态

4. 线程合并 Join

  • Join 线程合并,待此线程执行完毕后,再执行其他线程
  • 插队
public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("线程VIP来了"+i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestJoin testJoin = new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();

        for (int i = 0; i < 500; i++) {
            if(i==200){
                //插队
                thread.join();
            }
            System.out.println("main "+i);
        }
    }
}
  • 其他线程进入阻塞状态,直到join的线程执行完毕

5.观测线程状态

    public static void main(String[] args) throws InterruptedException {
        //使线程睡眠5秒
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        //观察状态
        Thread.State state = thread.getState();
        System.out.println(state);//NEW

        //观察启动后
        thread.start();
        state = thread.getState();
        System.out.println(state);//RUNNABLE

        while (state!=Thread.State.TERMINATED){
            state = thread.getState();
            Thread.sleep(100);
            System.out.println(state);//TERMINATED
        }
        //thread.start();
    }
    terminated后不能再start(),会返回IllegalThreadStateException

6.线程优先级

public class TestPriority {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());

        MyPriority myPriority = new MyPriority();
        Thread t1 = new Thread(myPriority);
        Thread t2 = new Thread(myPriority);
        Thread t3 = new Thread(myPriority);
        Thread t4 = new Thread(myPriority);
        Thread t5 = new Thread(myPriority);
        Thread t6 = new Thread(myPriority);
        //先设置优先级再启动
        t1.start();

        t2.setPriority(5);
        t2.start();

        t3.setPriority(1);
        t3.start();

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

        t5.setPriority(8);
        t5.start();

        t6.setPriority(6);
        t6.start();
    }
}

class MyPriority implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
    }
}

先设置优先级,再启动。
优先级低的只是意味着获得CPU调度的概率低,并不是优先级底就不会被调用了。

7.守护线程 (daemon)

  • 线程分为用户现场和守护线程
  • 虚拟机必须确保用户线程执行完毕
  • 虚拟机不用等待守护线程执行完毕
  • 守护线程例子: 后台记录操作日志,监控内存,垃圾回收等等。
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();

        Thread thread1 = new Thread(you);
        thread1.start();
    }
}
class God implements Runnable{

    @Override
    public void run() {
        while (true) {
            System.out.println("Still Alive");
        }
    }
}
class You implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 36500; i++) {
            System.out.println("活着");
        }
        System.out.println("=========死了==============");
    }
}

五、线程同步

1、三大线程不安全案例

不安全的买票

public class UnSafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket,"小明").start();
        new Thread(buyTicket,"小红").start();
        new Thread(buyTicket,"黄牛党").start();
    }
}

class BuyTicket implements Runnable{
    private int ticketNums = 10;
    boolean flag = true;
    @Override
    public void run() {
        //买票
        while (flag){
            buy();
        }
    }

    public void buy(){
        if (ticketNums<=0){
            flag = false;
            return;
        }

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
    }
}

不安全的取钱

public class UnSafeBank {
    public static void main(String[] args) {

        Account account = new Account(100,"理财基金");

        TakeMoney you = new TakeMoney(account,50,"你");
        TakeMoney girlFriend = new TakeMoney(account,100,"女朋友");

        you.start();
        girlFriend.start();

    }
}

//账户
class Account{
    int money;//余额
    String name;//卡名

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

//模拟取钱
class TakeMoney extends Thread{

    Account account;
    int takeMoney;
    int nowMoney;

    public TakeMoney(Account account, int takeMoney, String name) {
        super(name);
        this.account = account;
        this.takeMoney = takeMoney;
    }

    @Override
    public void run() {
        //判断有没有钱
        if(account.money-this.takeMoney<0){
            System.out.println("账户余额不足~,还剩 "+account.money);
            return;
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        account.money = account.money - takeMoney;
        nowMoney = nowMoney + takeMoney;
        System.out.println(account.name+"-->"+"账户余额:"+account.money);
        System.out.println(this.getName()+"-->"+"手里钱:"+nowMoney);
    }
}

不安全的集合

public class UnSafeList {
    public static void main(String[] args) throws InterruptedException {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                list.add(Thread.currentThread().getName());
            }).start();
        }

        Thread.sleep(10);

        System.out.println("list's size = "+list.size());
    }
}

2、synchronize同步

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

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

1. synchronized 方法:通过在方法声明中加入 synchronized关键字来声明 synchronized 方法。如:

public synchronized void accessVal(int newVal);

synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized 方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明为 synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。

在 Java 中,不光是类实例,每一个类也对应一把锁,这样我们也可将类的静态成员函数声明为 synchronized ,以控制其对类的静态成员变量的访问。

synchronized 方法的缺陷:若将一个大的方法声明为synchronized 将会大大影响效率,典型地,若将线程类的方法 run() 声明为 synchronized ,由于在线程的整个生命期内它一直在运行,因此将导致它对本类任何 synchronized 方法的调用都永远不会成功。当然我们可以通过将访问类成员变量的代码放到专门的方法中,将其声明为 synchronized ,并在主方法中调用来解决这一问题,但是 Java 为我们提供了更好的解决办法,那就是 synchronized 块。

2. synchronized 块:通过 synchronized关键字来声明synchronized 块。语法如下:

synchronized(syncObject) {
//允许访问控制的代码 
}

synchronized 块是这样一个代码块,其中的代码必须获得对象 syncObject (如前所述,可以是类实例或类)的锁方能执行,具体机制同前所述。由于可以针对任意代码块,且可任意指定上锁的对象,故灵活性较高。

解决三个线程不安全问题

public class UnSafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket buyTicket = new BuyTicket();

        new Thread(buyTicket,"小明").start();
        new Thread(buyTicket,"小红").start();
        new Thread(buyTicket,"黄牛党").start();
    }
}

class BuyTicket implements Runnable{
    private int ticketNums = 10;
    boolean flag = true;
    @Override
    public void run() {
        //买票
        while (flag){

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            buy();
        }
    }

    //synchronized,同步方法,锁的是this
    public synchronized void buy(){
        if (ticketNums<=0){
            flag = false;
            return;
        }

        System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
    }
}
public class SafeBank {
    public static void main(String[] args) {

        Account account = new Account(1000,"理财基金");

        TakeMoney you = new TakeMoney(account,50,"你");
        TakeMoney girlFriend = new TakeMoney(account,100,"女朋友");

        you.start();
        girlFriend.start();

    }
}

//账户
class Account{
    int money;//余额
    String name;//卡名

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

//模拟取钱
class TakeMoney extends Thread{

    Account account;
    int takeMoney;
    int nowMoney;

    public TakeMoney(Account account, int takeMoney, String name) {
        super(name);
        this.account = account;
        this.takeMoney = takeMoney;
    }

    @Override
    public void run() {

        //锁住account,因为account在增加减少【锁住变化的量】
        synchronized(account){
            //判断有没有钱
            if(account.money-this.takeMoney<0){
                System.out.println("账户余额不足~,还剩 "+account.money);
                return;
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            account.money = account.money - takeMoney;
            nowMoney = nowMoney + takeMoney;
            System.out.println(account.name+"-->"+"账户余额:"+account.money);
            System.out.println(this.getName()+"-->"+"手里钱:"+nowMoney);
        }


    }
}
public class UnSafeList {
    public static void main(String[] args) throws InterruptedException {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 10000; i++) {
            new Thread(()->{
                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }
            }).start();
        }

        Thread.sleep(10);

        System.out.println("list's size = "+list.size());
    }
}

锁的对象是会产生变化的量

CopyOnWriteArrayList 同步List

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

六、死锁

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

public class DeadLock {
    public static void main(String[] args) {
        MakeUp girl1 = new MakeUp(0, "灰姑娘");
        MakeUp girl2 = new MakeUp(1, "白姑娘");
        girl1.start();
        girl2.start();
    }
}

class Lipstick{
}
class Mirror{
}
class MakeUp extends Thread{
    static Mirror mirror = new Mirror();
    static Lipstick lipstick = new Lipstick();
    int choice;
    String name;

    public MakeUp(int choice, String name) {
        this.choice = choice;
        this.name = name;
    }

    @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.name + "获得口红");
                Thread.sleep(1000);
//                synchronized (mirror){
//                    System.out.println(this.name + "获得镜子");
//                }
            }
            synchronized (mirror){
                System.out.println(this.name + "获得镜子");
            }
        }else{
            synchronized (mirror){
                System.out.println(this.name + "获得镜子");
                Thread.sleep(2000);
//                synchronized (lipstick){
//                    System.out.println(this.name + "获得口红");
//                }
            }
            synchronized (lipstick){
                System.out.println(this.name + "获得口红");
            }
        }
    }
}

产生死锁的必要条件

死锁

七、Lock 锁

  • ReentrantLock 可重入锁
public class TestLock {
    public static void main(String[] args) {
        Lock2 lock2 = new Lock2();

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

class Lock2 implements Runnable{
    private int ticketNums = 10;

    private final ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try{
                //加锁
                reentrantLock.lock();
                if(ticketNums<=0){
                    break;
                }else{
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                }
            }
            finally {
                //解锁
                reentrantLock.unlock();
            }
        }
    }
}
  • Lock是显式锁,需要手动关闭锁,synchronized是隐式锁,出作用域自动释放
  • Lock只能锁代码块,synchronized能锁代码块和方法
  • Lock锁,JVM花费更少的时间来调度线程,性能更好,有更好的扩展性
  • 优先使用顺序 Lock > 同步代码块 > 同步方法

八、线程协作

1. 生产者消费者模式

  • 不是设计模式,是一种问题


解决方式

2.管程法

public class TestPC {
    public static void main(String[] args) {
        KeepSpace keepSpace = new KeepSpace();

        new Thread(new Producer(keepSpace)).start();
        new Thread(new Customer(keepSpace)).start();

    }

}

class Food{
    int id;
    public Food(int id) {
        this.id = id;
    }
}

class Producer implements Runnable{

    KeepSpace keepSpace;

    public Producer(KeepSpace keepSpace) {
        this.keepSpace = keepSpace;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            keepSpace.push(new Food(i));
            System.out.println("生产了第"+i+"个食物");
        }
    }
}

class Customer implements Runnable{

    KeepSpace keepSpace;

    public Customer(KeepSpace keepSpace) {
        this.keepSpace = keepSpace;
    }

    @Override
    public void run() {
        //消费者消费
        for (int i = 0; i < 100; i++) {
            System.out.println("消费了第"+keepSpace.pop().id+"个食物");
        }
    }
}

class KeepSpace{
    //空间大小
    Food[] foods = new Food[10];
    //空间计数器
    int count = 0;

    public synchronized void push(Food food){
        //空间满了,等待消费者消费
        if (count==foods.length){
            //通知消费者消费
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        //空间没有满,继续制作
        foods[count] = food;
        count++;
        //有吃的,可以通知消费者消费了
        this.notifyAll();
    }

    //消费者消费
    public synchronized Food pop(){
        //判断能否消费
        if (count==0){
            //等待生产者生产
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;
        Food food = foods[count];
        //吃完了,通知生产者生产
        this.notifyAll();
        return food;
    }
}

3.信号灯法

利用标志位来进行控制

//信号灯法,标志位解决 length为1的管程法
public class TestPC2 {
    public static void main(String[] args) {
        TV tv = new TV();
        new Acter(tv).start();
        new audience(tv).start();
    }
}

//生产者 演员
class Acter extends Thread{
    TV tv;

    public Acter(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("DC");
            }
        }
    }
}
//消费者 观众
class audience extends Thread{
    TV tv;

    public audience(TV tv) {
        this.tv = tv;
    }
    @Override
    public void run(){
        for (int i = 0; i < 20; i++) {
            tv.watch();
        }
    }
}
//产品 节目
class TV{
    String show;
    boolean flag = true;
    //表演
    public synchronized void play(String show){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("演员表演了:" + show);
        //通知观众观看
        this.notifyAll(); // 通知唤醒
        this.show = show;
        this.flag = !this.flag;
    }
    //观看
    public synchronized void watch(){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("观众观看:"+ show);
        //通知演员表演
        this.notifyAll();
        this.flag = !this.flag;
    }
}

八、线程池

  • JDK5.0起 线程池相关API : ExecutorService 和 Executors
public class TestPoll {
    public static void main(String[] args) {
        //1.创建服务,创建线程池
        //newFixedThreadPool(线程池大小)
        ExecutorService service = Executors.newFixedThreadPool(10);
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        service.execute(new MyThread());
        //2.关闭连接
        service.shutdown();
    }
}

class MyThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容

  • 目录 基本概念:程序、进程、线程线程的创建与使用线程的生命周期线程的同步线程的通信 一 程序、进程、线程 程序(p...
    Lifg阅读 188评论 0 0
  • 继承Thread父类 线程代码执行顺序和调用顺序无关,例如: 上述代码执行理论上“MyThread”和“mainT...
    onlyHalfSoul阅读 379评论 1 1
  • Java多线程入门不完全指南 序言 最近在读《把时间当作朋友》,序言就教导我“无论是谁,都是在某一刻意识到时间的珍...
    leo5592368阅读 3,541评论 2 5
  • Java多线程入门 1. 并发与并行 并发 : 指两个或者多个事件在同一时间段运行 并行 : 指两个或者多个事件在...
    王二麻子88阅读 161评论 0 0
  • 对多线程的理解 进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位; 多线程是随着多核CPU...
    GeekAmI阅读 309评论 0 1