转载: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中,线程的生命周期包含五个基本状态,分别是新建状态(New)、就绪...
### Java多线程编程总结 #### 一、Java线程:概念与原理 1. **操作系统中线程和进程的概念** - 当前的操作系统通常为多任务操作系统,多线程是实现多任务的一种手段。 - **进程**:指内存中运行的应用程序,每个...
Java多线程是Java编程中的一个核心概念,它在现代软件开发中扮演着至关重要的角色。多线程允许程序同时执行多个任务,提高了系统资源的利用率,提升了应用程序的响应速度和并发性能。对于大型分布式系统、Web应用...
java多线程全面总结,简单的介绍多线程技术中的各种应用问题,是你对多线程有更多的认识!
### Java多线程问题总结 #### 一、多线程的作用与优势 1. **发挥多核CPU的优势:** - 当今计算机硬件普遍配备有多核CPU,利用多线程技术能够有效地分配任务到不同的核心上,使得计算资源得到最大化利用。在双核...
Java多线程是Java编程语言中的一个重要特性,它允许开发者创建并发执行的多个线程,从而提高程序的执行效率和响应速度。Java中实现多线程主要有两种方式:继承Thread类和实现Runnable接口。 ### 继承Thread类 在...
### Java多线程分页查询知识点详解 #### 一、背景与需求分析 在实际的软件开发过程中,尤其是在处理大量数据时,如何高效地进行数据查询成为了一个关键问题。例如,在一个用户众多的社交平台上,当用户需要查看...
总结一下,Java多线程涉及的内容广泛,包括线程的基本概念、创建、状态转换、调度和优先级管理。理解并掌握这些知识点对于编写高效并发的Java程序至关重要,也是面试中必不可少的技术点。在实际编程中,合理利用多...
总结起来,“JAVA多线程编程技术PDF”涵盖了多线程的基本概念、同步机制、线程通信、死锁避免、线程池以及线程安全的集合类等内容。通过深入学习这份资料,开发者可以全面掌握Java多线程编程技术,提升程序的并发...
Java多线程是Java编程中的核心概念,它允许程序同时执行多个任务,从而提升系统效率。在Java中,实现多线程主要有两种方式:继承Thread类和实现Runnable接口。下面是对Java多线程学习的详细解析。 1. **多线程概述*...
在“Java多线程学习总结6”这个主题中,我们可以深入探讨Java多线程的实现、管理及优化。下面将详细阐述相关知识点。 1. **线程的创建方式** - **继承Thread类**:自定义类继承Thread类,并重写run()方法,创建...
Java多线程程序设计是Java开发中的重要组成部分,它允许程序在同一时间执行多个任务,从而提高了系统的效率和响应性。本文将深入探讨Java多线程的相关概念和实现方式。 一、理解多线程 1. **线程定义**:线程是一...
总结来说,这个实验源码涵盖了Java多线程的基础和应用,包括线程的创建、运行、同步、通信,以及网络编程和数据库操作。通过这些实验,学生可以深入理解Java并发编程的核心概念,并掌握实际开发中的多线程设计技巧。
并发包中提供了很多高级功能,比如线程池、同步工具、并发集合等,这些都极大地丰富了Java多线程编程的生态。 在讨论Java多线程编程时,以下几个知识点是核心内容: 1. 线程的概念与原理:在操作系统中,线程是...
总结来说,这个实验旨在帮助开发者深入理解Java多线程的概念,熟练运用`Thread`类的`run`和`start`方法,以及如何通过进度条来实时展示多线程任务的执行进度。通过实践,开发者可以更好地掌握并发编程的技巧,这对于...