java 多线程初探

一直都很想写关于多线程的东西,以来可以巩固巩固自己的知识,而来可以看看自己的掌握的水平,因为一直都觉得这方面挺有意思的好了。废话不多说,入正题。

java多线程,我们首先想多的是什么。进程,Thread,Runnable,start,run...

那我们就先从他们入手了。为什么会想到进程呢。以为一直都是多线程多进程的说。那他们有什么区别。

进程:进程是程序的运行和操作系统分配资源的最基本的独立单位。每个进程在没有特殊的处理下,是各自独立的。

线程呢:线程不能独立存在,线程必须依附于进程,线程共享进程的资源,从某种意义上来说,使用线程对系统开销更小,效率应该高点。

在java中,主要两种方式能够实现多线程的编程。一种是继承Thread类,一种是实现Runnable接口。要说两者的区别:我觉得无非是

1.Runnable更适合线程间的资源共享,也更推荐使用Runnable。

2.Runnable是接口所以可以避免Thread单继承的缺陷。

好了,这只是嘴上说说而已,还是来看看具体代码吧。

我们来分别实现一个经典的窗口买票程序:

继承Thread:

<span style="font-size:14px;">class SalerThread extends Thread{
    private int sum = 5;

    public void sale(){
        System.out.println(getName() + "号窗口正在售票,还剩" + (--sum) + "张票.");
    }

    @Override
    public void run(){
        while (sum > 0) {
            sale();
        }
    }
}
public class TestThread {
    public static void main(String[] args) {
        SalerThread thread = new SalerThread();
        SalerThread thread1 = new SalerThread();
        SalerThread thread2 = new SalerThread();
        thread.setName("1");
        thread1.setName("2");
        thread2.setName("3");
        thread.start();
        thread1.start();
        thread2.start();
	}
}

	我们可以看到结果,很明显不符合我们的实际情况,但是但我们换成实现Runnable呢!
class SalerRunnale implements Runnable{
    private int sum = 5;

    public void sale(){
            System.out.println(Thread.currentThread().getName() + "号窗口正在售票,还剩" + (--sum) + "张票.");
    }

    @Override
    public void run() {
        while (sum > 0) {
            sale();
        }
    }
}
public class TestThread {
    public static void main(String[] args) {
        SalerRunnale runnale = new SalerRunnale();
        Thread thread = new Thread(runnale, "1");
        Thread thread1 = new Thread(runnale, "1");
        Thread thread2 = new Thread(runnale, "1");
        thread.start();
        thread1.start();
        thread2.start();
    }
}</span>

两个线程主体基本一样,就是在调用Thread中的方法时,我们需要多写一步Thread.currentThread.

也许你想问了,为什么我们是调用start()呢,我们当然也可以调用run()啦,我保证,编译器不会报错,而且有结果,但是,这个结果是按照我们调用所谓的线程的顺序输出的。

也许你会想,哼,肯定只是偶然,但当我们多次调用之后,结果依然不变。好了,其实,在直接调用run()方法时,jvm只是把它当做一个普通方法进行调用,并不会给他重新分配一个线程。所以一定要记住,在写新线程的时候,调用的是它的start()方法,而不是我们重写的run()方法。

好了,现在聊一聊多线程的一些状态:

线程可以有6种状态:

*New(新创建)

*Runnable(可运行)

*Blocked(被阻塞)

*Waiting(阻塞)

*Timed waiting(计时等待)

*Terminated(被终止)

                                                                                  摘自:http://my.oschina.net/mingdongcheng/blog/139263

线程之间是有自己的优先级的,相信这个词你应该很熟悉。默认情况下,子线程是继承父线程的优先级,当然,你也可以通过调用setPriority方法来设置优先级。线程的优先级应该在MIN_PRIORITY(Thread中定义为1)到MAX_PRIORITY(Thread中定义为10)。

有了jvm默认是先调用优先级高的线程,默认的是NORM_PRIORITY(Thread中定义5),当我们在线程中调用yield()时,会使当前线程处于一种让步的状态,即先让不低于自己的程序先运行,假如没有比自己高的呢,依旧自己运行。当然这不是绝对的,他依靠操作系统的支持。

有一种线程叫做守护线程。顾名思义,他是需要被守护者的,假如只有他自己则没有意义。所以,他不会独立存在。调用他们非常简单。只需要在start()之前调用setDeamon(true)即可。

但是,他依旧有缺陷,守护进程不应该去访问固有资源,如文件、数据库,因为他会在任何时候甚至在一个操作的中间发生中断。

我们在一个线程运行到一半想终止他怎么办。java一开始提供了stop方法,但是因为他很不安全,所以被弃用了。不仅如此,java还提供线程挂起的方法suspend和唤醒的方法resume。不过suspend很容易导致死锁的情况,所以也已经被抛弃了。

但是假如我们需要暂停一个线程怎么办。

我们可以设置一个boolean值。

如private boolean running;

需要暂停的时候

public synchronized void stop(){

running = false;

}

而我们在run方法中假如running的条件判断

while (running){

//your code...

}

如此,我们便实现了自己的暂停方法。当然才学到这的你可能会问为什么要加synchronized呢,他是什么意思呢。这个问题我们稍后会介绍,当然,你也可以看看我之前写的那一篇博客---超链接---。

好了,到这,我们还需要说一下线程的中断。线程在执行完run方法最后一条语句后,或者有未捕获的异常时,线程会终止。当然,我们可以调用interrupt方法来终止线程。每个线程都有表示中断状态的boolean值。每个线程都应该不时检查这个标志位,当然,假如在调用它之前,线程已经调用了wait,sleep或者join,他的interrupt
status将会被清除,并且会抛出一个InterruptedException.异常。

Thread还有一个join方法,在一个线程A中调用另一个线程B,线程B调用join,那么线程A将等待线程B执行完毕。或者调用join的重载方法,设置时间表示之多等待多久。

<span style="font-size:14px;"><span style="font-size:12px;">class RunnableA implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 5; i++){
            System.out.println("threadA :" + i);
        }
    }
}

public class TestForJoin {
    public static void main(String[] args) throws InterruptedException {
        RunnableA runnableA = new RunnableA();
        Thread threadA = new Thread(runnableA);
        threadA.start();
//        threadA.join();
        for (int i = 0; i < 5; i++){
            System.out.println(Thread.currentThread().getName() + " :" + i);
        }
    }
}</span></span>

当我们注释threadA.join();时,结果如图:

当我们取消注释:

假如是独立的两个线程,一个线程调用join,对另外一个线程是不起作用的。

<span style="font-size:14px;"><span style="font-size:12px;">public class TestForJoin {
    public static void main(String[] args) throws InterruptedException {
        RunnableA runnableA = new RunnableA();
        Thread threadA = new Thread(runnableA, "ThreadA");
        Thread ThreadB = new Thread(runnableA, "ThreadB");
        threadA.start();
        threadB.start();
        threadA.join();
        for (int i = 0; i < 5; i++){
            System.out.println(Thread.currentThread().getName() + " :" + i);
        }
    }
}</span></span>

因为在一个线程A中调用另一个线程B,B线程调用join,这只会阻塞A线程,假如同时存在线程C,C依旧可以和B抢占CPU.

	我们说过线程之间的资源出自身的一些变量方法,其余都共享,所以,多线程必定会产生多个线程同时修改一块内存块的问题。我们该如何避免呢。
	最简单也是最有效的方法当然是不使用多线程啦。但是当我们必须使用多线程的时候我们该怎么办。
	我们先来做一个小程序,多线程按顺序打印1-9,要求先打印1-3,在打印4-6,最后全部输出。
<span style="font-size:14px;"><span style="font-size:12px;">public class ThreadPrint {
    static Lock lock = new ReentrantLock();
    static Condition reachThree = lock.newCondition();
    static Condition reachSix = lock.newCondition();

    public static void main(String[] args) {
        final int[] integer = {0};

        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    for (int i = 1; i <= 3; i++) {
                        System.out.println(i);
                    }
                    integer[0] = 3;
                    reachThree.signal();
                } finally {
                    lock.unlock();
                }
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    while (integer[0] != 3) {
                        reachThree.await();
                    }
                    for (int i = 4; i <= 6; i++) {
                        System.out.println(i);
                    }
                    integer[0] = 6;
                    reachSix.signal();
                } catch (InterruptedException e) {

                } finally {
                    lock.unlock();
                }
            }
        });

        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try {
                    while (integer[0] != 6) {
                        reachSix.await();
                    }
                    for (int i = 6; i <= 9; i++) {
                        System.out.println(i);
                    }
                    integer[0] = 9;
                } catch (InterruptedException e) {

                } finally {
                    lock.unlock();
                }
            }
        });
        threadA.start();
        threadB.start();
        threadC.start();
    }

}</span></span>

Lock是concurrent中给我们提供的一种锁机制,当一个线程进入一个类时,便给它加上锁,当还有其他的线程在之前线程还未退出,请求范文该类时,就会被阻塞,直至第一个进入的线程退出并解除锁。condition是一个条件,一个锁可以有一个或多个条件。当线程因为一个条件被阻塞时,会自动释放它的锁,一边其他线程进入并唤醒它。我们调用的锁ReentrantLock是一种可重入的锁,但一个线程进入一个类,调用一个含锁的方法时,它的计数器会加1,当释放一个锁时便减一,每次有其他线程进入该类会检查计数器是否为0.我们还可以在调用构造函数时将它设置成公平锁new ReentrantLock(true),即等待时间越长越有可能获得该锁,但是它的效率相比不公平锁会低很多,所以不是必须使用它,我们总是使用缺省的不公平锁。

	当然还有一种ReentrantReadWriteLock,当线程频繁对数据进行读操作时,他是一个很好的选择,它有读锁和写锁,它允许多个线程进行读取,指定数量的线程进行写操作。所以效率高。每次进行读操作时利用ReentrantReadWriteLock.ReadLock,写操作便用ReentrantReadWriteLock。
	当然java还提供一种更为简单的机制synchronized,可以设置同步块和同步方法,具体可以参考我的另一篇博文:初探java 对象中wait(),notify(),notifyAll() 和线程中的synchronized。需要多说的是,synchronized调用的实际上是一个内部锁,它只含有一个条件。
	你会想那什么时候用synchronized什么时候用Lock呢。
	1.其实最好是两个都不用,因为在我们的concurrent包中,有同步队列,他会自动帮我们处理数据的同步问题。
	2.如果synchronized能够符合我们要求时,尽量使用它。
	3.当我们需要用到Lock/Condition的特殊功能时,才考虑使用它。

	
				
时间: 2024-10-12 06:02:10

java 多线程初探的相关文章

Java多线程系列--“JUC锁”11之 Semaphore信号量的原理和示例

概要 本章,我们对JUC包中的信号量Semaphore进行学习.内容包括:Semaphore简介Semaphore数据结构Semaphore源码分析(基于JDK1.7.0_40)Semaphore示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3534050.html Semaphore简介 Semaphore是一个计数信号量,它的本质是一个"共享锁". 信号量维护了一个信号量许可集.线程可以通过调用acquire()来获取信号量的许可

从JAVA多线程理解到集群分布式和网络设计的浅析

对于JAVA多线程的应用非常广泛,现在的系统没有多线程几乎什么也做不了,很多时候我们在何种场合如何应用多线程成为一种首先需要选择的问题,另外关于java多线程的知识也是非常的多,本文中先介绍和说明一些常用的,在后续文章中如果有必要再说明更加复杂的吧,本文主要说明多线程的一下几个内容: 1.在应用开发中什么时候选择多线程? 2.多线程应该注意些什么? 3.状态转换控制,如何解决死锁? 4.如何设计一个具有可扩展性的多线程处理器? 5.多线程联想:在多主机下的扩展-集群? 6.WEB应用的多线程以及

java多线程心得

多并发的时候,在什么情况下必须加锁?如果不加锁会产生什么样的后果. 加锁的场景跟java的new thread和Runnable的关系是什么? 看看java的concurrentMap源码. 还有spring 的web.xml启动执行源码 spring aop http://www.cnblogs.com/FDROSE1001/p/3661895.html activemq的本质是什么? java的jms hibernate由配置文件映射到实体类的本质是什么? java反射 spring aop

Rhythmk 一步一步学 JAVA (21) JAVA 多线程

1.JAVA多线程简单示例 1.1 .Thread  集成接口 Runnable 1.2 .线程状态,可以通过  Thread.getState()获取线程状态: New (新创建) Runnable (可以运行) Blocked  (被阻塞) Waiting  (等待) Timed waiting (计时等待) Terminated  (被终止) ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27

(转载)Java多线程入门理解

转载出处http://blog.csdn.net/evankaka 写在前面的话:此文只能说是java多线程的一个入门,其实Java里头线程完全可以写一本书了,但是如果最基本的你都学掌握好,又怎么能更上一个台阶呢?如果你觉得此文很简单,那推荐你看看Java并发包的的线程池(Java并发编程与技术内幕:线程池深入理解),或者看这个专栏:Java并发编程与技术内幕.你将会对Java里头的高并发场景下的线程有更加深刻的理解. 目录(?)[-] 一扩展javalangThread类 二实现javalan

Java多线程系列--“JUC锁”02之 互斥锁ReentrantLock

ReentrantLock介绍 ReentrantLock是一个可重入的互斥锁,又被称为"独占锁". 顾名思义,ReentrantLock锁在同一个时间点只能被一个线程锁持有:而可重入的意思是,ReentrantLock锁,可以被单个线程多次获取.ReentrantLock分为"公平锁"和"非公平锁".它们的区别体现在获取锁的机制上是否公平."锁"是为了保护竞争资源,防止多个线程同时操作线程而出错,ReentrantLock在

synchronized与static synchronized 的差别、synchronized在JVM底层的实现原理及Java多线程锁理解

本Blog分为例如以下部分: 第一部分:synchronized与static synchronized 的差别 第二部分:JVM底层又是怎样实现synchronized的 第三部分:Java多线程锁,源码剖析 第一部分:synchronized与static synchronized的差别 1.synchronized与static synchronized 的差别 synchronized是对类的当前实例进行加锁,防止其它线程同一时候訪问该类的该实例的全部synchronized块.注意这里

深入聊聊Java多线程

一.背景 在没有学习Java多线程以前,总觉得多线程是个很神秘的东西,只有那些大神才能驾驭,新年假期没事就来学习和了解一下Java的多线程,本篇博客我们就来从头说一下多线程到底是怎么回事. 二.概述 1.进程的概念 每一个正在运行的程序都是一个进程,它是系统进行资源分配和调用的独立单位.且 每一个进程都有自己的内存空间和系统资源. 2.线程的概念 是进程中的单个顺序控制流,是一条执行路径.每个进程都可以拥有一个或者多个线程.各个线程之间都共享所属的那个进程的内存空间和系统资源. 3.单线程和多线

Java多线程 2 线程的生命周期和状态控制

一.线程的生命周期 线程状态转换图: 1.新建状态 用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态.处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable). 注意:不能对已经启动的线程再次调用start()方法,否则会出现Java.lang.IllegalThreadStateException异常. 2.就绪状态 处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列(尽管是采用队列形式,事实上,把它