多线程应用总结

1.常用的锁操作

1.1:synchronized

通过对象的内存地址来锁定代码块

 private int count =10;
    private Object o=new Object();
    public void m(){
        synchronized (o){  
            count--;
            System.out.println(Thread.currentThread().getName()+" count = "+count);
        }
    }

加在方法上,锁定这个方法的代码块,锁的对象为当前对象

    private int count=10;
    public synchronized void m(){
        count--;
        System.out.println(Thread.currentThread().getName()+" count = "+count);
    }

一个同步方法可以调用另一个同步方法,一个线程已经拥有了某个对象的值,再次申请的时候仍然会得到该对象的锁,也就是说synchronized获得的锁是可重入的

synchronized void m1(){
        System.out.println("m1 start");
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        m2();
    }

    synchronized void m2(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("m2");
    }

1.2:reentrantlock

lock.lock(); //相当于synchronized(this),注意reentrantlock必须手动释放锁,所以写在finally里面,并且需要把加锁的过程写在try代码块外面,避免加锁失败导致释放锁出现异常

 Lock lock=new ReentrantLock();

    void m1(){
        lock.lock();    //相当于synchronized(this)
        try {
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    void m2(){
        lock.lock();
        try {
            System.out.println("m2....");
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLock2 r1=new ReentrantLock2();
        new Thread(r1::m1).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(r1::m2).start();
    }

使用trylock进行尝试锁定,不管锁定与否,方法都将继续执行,trylock也可以指定时间,由于trylock(time)抛出异常,所以要注意unlock的处理,必须方法finally中

 Lock lock=new ReentrantLock();

    void m1(){
        lock.lock();    //相当于synchronized(this)
        try {
            for (int i = 0; i < 10; i++) {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    /**
     * 使用trylock进行尝试锁定,不管锁定与否,方法都将继续执行
     * 可以根据trylock的返回值来判断是否锁定
     * 也可以指定trylock的时间,由于trylock(time)抛出异常,所以要注意unlock的处理,必须方法finally中
     */
    void m2(){
        /*boolean locked=lock.tryLock();
        System.out.println("m2..."+locked);
        if (locked) lock.unlock();*/

        boolean locked=false;
        try {
            locked=lock.tryLock(5,TimeUnit.SECONDS);
            System.out.println("m2...."+locked);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (locked) lock.unlock();
        }
    }

    public static void main(String[] args) {
        ReentrantLock3 r1=new ReentrantLock3();
        new Thread(r1::m1).start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(r1::m2).start();
    }

使用ReentrantLock还可以调用lockInterruptibly方法,可以对线程interrupt方法做出响应,在一个线程等待锁的过程中,可以被打断

 public static void main(String[] args) {
        Lock lock=new ReentrantLock();

        Thread t1=new Thread(()->{
            lock.lock();
            try {
                System.out.println("t1 start");
                TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
                System.out.println("t1 end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        t1.start();

        Thread t2=new Thread(()->{

            try {
                lock.lockInterruptibly(); //可以对interrupt()做出响应
                System.out.println("t2 start");
                TimeUnit.SECONDS.sleep(5);
                System.out.println("t2 end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }

        });

        t2.start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        t2.interrupt();//打断线程2的等待
    }

ReentrantLock还可以指定公平锁

 private static ReentrantLock lock=new ReentrantLock(true);//参数为true表示为公平锁,轻对比输出结果

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            lock.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"获得锁");
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        ReentrantLock5 r1=new ReentrantLock5();
        Thread th1=new Thread(r1);
        Thread th2=new Thread(r1);
        th1.start();
        th2.start();
    }

2容器

  • HashMap:线程不安全,不能保证数据的同步,常用于单线程
  • Hashtable:线程安全,但效率很低,它的初始容量和扩容策略跟HashMap都不同,而且键和值不能为null
  • TreeMap:线程不安全,它使用的存储结构是平衡二叉树也称红黑树,适用于需要排序的Map
  • LinkedHashMap:线程不安全,是HashMap的子类,常用于插入顺序和取出顺序一样的场景
  • Collections.synchronizedXXX:传入一个不安全的容器返回一个加锁的容器,也就是安全的,常用于并发小的场景
  • ConcurrentHashMap:线程安全的,使用ReentrantLock实现了分段锁技术,为了解决HashMap不安全,Hashtable效率太低而诞生的
  • ConcurrentSkipListMap:使用跳表实现,是线程安全并且有序的,适用于高并发
  • ArrayList:线程不安全的数组,删除和插入的开销比较大,适用于单线程随机访问集合元素的场景
  • LinkedList:线程不安全的,使用双链表实现,删除和插入相对于ArrayList更快,适用于写操作比读操作多的场景
  • CopyOnWriteArrayList:写时复制容器,常用于读操作远远大于写操作,免去了读时加锁,避免了资源浪费

3队列

  • CocurrentLinkedQueue:基于链接节点的无界线程安全队列,按照 FIFO(先进先出)原则对元素进行排序
  • LinkedBlockingQueue:使用链表来实现的阻塞式队列,可并行执行读写操作,当队列满了时做添加操作和队列空了时做取出操作就会出现阻塞
  • ArrayBlockingQueue基于数组的有界阻塞式队列,是一个线程安全的队列,按照先进先出排序
  • LinkedTransferQueue:无界的阻塞队列,适用于消费者先启动,生产者后启动场景
  • DelayQueue:延迟无界队列,在指定时间才能获取队列元素,常用于定时执行任务
  • SynchronusQueue:特殊的TransferQueue,容量为0,使用时必须要有一个消费者在等待中

4线程池

  • newFixedThreadPool:指定数量的线程池,当任务大于指定数量时会进入等待,如果不手动关闭则会一直处于启动状态
  • newCachedThreadPool:可缓存的线程池,灵活创建回收线程,默认线程收回为60秒,最大为当前系统支撑的最大线程
  • newSingleThreadExecutor:单线程化线程池,串行执行所有任务,保证了所有任务的执行顺序按照任务的提交顺序执行
  • newScheduledThreadPool:定时执行的线程池,适用于周期性执行任务的场景,于定时器不同的是他是一个线程池,里面的线程是可复用的
  • newWorkStealingPool:JDK1.8增加的具有抢占式操作的线程池也叫工作窃取线程池,它创建的线程属于精灵线程,只要jvm不结束它就不会结束,适合于使用在很耗时的操作
  • ForkJoinPool:将一个任务拆分成多个“小任务”并行计算,可拆分可合并,适用于计算密集型的任务

补充

volatile关键字,使其具有内存可见性,使一个变量在多个线程间可见,例如当A B线程都用到一个变量时,java默认是A线程中保留一份copy,这样如果B线程修改了该变量,则A线程未必知道, 使用volatile关键字,会让所有线程都读到该变量的修改值
ThreadLocal线程局部变量,ThreadLocal是使用空间换时间,synchronized是使用时间换空间

public class ThreadLocal1 {
    volatile static Person p =new Person();

    public static void main(String[] args) {
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(p.name);
        }).start();


        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            p.name="lisi";
        }).start();
    }
}

class Person{
    String name="zhangsan";
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值