多线程(五)————线程同步和线程间通信

ArrayList 是线程不安全的

  • ArrayList是线程不安全的,而juc下CopyOnWriteArrayList是线程安全的。
//测试JUC安全类型的集合
public class Test1  {
     
    public static void main(String[] args) throws InterruptedException {
     
        //多个线程同时往CopyOnWriteArrayList加入元素
        CopyOnWriteArrayList<String> list1=new CopyOnWriteArrayList<String>();
        for (int i = 0; i < 10000; i++) {
     
            new Thread(()->{
     list1.add(Thread.currentThread().getName());}).start();
        }
        //多个线程同时往ArrayList加入元素
        ArrayList<String> list2 =new ArrayList<String>();
        for (int i = 0; i < 10000; i++) {
     
            new Thread(()->{
     list2.add(Thread.currentThread().getName());}).start();
        }
        //主线程等待所有线程跑完再输出
        Thread.sleep(10000);
        System.out.println("list1 has "+list1.size()+" elements");
        System.out.println("list2 has "+list2.size()+" elements");
    }
}

多线程(五)————线程同步和线程间通信_第1张图片

Lock锁

  • 从JDK5.0开始,java提供了更强大的线程同步机制——通过显示定义同步锁对象来实现同步。同步锁使用Lock充当
  • java.util.concurrent.locks.Lock接口是龙之多个线程对共享资源访问的工具。锁提供了副共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得Lock对象
  • ReentrantLock类实现了Lock,它拥有和synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以加显示锁、施放锁。
//测试Lock
public class Test2 {
     
    public static void main(String[] args) {
     
        Lock lock=new Lock();
        new Thread(lock).start();
        new Thread(lock).start();
        new Thread(lock).start();
    }
}
class Lock implements Runnable{
     
    int num=10;
    //定义Lock
    private final ReentrantLock lock=new ReentrantLock();
    @Override
    public void run() {
     
        while (true){
     
            try {
     
                //加锁
                lock.lock();
                if(num>0){
     
                    try {
     
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
     
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"--->"+num--);
                }else{
     
                    break;
                }
            }finally {
     
                //解锁
                lock.unlock();
            }
        }
    }
}

  • Lock 和 synchronized关键字的一些区别:
    • Lock是显示锁,手动开启和关闭,而synchronized关键字是隐式锁,出了作用域自动施放。
    • Lock只有代码块锁,synchronized有代码块锁和方法锁。
    • 使用Lock锁,JVM花费较少的时间来调度线程,性能更好。而且具有更好的扩展性
    • 最好是优先使用Lock锁,少用synchronized的方法锁
    • 注意:一般Lock锁写在try-finally中,在finally中施放锁。

** 线程协作 **

生产者和消费者问题

  • 对于生产者,没有生产产品之前,要通知消费者等待,而生产了产品之后,要马上通知消费者消费

  • 对于消费者,在消费之后,要通知生产者已经结束消费,需要生成新的产品以供消费

  • 在此问题中,只有synchronized是不够的

    • 可以阻止多个线程同时更新同一个资源,实现了同步。
    • 但是不能实现线程之间传递信息
  • java提供了几个方法来解决线程间通信问题

方法名 作用
wait() 线程一直等待,直到其他线程通知,与sleep不同,wait会施放锁
wait(long timeout) 指定等待的毫秒数
notify() 唤醒一个处于等待状态的线程
notifyAll() 唤醒同一个对象上所有调用wait方法的线程,优先级高的优先调度
  • 注意:都是Object类中的方法,都只能在同步方法或者同步代码块中使用,否则抛出IllegalMonitorStateException异常

解决方式1---->管程法

  • 生产者:负责生产数据的模块(可能是方法、对象、线程、进程)
  • 消费者:负责处理数据的模块(可能是方法、对象、线程、进程)
  • 缓冲区:消费者不能直接使用生产者的数据
  • 生产者把生产好的数据存进缓冲区,消费者从缓冲区中获得数据
//测试管程法
//生产者 消费者 产品 缓冲区
public class Test1 {
     
    public static void main(String[] args) {
     
        SynContainer container=new SynContainer();
        new Producer(container).start();
        new Consumer(container).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++) {
     

            container.push(new Chicken(i+1));
        }
    }
}
//消费者
class Consumer extends Thread{
     
    SynContainer container;
    public Consumer(SynContainer container){
     
        this.container=container;
    }

    @Override
    public void run() {
     
        for (int i = 0; i < 100; i++) {
     
            Chicken chicken=container.pop();

        }
    }
}
//产品
class Chicken{
     
    int index;
    public Chicken(int index){
     
        this.index=index;
    }
}
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();
            }
        }
        //丢入产品
        System.out.println("正在生产第"+chicken.index+"只鸡");
        chickens[count]=chicken;
        count++;
        //通知消费者消费
        notifyAll();

    }
    //消费方法
    public synchronized Chicken pop(){
     
        //判断能否消费
        if (count==0){
     
            //等待生产者生产,消费者等待
            try {
     
                this.wait();
            }catch (InterruptedException e){
     
                e.printStackTrace();
            }
        }
        count--;
        Chicken chicken=chickens[count];
        System.out.println("正在消费第"+chicken.index+"只鸡");
        chickens[count]=null;
        notifyAll();
        return chicken;

    }
}

解决方式2---->红绿灯法

  • 使用添加标志位的方法来进行通信
//信号灯法:通过标志位解决
public class Test2 {
     
    public static void main(String[] args) {
     
        TV tv=new TV();
        new Watcher(tv).start();
        new Player(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++) {
     
            this.tv.play("节目"+i);
        }
    }
}
//消费者
class Watcher extends Thread{
     
    TV tv;
    public Watcher(TV tv){
     
        this.tv=tv;
    }
    @Override
    public void run() {
     
        for (int i = 0; i < 20; i++) {
     
            this.tv.watch();
        }
    }
}
//产品
class TV{
     
    String voice;
    boolean flag=true;
    //生产
    synchronized public void play(String voice){
     
        if (!flag){
     
            try{
     
                this.wait();
            }catch (InterruptedException e){
     
                e.printStackTrace();
            }
        }
        System.out.println("正在表演:"+voice);
        this.voice=voice;
        this.flag=!this.flag;
        notifyAll();
    }
    //消费
    synchronized void watch(){
     
        if (flag){
     
            try {
     
                this.wait();
            }catch (InterruptedException e){
     
                e.printStackTrace();
            }
            System.out.println("正在观看:"+voice);
            //通知表演
            voice=null;
            this.flag=!this.flag;
            notifyAll();
        }
    }
}

线程池

  • 经常创建和销毁、使用量特别大的资源,如并发状况下的线程,对性能影响很大
  • 提前创建好多个线程,放入线程池中,使用时直接获取,使用完毕再放回线程池中。可以避免频繁创建销魂、实现重复利用。
  • 好处:
    • 提高响应速度(减少创建线程损耗)
    • 降低资源消耗(线程重复利用,不需要每次都创建新线程)
    • 便于管理
      • corePoolSize:线程池大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时保持最多多长时间会终止
public class Test3 {
     
    public static void main(String[] args) {
     
        //创建线程池
        //参数为线程池大小
        ExecutorService service= Executors.newFixedThreadPool(4);
        //执行
        service.execute(new MyThread());
        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());
        }
    }
}

你可能感兴趣的:(追随狂神学Java,java,多线程)