多线程生产消费问题,信号灯法,管程法,线程的锁以及唤醒机制

线程和进程简介

线程: 一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。

进程:进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程可以运行多个线程,但一个进程中至少有一个线程,不然没有存在的意义,多个线程可共享数据。线程是CPU调度和执行的最小单元。

多线程的好处:

可以提高 CPU 的利用率。在多线程程序中,一个线程必须等待的时候,CPU 可以运行其它的线程而不是等待,这样就大大提高了程序的效率。也就是说允许单个程序创建多个并行执行的线程来完成各自的任务

缺点:

- 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多;
- 多线程需要协调和管理,所以需要 CPU 时间跟踪线程;
- 线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题

调用run()法和调用start()方法是不一样的


start() 方法用于启动线程,run() 方法用于执行线程的运行时代码。run() 可以重复调用,而 start() 只能调用一次。

start()方法来启动一个线程,真正实现了多线程运行。调用start()方法无需等待run方法体代码执行完毕,可以直接继续执行其他的代码; 此时线程是处于就绪状态,并没有运行。 然后通过此Thread类调用方法run()来完成其运行状态, run()方法运行结束, 此线程终止。然后CPU再调度其它线程。

run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。
 

2.线程的四种创建方式

1.继承Thread类

2.实现Runnable接口

3.Callaable接口

4.线程池创建线程

1.继承Thread类

  1. 继承Thread类
  2. 重写run()方法
  3. 创建线程对象,调用start()方法启动

不建议使用:避免oop单继承局限性

 2.实现Runnable接口

  1. 实现Runnable接口
  2. 重写run()方法
  3. 创建线程对象,通过线程对象开启线程
  4. 调用stert()方法

推荐使用: 避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

 3.实现Callable 接口

  1. 实现callable接口
  2. 重写call()方法,有返回值
  3. 创建执行服务,通过服务提交线程
  4. 获取线程返回值
  5. 关闭返回值

静态代理

你:真是角色

婚庆公司:代理你,处理结婚的事

结婚:实现都实现结婚接口即可

真是对象和代理对象都要实现同一个接口

代理对象要代理真实角色

好处就是代理对象可以做很多真实对象做不了的事

真实对象专注做自己的事


//总结
//真实对象和代理对象都要实现同一个接口
//代理对象要代理真实角色


//好处:好处就是代理对象可以做很多真实对象做不了的事
//真实对象专注做自己的事
public class StaticProxy {
    public static void main(String[] args) {
        You you = new You(); //你 结婚

        new Thread(() -> System.out.println("爱你")).start();
        new WeddingCompany(new You()).HappyMarry();

//        WeddingCompany windowConstants = new WeddingCompany( you);
        windowConstants.HappyMarry();

    }

}

interface  Marry{
    void HappyMarry();
}

//真实角色
class You implements Marry {

    public void HappyMarry() {
        System.out.println("结婚了!!!");
    }
}

//代理角色 ,帮助你结婚
class WeddingCompany implements Marry {
//    代理谁--> 真实目标角色
    private Marry target;

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

    public void HappyMarry() {
        befotr();
        this.target.HappyMarry();//真实对象调用结婚
        after();
    }

    private void befotr() {
        System.out.println("婚期前");
    }

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

线程的状态

new Thread() -->创建状态 调用start()--> 就绪状态  等待cpu调度--> 运行状态 --> 死亡状态

new Thread() -->创建状态 调用start()--> 就绪状态(等待cpu调度,调用sleep或者wait或者同步锁)---> 阻塞状态(阻塞解除后重新进入就绪状态,等待cpu调度)----> 就绪状态-->  运行状态(进入运行状态,线程才真正执行线程体的代码块)  --> 死亡状态(线程中断或结束,一旦进入死亡状态,就不能再次启动)

线程的方法 

停止线程 

推荐线程自己停止

建议使用一个标志位进行终止变量,放flag=false,线程终止

package state;

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

    //    设置公开的方法停止线程,转换位标识
    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("线程停止");
            }
        }
    }
}

 线程休眠

  1. sleep(1000)指定当前线程阻塞时间1000毫秒
  2. sleep存在异常 InterruptedException
  3. sleep时间到达后,线程进入就绪状态
  4. sleep可以模拟网络延时
  5. 每个对象都有锁,sleep不会释放锁
package state;

//过去当前系统时间
import java.text.SimpleDateFormat;
import java.util.Date;


public class TestSleep  {

    public static void main(String[] args) {
//        打印当前时间
        Date startTime = new Date(System.currentTimeMillis()); // 获取当前系统时间
        while (true) {
            try {
                Thread.sleep(1000);
                System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
                Date date = new Date(System.currentTimeMillis());//更新当前时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }}

//模拟倒计时
public class TestSleep  {
    public static void main(String[] args) {
        try {
            tenDown();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void tenDown() throws InterruptedException {
        int num = 10;
        while (true) {
            Thread.sleep(1000);
            System.out.println(num--);
            if (num <= 0) {
                break;
            }
        }
    }
}

线程礼让

  1. 让当前正在执行的线程暂停,但不阻塞
  2. 线程从运行状态转为就绪状态
  3. 让CPU重新调度,不一定能成功
package state;

//礼让测试
public class TestYield {
    public static void main(String[] args) {
        Myyield myyield = new Myyield();
        new Thread(myyield, "a").start();
        new Thread(myyield, "b").start();

    }
}

class Myyield implements Runnable {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "开始执行");
        Thread.yield(); //礼让
        System.out.println(Thread.currentThread().getName()+"线程停止");
    }
}

 Join合并线程

Join合并线程,等待此线程执行完后,在执行其他线程,其他线程阻塞,(想想成为插队)

package state;

//测试Join
public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.err.println("vip插-----队了" + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {
//        启动线程
        TestJoin testStop = new TestJoin();
        Thread thread = new Thread(testStop);


//        主线程
        for (int i = 0; i < 1000; i++) {
            if (i == 200) {
                thread.start();
                thread.join();//插队了
            }
            System.out.println("main" + i);
        }
    }
}

线程状态观测

Thread.State

package state;

//观测线程状态
public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("******");
        });

//        观察状态
        Thread.State state = thread.getState();
        System.out.println(state); // NEW
//        观察启动后
        thread.start();//启动线程
        state = thread.getState();
        System.out.println(state); //Run

        while (state != Thread.State.TERMINATED) { //只要线程不终止,就一直输出状态
            Thread.sleep(100);
            state = thread.getState(); //更新线程状态
            System.out.println(state); //输出状态
        }
//        thread.start(); //线程只能启动一次,线程死亡后不能再次启动
    }
}

 线程优先级

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

优先级用数字表示,从0-10

  1. MAX_PRIORITY为10,为线程最高优先级;
  2. MIN_PRIORITY取值为1,为线程最低优先级;
  3. NORM_PRIORITY 取值为5,为线程中间位置的优先级

 使用getPriority(),setPriority(int x),获取,改变线程优先级

线程优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调,都是看CPU的调度,有可能CPU会调度优先级比较低的(性能倒置)

package state;

//测试线程优先级
public class TestPriority extends Thread{
    public static void main(String[] args) {
//        主线程优先级默认 5
        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);

//        设置优先级在启动
        t1.start();

        t2.setPriority(1);
        t2.start();

        t3.setPriority(4);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY); //MAX_PRIORITY == 10
        t4.start();

        t5.setPriority(11);
        t5.start();
    }
}

class MyPriority implements Runnable {

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

    }
}

守护(daemon)线程

  1. 线程分为用户线程守护线程
  2. 虚拟机必须确保用户线程(main())执行完毕
  3. 虚拟机不用等待守护线程(gc())执行完毕
  4. 后台记录操作日志,监控内存,垃圾回收等
package state;

import java.util.concurrent.Callable;

//测试守护线程,上帝守护你
public class TestDeamo {
    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();//你启动 y用户线程启动
    }
}

//上帝
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 < 365000; i++) {
            System.err.println("你一辈子都开心的活着");
        }
        System.err.println("goodby world");
    }
}

线程同步

多个线程操作同一个资源

并发:同一个对象被多个线程同时操作(买票)

线程同步:

显示情况中,遇到:"用一个资源,多个人想要使用"的我问题,最天然的解决方案就是排队(队列)

处理多线程问题时,多个线程访问同一个对象(并发),并且某些线程想要修改这个对象,这个时候就需要线程同步,线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用

线程同步需要(队列+锁)

线程同步:

由于同一进程的多个线程共享一块存储空间,在带来方便的同时,也带来了访问冲突的问题,为了保证数据在方法中被访问时的正确性,在访问时加入锁机制synchronization ,当一个线程获得对象的排他锁,独占资源,其他线程必须等待,使用后释放锁即可,但存在以下问题

一个线程持有锁会导致其他所有需要此锁的对象挂起

在多线程竞争下,加锁,释放锁会导致比较多的上下文切换 和 调度延时,引起性能问题

如果一个优先级高的线程等待一个优先级低的线程释放锁 ,会导致优先级倒置,引起性能问题

三大线程不安全案例

1.买票:会出现超卖,还有可能出现负票 

package syn;

//不安全的买票
public class UnsafeByTicket {
    public static void main(String[] args) {
        ByTicket station = new ByTicket();
        new Thread(station,"我").start();
        new Thread(station,"你").start();
        new Thread(station,"黄牛").start();

    }
}

class ByTicket implements Runnable {

    //票
    private int ticketNum = 10;
    boolean flag = true; //外部停止

    @Override
    public void run() {
//        买票
        while (flag) {
            buy();
        }
    }

    private void buy() {
//判断是否有票
        if (ticketNum <= 0) {
            flag = false;
            return;
        }

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

//        买票
        System.out.println(Thread.currentThread().getName() + "拿到" + ticketNum--);
    }
}

//运行结果
//        我拿到10
//        黄牛拿到9
//        你拿到8
//        我拿到7
//        黄牛拿到7
//        你拿到6
//        你拿到4
//        黄牛拿到5
//        我拿到5
//        我拿到3
//        黄牛拿到3
//        你拿到3
//        黄牛拿到2
//        我拿到2
//        你拿到2
//        你拿到1
//        我拿到-1
//        黄牛拿到0

案列二 :账户取钱,取到的钱大于存款

package syn;

//不安全的取钱
public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account(100, "基金" );
        Drawing you = new Drawing(account, 50, "你");
        Drawing griFriend = new Drawing(account, 100, "griFriend");
        you.start();
        griFriend.start();

    }
}

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

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

//银行:模拟取钱
class Drawing extends Thread {
    Account account; //账户
    //取了多钱
    int drawingMoney;
    //s手里的余额
    int nowMoney;

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }
    //取钱
    @Override
    public void run() {
//        判断有没有钱
        if (account.money - drawingMoney <= 0) {
            System.out.println(Thread.currentThread().getName() + "取,钱不够");
        }
//sleep 放大问题的发生性
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //卡内余额== 余额- 取得钱
        account.money = account.money - drawingMoney;
        //手里的钱
        nowMoney = nowMoney + drawingMoney;
        System.out.println(account.name + "余额为: " + account.money);
//        Thread.currentThread().getName() = this.getName()
        System.out.println(this.getName() + "手里的钱" + nowMoney);
    }
}

//        griFriend取,钱不够
//        基金余额为: 0
//        基金余额为: -50
//        griFriend手里的钱100
//        你手里的钱50

案例三 :不安全的集合

package syn;

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

//线程不安全的集合
public class UnsafeList {
    public static void main(String[] args) {
        List<String> list = new ArrayList<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());
    }
}
//理论上有10000条数据,实际输出为9998

同步方法

通过private关键字保证数据对象只能被方法访问,所以只需要针对方法提出一套机制,也就是synchronized关键字,有两种用法,synchronized方法 synchronized块 

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

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

缺陷:synchronize会影响效率 ,锁的太多,浪费资源

同步块:synchronized(Obj){}

Obj称为同步监视器

  1. Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
  2. 同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class

同步监视器执行过程

  1. 第一个线程访问,锁定同步监视器,执行其中代码
  2. 第二个线程访问,发现同步监视器被锁定,无法访问
  3. 第一个程序访问完毕,解锁同步监视器
  4. 第二个线程访问,发现同步监视器没有锁,然后锁定访问

测试买票

package syn;

//不安全的买票
public class UnsafeByTicket {
    public static void main(String[] args) {
        ByTicket station = new ByTicket();
        new Thread(station,"我").start();
        new Thread(station,"你").start();
        new Thread(station,"黄牛").start();

    }
}

class ByTicket implements Runnable {

    //票
    private int ticketNum = 10;
    boolean flag = true; //外部停止

    @Override
    public void run() {
//        买票
        while (flag) {
            try {
                buy();
                //        模拟延时
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

//    加上synchronized 变成同步方法,锁的是this
    private synchronized void buy() throws InterruptedException {
//判断是否有票
        if (ticketNum <= 0) {
            flag = false;
            return;
        }

//        买票
        System.out.println(Thread.currentThread().getName() + "拿到" + ticketNum--);
    }
}

//运行结果
//        我拿到10
//        黄牛拿到9
//        你拿到8
//        我拿到7
//        黄牛拿到6
//        你拿到5
//        黄牛拿到4
//        你拿到3
//        我拿到2
//        你拿到1

 银行案例

package com.hx.vipmodel.controller;

//不安全的取钱
public class UnsafeBank {
    public static void main(String[] args) {
        //账户
        Account account = new Account(100, "基金" );
        Drawing you = new Drawing(account, 50, "你");
        Drawing griFriend = new Drawing(account, 100, "griFriend");
        you.start();
        griFriend.start();

    }
}

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

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

//银行:模拟取钱
class Drawing extends Thread {
    Account account; //账户
    //取了多钱
    int drawingMoney;
    //s手里的余额
    int nowMoney;

    public Drawing(Account account, int drawingMoney, String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }
    //取钱
//    sunchronized 默认锁的是this
    @Override
    public void run() {

//        锁的对象就是变化的量,需要操作的对象(锁银行,锁不住)
        synchronized (account) {
//        判断有没有钱
            if (account.money - drawingMoney <= 0) {
                System.out.println(Thread.currentThread().getName() + "取,钱不够");
                return;
            }
//sleep 放大问题的发生性
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //卡内余额== 余额- 取得钱
            account.money = account.money - drawingMoney;
            //手里的钱
            nowMoney = nowMoney + drawingMoney;
            System.out.println(account.name + "余额为: " + account.money);
//        Thread.currentThread().getName() = this.getName()
            System.out.println(this.getName() + "手里的钱" + nowMoney);
        }

    }
}

//    输出
//    基金余额为: 50
//    你手里的钱50
//    griFriend取,钱不够

JUC--CopyOnWriteArrayList

package com.hx.vipmodel.controller;

import java.util.concurrent.CopyOnWriteArrayList;

//测试JUC安全类型集合
public class TestJUC {
    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(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(list.size());
    }
}

//期望size为1000 实际输出10000

死锁

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

死锁解决方案

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

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

 破坏上述4个条件任意一个或多个条件就会避免死锁的发生

package com.hx.vipmodel.controller;

//死锁:多个线程会互相持有对方需要的资源,行程僵持
public class DeadLok {
    public static void main(String[] args) {
        Makeup m1 = new Makeup(0, "灰姑娘");
        Makeup m2 = new Makeup(1, "白雪");

        m1.start();
        m2.start();

    }
}

//口红
class Lipstick{
}

//镜子
class Mirror {
}

class Makeup extends Thread {

//    需要的资源只有一份,用static保证只有一份
    static Lipstick lipstick = new Lipstick();
    static Mirror mirror = new Mirror();

    int choice; //选择
    String girName; //使用化妆品的人

    Makeup(int choice, String girName) {
        this.choice = choice;
        this.girName = girName;
    }

    @Override
    public void run() {
//        化妆
        try {
            makeip();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //    化妆,互相持有对方的资源,但是需要拿到对方的资源
    private void makeip() throws InterruptedException {
        if (choice == 0) {
            synchronized (lipstick) {//获得口红的锁
                System.out.println(this.girName + "得到口红的锁");
                Thread.sleep(1000);
                synchronized (mirror) { //一秒后想获得镜子
                    System.out.println(this.girName + "获得镜子的锁");
                }
            }
        } else {
            synchronized (mirror) {//获得镜子的锁
                System.out.println(this.girName + "获得镜子的锁");
                Thread.sleep(2000);
                synchronized (lipstick) {//一秒后想获得口红
                    System.out.println(this.girName+"获得镜子的锁");
                }
            }
        }
    }
}
//输出
//灰姑娘得到口红的锁
//白雪获得镜子的锁
//m1先拿到口红,m2拿到镜子 ,互相抱着对方想要的资源,互相僵持,程序死锁,不会运行结束


    //    化妆,互相持有对方的资源,但是需要拿到对方的资源
//    private void makeip() throws InterruptedException {
//        if (choice == 0) {
//            synchronized (lipstick) {//获得口红的锁
//                System.out.println(this.girName + "得到口红的锁");
//                Thread.sleep(1000);
//                }
//            synchronized (mirror) { //一秒后想获得镜子
//                System.out.println(this.girName + "获得镜子的锁");
//            }
//        } else {
//            synchronized (mirror) {//获得镜子的锁
//                System.out.println(this.girName + "获得镜子的锁");
//                Thread.sleep(2000);
//                }
//            synchronized (lipstick) {//一秒后想获得口红
//                System.out.println(this.girName+"获得镜子的锁");
//
//            }
//        }
//    }
//m1先拿到口红,m2拿到镜子,修改后不让对方抱着对方想要的资源,将synchronized 提出来后死锁问题就解决

//输出
//    白雪获得镜子的锁
//    灰姑娘得到口红的锁
//    灰姑娘获得镜子的锁
//    白雪获得镜子的锁

Lock锁

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

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

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

synchronize与lock的对比

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

Lock只有代码块锁,synchronized有代码块锁和时间锁

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

使用的优先顺序

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

测试lock锁

package com.hx.vipmodel.controller;

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(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNum--);
                } else {
                    break;
                }
            }finally {
//              解锁
                lock.unlock();
            }
        }
    }
}
//输出
//10
//9
//8
//7
//6
//5
//4
//3
//2
//1

线程协作 :

应用场景:产者,消费者

  • 假设仓库中只能存放一件上品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费,
  • 如果仓库中没有商品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止
  • 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入商品为止

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

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

java提供了几个方法解决线程之间的通信问题

 wait() ,表示线程一直等待,直到其他线程通知,与sleep不同,wait()会释放锁

wait(long timeout) 指定等待的毫秒数

notify()唤醒一个处于等待状态的线程

notifyAll()唤醒同一个对象上所有调用wait()方法的线程,优先级高的线程优先调度

注意:只要是Object类的方法,都只能在同步方法或同步代码块中,否则会抛出异常

 解决方式1:管程法

并发协作模式,"生产者/消费者模式" -->管程法

  • 生产者:负责生产数据的模块(可能是方法,对象,线程,进程)
  • 消费者:负责处理数据的模块(可能是方法.对象.线程.进程)
  • 缓冲区:消费者不能直接使用生产者的数据,他们之间有个缓冲区

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

package syn;
//测试:产者消费者模型 -->利用缓冲区解决:管程法

import java.nio.channels.Channel;

//生产者 ,消费者 ,产品 ,缓冲区
public class TestTcp {
    public static void main(String[] args) {
        SynContainer synContainer = new SynContainer();
        new Produtor(synContainer).start();
        new Consumer(synContainer).start();
    }
}

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

//消费者
class Consumer extends Thread {
    SynContainer container;
    public Consumer(SynContainer container) {
        this.container = container;
    }

//    x消费者
    @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();
            }
        }
//        如果没有满 ,需要丢入产品
        chickens[count] = chicken;
        count++;
        //可以通知消费者消费了
        this.notifyAll();
    }

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

 解决方式2:

并发协作模型:"生产者/消费者" -->信号灯管法

判断一个标志位,如果为true,就让其等待,如果等于false,就让其通知另外一个人,像红绿灯一样,"红灯停,绿灯行"来判断线程什么时候监听什么时候唤醒

package syn;

//测试生产者消费者问题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 {
    //演员表演,观众等待
    // 观众观看,演员等待
    String voice; //表演的节目
    boolean flag = true; //t演员表演,f演员等待
    //表演
    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;
    }
}

线程池:

背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。


好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建

便于线程管理

  • corePoolSize:核心池的大小
  • maximumPoolSize:最大线程数
  • keepAliveTime:线程没有任务时最多保持多长时间后会终止
     

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

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

  • void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
  • <T> Future<T> submit(Callable<T> task):执行任务,有返回值,一般又来执行Callable
  • void shutdown()︰关闭连接池

Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池

package syn;

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

//测试线程池
public class TestPool {
    public static void main(String[] args) {
//        1.创建服务,创建线程池
//        2.设置线程池大小newFixedThreadPool
        ExecutorService service = Executors.newFixedThreadPool(10);
//        执行
        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() {
//        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() );
//        }
    }
}

实现多线程的方式:

package syn;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

//回顾所有的线程池的创建
public class AllThread {
    public static void main(String[] args) {
        new MyThread1().start();
        new Thread(new MyThread2()).start();
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new MyThread3());
        new Thread(futureTask).start();
        try {
            Integer integer = futureTask.get();
            System.err.println(integer);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

//1.继承Thread
class MyThread1 extends Thread {
    @Override
    public void run() {
        System.out.println("extends Thread ");
    }
}

//实现Runnable接口
class MyThread2 implements Runnable {
    @Override
    public void run() {
        System.out.println("implements Runnable");
    }
}

//实现Callable接口
class MyThread3 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("implements Callable");
        return 100;
    }
}



 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值