学习互联网架构第一课(线程基础)

        首先,我们要搞清楚为什么要学习并发编程?主要有以下三点:

第一点:面试非常重要

        企业面试程序员标准,考察因素:

        1.考察我公司技术你是否熟悉50%以上,或者我们公司有特殊的技术需求,正好你熟悉,那么可能会考虑录用你。

        2.细节、态度、人品问题。(1、2条件满足基本上就会录用你)

        3.知识面、潜力(这是加分项,如果能把并发的一些顶层原理都能说清楚的话,肯定能给面试官不错的印象,面试就基本上成功了)

第二点:对自己的技术提升很有帮助

        这也是我们最受益的一点,就是你并发编程技术学到手了,有了一个知识面的扩展,眼界更宽了。

第三点:触类旁通

         如果你学好了并发编程,在以后的分布式系统中,你都可以找到类似并发、分布式、并行处理问题的概念。

         接着,我们要明确该如何学习并发编程?

         在公司里面其实很多JAVA程序员,亦或是所谓的技术Leader,他们可能知道多线程中有synchronized、volatile、ReentrantLock、concurrent下数据包等等...这些看似高深的代名词,但是不等于他们就会懂得如何去使用,滥用的结果往往需要自己承担相应的后果。其实并发编程没有我们想象的那么复杂,我们只需要掌握最基本的概念就可以很轻松的入门,然后从中剖析这些概念的本质,结合实际业务逻辑去应用上去,那么你就会成为并发编程方面的专家。

          接着我们来学习什么是线程安全

          线程安全概念:当多个线程访问某一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的。

          提到线程安全就不得不提到synchronized,接触过多线程的同学肯定对它都不陌生,它可以在任意对象及方法上加锁,而加锁的这段代码成为"互斥区"或"临界区"。

示例一:多个线程一个锁

          下面我们便来看一个最简单的多线程的例子

package com.internet.thread;

public class MyThread extends Thread{
   //定义变量的初始值为5
   private int count=5;
   
   //synchronied加锁
   public synchronized void run () {
	   count--;
	   System.out.println(this.currentThread().getName()+"count="+count);
   }
   
   public static void main(String[] args){
	   /**
	    * 分析:当多个线程访问myThread的run方法时,以排队的方式进行处理(这里排队是按照CPU分配的先后顺序而定的)
	    * 一个线程想要执行synchronized修饰的方法里的代码:
	    * 1 尝试获得锁
	    * 2 如果拿到锁,执行synchronized代码体内容。拿不到锁,这个线程就会不断的尝试获得这把锁,直到拿到为止
	    * 而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题)
	    */
	   MyThread myThread = new MyThread();
	   Thread t1 = new Thread(myThread,"t1");
	   Thread t2 = new Thread(myThread,"t2");
	   Thread t3 = new Thread(myThread,"t3");
	   Thread t4 = new Thread(myThread,"t4");
	   Thread t5 = new Thread(myThread,"t5");
	   t1.start();
	   t2.start();
	   t3.start();
	   t4.start();
	   t5.start();
   }
}
        上面的代码是加了锁的(在定义run方法时加了synchronized关键字 ),这时运行main方法是没有线程问题的,如下图所示。我们可以看到线程执行的顺序是1、4、5、3、2,而不是我们书写的1、2、3、4、5,这边是我们上面说的排队是按照CPU分配的顺序先后顺序而定。

         如果我们把run方法的synchronized关键字去掉,这时便会有线程安全问题,运行结果如下所示,可以看到前两个数字竟然一样,这说明此时是线程不安全的。

t1count=3
t2count=3
t4count=2
t3count=1
t5count=0
          示例总结:当多个线程访问myThread的run方法是,以排队的方式进行处理(这里排队是按照CPU分配的先后顺序而定的),一个线程想要执行synchronized修饰的方法里的代码,首先是尝试获得锁,如果得到锁,执行synchronized代码体内容,拿不到锁,这个线程就会不断的尝试获得这把锁,直到拿到为止,而且是多个线程同时去竞争这把锁。(也就是会有锁竞争的问题,锁竞争问题对于线程比较少的情况倒是影响不大,但是如果线程很多的话,同一时间有很多线程要抢夺一把锁,CPU会瞬间达到很高的占用率,很有可能就宕机了,其实这和我们双十一抢购商品是一样的,在0点0分0秒瞬间有几十万几百万的请求过来,这对于一般的服务器来说,瞬间就崩溃了,因此我们尽量避免 锁竞争的问题)
示例二:多个线程多个锁

        多个线程多个锁:多个线程,每个线程都可以拿到自己指定的锁,分别获得锁之后,执行synchronized方法体的内容。

        新建类MultiThread,如下所示。

package com.internet.thread;

public class MultiThread {
    //定义一个静态变量num,并附初始值为0
	private int num = 0;
	public synchronized void printNum(String tag){
		try {
			if(tag.equals("a")){
				num = 100;
				System.out.println("tag a, set num over!");//打印给num赋值完毕
				Thread.sleep(1000);//休息1秒,之所以这样是为了让大家看到两个线程互不干扰,如果不休息的话,瞬间执行完了,看不出效果
			}else {
				num = 200;
				System.out.println("tag b, set num over!");
			}
			System.out.println("tag " + tag + ", num = "+num);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	//注意观察run方法输出顺序
	public static void main(String[] args){
		//两个不同的对象
		final MultiThread m1 = new MultiThread();
		final MultiThread m2 = new MultiThread();
		
		Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				m1.printNum("a");
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				m2.printNum("b");
			}
		});
		
		t1.start();
		t2.start();
	}
}
        我们运行该类,会看到如下结果,可以看到线程t1和t2互不影响,各自运行各自的,我们让t1线程休息了1秒钟,这样t2线程执行完了,t1线程才执行完。synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁,所以上面代码中哪个线程先执行synchronized关键字的方法,那个线程就持有该方法所属对象的锁(Lock),两个对象,线程获得的就是两个不同的锁,他们互不影响。

        有一种情况则是相同的锁,那就是在静态方法上加synchronized关键字,表示锁定.class类,类一级别的锁(独占.class类)。为了看效果,我们在变量num前面加static关键字, 给printNum方法加上static关键字,如下图所示。


         现在我们再运行main方法,结果如下所示,这时num和printNum便排队执行了,先执行完线程t1之后再执行线程t2。

tag a, set num over!
tag a, num = 100
tag b, set num over!
tag b, num = 200
         下面我们聊聊对象锁的同步和异步

同步:synchronized

         同步的概念就是共享,我们要牢牢记住"共享"这两个字,如果不是共享的资源,就没有必要进行同步(举个简单例子,我们存款和取款,一定要同步,因为不同步的 话,资金就乱了,这是不能忍受的)。

异步:asynchronized

         异步的概念就是独立,相互之间不受到任何制约。就好像我们学习http的时候,在页面发起的Ajax请求,我们还可以继续浏览或操作页面的内容,二者之间没有任何关系。

         同步的目的就是为了线程安全,其实对于线程安全来说,需要满足两个特性:原子性(同步)和可见性。

         我们还是来一起看个例子。

示例三

         新建MyObject类,如下所示:

package com.internet.thread;

public class MyObject {
    public synchronized void method1(){
    	try {
			System.out.println(Thread.currentThread().getName());
			Thread.sleep(4000);
		} catch (Exception e) {
			e.printStackTrace();
		}
    }
    
    public void method2(){
    	System.out.println(Thread.currentThread().getName());
    }
    
    public static void main(String[] args){
    	final MyObject mo = new MyObject();
    	/**
    	 * 分析:
    	 * t1线程先持有object对象的Lock锁,t2线程可以以异步的方式调用对象中的非synchronized修饰的方法
    	 * t1线程先持有object对象的Lock锁,t2线程如果在这个时候调用对象中的同步(synchronized)方法则需等待,也就是同步
    	 */
    	Thread t1 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				mo.method1();
			}
		},"t1");
    	
    	Thread t2 = new Thread(new Runnable() {
			
			@Override
			public void run() {
				mo.method2();
			}
		},"t2");
    	
    	t1.start();
    	t2.start();
    }
}
         运行上面的main方法,我们会看到t1和t2同时打印出来了,这说明此时method1和method2是异步执行的。

         现在我们给method2方法也加上synchronized关键字

public synchronized void method2(){
    	System.out.println(Thread.currentThread().getName());
    }
         再运行main方法,这回我们看到t1信息输出4秒后才会看到t2信息,出现这种情况的原因是,我们只new了一个对象,而synchronized关键字锁的便是对象,由于method1和method2现在都被synchronized关键字修饰,因此只要是访问该对象的这两个方法的线程都会进行同步操作,也就是说谁先拿到对象的锁谁便先执行,执行完之后,释放锁,这时下一个线程才能执行。

         总结下示例三:

         A线程先持有object对象的Lock锁,B线程如果在这个时候调用对象中的同步(synchronized)方法则需要等待,也就是同步。

        A线程先持有object对象的Lock锁,B线程可以以异步的方式调用对象中的非synchronized修饰的方法。








  • 5
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值