`
fackyou200
  • 浏览: 310785 次
  • 性别: Icon_minigender_1
  • 来自: 山西太原
社区版块
存档分类
最新评论

Java多线程大总结

 
阅读更多

转载:http://50vip.com/blog.php?i=151

 

Java多线程在任何互联网公司的任何笔试任何面试几乎都会有,下面对Java的Thread多线程做一个总结,目的在于看完这篇文章,多线程的笔试面试再也不是问题。(文中标红的地方为重中之重)

主要从以下几个方面说明:

一.两种实现Java多线程的方法、注意点及区别

二.Java多线程中对于线程操作的方法

三.Java多线程中资源同步的方法


一、实现Java多线程的方法

这部分大部分人看过书应该都很清楚,有两种方法:继承Thread类、实现Runnable接口。

1.继承Thread类

大致代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.vip.thread;
/**
 * 继承Thread类实现多线程
 * @author Xewee.Zhiwei.Wang
 * @version 2013-3-25 下午3:00:21
 * @Contract wzwahl36@QQ.com or http://50vip.com/
 */
public class ThreadTest extends Thread {
    /**
     * 重写run方法
     */
    public void run() {
        for (int i = 0; i < 520; i++) {
            System.out.println("I Love You...");
        }
    }
    public static void main(String[] args) {
        new ThreadTest().start();
    }
}

2.实现Runnable接口

大致代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.vip.thread;
/**
 * 多线程实现runnable接口
 * @author Xewee.Zhiwei.Wang
 * @version 2013-3-25 下午2:59:03
 * @Contract wzwahl36@QQ.com or http://50vip.com/
 */
public class RunnableTest implements Runnable {
    public void run() {
        for (int i = 0; i < 520; i++) {
            System.out.println("I Love You...");
        }
    }
    public static void main(String[] args) {
        new ThreadTest().start();
    }
}

这两段代码需要注意的是:运行多线程使用的方法是start(),而不是覆写的run()方法,但是实际执行的代码块还是run()方法中的。为什么不能使用run(),主要原因是以为多线程操作会执行操作系统底层native方法,这一点你可以看start()方法的源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0 || this != me)
            throw new IllegalThreadStateException();
        group.add(this);
    start0();
    if (stopBeforeStart) {
        stop0(throwableFromStop);
    }
}
private native void start0();

从 代码可以看出:1.执行start()方法之后,此处调用的是start0()。并且这个这个方法用了native关键字,此关键字表示调用本地操作系统 的函数。因为多线程的实现需要本地操作系统的支持。2.start()方法多次执行会抛出 java.lang.IllegalThreadStateException异常。

3.两种实现方式的区别,如何选择?

实际上,看到Thread类的源代码之后,你就会发现其实Thread也是实现Runnable接口的:

1
2
3
4
5
6
7
class Thread implements Runnable {
    public void run() {
        if (target != null) {
             target.run();
        }
    }
}

其实Thread中的run方法调用的是Runnable接口的run方法。不知道大家发现没有,Thread和Runnable都实现了run方法,这种操作模式其实就是代理模式,这是23种设计模式中的一种。

既然二者同宗同源,那么怎么选择使用哪种方式实现Java多线程?个人推荐使用实现Runnable接口的方法,它有以下几个优点:

1)适合多个相同的程序代码的线程去处理同一个资源;

2)可以避免java中的单继承的限制;

3)增加程序的健壮性,代码可以被多个线程共享,代码和数据独立。

最后,PS一个:main方法其实也是一个线程。 在java中所以的线程都是同时启动的,至于什么时候,哪个先执行,完全看谁先得到CPU的资源。在java中,每次程序运行至少启动2个线程。一个是 main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM实际在就是在操作系统中启动了一 个进程。


二、Java多线程操作方法

主要操作方法有:isAlive()是否存活、join()强制执行、yield()暂停、sleep()休眠、interrupt()中断等等。另外还有设置线程优先级、守护线程的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.vip.thread;
/**
 * Java线程操作
 * @author Xewee.Zhiwei.Wang
 * @version 2013-3-25 下午3:29:38
 * @Contract wzwahl36@QQ.com or http://50vip.com/
 */
public class Test implements Runnable {
    public void run() {
        for (int i = 0; i < 520; i++) {
            System.out.println(Thread.currentThread().getName() + ": I Love You...");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Test(), "线程1");
        t1.start();
        System.out.println(t1.isAlive());
        Thread.sleep(2000);
        System.out.println(t1.isAlive());
    }
}

上述代码中用到了isAlive()、sleep()方法,程序执行结果是先true,后false。除此之外,其他的一些方法后面我会加入更多的、更有说服力的实例代码来说明。

另外再贴一段关于Thread的wait,notify,wait,sleep简单演示代码:

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
28
29
30
31
32
33
34
35
public class Test extends Thread {
    Object lock = null;
    boolean notifyFlag = false;
    public Test(Object lock, boolean notifyFlag) {
        this.lock = lock;
        this.notifyFlag = notifyFlag;
    }
    @Override
    public void run() {
        synchronized (lock) {
            System.out.println((notifyFlag == true ? "(notifyThread)" : "") + Thread.currentThread().getName()
                    + " hava in partA");
            try {
                // Thread.sleep(10000); // A
                if (notifyFlag)
                    lock.notifyAll();  // C
                // lock.notify();      // B
                else
                    lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println((notifyFlag == true ? "(notifyThread)" : "") + Thread.currentThread().getName()
                    + " hava in partB");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        new Test(lock, false).start();
        new Test(lock, false).start();
        new Test(lock, false).start();
        Thread.sleep(10000);
        new Test(lock, true).start();
    }
}

最后,在Java多线程操作方法结束之前PS一个:主线程(main方法线程)也有可能在子线程结束之前结束。并且子线程不受影响,不会因为主线程的结束而结束。在 java程序中,只要前台有一个线程在运行,整个java程序进程不会消失,所以此时可以设置一个后台守护线程,这样即使java进程小时了,这个后台线 程依然能够继续运行。在Eclipse中执行下面的代码,你会发现Eclipse标志程序运行的红色点点变灰了之后,表示主线程已经退出消失,但是在任务 管理器中仍然可以看到javaw.exe进程,这就是守护线程的作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.vip.thread;
/**
 * Java线程操作
 * @author Xewee.Zhiwei.Wang
 * @version 2013-3-25 下午3:29:38
 * @Contract wzwahl36@QQ.com or http://50vip.com/
 */
public class Test implements Runnable {
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + ": I Love You...");
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Test(), "线程1");
        t1.setDaemon(true);
        t1.start();
    }
}

 


三、Java多线程的资源同步(线程同步)

前面的一、二都是基础,几乎是用过Java多线程的孩子都知道上面的,但是简单实用过Java的确不一定知道资源或者线程的同步问题,所以这个问题也是面试官考核你对Java多线程的掌握程度。

学 过数据库系统的都知道:DBMS在进行数据库操作时必须遵守几个原则:一致性、完整性、永久性、原子性。遵守这几个原则的原因是防止多客户端操作时的数据 读脏问题,特别是对于数据一致性比较重要的时候,比如书本上最喜欢的例子就是银行存款取款问题。那么要遵守这几个原则我们在程序中该怎么做呢?那就是事务 操作,要么commit,要么rollback。

同样的,多线程操作和DBMS的事务操作类似,需要对共同访问的数据和资源进行同步(事务一致性),除此之外,线程同步还需要考虑线程造成死锁的情况,那么线程同步有哪些方式方法?所谓同步就是在同一时间,只能有一个线程在访问共享资源。

1.synchronized关键字

synchronized可以修饰一个代码块,也可以修饰一个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.vip.thread;
/**
 * Java线程操作
 * @author Xewee.Zhiwei.Wang
 * @version 2013-3-25 下午3:29:38
 * @Contract wzwahl36@QQ.com or http://50vip.com/
 */
public class Test implements Runnable {
    public void run() {
        for(int i = 0; i < 520; ++i){
            synchronized (this) {
                System.out.println(Thread.currentThread().getName() + ": I Love You...");
            }
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Test(), "线程1");
        Thread t2 = new Thread(new Test(), "线程2");
        t1.start();
        t2.start();
    }
}

上述代码是用synchronized修饰一个代码块的,其中,虽然有两个线程,但是这两个线程不可能同时运行被synchronized包围的代码块(本示例中并没有线程之间的共享资源)。

下面在举一个使用synchronized修饰方法的例子,这个例子很常见的就是在单例模式中(单例模式也是一个很重要的笔试面试点),直接贴代码,知道单例模式的再学习一下,不知道的好好学习一下,并google其他的博文。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.vip.singleton;
/**
 * 单例模式
 * @author Xewee.Zhiwei.Wang
 * @version 2013-3-25 下午4:13:17
 * @Contract wzwahl36@QQ.com or http://50vip.com/
 */
public class Singleton {
    private static Singleton INSTANCE = null;
    // Private constructor suppresses
    private Singleton() {}
    // default public constructor
    public synchronized static Singleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
}

写在synchronized的最后,还是PS一下:synchronized是一个大考点,需要说明一下的情况是:当一个类A的多个方法(b、c)都用synchronized修饰时,再new 2了线程,这两个线程持有同一个A的对象时,线程1调用b方法的同时,线程2可以调用c方法吗?答案是不行的。因此,在一个项目中如果过多的使用synchronized关键字,将会导致过多资源的同步,首先造成的就是程序执行效率低,另外一个问题就是容易造成死锁:因此,线程资源同步的粒度越小越好...

有关于synchronized的问题,我会另外开博文详细说明清楚,这里就点到为止。

2.java.util.concurrent.locks包下的接口Lock

Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题。示代码如下:

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
28
29
public class LockTest {
    public static void main(String[] args) {
        final Outputter1 output = new Outputter1();
        new Thread() {
            public void run() {
                output.output("zhangsan");
            };
        }.start();     
        new Thread() {
            public void run() {
                output.output("lisi");
            };
        }.start();
    }
}
class Outputter1 {
    private Lock lock = new ReentrantLock();// 锁对象
    public void output(String name) {
        // TODO 线程输出方法
        lock.lock();// 得到锁
        try {
            for(int i = 0; i < name.length(); i++) {
                System.out.print(name.charAt(i));
            }
        } finally {
            lock.unlock();// 释放锁
        }
    }
}

这 样就实现了和sychronized一样的同步效果,需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而是用 Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内。

如果说这 就是Lock,那么它不能成为同步问题更完美的处理方式,下面要介绍的是读写锁(ReadWriteLock),我们会有一种需求,在对数据进行读写的时 候,为了保证数据的一致性和完整性,需要读和写是互斥的,写和写是互斥的,但是读和读是不需要互斥的,这样读和读不互斥性能更高些,这种精确的加锁方式是 synchronized无法实现的。来看一下不考虑互斥情况的代码原型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ReadWriteLockTest {
    public static void main(String[] args) {
        final Data data = new Data();
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                public void run() {
                    for (int j = 0; j < 5; j++) {
                        data.set(new Random().nextInt(30));
                    }
                }
            }).start();
        }      
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                public void run() {
                    for (int j = 0; j < 5; j++) {
                        data.get();
                    }
                }
            }).start();
        }
    }
}
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
28
29
30
31
32
33
class Data {   
    private int data;// 共享数据
    private ReadWriteLock rwl = new ReentrantReadWriteLock();  
    public void set(int data) {
        rwl.writeLock().lock();// 取到写锁
        try {
            System.out.println(Thread.currentThread().getName() + "准备写入数据");
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.data = data;
            System.out.println(Thread.currentThread().getName() + "写入" + this.data);
        } finally {
            rwl.writeLock().unlock();// 释放写锁
        }
    }  
    public void get() {
        rwl.readLock().lock();// 取到读锁
        try {
            System.out.println(Thread.currentThread().getName() + "准备读取数据");
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "读取" + this.data);
        } finally {
            rwl.readLock().unlock();// 释放读锁
        }
    }
}

这个代码就是说明如何使用手动加锁的方式进行线程资源同步,并获得更高的性能。

3.ThreadLocal

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。

ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。

在 同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写, 什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。而ThreadLocal则从另一个角度来解决多线程的并发访 问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就 没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

由 于ThreadLocal中可以持有任何类型的对象,低版本JDK所提供的get()返回的是Object对象,需要强制类型转换。但JDK 5.0通过泛型很好的解决了这个问题,在一定程度地简化ThreadLocal的使用,概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换 空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变 量,因此可以同时访问而互不影响。

ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:

void set(Object value)

设置当前线程的线程局部变量的值。

public Object get()

该方法返回当前线程所对应的线程局部变量。

public void remove()

将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

protected Object initialValue()

返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

值 得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法也相应进 行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。

有关于ThreadLocal的内容非常之多,很多数据库连接池就是基于ThreadLocal的,例如OSChina的数据库链接池就是使用ThreadLocal自己封装的。关于ThreadLocal的内容大家可以自己再搜索学习一下。

分享到:
评论

相关推荐

    Java多线程知识点总结

    Java多线程是Java编程语言中一个非常重要的概念,它允许开发者在一个程序中创建多个执行线程并行运行,以提高程序的执行效率和响应速度。在Java中,线程的生命周期包含五个基本状态,分别是新建状态(New)、就绪...

    Java多线程编程总结

    ### Java多线程编程总结 #### 一、Java线程:概念与原理 1. **操作系统中线程和进程的概念** - 当前的操作系统通常为多任务操作系统,多线程是实现多任务的一种手段。 - **进程**:指内存中运行的应用程序,每个...

    Java多线程的总结

    Java多线程是Java编程中的一个核心概念,它在现代软件开发中扮演着至关重要的角色。多线程允许程序同时执行多个任务,提高了系统资源的利用率,提升了应用程序的响应速度和并发性能。对于大型分布式系统、Web应用...

    java多线程全面总结

    java多线程全面总结,简单的介绍多线程技术中的各种应用问题,是你对多线程有更多的认识!

    java多线程编程总结

    ### Java多线程编程总结 #### 一、Java线程:概念与原理 - **操作系统中线程和进程的概念** 当前的操作系统通常都是多任务操作系统,多线程是一种实现多任务的方式之一。在操作系统层面,进程指的是内存中运行的...

    40个Java多线程问题总结

    ### Java多线程问题总结 #### 一、多线程的作用与优势 1. **发挥多核CPU的优势:** - 当今计算机硬件普遍配备有多核CPU,利用多线程技术能够有效地分配任务到不同的核心上,使得计算资源得到最大化利用。在双核...

    java多线程总结(一)

    Java多线程是Java编程语言中的一个重要特性,它允许开发者创建并发执行的多个线程,从而提高程序的执行效率和响应速度。Java中实现多线程主要有两种方式:继承Thread类和实现Runnable接口。 ### 继承Thread类 在...

    java多线程分页查询

    ### Java多线程分页查询知识点详解 #### 一、背景与需求分析 在实际的软件开发过程中,尤其是在处理大量数据时,如何高效地进行数据查询成为了一个关键问题。例如,在一个用户众多的社交平台上,当用户需要查看...

    JAVA多线程(精典总结)

    总结一下,Java多线程涉及的内容广泛,包括线程的基本概念、创建、状态转换、调度和优先级管理。理解并掌握这些知识点对于编写高效并发的Java程序至关重要,也是面试中必不可少的技术点。在实际编程中,合理利用多...

    JAVA多线程编程技术PDF

    总结起来,“JAVA多线程编程技术PDF”涵盖了多线程的基本概念、同步机制、线程通信、死锁避免、线程池以及线程安全的集合类等内容。通过深入学习这份资料,开发者可以全面掌握Java多线程编程技术,提升程序的并发...

    Java 多线程学习总结归纳(附代码)

    Java多线程是Java编程中的核心概念,它允许程序同时执行多个任务,从而提升系统效率。在Java中,实现多线程主要有两种方式:继承Thread类和实现Runnable接口。下面是对Java多线程学习的详细解析。 1. **多线程概述*...

    Java 多线程学习总结6

    在“Java多线程学习总结6”这个主题中,我们可以深入探讨Java多线程的实现、管理及优化。下面将详细阐述相关知识点。 1. **线程的创建方式** - **继承Thread类**:自定义类继承Thread类,并重写run()方法,创建...

    我总结的Java多线程程序设计

    Java多线程程序设计是Java开发中的重要组成部分,它允许程序在同一时间执行多个任务,从而提高了系统的效率和响应性。本文将深入探讨Java多线程的相关概念和实现方式。 一、理解多线程 1. **线程定义**:线程是一...

    Java多线程英文版

    ### Java多线程编程知识点详解 #### 一、引言 在现代软件开发中,多线程编程是一项不可或缺的技术,尤其是在高性能和响应性要求较高的应用中。Java作为一种广泛使用的编程语言,提供了强大的多线程支持。本文将...

    武汉理工大学Java多线程实验源码

    总结来说,这个实验源码涵盖了Java多线程的基础和应用,包括线程的创建、运行、同步、通信,以及网络编程和数据库操作。通过这些实验,学生可以深入理解Java并发编程的核心概念,并掌握实际开发中的多线程设计技巧。

    Java多线程编程总结.doc

    Java多线程编程是Java开发中的重要组成部分,它允许程序同时执行多个任务,提升软件的效率和响应性。本文档详细介绍了Java中的线程概念、原理以及如何在Java中创建和启动线程。 首先,线程是操作系统实现多任务处理...

    java多线程编程大总结

    并发包中提供了很多高级功能,比如线程池、同步工具、并发集合等,这些都极大地丰富了Java多线程编程的生态。 在讨论Java多线程编程时,以下几个知识点是核心内容: 1. 线程的概念与原理:在操作系统中,线程是...

    java多线程之赛马程序实验8多线程练习下载进度

    总结来说,这个实验旨在帮助开发者深入理解Java多线程的概念,熟练运用`Thread`类的`run`和`start`方法,以及如何通过进度条来实时展示多线程任务的执行进度。通过实践,开发者可以更好地掌握并发编程的技巧,这对于...

Global site tag (gtag.js) - Google Analytics