多线程学习

多线程的三种实现方式:
1、继承Thread类,重写run方法

两个问题:
1)为什么要重写run方法?
因为run()是用来封装被线程执行的代码
2)run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程。
start():启动线程,然后由JVM调用此次线程的run()方法

2、实现Runnable接口,重写run()方法 ------------无返回值
3、实现Callable接口,重写call()方法,通过中介FutureTask来将获取结果----------有返回值

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println("跟女孩表白"+i);
        }
        return "答应";
    }
}


    public static void main(String[] args) throws ExecutionException, InterruptedException {
       //线程开启后需要执行里面的call()方法
        MyCallable mc=new MyCallable();
        //可以获取线程执行完毕的结果,也可以作为参数传递给Thread对象
        FutureTask ft=new FutureTask(mc);
        //创建线程对象
        Thread t1=new Thread(ft);
        //设置线程的名字----线程是有默认名字的
        t1.setName("大白");
        //开启线程
        t1.start();
        //获取线程的结果以及线程的名字
        System.out.println(ft.get()+t1.getName());
    }

三种实现多线程方式的对比:
在这里插入图片描述
线程的优先级,可以通过Thread中的setPriority()方法设置。
扩展:守护线程–当主线程停止,守护线程也不再执行,可以通过Thread中的setDaemon(true)设置线程为守护线程。

public class MyCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+i);
        }
        return "线程执行完毕";
    }
}


public class MyCallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //优先级:1-10
        MyCallable mc=new MyCallable();
        MyCallable mc2=new MyCallable();
        FutureTask ft=new FutureTask(mc);
        FutureTask ft2=new FutureTask(mc2);
        Thread t1=new Thread(ft);
        Thread t2=new Thread(ft2);
        t1.setName("大白");
        //-------------------------------------  设置t1优先级为10
        t1.setPriority(10); 
        t2.setName("小黑");
        //---------------------------------------设置t2优先级为1
        t2.setPriority(1);
        t1.start();
        t2.start();
    }
}

线程的安全问题:

锁多条语句操作共享数据,可以使用共享代码块实现
格式:
synchronized(任意对象){
多条语句操作共享数据的代码
}

默认情况下是打开的,当有线程进去就会自动关闭,当线程执行完,锁才会自动打开。
同步的好处与弊端
利:解决了多线程数据的安全问题
弊:当线程很多时,每个线程都要去判断同步上的锁,耗费资源且降低效率
Lock锁:
synchronized无法直接看到在哪上了锁,在哪释放锁,为了更清晰的表达加锁和释放锁,JDK5后提供了一个新的锁对象Lock
Lock实现提供比synchronized更为广泛的锁定操作
void lock():获取锁
void unlock(): 释放锁
Lock接口是不能直接实例化的,这里采用它的实现类ReentrantLock来实例化
ReentrantLock lock=new ReentrantLock();
lock.lock();
代码块;
lock.unlock(); //一般情况下,释放锁放到finally里执行,避免出异常锁还没有释放

synchronized和lock锁都属于悲观锁,下面引入其他博主关于悲观锁和乐观锁的对比,应付面试必备!
synchronized底层实现
悲观锁&乐观锁区别

多线程和MQ的思考?
偶然想到多线程可以执行任务,MQ也可以执行任务,两者的选择有何不同?
个人的理解,计算的时候,一般是每一步的计算量很大,分成几步去算的话,使用多线程效率就变高了。而MQ一般是削峰填谷的时候用。在线程池里有个阻塞队列,其作用就是为了在面对用户数多,使用多线程时可能会爆时的一种解决策略,用来控制核心线程数、最大数量。在多线程里涉及AQS相关的问题。

Volatile关键字

小案例:男女生结婚基金,基金相当于共享数据,男女生相当于两条线程;
public class Boy extends Thread {
    @Override
    public void run() {
        try {
            sleep(10);        //让男孩线程睡上一会
            Money.money=9000; 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }


public class Girl extends Thread {
    @Override
    public void run() {
        while (Money.money==10000){

        }
        System.out.println("结婚基金不是10000了");
    }
}
}


public class Money {
    public  static  int money=10000;
}


public class Test {            //结果为程序一直停不下来
    public static void main(String[] args) {

        Girl girl=new Girl();
        girl.setName("女孩");
        Boy boy=new Boy();
        boy.setName("男孩");
        girl.start();
        boy.start();
    }
    
}

在这里插入图片描述
上述图小结:
1)堆内存是唯一的,每一个线程都有自己的线程栈;
2)每一个线程在使用堆里面变量的时候,都会拷贝一份到变量的副本中;
3)在线程中,每一次使用都是从变量的副本中获取的
解决上述问题即用到了关键字volatile,它会强制线程每次在使用时,都会看一下共享区域最新的值!但是它不能保证原子性!

synchronized同步代码块也可以解决该问题,但其原理不同,如下
1、线程获取锁 2、情况变量副本 3、拷贝共享数据最新的值到变量副本中 4、执行代码 5、将修改后的变量副本的值赋值给共享数据 6、释放锁

注意:Volatile关键字不能保证原子性,因为当A线程拿到数据修改后如果还没有写到共享数据中,线程B从共享数据中拿到的还是原来的旧数据,加synchronized同步代码块虽然可以解决共享数据修改问题,但效率较慢,jdk5后提供了原子类AtomicInteger(实现原理:自旋锁+CAS算法,图如下)

在这里插入图片描述
小结:
CAS算法:在修改共享数据时,把原来的数据记录下来,如果现在内存里的值和旧值相等,证明没有其他线程操作过内存,则修改成功,如果不相等,说明其他线程修改过,则重新获取最新的内存值,这个重新获取的过程就称之为自旋。

Volatile和Synchronized有什么区别?Volatile能不能保证线程安全?DCL(Double Check Lock)单例为什么要加Volatile?
1、Synchronized关键字用来加锁,Volatile只是保证变量的线程可见性,通常适用于一个线程写,多个线程读的场景;
2、不能,Volatile关键字只能保证线程可见性,不能保证原子性;
3、Volatile防止指令重排。DCL中,防止高并发情况下,指令重排造成的线程安全问题。

在并发情况下,常见的面试题有ConcurrentHashMap为什么能够保证数据添加的安全性,此处引入一篇讲解其源码还不错的博客ConcurrentHashMap 1.7和1.8区别

下面的程序验证了两个事实:
 1、程序的执行是顺序性的;
 2、主线程对子线程抓取异常,不生效,因为不是同一个线程;

public class Demo {

    public static void main(String[] args) throws InterruptedException {
		
    	try {
    	 	new Thread(()->{
        		for (int i = 0; i <10; i++) {
    				System.out.print(i);
    			}
        		
        		throw new RuntimeCryptoException(); //自定义异常也可以正常抛出
        		
        	}).start();
		} catch (Exception e) {
			System.out.println("此处异常不会打印,因为是主线程的");
		}finally {
			System.out.println("这一行先打印,因为是先运行主线程");
		}
   
    	Thread.sleep(1000); //此处睡眠的是主线程  	
    	
	}
==================打印台输出结果为============================
这一行先打印,因为是先运行主线程
0123456789
Exception in thread "Thread-0" org.bouncycastle.crypto.RuntimeCryptoException
	at com.Demo.lambda$0(Demo.java:15)
	at java.lang.Thread.run(Unknown Source)

}

在并发工具类CountDownLatch中,翻阅到其是使用AQS实现,上链接AQS详解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值