资讯专栏INFORMATION COLUMN

java并发编程学习1--基础知识

huangjinnan / 1031人阅读

摘要:死亡状态线程退出有可能是正常执行完成也有可能遇见异常退出。类有新建与死亡状态返回其余状态返回判断线程是否存活。线程因某些原因进入阻塞状态。执行同步代码块的过程中执行了当前线程放弃开始睡眠进入就绪状态但是不会释放锁。

【java内存模型简介

JVM中存在一个主存区(Main Memory或Java Heap Memory),Java中所有变量都是存在主存中的,对于所有线程进行共享,而每个线程又存在自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作并非发生在主存区,而是发生在工作内存中,而线程之间是不能直接相互访问,变量在程序中的传递,是依赖主存来完成的。而在多核处理器下,大部分数据存储在高速缓存中,如果高速缓存不经过内存的时候,也是不可见的一种表现。在Java程序中,内存本身是比较昂贵的资源,其实不仅仅针对Java应用程序,对操作系统本身而言内存也属于昂贵资源,Java程序在性能开销过程中有几个比较典型的可控制的来源。synchronized和volatile关键字提供的内存中模型的可见性保证程序使用一个特殊的、存储关卡(memory barrier)的指令,来刷新缓存,使缓存无效,刷新硬件的写缓存并且延迟执行的传递过程,无疑该机制会对Java程序的性能产生一定的影响。

【java线程的运行机制

在java虚拟机进程中,执行程序代码的任务是由线程看来完成的。每个线程都有一个独立的程序计数器和方法调用栈。程序计数器:pc寄存器,当线程执行一个方法时,程序计数器指向方法区中下一条要执行的字节码指令。方法调用栈:用来跟踪线程运行中一系列方法的调用过程,栈中的元素称为栈帧。每当线程调用一个方法,就会压栈一个新帧,帧用来保存方法的参数,局部变量,运算过程中产生的临时数据。java虚拟机的主线程是它从启动类的main()方法开始运行。此外,用户也可以创建自己的线程,两种方式:继承 Thread 类,实现 Runnable 接口。
但是运行一个线程必须使用Thread.strat(),切记:1.不可直接运行run(),直接运行run()只是单纯的方法调用,并不会产出新的线程。2.不要随意覆盖start(),如果必须覆盖记得首先调用super.start()。线程是不会顺序执行的,一切都由操作系统调度决定,并且一个线程只能启动一次,第二次启动会抛出:IllegalThreadStateException,但是并不会影响之前启动的线程工作。

public class MyRunnable implements Runnable{
            @Override
            public void run() {
                    System.out.println("runnable running");
           }
    }

    
    public class MyThread extends Thread{
            @Override
             public void run(){
                System.out.println("thread running");
            }
    }
【java线程状态

新建状态:new 语句创建的状态,此时它和其他java对象一样,仅仅在堆中被分配了内存。

就绪状态:当一个线程被其他线程调用了start(),此时jvm会为它创建程序计数器和方法调用栈。处于改状态的线程位于可运行池,等待获取CPU的执行权。

运行状态:处于改状态的线程占用CPU,正在执行程序代码。如果计算机只有一个单核CPU那么永远hi只有一个线程处于改状态。只有处于就绪状态的线程才可能成为运行状态。

阻塞状态:线程因为某些原因放弃了CPU暂停执行。此时线程放弃CPU的执行权,直到进入就绪状态才可能再次变为运行状态。阻塞状态3中情况:

对象等待池阻塞:线程执行了某个对象的wait(),线程被jvm放入这个对象的等待池之中。(用sleep()方法的过程中,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备。)

对象同步锁阻塞:线程试图获取对象的同步锁,如果同步锁已经被其他线程持有,jvm会把该线程放入对象锁池中。

其他阻塞状态:当前线程执行sleep(),或者调用其它线程的join()(把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。),或者发出了IO请求。

死亡状态:线程退出run(),有可能是正常执行完成,也有可能遇见异常退出。但是都不会对其他线程造成影响。Thread类有isAlive()(新建与死亡状态返回false,其余状态返回true)判断线程是否存活。

【线程调度

一个单核CPU在一个时刻只能执行一个机器指令。线程只有通过获得CPU才能执行自己的程序代码。所谓多线程的并发执行,其实从宏观上来看:各个线程轮流获得CPU的使用权,分别执行各自的任务。jvm采用抢占式调度模型,是指先让高优先级线程获得CPU。如果优先级相同,随机选择一个执行。处于运行状态的线程或一直执行,直到不得不放弃CPU,一般有如下原因:

1. jvm让其放弃CPU转入就绪状态。        
2. 线程因某些原因进入阻塞状态。       
3. 运行结束退出run()。

值得注意一点:java的线程优先级使用Thread.setPriority(int)设置,通常三个静态常量选择:Thread.MAX_PRIORITY(默认:10),Thread.MIN_PRIORITY(默认:1),Thread.NORM_PRIORITY(默认:5)。但是各个操作系统的线程优先级并不相同,所以为了确保程序能够在不同平台正常执行,我们只是用这三个值,不会使用1-10中的其他数字。常用方法:

Thread.sleep(long millis): 当前线程放弃CPU进入阻塞状态,经过milli毫秒后恢复就绪状态,不放弃对象锁的持有。

Thread.yield(): 让出CPU执行权进入就绪状态,给另一个拥有相同或者大于优先级的线程,如果没满足条件的线程,则什么都不做。

Thread.join(): 当前线程调用另一个线程的join(),并且等待被调用线程执行完后再继续执行。

Object.wait(): 当前线程必须拥有当前对象锁。如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常。唤醒当前对象锁的等待线程使用notify或notifyAll方法,也必须拥有相同的对象锁,否则也会抛出IllegalMonitorStateException异常。waite()和notify()必须synchronized函数或synchronized block中进行调用。如果在non-synchronized函数或non-synchronized block中进行调用,虽然能编译通过,但在运行时会发生IllegalMonitorStateException的异常。

Object.notify(): 执行该方法的线程随机唤醒对象等待池中的一个线程,并将其装入对象锁池之中。

【线程的同步与并发

并发编程三个概念:

原子性:一个操作或者多个操作,要么全部成功,要么全部失败。

可见性:当多个线程访问同一变量时,一个线程修改了该变量的值,其他线程能立即看到修改后的值。

有序性:程序执行的顺序按照代码的先后顺序执行。(你以为这是废话?请了解指令重排序)。这三个特性中2,3可以由volatile关键字保证(2.缓存一致性协议,3.禁止指令重排序),1只能由同步方式保证。

同步是解决资源共享的有效手段。当一个线程在操作共享变量的时候,其他线程只能等待。只有当该线程执行完同步代码块后,其他线程才能有机会操作共享资源。通常有如下几种同步方式:

synchorized关键字: 修饰方法或者使用同步代码块。

ReentrantLock重入锁对象: 锁住共享变量的操作。

使用并发数据结构对象:Atomic系列,Concurrent系列等。

但是同步的操作,代价较大,我们应该尽可能减少同步操作,是的一个线程能尽快的释放锁,减少其他线程执行的时间。由于等待一个锁的线程只有在获得了这把锁之后,
才能继续执行所以让持有锁的线程及时释放锁的相当重要的。

以下情况线程释放锁:

执行完同步代码块。

执行同步代码块的过程中,遇见异常,线程死亡,锁被释放。

执行同步代码块的过程中,执行了锁所属对象的wait(),这个线程会释放锁进入对象等待池。

以下情况线程不会释放锁:

执行同步代码块的过程中,执行了Thread.sleep(),当前线程放弃CPU开始睡眠进入阻塞状态,但是不会释放锁。

执行同步代码块的过程中,执行了Thread.yield(),当前线程放弃CPU开始睡眠进入就绪状态,但是不会释放锁。

执行同步代码块的过程中,其他线程执行了当前线程的suspend()(已废弃,同时废弃的还有:Thread.stop(),Thread.resume()),当前线程被暂停,但是不会释放锁。 死锁两个线程互相等待对方持有的锁,统统进入阻塞状态,jvm不检测也不避免这种情况。

【线程通信

不同的线程需要协作完成工作(一种情况是:线程2需要线程1的执行结果)。

- Object.wait(): 执行该放大的线程释放它持有的该对象的共享锁(前提时必须持有该共享锁),该线程进入对象等待池,等待其他线程将其唤醒。
    
- Object.notify(): 执行该方法的线程随机唤醒对象等待池中的一个线程,并将其装入对象锁池之中。

补充:

1.锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权。但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。

2.等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁(因为wait()方法必须出现在synchronized中,这样自然在执行wait()方法之前线程A就已经拥有了该对象的锁),同时线程A就进入到了该对象的等待池中。如果另外的一个线程调用了相同对象的notifyAll()方法,那么处于该对象的等待池中的线程就会全部进入该对象的锁池中,准备争夺锁的拥有权。如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池。步骤如下(后面会有代码实例):

   1. t1执行s的一个同步代码块,t1持有s的共享锁,t2在s的锁池中等待。
   
   2. t1在同步代码中执行s.wait(0,t1释放s的共享锁,进入s的等待池。
   
   3. s的锁池中t2获得共享锁执行s的另一同步代码块。
   
   4. t2在同步代码块中执行s.notify(),JVM将t1从s的等待池转入s的锁池。
   
   5. t2完成同步代码,释放锁,t1获得锁继续执行同步代码。        
    

eg:两个线程,一个线程将某个对象的某个成员变量的值加1,而另外一个线程将这个成员变量的值减1.使得该变量的值始终处于[0,2].初始值为0:

【中断阻塞

当一个线程处于阻塞状态时,另一个线程调用阻塞线程的interrupt(),阻塞线程收到InterruptException,并退出阻塞状态,开始进行异常处理。代码:

@Override            
        public void run() {                
            System.out.println("runnable running");                
            try {                   
                 Thread.sleep(1l);                
            } catch (InterruptedException e) {     
                  //-----start异常处理----                    
                e.printStackTrace();                   
                  //-----end异常处理-----                
            }            
        }
【总结

并发编程的知识非常复杂,以上只是一些皮毛,后续还将学习Synchronized,ReentrantLock,Future,FutureTask,Executor,Fork/Join,CompletableFuture,Map-Reduce等相关知识,最后用一个实际项目来完成这部分知识的学习。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68063.html

相关文章

  • Java多线程学习(七)并发编程中一些问题

    摘要:相比与其他操作系统包括其他类系统有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。因为多线程竞争锁时会引起上下文切换。减少线程的使用。很多编程语言中都有协程。所以如何避免死锁的产生,在我们使用并发编程时至关重要。 系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)syn...

    dingding199389 评论0 收藏0
  • 学习Java必读的10本书籍

    摘要:学习编程的本最佳书籍这些书涵盖了各个领域,包括核心基础知识,集合框架,多线程和并发,内部和性能调优,设计模式等。擅长解释错误及错误的原因以及如何解决简而言之,这是学习中并发和多线程的最佳书籍之一。 showImg(https://segmentfault.com/img/remote/1460000018913016); 来源 | 愿码(ChainDesk.CN)内容编辑 愿码Slo...

    masturbator 评论0 收藏0
  • java并发编程学习---之一

    摘要:开始学习也有一段时间了,一些基础的书也扫了一遍了。最近慢慢开始看和,后者的话和有类似之处,都是一些编程经验的编程的世界里好多的东西都是相同的。这里其实是对的最佳实践,之后该对象已经变成一个过期的引用了,此时就应该清空这个引用。 开始学习java也有一段时间了,一些基础的书也扫了一遍了(think in java/core java volume 1)。最近慢慢开始看和,后者的话和有类似...

    chavesgu 评论0 收藏0
  • Java学习必备书籍推荐终极版!

    摘要:实战高并发程序设计推荐豆瓣评分书的质量没的说,推荐大家好好看一下。推荐,豆瓣评分,人评价本书介绍了在编程中条极具实用价值的经验规则,这些经验规则涵盖了大多数开发人员每天所面临的问题的解决方案。 很早就想把JavaGuide的书单更新一下了,昨晚加今天早上花了几个时间对之前的书单进行了分类和补充完善。虽是终极版,但一定还有很多不错的 Java 书籍我没有添加进去,会继续完善下去。希望这篇...

    Steve_Wang_ 评论0 收藏0
  • Java多线程学习(七)并发编程中一些问题

    摘要:因为多线程竞争锁时会引起上下文切换。减少线程的使用。举个例子如果说服务器的带宽只有,某个资源的下载速度是,系统启动个线程下载该资源并不会导致下载速度编程,所以在并发编程时,需要考虑这些资源的限制。 最近私下做一项目,一bug几日未解决,总惶恐。一日顿悟,bug不可怕,怕的是项目不存在bug,与其惧怕,何不与其刚正面。 系列文章传送门: Java多线程学习(一)Java多线程入门 Jav...

    yimo 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<