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

来源:互联网 发布:新兴重工查知的老婆 编辑:程序博客网 时间:2024/05/01 21:42
【java内存模型简介:    JVM中存在一个主存区(Main Memory或Java Heap Memory),Java中所有变量都是存在主存中的,对于所有线程进行共享,而每个线程又存在自己的工作内存(Working Memory),    工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作并非发生在主存区,而是发生在工作内存中,而线程之间是不能直接相互访问,变量在程序中的传递,是依赖主存来完成的。    而在多核处理器下,大部分数据存储在高速缓存中,如果高速缓存不经过内存的时候,也是不可见的一种表现。    在Java程序中,内存本身是比较昂贵的资源,其实不仅仅针对Java应用程序,对操作系统本身而言内存也属于昂贵资源,Java程序在性能开销过程中有几个比较典型的可控制的来源。    synchronizedvolatile关键字提供的内存中模型的可见性保证程序使用一个特殊的、存储关卡(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中情况:            1.对象等待池阻塞:线程执行了某个对象的wait(),线程被jvm放入这个对象的等待池之中。(用sleep()方法的过程中,线程不会释放对象锁。
                 而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备。)            2.对象同步锁阻塞:线程试图获取对象的同步锁,如果同步锁已经被其他线程持有,jvm会把该线程放入对象锁池中。            3.其他阻塞状态:当前线程执行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的异常。【线程的同步与并发:    并发编程三个概念:        1.原子性:一个操作或者多个操作,要么全部成功,要么全部失败。        2.可见性:当多个线程访问同一变量时,一个线程修改了该变量的值,其他线程能立即看到修改后的值。        3.有序性:程序执行的顺序按照代码的先后顺序执行。(你以为这是废话?请了解指令重排序)。    这三个特性中2,3可以由volatile关键字保证(2.缓存一致性协议,3.禁止指令重排序),1只能由同步方式保证。    同步是解决资源共享的有效手段。当一个线程在操作共享变量的时候,其他线程只能等待。只有当该线程执行完同步代码块后,其他线程才能有机会操作共享资源。    通常有如下几种同步方式:        1.synchorized关键字:修饰方法或者使用同步代码块。        2.ReentrantLock重入锁对象:锁住共享变量的操作。        3.使用并发数据结构对象:Atomic系列,Concurrent系列等。    但是同步的操作,代价较大,我们应该尽可能减少同步操作,是的一个线程能尽快的释放锁,减少其他线程执行的时间。由于等待一个锁的线程只有在获得了这把锁之后,    才能继续执行所以让持有锁的线程及时释放锁的相当重要的。    以下情况线程释放锁:        1.执行完同步代码块。        2.执行同步代码块的过程中,遇见异常,线程死亡,锁被释放。        3.执行同步代码块的过程中,执行了锁所属对象的wait(),这个线程会释放锁进入对象等待池。    以下情况线程不会释放锁:        1.执行同步代码块的过程中,执行了Thread.sleep(),当前线程放弃CPU开始睡眠进入阻塞状态,但是不会释放锁。        2.执行同步代码块的过程中,执行了Thread.yield(),当前线程放弃CPU开始睡眠进入就绪状态,但是不会释放锁。        3.执行同步代码块的过程中,其他线程执行了当前线程的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:
public class Target {    private int count;    public int getCount(){return count;}    public synchronized void increase(){        if(count > 1){            try{                wait();            }            catch (InterruptedException e){                e.printStackTrace();            }        }        count++;        System.out.println(Thread.currentThread().getName() + ":" + count);        notify();    }    public synchronized void decrease(){        if(count < 1){            try{                //等待,由于Decrease线程调用的该方法,                //所以Decrease线程进入对象t(main函数中实例化的)的等待池,并且释放对象t的锁                wait();//Object类的方法            }            catch (InterruptedException e){                e.printStackTrace();            }        }        count--;        System.out.println(Thread.currentThread().getName() + ":" + count);        //唤醒线程Increase,Increase线程从等待池到锁池        notify();    }}
public class Decrease extends Thread {    private Target t;    public Decrease(Target t) {        this.t = t;    }    @Override    public void run(){        t.decrease();    }}
public class Increase extends Thread{    private Target t;    public Increase(Target t){        this.t = t;    }    @Override    public void run(){        t.increase();    }}

@Testpublic void testTarget() throws InterruptedException {    Target t = new Target();    Thread t1 = new Increase(t);    t1.setName("Increase1");    Thread t2 = new Decrease(t);    t2.setName("Decrease2");    t1.start();    t2.start();    t1.join();    t2.join();    System.out.println(t.getCount());}
【中断阻塞 当一个线程处于阻塞状态时,另一个线程调用阻塞线程的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等相关知识,最后用一个实际项目来完成这部分知识的学习。
原创粉丝点击