【狂神说java多线程02】2019年5月:线程同步,买票,取款,list不安全例子。死锁,线程协作,生产者消费者,线程池,callable,FutureTask

1. 线程同步

多个线程操作同一个资源,

同一个对象被多个线程 同时操作,

  • 同一个资源,多人都想使用

线程同步:是一种等待机制,

  • 多个需要同时访问此对象的线程,

  • 进入这个 对象的等待池,形成队列,

  • 等待前面线程使用完毕,

  • 下一个线程再使用。

线程同步:形成条件,队列+锁(对象本身)

  • 在访问时 加入锁机制 synchronized
  • 当一个线程 获得对象的排它锁,独占资源,
  • 其他线程 必须等待,使用后 释放锁即可。

排它锁问题:

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

买票逻辑

public class BuyTicket implements Runnable {
    //票
    private int nums = 10;

    @Override
    public void run() {

        while (nums > 0) {

            System.out.println(Thread.currentThread().getName() + "买到了票" + nums--);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }



    public static void main(String[] args) {
        BuyTicket b = new BuyTicket();

        new Thread(b, "线程1").start();
        new Thread(b, "线程2").start();

    }
}
线程2买到了票10
线程1买到了票9
线程1买到了票7
线程2买到了票8
线程2买到了票6
线程1买到了票6
线程2买到了票5
线程1买到了票5
线程2买到了票4
线程1买到了票3
线程1买到了票1
线程2买到了票2
  • 每个线程 在自己的 工作内存交互,内存控制不当 会造成 数据不一致。

  • 最后一个票,每个人都 会把 1 拿到 自己的工作内存,

    • 第一个人买了 变成0,第二个变成了 -1

银行取款

public class UnsafeBank {
    public static void main(String[] args) {
        Account account = new Account(100, "结婚基金");

        Drawing you = new Drawing(account, 50, "你");
        Drawing girlFriend = new Drawing(account, 100, "girlFriend");

        you.start();
        girlFriend.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;// 取了多少钱
    int nowMoney;// 现在你手里 有多少钱

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

    @Override
    public void run() {
        //如果卡里的钱 - 取了多少钱 <0
        if (account.money - drawingMoney < 0) {
            System.out.println(Thread.currentThread().getName() + "钱不够,取不了");
            return;
        }

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

        //卡内余额 = 卡内余额 - 取了多少钱。同时操作这个,只有一个赋值成功的。
        account.money = account.money - drawingMoney;
        //你手里的钱
        nowMoney = nowMoney + drawingMoney;
        //打印余额
        System.out.println(this.getName() + "取得" + account.name + "余额为:" + account.money);
        //打印手里的钱
        //Thread.currentThread().getName() 就是  this.getName()
        System.out.println(this.getName() + "手里的钱为:" + nowMoney);
    }

}
girlFriend取得结婚基金余额为:-50 
//打印时 各自都 减去过,减去那一步 没有乱。所以为 -50
girlFriend手里的钱为:100
你取得结婚基金余额为:-50
你手里的钱为:50
你取得结婚基金余额为:50
你手里的钱为:50
girlFriend取得结婚基金余额为:50 //减去后变为了0,但打印时:被 你线程 100-50覆盖了ss
girlFriend手里的钱为:100
你取得结婚基金余额为:50
你手里的钱为:50

girlFriend取得结婚基金余额为:-50 //余额为50,又取了100
girlFriend手里的钱为:100

List线程不安全

        List<String> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                list.add(Thread.currentThread().getName());
            }).start();
        }
        try {
            //必须要睡眠一下,等 开的线程执行完毕
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(list.size());
//997
//如果为 1万。真实为:9996
// 添加到了 同一个位置

2. 线程同步

  • Synchronize d,方法 和 块
    • 控制对象的访问,每个对象 对应一把锁,
    • 必须获得 调用该方法的 对象锁 才能执行,否则阻塞。
      • 一旦执行,就独占该锁,直到 方法返回,才释放锁。
    • 将一个方法 申明为 Synchronized将会影响效率
      • 需要 修改的内容 才需要锁

买票问题

public class BuyTicket implements Runnable {
    //票
    private int nums = 5;
    //停止标志
    private boolean flag = true;

    @Override
    public void run() {
        //如果为true,一直循环
        while (flag) {
            System.out.println("线程进入:" + Thread.currentThread().getName());
            buy();
        }
    }

    //加锁
    private synchronized void buy() {
        if (nums <= 0) {
            System.out.println(Thread.currentThread().getName() + "没票了" + nums);
            flag = false;
            return;
        }

        Thread.sleep(1000);

        System.out.println(Thread.currentThread().getName() + "买到了票" + nums--);
    }


    public static void main(String[] args) {
        BuyTicket b = new BuyTicket();

        new Thread(b, "线程1").start();
        new Thread(b, "线程2").start();

    }
}
             System.out.print(" "); //打印一个空格,就能抢占了,否则 CPU太怪,线程1 一下买光了
 线程1买到了票5
 线程1买到了票4
 线程2买到了票3
 线程2买到了票2
 线程2买到了票1
 线程2没票了0
 线程1没票了0

银行取款

synchronized (obj){
}
  • obj 称为 同步监视器

  • 推荐使用 共享资源

  • 同步方法,的 同步监视器 就是this,或者是 class

  • 执行过程

    • 锁定 obj,执行其中的代码
    • 第二个线程访问,发现 同步监视器被锁定,无法访问
    • 第一个线程 访问完毕,解锁 同步监视器
    • 第二个线程访问,发现 已经没有锁了,然后锁定并访问。
run里的所有代码,都放入 代码块

synchronized (Account.class) {
}
synchronized (account) { //都一样,锁共享对象,变化的量,才行
}

锁 list

                synchronized (list){
                    list.add(Thread.currentThread().getName());
                }

CopyOnWriteArrayList

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Callable;
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 10000; i++) {
            Thread t = new Thread(() -> {
                synchronized (list) {
                    list.add(Thread.currentThread().getName());
                }
            });
            t.start();
            try {
                t.join(); //主线程等待,t线程强制执行完毕
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

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

源码

final transient ReentrantLock lock = new ReentrantLock();  //可重入锁
private transient volatile Object[] array;

transient是短暂的意思。对于transient 修饰的成员变量,在类的实例对象的序列化处理过程中会被忽略。
    
volatile 可见性,就是改了值,立刻就变成最新的值。
可见性:指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
不保证原子性 
transient
英
/ˈtrænziənt/
美
/ˈtrænʃ(ə)nt/
全球发音
简明 牛津 新牛津 韦氏 柯林斯 例句 百科
adj.
转瞬即逝的,短暂的;暂住的,(工作)临时的
n.
暂住者,流动人口;(电流、电压、频率的)瞬变

volatile
英
/ˈvɒlətaɪl/
美
/ˈvɑːlət(ə)l/
全球发音
adj.
易变的,动荡不定的,反复无常的;(情绪)易变的,易怒的,突然发作的;(液体或固体)易挥发的,易气化的;(计算机内存)易失的

3. 死锁

互相等待 其他线程占有的资源 才能运行,

  • 同步块 同时拥有 两个以上对象的锁,可能发生

代码

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) {
        super(girlName);
        this.choice = choice;
        this.girlName = girlName;
    }

    @Override
    public void run() {
        makeUp();
    }

    private void makeUp() {
        if (choice == 0) {
            synchronized (lipstick) {
                System.out.println(this.getName() + " 获得口红的锁");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //放在里面,就死锁,放外面就是解决死锁。 相当于 这个 ==0 分支,放开锁,然后进入等待。
            synchronized (mirror) {
                System.out.println(this.getName() + " 获得镜子的锁");
            }
        } else {
            synchronized (mirror) {
                System.out.println(this.getName() + " 获得镜子的锁");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lipstick) {
                    System.out.println(this.getName() + " 获得口红的锁");
                }
            }

        }

    }
}
英
/ˈlɪpstɪk/
美
/ˈlɪpstɪk/
全球发音
简明 牛津 新牛津 韦氏 柯林斯 例句 百科
n.
口红,唇膏

mirror
英
/ˈmɪrə(r)/
美
/ˈmɪrər/
全球发音
简明 牛津 新牛津 韦氏 柯林斯 例句 百科
n.
镜子;写照,真实反映;(计算机)镜像网点;榜样
灰姑娘 获得口红的锁
白雪公主 获得镜子的锁 。尝试获得 口红,灰姑娘口红的锁 睡1秒就放开了。

白雪公主 获得口红的锁 。
灰姑娘 获得镜子的锁 。白雪公主,执行完毕,释放镜子的锁。灰姑娘拿到。

必要条件

  • 互斥
    • 一个资源,只能被 一个线程使用
  • 请求与保持
    • 请求资源而阻塞,对 已获得的资源 保持不放
  • 不剥夺
    • 已获得的资源,在未使用完之前,不能强行剥夺
  • 循环等待
    • 进程之间 形成一种 头尾相接 的循环等待 资源关系

4. Lock锁

  • jdk 1.5。线程同步机制。
    • 通过 显式 定义 同步锁 对象来实现同步。
    • 同步锁 使用 Lock对象 充当。
import java.util.concurrent.locks.Lock;
public interface Lock {
}
  • 接口是 控制多个线程 对 共享资源 进行访问的 工具。

    • 提供了 对共享资源的 独立访问,
    • 每次只能 有一个线程 对 lock 对象加锁,
    • 线程开始 访问 共享资源 之前 应 先获得 lock对象
  • ReentrantLock,还有 ReentrantReadWriteLock
    可重入锁
    
    Reentrant
    英
    /riːˈentrənt/
    美
    /rɪˈentrənt/
    全球发音
    简明 韦氏 例句 百科
    adj.
    再进去的;凹角的
    n.
    凹角;再进入
    
    • 实现了 Lock,拥有 与syn chro nize d 相同的 并发性 和 内存语义
    • 可以显式 加锁,释放锁

ReentrantLock

public class TestLock implements Runnable {
    int ticketNums = 10;
    private final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        TestLock test = new TestLock();
        Thread t1 = new Thread(test);
        Thread t2 = new Thread(test);
        t1.start();
        t2.start();
    }

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

                lock.lock();
                if (ticketNums > 0) {
                    System.out.println(Thread.currentThread().getName() + "买到了票" + ticketNums--);
                } else {
                    break;
                }
            } finally {
                // 一般写到 finally 里
                lock.unlock();
            }
        }
    }
}
Thread-0买到了票10
Thread-1买到了票9
Thread-0买到了票8
Thread-1买到了票7
Thread-1买到了票6
Thread-0买到了票5
Thread-0买到了票4
Thread-1买到了票3
Thread-1买到了票2
Thread-0买到了票1
  • Lock 是显式锁,手动开启 和 关闭

    • 只有 代码块锁
    • 性能更好,jvm将 花费较少的时间来 调度线程
    • 扩展性好,更多的子类
  • synchronize d,是 隐式锁,出了作用域 自动释放

    • 有代码块 和 方法锁
  • Lock > 同步代码块 > 同步方法

5. 线程协作

生产者 消费者

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

  • 有一个设计模式叫观察者,就是 注册到一个list里,改变后 通知所有list里的对象。

  • 仓库只能存放一件产品,

    • 生产者 生产出来的产品 放入仓库,
    • 消费者 将仓库中 产品 取走消费
  • 如果仓库没有产品,生产者 将产品放入仓库,否则等待

    • 直到 产品被消费者 取走
  • 如果 有产品,消费者 则可以 取走消费,否则等待

    • 直到 仓库中 再次放入产品
  • producer

    • 数据缓冲区
      • consumer
  • 生产者 和 消费者 共享同一个资源,并且 相互依赖。

  • 生产者:

    • 没有生产产品 之前,通知消费者等待。
    • 生产了产品,通知 消费者 消费
  • 消费者:

    • 消费之后,通知 生产者,已经结束消费,需要 新产品
  • synchronize d 实现了 同步,

    • 不能用来 实现 不同线程之间的消息传递(通信)

线程通信

  • 所有对象都有一个锁

  • Object 类的方法,

    • 只能在 同步方法 或 同步代码块中使用
      • 否则抛出 ILLegal Monitor State Exception
ILLegal
英
/ɪˈliːɡ(ə)l/
美
/ɪˈliːɡ(ə)l/
全球发音
简明 牛津 新牛津 韦氏 柯林斯 例句 百科
adj.
非法的,违法的;违规的
n.
非法移民,非法劳工

Monitor
英
/ˈmɒnɪtə(r)/
美
/ˈmɑːnɪtər/
全球发音
简明 牛津 新牛津 韦氏 柯林斯 例句 百科
n.
显示器,监控器;监视仪,监护仪;监督员,监察员;(学校里的)班长,级长;(电台的)监听员;扬声器;巨蜥;浅水重炮舰
wait()    线程一直等待,直到 其他线程通知,会释放锁, 而sleep是抱着锁睡。
wait()    指定等待的 毫秒数
notify()  唤醒 一个处于 等待状态的线程
notifyAll()唤醒 同一个对象上 所有调用 wait()方法的线程
				优先级 高的线程,优先调度

并发协作模型:管程法

  • 生产者
  • 消费者
  • 缓冲区:消费者 不能直接使用 生产者的数据,他们之间 有个缓冲区
    • 生产者 将生产好的 数据放入 缓冲区
    • 消费者 从缓冲区 拿出数据
    • 这个池子,可以放 很多个 数据。

并发协作模型:信号灯法

  • 标志位:
    • 如果为真,就等待。
    • 如果为假,就通知另外一个人。

管程法实现

public class TestPC {
    public static void main(String[] args) {
        SynContainer container = new SynContainer();
        new Producer(container).start();
        new Consumer(container).start();
    }
}

@AllArgsConstructor
class Producer extends Thread {
    SynContainer container;

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

@AllArgsConstructor
class Consumer extends Thread {
    SynContainer container;

    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            container.pop();
        }
    }
}

@AllArgsConstructor
class Chicken {
    int id;
}

class SynContainer {
    Chicken[] chickens = new Chicken[5];
    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(); //this是SynContainer,所以 不加all更好,就是唤醒对方。
    }

    public synchronized Chicken pop() {
        //判断count ==10或0的那一句,最好不要用if,应该用while,
        //否则当有多个消费者的时候,会出现脏判断的

        if (count == 0) {
            try {
                //没有鸡,等待生产者生产。消费者 进行等待
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //可以消费
        count--;
        //吃完一只鸡,通知生产者 生产
        Chicken product = chickens[count];

        System.out.println("消费了->" + product.id + "只鸡");

        //吃完了,通知生产者 生产
        this.notifyAll();

        return product;
    }
}
生产了1只鸡
生产了2只鸡
生产了3只鸡
生产了4只鸡
生产了5只鸡
消费了->5只鸡
消费了->4只鸡
消费了->3只鸡
消费了->2只鸡
消费了->1只鸡
生产了6只鸡
消费了->6只鸡
生产了7只鸡
消费了->7只鸡
生产了8只鸡
消费了->8只鸡
生产了9只鸡
消费了->9只鸡
生产了10只鸡
消费了->10只鸡

信号灯法

public class TestPc2 {
    public static void main(String[] args) {
        TV tv = new TV();
        new Player(tv).start();

        new Watcher(tv).start();
    }
}

//演员
@AllArgsConstructor
class Player extends Thread {
    TV tv;

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {

            if (i % 2 == 0) {
                this.tv.play("快乐大本营");
            } else {
                this.tv.play("抖音记录美好生活");
            }
        }
    }
}

//观众
@AllArgsConstructor
class Watcher extends Thread {
    TV tv;

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

class TV {
    String voice;
    //演员表演,观众等待 T
    //观众观看,演员等待 F
    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;
    }
}
演员表演了:快乐大本营
观看了快乐大本营
演员表演了:抖音记录美好生活
观看了抖音记录美好生活
演员表演了:快乐大本营
观看了快乐大本营
演员表演了:抖音记录美好生活
观看了抖音记录美好生活
演员表演了:快乐大本营
观看了快乐大本营

6. 线程池

callable

//1. 实现接口,需要返回值类型
public class MyCallable implements Callable<Boolean> {

    //2. 方法 需要抛出异常。不重写带异常的 也行。
    @Override
    public Boolean call() throws Exception {

        System.out.println(Thread.currentThread().getName() + "我还在听课");

        //3. 创建 目标对象
        return false;
    }

    public static void main(String[] args) {

        //4. 创建执行服务
        ExecutorService ser = Executors.newFixedThreadPool(3);
        //5. 提交执行
        Future<Boolean> r = ser.submit(new MyCallable());

        System.out.println("随机执行的");
        try {
            //6. 获取结果
            Boolean b = r.get();

            System.out.println("必然在子线程之后执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //7. 关闭服务
        ser.shutdown();
    }
}
  • 创建 和 销毁,使用量 特别大的资源,对 性能影响很大。

  • 提前 创建好线程,放入线程池,使用时 直接获取,使用完放回。

    • 避免 频繁创建和销毁,实现 重复利用。
    • 类似:生活中的 公共交通工具。
  • 好处:

    • 提高 响应速度,减少了 创建新线程的时间,
    • 降低 资源消耗,(不需要每次都创建)
    • 便于线程管理:
      • corePoolSize:核心池的 大小
      • maxi mum PoolSize:最大线程数
      • keepAliveTime:线程没有任务时 最多保持 多长时间 后终止。

JDK1.5 线程池:Executor Service 和 Executors

  • ExecutorService:真正的线程池接口,子类:ThreadPoolExecutor

    • execute(Runnable c):执行任务,没有返回值
    • submit(Callable task ):执行任务,有返回值
    • shutdown():关闭连接池
  • Executors :工具类,线程池的工厂类,用于创建并返回 不同类型的线程池。

  • 源码

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
public class MyRunnable implements Runnable {

    public static void main(String[] args) {
        ExecutorService ser = Executors.newFixedThreadPool(3);
        ser.execute(new MyRunnable());
        ser.execute(new MyRunnable());
        
        ser.shutdown();
    }

    @Override
    public void run() {
        System.out.println("子线程执行了");
    }
}

FutureTask

  • 运行 Callable的实现类

        FutureTask<Boolean> f = new FutureTask(new MyCallable());
        new Thread(f).start();
        try {
            Boolean o = f.get();
            System.out.println(o);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值