JUC-02-八锁问题,集合类的安全问题,Callable

01,八锁问题

  • 对象锁(synchronized method{}

在普通方法上加synchronized关键字,是对象锁,锁的是当前方法的调用者,只要不是同一个实例,就不需要抢锁,例如:一个类的两个不同的实例,是不需要抢锁的。

  • 类锁(static sychronized method{}

在静态方法上加synchronized关键字,是类锁,锁的是类,只要是同一个类的实例都需要抢锁

  • 注意:对象锁和类锁不是同一个锁(一定记住这句话)

  • 8锁问题:关于锁的8个问题,只要理解上面的话,这些问题就迎刃而解了。

问题一

  • 示例程序
public class Test01 {
    public static void main(String[] args) {
        Phone phone =new Phone();
        new Thread(()->{
            phone.sendMsg();
        },"thread_A").start();

        //使用java.util.concurrent.TimeUtil来进行睡眠
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        },"Thread_B").start();
    }
}

class Phone{
    public synchronized void sendMsg(){
        System.out.println("sendMsg.....");
    }
    
    public synchronized void call(){
        System.out.println("call....");
    }
}

  • 示例分析:
synchronized 锁只有一个锁,线程A先拿到锁,所以先执行线程A中的打印操作,在执行线程B

输出结果为:
sendMsg.....
call....

问题二

  • 示例程序
public class Test01 {
    public static void main(String[] args) {
        Phone phone =new Phone();
        new Thread(()->{
            phone.sendMsg();
        },"thread_A").start();

        //使用java.util.concurrent.TimeUtil来进行睡眠
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        },"Thread_B").start();
    }
}

class Phone{
    public synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg.....");
    }

    public synchronized void call(){
        System.out.println("call....");
    }
}
  • 示例分析
因为有锁,如果不设置线程等待【wait】,使用synchronized修饰的方法会【抱着锁睡觉】

输出结果为:
sendMsg.....
call....

问题三

  • 示例程序
public class Test02 {
    public static void main(String[] args) {
        Phone2 phone =new Phone2();
        
        new Thread(()->{
            phone.sendMsg();
        },"thread_A").start();

        //使用java.util.concurrent.TimeUtil来进行睡眠
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
        new Thread(()->{
           phone.call();
       },"Thread_B").start();
       
       new Thread(()->{
            phone.hello();
        },"Thread_C").start();

       
    }
}

class Phone2{
    public synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg.....");
    }
    
    public synchronized void call(){
        System.out.println("call....");
    }
    //没有锁,不受锁的影响
    public void hello(){
        System.out.println("hello....");
    }
}

  • 示例分析
synchronized锁,同被synchronized关键字修饰的方法才会抢这个锁
由于hello没有被synchronized关键字修饰,所以不用抢这个锁

虽然Thread_A先拿到了锁,但是它睡眠了,所以CPU会调度执行下一个线程,又因为Thread_B
需要等Thread_A释放锁,所以执行结果为:
hello....
sendMsg.....
call....

问题四:

  • 示例程序
public class Test02 {
    public static void main(String[] args) {
        Phone2 phone =new Phone2();//对象一
        Phone2 phone2=new Phone2();//对象二
        new Thread(()->{
            phone.sendMsg();
        },"thread_A").start();

        //使用java.util.concurrent.TimeUtil来进行睡眠
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
           phone2.call();
       },"Thread_B").start();

    }
}

class Phone2{
    //因为有锁,如果不设置行线程等待,使用synchronized修饰的方法会【抱着锁睡觉】
    public synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg.....");
    }

    public synchronized void call(){
        System.out.println("call....");
    }
}
  • 示例分析
synchronized 锁修饰普通方法,锁的是【方法的调用者(phone),也就是对象】,由于有两个调用者,
所以有两个锁,不用抢

虽然先执行了Thread_A线程,但是它睡眠了,所以CPU会调度执行下一个线程(线程中的方法带锁,但是不是同一把锁)

输出结果:
call....
sendMsg.....

问题五

  • 示例程序
public class Test03 {
    public static void main(String[] args) {
        Phone3 phone =new Phone3();
        new Thread(()->{
            phone.sendMsg();
        },"thread_A").start();

        //使用java.util.concurrent.TimeUtil来进行睡眠
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
       new Thread(()->{
            phone.call();
        },"Thread_B").start();
    }
}

class Phone3{
    //静态方法:类加载就有了,不属于任何对象,属于类的实例共有此时锁的是Class,也就是类锁
    public static synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg.....");
    }
    public static synchronized void call(){
        System.out.println("call....");
    }
}
  • 示例分析
sychronized修饰静态方法,锁的是类【Class】,所有该类的实例,还需要抢锁

输出结果:
sendMsg.....
call....

问题六

  • 示例程序
public class Test03 {
    public static void main(String[] args) {
        Phone3 phone =new Phone3();
        Phone3 phone2=new Phone3();
        new Thread(()->{
            phone.sendMsg();
        },"thread_A").start();

        //使用java.util.concurrent.TimeUtil来进行睡眠
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

       new Thread(()->{
           phone2.call();
       },"Thread_C").start();
    }
}

class Phone3{
    public static synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg.....");
    }
    public static synchronized void call(){
        System.out.println("call....");
    }
}

  • 示例分析
sychronized修饰静态方法,锁的是类【Class】,所有该类的实例,还需要抢锁
由于锁的是【Class】,类锁,就是只要是同一个类的对象,就算两个对象也需要抢锁

输出结果:
sendMsg.....
call....

问题七

  • 示例程序
public class Test04 {
    public static void main(String[] args) {
        Phone4 phone =new Phone4();
        Phone4 phone2=new Phone4();
        new Thread(()->{
            phone.sendMsg();
        },"thread_A").start();

        //使用java.util.concurrent.TimeUtil来进行睡眠
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone2.call();
        },"Thread_B").start();

    }
}

class Phone4{
    public static synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg.....");
    }
    public synchronized void call(){
        System.out.println("call....");
    }
}
  • 示例分析
synchronized关键字修饰的静态方法的锁的是类【Class】
被synchronized关键字修饰的普通方法的锁的是对象【调用者】

不用抢锁,虽然先执行Thread_A,但是它睡眠了,所以CPU会调度其他线程执行(由于不是同一把锁)
输出结果:
call....
sendMsg.....

问题八(注意和问题七的对比)

  • 示例程序
public class Test04 {
    public static void main(String[] args) {
        Phone4 phone =new Phone4();
        new Thread(()->{
            phone.sendMsg();
        },"thread_A").start();

        //使用java.util.concurrent.TimeUtil来进行睡眠
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            phone.call();
        },"Thread_B").start();

    }
}

class Phone4{
    public static synchronized void sendMsg(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("sendMsg.....");
    }
    public synchronized void call(){
        System.out.println("call....");
    }

}
  • 示例分析
虽然是同一个对象,但是一个拿的是对象锁,一个是类锁,是两把锁,不用抢锁

运行结果:
call....
sendMsg.....

02,集合类的安全问题

线程安全的List:java.util.concurrent.CopyOnWriteArrayList

List:

  • 单个线程十分安全
  • 多线程不安全
  • 并发下会报错,java.util.ConcurrentModificationException:并发修改异常

单线程下的List

public class ListTest {
    public static void main(String[] args) {
        //单个线程十分安全,但是并发下安全吗?
        List<String> lists= Arrays.asList("aismall","is","very","beautiful");
        lists.forEach(System.out::println);
        }
}

多下程下的List

public class ListTest {
    public static void main(String[] args) {
        //并发下会报错:并发下ArrayList不安全
        List<String> list=new ArrayList<>();
        for (int i=0;i<20;i++){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

如何解决多线程下的List的不安全问题

解决方案1:使用vector(默认安全)
List<String> list=new Vector();
这个方法过于老旧

解决方法2:使用Collections中的静态方法,synchronizedList
List<String> list=Collections.synchronizedList(new ArrayList<>());

解决方法3:使用JUC包下的copyOnWrite(写入时复制)
List<String> list=new CopyOnWriteArrayList<>();

问题1:CopyOnWrite比Vector优越在哪里?
Vector的add方法添加了synchronized锁,效率比较慢
CopyOnWrite里面使用的是lock锁,效率快一点

具体的可以查看vector.add和CopyOnWriteArrayList的源码
public class ListTest {
    public static void main(String[] args) {
        List<String> list=Collections.synchronizedList(new ArrayList<>());
        for(int i=0;i<10;i++){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
       
        List<String> list2=new CopyOnWriteArrayList<>();
        for(int i=0;i<10;i++){
            new Thread(()->{
                list2.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(list);
            },String.valueOf(i)).start();
        }
    }
}

线程安全的map:java.util.concurrent.ConcurrentHashMap

map:

  • 单个线程十分安全
  • 多线程不安全
  • 并发下会报错,java.util.ConcurrentModificationException:并发修改异常

多下程下的map

public class MapTest {
    public static void main(String[] args) {
        //默认构造函数等价于什么?  new HashMap(16,0.75);
        //HashMap默认容量为16,超过12个就会进行扩容
        
        Map<String,String> map=new HashMap<>();
        for(int i=0;i<20;i++){
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

如何解决多线程下的map的不安全问题

解决办法:使用java.util.concurrent.ConcurrentHashMap

具体可查看ConcurrentHashMap源码
public class MapTest {
    public static void main(String[] args) {
        //map 是这样用的吗? 不是,工作中不用HashMap

      Map<String,String> map=new ConcurrentHashMap<>();
        for(int i=0;i<20;i++){
            new Thread(()->{
                map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

线程安全的Set:java.util.concurrent.CopyOnWriteArraySet

Set:

  • 单个线程十分安全
  • 多线程不安全
  • 并发下会报错,java.util.ConcurrentModificationException:并发修改异常

多下程下的Set

public class SetTest {
    public static void main(String[] args) {
        //单个线程十分安全,但是并发下安全吗?
        //注意:Set和List同属于java.util.Collection接口
        
        //并发下会报错:并发下Set不安全
        Set<String> set=new HashSet<>();
        for(int i=0;i<20;i++){
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

如何解决多线程下的set的不安全问题

解决方案1:使用vector(默认安全)
List<String> list=new Vector();
这个方法过于老旧

解决方法2:使用Collections中的静态方法,synchronizedList
Set<String> set= Collections.synchronizedSet(new HashSet<>());

解决方法3:使用JUC包下的copyOnWrite(写入时复制)
List<String> list=new CopyOnWriteArraySet<>();

问题1:CopyOnWrite比Vector优越在哪里?
Vector的add方法添加了synchronized锁,效率比较慢
CopyOnWrite里面使用的是lock锁,效率快一点
public class SetTest {
    public static void main(String[] args) {
         Set<String> set= Collections.synchronizedSet(new HashSet<>());
         for(int i=0;i<20;i++){
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }

        Set<String> set2=new CopyOnWriteArraySet<>();
        for(int i=0;i<20;i++){
            new Thread(()->{
                set2.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

03,java.util.concurrent.Callable接口

  • 在前面我们已经把java.util.concurrent.locks包下的三个接口讲完了,现在我们开始陆续介绍java.util.concurrent包下的一些东西
  • 我们先介绍一下java.util.concurrent包下的一个Callable接口
  • 可以理解为多线程创建的第三种方式
Callable接口在:java.util.concurrent包下
 
功能:类似于Runnable接口,可以创建一个可以被其他线程执行的实例

差别:Callable接口
	- 可以有返回值
	- 可以抛出异常
	- 方法不同,一个是run(),一个是call()

传统方式创建线程

传统方式:
- 1.编写一个类继承Runnab接口
- 2.重写Runnable接口中的run()方法,
- 3.创建一个线程类的实现:MyThread mythread=new MyThread();
- 4.把线程的实现类丢入到新建的线程类中:new Thread(mythread).start();

缺点:没有返回值注意:【通过查Thread类发现,此类的构造方法中只能接收Runnable的实现】
public class CallableTest01 {
    public static void main(String[] args) {
        MyThread myThread=new MyThread();
        new Thread(myThread,"Thread_A").start();
        new Thread(myThread,"Thread_B").start();
    }
}

class MyThread implements Runnable{
    @Override
    public void run(){
     	System.out.println("传统方式创建线程.....");
    }
}

使用Callable接口创建线程

注意:通过查Thread类发现,此类的构造方法中只能接收Runnable的实现类

所以要想使用Callable创建线程,必须要和Thread或者Runnable接口扯上关系

经过查看JDK文档发现:
Runnable有个实现类FutureTask,在java.util.concurrent包下面
FutureTask有个构造可以接收Callable接口的实现类

于是乎我们可以这样
1.创建一个类实现Callable接口(通过泛型指定返值的类型)
2.把实现类的实例放到FutureTask的构造中,FutureTask是Runnable接口的实现类
3.把FutureTask的实例放到Thread中用来创建线程

Callable可以有返回值,返回值在FutureTask里面,使用get方法获取返回值

在这里插入图片描述

public class CallableTest01 {
    public static void main(String[] args) {
        
        MyThread2 myThread2=new MyThread2();
        FutureTask futureTask=new FutureTask(myThread2);
        new Thread(futureTask,"Thread_A").start();
       
        new Thread(new FutureTask<String>(new MyThread2()),"Thread_B").start();
      
        try {
            String returnValue=(String) futureTask.get();//此方法会产生阻塞,一般往后放
            System.out.println(returnValue);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

    }
}

//注意:泛型的参数等于方法的返回值
class MyThread2 implements Callable<String>{
    @Override
    public String call() throws  InterruptedException{
        System.out.println("Callable.call.....");
        return "aismall";
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彤彤的小跟班

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值