JAVA多线程
最近在做代码优化时学习和研究了下JAVA多线程的使用,看了菜鸟们的见解后做了下总结。
1.JAVA多线程实现方式
JAVA多线程实现方式主要有三种:继承Thread类、实现Runnable接口、使用ExecutorService、Callable、Future实现有返回结果的多线程。其中前两种方式线程执行完后都没有返回值,只有最后一种是带返回值的。
2.继承Thread类实现多线程
继承Thread类的方法尽管被我列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。例如:
public class MyThread extends Thread {
public void run() {
System.out.println("MyThread.run()");
}
}
在合适的地方启动线程如下:
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
3.实现Runnable接口方式实现多线程
如果自己的类已经extends另一个类,就无法直接extends Thread,此时,必须实现一个Runnable接口,如下:
public class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println("MyThread.run()");
}
}
为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例:
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
事实上,当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考JDK源代码:
public void run() {
if (target != null) {
target.run();
}
}
4.使用ExecutorService、Callable、Future实现有返回结果的多线程
ExecutorService、Callable、Future这个对象实际上都是属于Executor框架中的功能类。想要详细了解Executor框架的可以访问http://www.javaeye.com/topic/366591 ,这里面对该框架做了很详细的解释。返回结果的线程是在JDK1.5中引入的新特征,确实很实用,有了这种特征我就不需要再为了得到返回值而大费周折了,而且即便实现了也可能漏洞百出。
可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口。执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了,再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问题可以直接使用。代码如下:
import java.util.concurrent.*;
import java.util.Date;import java.util.List;import java.util.ArrayList;/*** Java线程:有返回值的线程
*
* @author wb_qiuquan.ying*/@SuppressWarnings("unchecked")public class Test {public static void main(String[] args) throws ExecutionException,
InterruptedException {
System.out.println("----程序开始运行----");
Date date1 = new Date();
int taskSize = 5;
// 创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 创建多个有返回值的任务
List<Future> list = new ArrayList<Future>();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + " ");
// 执行任务并获取Future对象
Future f = pool.submit(c);
// System.out.println(">>>" + f.get().toString()); list.add(f);
}
// 关闭线程池 pool.shutdown();
// 获取所有并发任务的运行结果
for (Future f : list) {
// 从Future对象上获取任务的返回值,并输出到控制台
System.out.println(">>>" + f.get().toString());
}
Date date2 = new Date();
System.out.println("----程序结束运行----,程序运行时间【" + (date2.getTime() - date1.getTime()) + "毫秒】");
}
}
class MyCallable implements Callable<Object>
{
private String taskNum;MyCallable(String taskNum) {
this.taskNum = taskNum;
}
public Object call() throws Exception { System.out.println(">>>" + taskNum + "任务启动");
Date dateTmp1 = new Date();
Thread.sleep(1000);
Date dateTmp2 = new Date();
long time = dateTmp2.getTime() - dateTmp1.getTime();
System.out.println(">>>" + taskNum + "任务终止");
return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
}
}
代码说明:
上述代码中Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads) //创建固定数目线程的线程池。
public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ExecutorService newSingleThreadExecutor()
创建一个单线程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。
ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成
Java线程中run和start方法的区别
Thread类中run()和start()方法的区别如下:
run()方法:在本线程内调用该Runnable对象的run()方法,可以重复多次调用;
start()方法:启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程;
package com.ljq.test;
public class ThreadTest {
public static void main(String[] args){
Thread thread=new ThreadDemo();
//第一种
//表明: run()和其他方法的调用没任何不同,main方法按顺序执行了它,并打印出最后一句
//thread.run();
//第二种
//表明: start()方法重新创建了一个线程,在main方法执行结束后,由于start()方法创建的线程没有运行结束,
//因此主线程未能退出,直到线程thread也执行完毕.这里要注意,默认创建的线程是用户线程(非守护线程)
//thread.start();
//第三种
//1、为什么没有打印出100句呢?因为我们将thread线程设置为了daemon(守护)线程,程序中只有守护线程存在的时候,是可以退出的,所以只打印了七句便退出了
//2、当java虚拟机中有守护线程在运行的时候,java虚拟机会关闭。当所有常规线程运行完毕以后,
//守护线程不管运行到哪里,虚拟机都会退出运行。所以你的守护线程最好不要写一些会影响程序的业务逻辑。否则无法预料程序到底会出现什么问题
//thread.setDaemon(true);
//thread.start();
//第四种
//用户线程可以被System.exit(0)强制kill掉,所以也只打印出七句
thread.start();
System.out.println("main thread is over");
System.exit(1);
}
public static class ThreadDemo extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("This is a Thread test"+i);
}
}
}
}
1: 守护线程 :
守护线程(DaemonThread)是某些提供通用服务的在后台运行的程序,是优先级最低的线程。当所有的非守护线程执行结束后,程序会结束所有的守护线程而终止运行。如果当前还有非守护线程的线程在运行,则程序不会终止,而是等待其执行完成。
例子:
import java.util.concurrent.TimeUnit;
public class SimpleDaemons implements Runnable {
@Override
public void run() {
try {
System.out.println("Start daemons");
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
System.out.println("sleep() interrupted");
} finally {
System.out.println("Finally is running");
}
}
}
public static void main(String[] args) {
try {
Thread thread = new Thread(new SimpleDaemons());
System.out.println(thread.isDaemon());
// thread.setDaemon(true); //设置为守护线程
thread.start();
} catch (Exception e) {
}
System.out.println("main end!");
}
输出结果:
Start daemons
Finally没有执行,如果注释掉daemon.setDaemon(true)设置守护进程这一句代码。
输出结果:
Start daemons
Finally is running
结果原因是,main()是这个程序中唯一的非守护线程,当没有非守护线程在运行时,JVM强制推出终止守护线程的运行。
通过Thread对象的setDaemon方法可以设置线程是否为守护线程,通过isDaemon方法可以判断线程对象是否为守护线程。
由守护线程创建的线程对象不论有没有通过setDaemon方法显式设置,都是守护线程。
2: synchronized线程同步
(1).对象方法同步: 每个对象有一个线程同步锁与之关联,同一个对象的不同线程在同一时刻只能有一个线程调用methodA方法。
public synchronized void methodA() {
System.out.println("object method synchronized!");
}
(2).类所有对象方法同步(标识为static的方法):静态方法的线程同步锁对类的所有对象都起作用,即所有对象的线程在同一时刻只能有一个类的一个线程调用该方法。
public synchronized static void methodB() {
System.out.println("synchronized static");
}
(3).对象同步代码块:使用当前对象作为线程同步锁,同一个对象的不同线程在同一时刻只能有一个线程调用methodC方法中的代码块。
public void methodC() {
synchronized (this) {
System.out.println("object synchronized block");
}
}
(4).类同步代码块: 使用类字节码对象作为线程同步锁,类所有对象的所有线程在同一时刻只能有一个类的一个线程调用methodD的同步代码块。
public void methodC() {
synchronized (NullTest.class) {
System.out.println("class synchronized block!");
}
}
注意:线程的同步是针对对象的,不论是同步方法还是同步代码块,都锁定的是对象,而非方法或代码块本身。每个对象只能有一个线程同步锁与之关联。
如果一个对象有多个线程同步方法,只要一个线程访问了其中的一个线程同步方法,其它线程就不能同时访问这个对象中任何一个线程同步方法。
synchronized的实现方式
synchronized的类型可以分为两种:
-
synchronized method -
synchronized block
两者的实现方式是不一样的,jvm规范中写道,编译后的synchronized method 会有一个ACC_SYNCHRONIZED 的flag,也就是说当jvm的方法调用指令(the method invocation instruction)从the run-time contant pool 中查找到这个method的时候,已经知道它是一个synchronized method,所以锁操作是由方法调用以及返回指令来控制的。
而 synchronized block 的锁是由 monitorenter 和 monitorexit 这两个指令来控制。
可以通过 javap 命令来“反汇编”一下class文件。
java代码:
public class StaticMethodTest {
public static synchronized void staticMethod() { }
public static void staticMethod1() {
synchronized (StaticMethodTest.class) {
// ...
}
}
public void memberMethod() { }
}
}
编译成class文件后执行
-
javap -verbose StaticMethodTest
两个静态方法的输出结果分别为
-
public static synchronized void staticMethod();
-
descriptor: ()V
-
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
-
Code:
-
stack=0, locals=0, args_size=0
-
0: return
-
LineNumberTable:
-
line 2: 0
-
public static void staticMethod1();
-
descriptor: ()V
-
flags: ACC_PUBLIC, ACC_STATIC
-
Code:
-
stack=2, locals=2, args_size=0
-
0: ldc #2 // class StaticMethodTest
-
2: dup
-
3: astore_0
-
4: monitorenter
-
5: aload_0
-
6: monitorexit
-
7: goto 15
-
10: astore_1
-
11: aload_0
-
12: monitorexit
-
13: aload_1
-
14: athrow
-
15: return
-
Exception table:
-
from to target type
-
5 7 10 any
-
10 13 10 any
其实不管静态方法还是成员方法,synchronized的实现方式都是一样的
类和对象
首先要了解的就是类究竟是怎么来的。
JVM拿到编译器编译好的class文件后,首先会把文件载入到内存中,class文件当然会有自己的格式,所以需要由ClassLoader来解析文件的内容,这个解析出来的内容会用一个Class类的实例 - Class object 来表示,这个object可以通过Java的 ClassName.class 来获取。
也就是说,Class object 是一个Class类型的实例(instance),而对象是一个ClassName的instance。Class 和 ClassName都是类型,ClassName是由class关键字定义的,而Class是内置类型。
因此成员方法的synchronized method 就等价于 synchronized (this) block,即下面两种方式是等价的。
-
public synchronized void fun1() {
-
// do something here
-
}
-
-
public synchronized void fun2() {
-
synchronized (this) {
-
// do something here
-
}
-
}
成员方法是属于this,而静态方法是属于Class Object,那么静态方法的 synchronized method也就等价于下面这种形式的 synchronized block 了。
-
public static synchronized void fun2() {
-
synchronized (ClassName.class) {
-
// do something here
-
}
-
}
验证代码
定义了三个静态方法,分别采用不同的锁机制,并且每个方法都是一个死循环。然后再定义三个线程分别执行调用三个方法。
-
import java.util.concurrent.*;
-
-
public class SynchronizedTest {
-
-
private static Object lock = new Object();
-
-
public synchronized static void fun() {
-
while (true) {
-
System.out.println("in fun");
-
try {
-
TimeUnit.MILLISECONDS.sleep(500);
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
-
-
public static void fun1() {
-
synchronized (SynchronizedTest.class) {
-
while (true) {
-
System.out.println("in fun1");
-
try {
-
TimeUnit.MILLISECONDS.sleep(500);
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
}
-
-
-
public static void fun2() {
-
synchronized (lock) {
-
while (true) {
-
System.out.println("in fun2");
-
try {
-
TimeUnit.MILLISECONDS.sleep(500);
-
} catch (InterruptedException e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
}
-
-
-
public static void main(String[] args) {
-
new Thread() {
-
@Override
-
public void run() {
-
fun();
-
}
-
}.start();
-
-
-
new Thread() {
-
@Override
-
public void run() {
-
fun1();
-
}
-
}.start();
-
-
-
new Thread() {
-
@Override
-
public void run() {
-
fun2();
-
}
-
}.start();
-
}
-
}
运行结果就是 in fun 和 in fun2 交替出现,就是没有 in fun1。
只要运行fun的线程不交出锁,fun1就无法被方法,因为他们都是共享了 Class object 的锁。
1. 类锁:在代码中的方法上加了static和synchronized的锁,或者synchronized(xxx.class)的代码段,如下文中的increament();
2.对象锁:在代码中的方法上加了synchronized的锁,或者synchronized(this)的代码段,只在当前类中有效。
3.私有锁:在类内部声明一个私有属性如private Object lock,在需要加锁的代码段synchronized(lock),如下文中的synMethodWithObj()。
2. synchronized methods(){} 与synchronized(this){}之间没有什么区别,只是 synchronized methods(){} 便于阅读理解,
而synchronized(this){}可以更精确的控制冲突限制访问区域,有时候表现更高效率。
3.synchronized关键字是不能继承的 也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。
继承类需要你显式的指定它的某个方法为synchronized方法;

浙公网安备 33010602011771号