那多线程在java中是如何实现的呢。



一、实现多线程的四种方式



1.继承Thread类,重写run方法



2.实现Runnable接口,实现run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target



3.通过Callable和FutureTask创建线程



4.通过线程池的方式创建线程。

代码实例:



public class ThreadDemo01 extends Thread{
    public ThreadDemo01(){
        //编写子类的构造方法,可缺省
    }
    public void run(){
        //编写自己的线程代码
        System.out.println(Thread.currentThread().getName());
    }
    public static void main(String[] args){ 
        ThreadDemo01 threadDemo01 = new ThreadDemo01(); 
        threadDemo01.setName("我是自定义的线程1");
        threadDemo01.start();       
        System.out.println(Thread.currentThread().toString());  
    }
}
public class ThreadDemo02 {

    public static void main(String[] args){ 
        System.out.println(Thread.currentThread().getName());
        Thread t1 = new Thread(new MyThread());
        t1.start(); 
    }
}

class MyThread implements Runnable{
    @Override
    public void run() {
        // TODO Auto-generated method stub
        System.out.println(Thread.currentThread().getName()+"-->我是通过实现接口的线程实现方式!");
    }   
}
public class ThreadDemo03 {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Callable<Object> oneCallable = new Tickets<Object>();
        FutureTask<Object> oneTask = new FutureTask<Object>(oneCallable);

        Thread t = new Thread(oneTask);

        System.out.println(Thread.currentThread().getName());

        t.start();

    }

}

class Tickets<Object> implements Callable<Object>{

    //重写call方法
    @Override
    public Object call() throws Exception {
        // TODO Auto-generated method stub
        System.out.println(Thread.currentThread().getName()+"-->我是通过实现Callable接口通过FutureTask包装器来实现的线程");
        return null;
    }   
}
public class ThreadDemo05{

    private static int POOL_NUM = 10;     //线程池数量

    /**
     * @param args
     * @throws InterruptedException 
     */
    public static void main(String[] args) throws InterruptedException {
        // TODO Auto-generated method stub
        ExecutorService executorService = Executors.newFixedThreadPool(5);  
        for(int i = 0; i<POOL_NUM; i++)  
        {  
            RunnableThread thread = new RunnableThread();

            //Thread.sleep(1000);
            executorService.execute(thread);  
        }
        //关闭线程池
        executorService.shutdown(); 
    }   

}

class RunnableThread implements Runnable  
{     
    @Override
    public void run()  
    {  
        System.out.println("通过线程池方式创建的线程:" + Thread.currentThread().getName() + " ");  

    }  
}


各实现方式的比较:


通过Thread和Runable的方式无法获取到线程执行的反回结果,因为Runable中的run方法反回的是void,而通过使用Callable和FutureTask创建线程,则可以获取到线程的执行结果,因为Callabel接口中的call方法返回一个对象,而在FutureTask中也提供了相应对线程操作的方法





二、线程的6种状态与转换


NEW状态,指线程刚刚创建,尚未启动。


Runnable状态,是线程正在正常运行, 可能会有某种耗时计算、IO等待、cpu时间片切换等,这个状态下可能发生的等待,而不是锁,sleep等。


Blocked这个状态下,是在多个线程有同步操作的场景,比如在等待另一个线程的synchronized块的执行释放,或者可重入的synchronized块里调用wait方法,也就是这里是线程在等待进入临界区。


WAITING这个状态下是指线程拥有了某个锁之后,调用了他的wait方法,等待其他线程notify或notifyall一遍该线程可以继续下一步操作,这里要区分BLOCKED和WATING的区别,一个是在临界点外面等待进入,一个是在临界点里面wait等待别人notify,线程调用了join方法,join了另外的线程的时候,也会进入WAITING状态,等待被他join的线程执行结束。


TIMED_WAITING这个状态就是在限的(时间限制)的WAITING,一般出现在调用wait(long),join(long)等情况下,另外一个线程sleep后也会进入TIMED_WAITING状态。


TERMINATED 这个状态下表示 该线程的run方法已经执行完毕了,基本上就等于死亡了。



三、几种线程间状态切换会调用方的区别


Thread.sleep(long millis) 一定是当前线程调用些方法,当前线程进入TIMED_WATING状态,但不释放锁,时间过后线程自动苏醒进入RUNNABLE状态,作用:给其他线程执行机会的最佳方式。


Thread.yield,一定是当前线程 调用此方法,当前线程 放弃时间片,由运行状态变为可运行状态,让os再次选择线程。作用:让相同优先级的线程 轮流执行,但并不保证一定会轮流执行。实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。thread.yield不会导致阻塞。


t.join/t.join(long millis),当前线程里调用其他线程1的join方法,当前线程进入WAITING状态,等待线程1执行完毕后,当前线程进入RUNNABLE状态


4.boj.wait(),当前线程调用对象的wait()方法,当前线程释放对象锁,进入WAITING状态,依靠notify/notifyAll()唤醒或者wait(long timeout)时间到自动唤醒。


5.obj.notify()唤醒在此对象监视器上等待的单个线程,选择任意的等待的进行唤醒。notifyAll()唤醒在些对象监视器上等待的所有线程。


四、多线程三个核心概念


原子性


即一个操作可能包含很多子操作,要么全部执行,要么全部不执行。


可见性


可见是指当多个线程并发访问共享变量时,一个线程对共享变量的修改,其他线程能够立即看到,CPU从主内存中读数据的效率相对来说不高,现在主流的计算机中,都有几级缓存。每个线程读取共享变量时,都会将该变量加载进其对应CPU的高速缓存里,修改该变量后,CPU会立即更新该缓存,但并不一定会立即将其写回主内存(实际上写回主内存的时间不可预期)。此时其它线程(尤其是不在同一个CPU上执行的线程)访问该变量时,从主内存中读到的就是旧的数据,而非第一个线程更新后的数据。


顺序性


顺序性指的是,程序执行的顺序按照代码的先后顺序执行。


五、如何解决多线程并发问题


1.保证操作的原子性


   a.常见的机制是锁和代码同步,使用锁,可以保证同一时间只有一个线程能拿到锁,也就保证了同一时间只有一个线程能执行锁之间的代码


同步方法或者同步代码块。使用


synchronized关键字后面括号内的对象。


无论使用锁还是synchronized,本质都是一样,通过锁来实现资源的排它性,从而实际目标代码段同一时间只会被一个线程执行,进而保证了目标代码段的原子性。这是一种以牺牲性能为代价的方法。


    b.CAS


Java中提供了对应的原子操作类来实现该操作,并保证原子性,其本质是利用了CPU级别的CAS指令。由于是CPU级别的指令,其开销比需要操作系统参与的锁的开销小。



2.保证可见性


java提供了volatile关键字来保证可见性。当使用volatile修饰某个变量时,它会保证对该变量的修改会立即被更新到内存中,并且将其它缓存中对该变量的缓存设置成无效,因此其它线程需要读取该值时必须从主内存中读取,从而得到最新的值。



3.保证顺序性


通过volatile关键字修饰变量和synchronized和锁来保证顺序性。JVM还通过被称为happens-before原则隐式地保证顺序性。两个操作的执行顺序只要可以通过happens-before推导出来,则JVM会保证其顺序性,反之JVM对其顺序性不作任何保证,可对其进行任意必要的重新排序以获取高效率。


程序的重排序


java程序在执行的过程中,会经过重排序以获取更高的执行效率,程序经过编译器和处理器都会对程序指令进行重排序,在单线程中对存在控制依赖的操作重排序,不会改变结果,但多线程中,可以会改变程序的执行结果,所以在JMM中使用了happens-before原则,来保证操作间的可见性。


happens-before原则(先行发生原则)


  • 程序顺序规则:一个线程中的每个操作,happens-before于随后该线程中的任意后续操作
  • 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的获取
  • volatile变量规则:对一个volatile域的写,happens-before于对这个变量的读
  • 传递性:如果A happens-before B,B happens-before C,那么A happens-before C
  • start规则:如果线程A执行线程B的start方法,那么线程A的ThreadB.start()happens-before于线程B的任意操作
  • join规则:如果线程A执行线程B的join方法,那么线程B的任意操作happens-before于线程A从TreadB.join()方法成功返回。

五、Synchronized


synchronized关键字是程序最常用的保证线程安全的手段。


它的作用就是确保线程互斥的访问同步代码,保证共享变量的可见性,有效解决程序执行重排问题


synchronized修饰方法和synchronized修饰代码块,可以锁对象也可以锁类,底层实现原理就是通过每个对象在内存中的分配都会有一个对象头来存储锁对象monitor,并用mark word字段来锁对象的信息,每个对象存在着与一个monitor与之关联,而synchorized就是通过获取这个monitor来获取锁的,对于代码块是使用monitorenter和monitorexit指令来标识同步的,而同步方法则是通过ACC-SYNCHRONIZED来标识的。


六、锁(Lock)


锁的四种状态


无锁状态、偏向锁、轻量级锁、重量级锁,随着锁的竞争可以从偏向锁升级为轻量级锁,再升级为重量级锁,


偏向锁:如果一个线程获得了锁,那就就会进入偏向模式,当这个线程再次请求锁时,无需再做任何同步操作,自动获得锁,提高程序性能。


轻量级锁,是在锁竞争激烈的情况下,当前锁被其他线程获得,偏向锁失效了,会进入轻量级锁,而不是一下子就进入重量级锁,从而提升程序效率的。


自旋锁,虚拟机为了避免线程直接在操作系统中挂起做的最后尝试,就在获取不到锁时,先进行一定时间空循环后,如果得到锁,就进入临界区,否则挂起,锁升级为重量级锁


重量级锁,是发生在当前锁状态为轻量级锁时,其他级程锁自旋失败后,升级为重量级锁,其他线程进入阻塞,性能降低。


锁消除,是发生在程序进行编译时会对程序进行扫描,如果发现加锁的代码中不存在共享资源竞争,那么为了提高程序的效率,会自动消除这个没必要的锁。


锁粗化


将连续的加锁,解锁的操作链接到一起,扩展成一个范围更大的锁,来提高程序效率。



java锁的种类


这个得从多个方面去说


在获取锁的时候有两种获取方式


公平锁:指多个线程间按照申请顺序获取锁


非公平锁,指多线程获取锁的顺序不是按照申请顺序。


从锁的特性上说,锁是一种可入重锁,指的是当一个线程在外层方法获取到锁时,在进入内层调用方法会自动获取锁。


从实现上锁有


互斥锁/读写锁(独享锁/共享锁)


互斥锁(独享锁,写锁)指的是锁一次只能被一个线程所持有 实现有ReentrantLock,和ReadWriteLock中的写锁


共享锁(读锁)指的是锁一次可被多个线程持有,实现有ReadWriteLock中的读锁


分段锁,是在concurrentHashMap实现线程安全时所有的一种锁的设计


在锁的应用方面可以有乐观锁,悲观锁


悲观锁指的是悲观的认为对一个数据的并发操作都是不安全的,采取加锁的形式处理


而乐观锁则认为对一个数据的并发操作乐观的认为是安全的,不加锁处理。


所以在java中大多数都是使用悲观所编程,而乐观锁则应用在CAS无锁编程中。