java多线程实例---很有用 详细介绍
Posted zhangtian6691844
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java多线程实例---很有用 详细介绍相关的知识,希望对你有一定的参考价值。
实现线程的方式有两种: 1、继承java.lang.Thread,并重写它的run()方法,将线程的执行主体放入其中。 2、实现java.lang.Runnable接口,实现它的run()方法,并将线程的执行主体放入其中。 ==多线程的执行逻辑: 当主线程被挂起时, 其它就绪的线程会根据选择最
实现线程的方式有两种:
1、继承java.lang.Thread,并重写它的run()方法,将线程的执行主体放入其中。
2、实现java.lang.Runnable接口,实现它的run()方法,并将线程的执行主体放入其中。
==>多线程的执行逻辑:
当主线程被挂起时, 其它就绪的线程会根据选择最高优先级的来执行;
当主线程的挂起时间 > 子线程的执行时间时,子线程执行完后回到主线程,等待主线程醒来.
当主线程的挂起时间 < 子线程的执行时间时,主线程挂起时间到的,自动醒来,回到主线程,此时可以判断子线程是否存在,若有,可stop之.
(2)
两种方式的区别:
继承Thread类的方式实现起来较为简单,但是继承它的类就不能再继承别的类了,因此也就不能继承别的类的有用的方法了。而使用是想Runnable 接口的方式就不存在这个问题了,而且这种实现方式将线程主体和线程对象本身分离开来,逻辑上也较为清晰,所以推荐大家更多地采用这种方式。
run()方法中包含的是线程的主体,也就是这个线程被启动后将要运行的代码,它跟线程的启动没有任何关系。
上面两种实现线程的方式在启动时会有所不同。
#
ThreadTest tt = new ThreadTest();
#
// 启动线程
#
tt.start();
#
// 创建一个线程实例
#
Thread t = new Thread(new RunnableTest());
#
// 启动线程
#
t.start();
线程的状态:
在Java 1.4及以下的版本中,每个线程都具有新建、可运行、阻塞、死亡四种状态,但是在Java 5.0及以上版本中,线程的状态被扩充为新建、可运行、阻塞、等待、定时等待、死亡六种。线程的状态完全包含了一个线程从新建到运行,最后到结束的整个生 命周期。线程状态的具体信息如下:
1. NEW(新建状态、初始化状态):线程对象已经被创建,但是还没有被启动时的状态。
这段时间就是在我们调用new命令之后,调用start()方法之前。
2. RUNNABLE(可运行状态、就绪状态):
在我们调用了线程的start()方法之后线程所处的状态。处于RUNNABLE状态的线程在JAVA虚拟机(JVM)上是运行着的,但是它可能还正在等待操作系统分配给它相应的运行资源以得以运行。
3. BLOCKED(阻塞状态、被中断运行):线程正在等待其它的线程释放同步锁,以进入一个同步块或者同步方法继续运行;或者它已经进入了某个同步块或同步 方法,在运行的过程中它调用了某个对象继承自java.lang.Object的wait()方法,正在等待重新返回这个同步块或同步方法。
4. WAITING(等待状态):当前线程调用了java.lang.Object.wait()、 java.lang.Thread.join()或者java.util.concurrent.locks.LockSupport.park()三个 中的任意一个方法,正在等待另外一个线程执行某个操作。比如一个线程调用了某个对象的wait()方法,正在等待其它线程调用这个对象的notify() 或者notifyAll()(这两个方法同样是继承自Object类)方法来唤醒它;或者一个线程调用了另一个线程的join()(这个方法属于 Thread类)方法,正在等待这个方法运行结束。
5. TIMED_WAITING(定时等待状态):当前线程调用了 java.lang.Object.wait(long timeout)、java.lang.Thread.join(long millis)、java.util.concurrent.locks.LockSupport.packNanos(long nanos)、java.util.concurrent.locks.LockSupport.packUntil(long deadline)四个方法中的任意一个,进入等待状态,但是与WAITING状态不同的是,它有一个最大等待时间,即使等待的条件仍然没有满足,只要到 了这个时间它就会自动醒来。
6. TERMINATED(死亡状态、终止状态):
线程完成执行后的状态。线程执行完run()方法中的全部代码,从该方法中退出,进入TERMINATED状态。还有一种情况是run()在运行过程中抛出了一个异常,而这个异常没有被程序捕获,导致这个线程异常终止进入TERMINATED状态。
(3)
实例一:
import java.io.*;
//多线程编程
public
class MultiThread
public
static
void main(String args[])
System.out.println(
"我是主线程!");
//下面创建线程实例thread1
ThreadUseExtends thread1=
new ThreadUseExtends();
//创建thread2时以实现了Runnable接口的THhreadUseRunnable类实例为参数
Thread thread2=
new Thread(
new ThreadUseRunnable(),
"SecondThread");
thread1.start();
//启动线程thread1使之处于就绪状态
//thread1.setPriority(6);//设置thread1的优先级为6
//优先级将决定cpu空出时,处于就绪状态的线程谁先占领cpu开始运行
//优先级范围1到10,MIN_PRIORITY,MAX_PRIORITY,NORM_PAIORITY
//新线程继承创建她的父线程优先级,父线程通常有普通优先级即5NORM_PRIORITY
System.out.println(
"主线程将挂起7秒!");
try
Thread.sleep(7000);
//主线程挂起7秒
catch (InterruptedException e)
return;
System.out.println(
"又回到了主线程!");
if(thread1.isAlive())
thread1.stop();
//如果thread1还存在则杀掉他
System.out.println(
"thread1休眠过长,主线程杀掉了thread1!");
else
System.out.println(
"主线程没发现thread1,thread1已醒顺序执行结束了!");
thread2.start();
//启动thread2
System.out.println(
"主线程又将挂起7秒!");
try
Thread.sleep(7000);
//主线程挂起7秒
catch (InterruptedException e)
return;
System.out.println(
"又回到了主线程!");
if(thread2.isAlive())
thread2.stop();
//如果thread2还存在则杀掉他
System.out.println(
"thread2休眠过长,主线程杀掉了thread2!");
else
System.out.println(
"主线程没发现thread2,thread2已醒顺序执行结束了!");
System.out.println(
"程序结束按任意键继续!");
try
System.in.read();
catch (IOException e)
System.out.println(e.toString());
//main
//MultiThread
class ThreadUseExtends
extends Thread
//通过继承Thread类,并实现它的抽象方法run()
//适当时候创建这一Thread子类的实例来实现多线程机制
//一个线程启动后(也即进入就绪状态)一旦获得CPU将自动调用它的run()方法
ThreadUseExtends()
//构造函数
public
void run()
System.out.println(
"我是Thread子类的线程实例!");
System.out.println(
"我将挂起10秒!");
System.out.println(
"回到主线程,请稍等,刚才主线程挂起可能还没醒过来!");
try
sleep(10000);
//挂起5秒
catch (InterruptedException e)
return;
//如果该run()方法顺序执行完了,线程将自动结束,而不会被主线程杀掉
//但如果休眠时间过长,则线程还存活,可能被stop()杀掉
class ThreadUseRunnable
implements Runnable
//通过实现Runnable接口中的run()方法,再以这个实现了run()方法的类
//为参数创建Thread的线程实例
//Thread thread2=new Thread(this);
//以这个实现了Runnable接口中run()方法的类为参数创建Thread类的线程实例
ThreadUseRunnable()
//构造函数
public
void run()
System.out.println(
"我是Thread类的线程实例并以实现了Runnable接口的类为参数!");
System.out.println(
"我将挂起1秒!");
System.out.println(
"回到主线程,请稍等,刚才主线程挂起可能还没醒过来!");
try
Thread.sleep(1000);
//挂起5秒
catch (InterruptedException e)
return;
//如果该run()方法顺序执行完了,线程将自动结束,而不会被主线程杀掉
//但如果休眠时间过长,则线程还存活,可能被stop()杀掉
//该程序可做的修改如改休眠时间或优先级setPriority()
实例二,请见附件。
要想解决“脏数据”的问题,最简单的方法就是使用synchronized关键字来使run方法同步,代码如下:
public synchronized void run() |
从上面的代码可以看出,只要在void和public之间加上synchronized关键字,就可以使run方法同步,也就是说,对于同一个
Java类的对象实例,run方法同时只能被一个线程调用,并当前的run执行完后,才能被其他的线程调用。即使当前线程执行到了run方法中的yield方法,也只是暂停了一下。由于其他线程无法执行run方法,因此,最终还是会由当前的线程来继续执行。先看看下面的代码:
sychronized关键字只和一个对象实例绑定
class Test public synchronized void method() public class Sync implements Runnable private Test test; public void run() test.method(); public Sync(Test test) this.test = test; public static void main(String[] args) throws Exception Test test1 = new Test(); Test test2 = new Test(); Sync sync1 = new Sync(test1); Sync sync2 = new Sync(test2); new Thread(sync1).start(); new Thread(sync2).start(); |
在Test类中的method方法是同步的。但上面的代码建立了两个Test类的实例,因此,test1和test2的method方法是分别执行的。要 想让method同步,必须在建立Sync类的实例时向它的构造方法中传入同一个Test类的实例,如下面的代码所示:
Sync sync1 = new Sync(test1); |
不仅可以使用synchronized来同步非静态方法,也可以使用synchronized来同步静态方法。如可以按如下方式来定义method方法:
class Test public static synchronized void method() |
建立Test类的对象实例如下:
对于静态方法来说,只要加上了synchronized关键字,这个方法就是同步的,无论是使用test.method(),还是使用Test.method()来调用method方法,method都是同步的,并不存在非静态方法的多个实例的问题。
在23种设计模式中的单件(Singleton)模式如果按传统的方法设计,也是线程不
安全的,下面的代码是一个线程不
安全的单件模式。
package test; // 线程安全的Singleton模式 class Singleton private static Singleton sample; private Singleton() public static Singleton getInstance() if (sample == null) Thread.yield(); // 为了放大Singleton模式的线程不安全性 sample = new Singleton(); return sample; public class MyThread extends Thread public void run() Singleton singleton = Singleton.getInstance(); System.out.println(singleton.hashCode()); public static void main(String[] args) Thread threads[] = new Thread[5]; for (int i = 0; i < threads.length; i++) threads[i] = new MyThread(); for (int i = 0; i < threads.length; i++) threads[i].start(); |
在上面的代码调用yield方法是为了使单件模式的线程不安全性表现出来,如果将这行去掉,上面的实现仍然是线程不安全的,只是出现的可能性小得多。
程序的运行结果如下:
25358555 26399554 7051261 29855319 5383406 |
上面的运行结果可能在不同的运行环境上有所有同,但一般这五行输出不会完全相同。从这个输出结果可以看出,通过getInstance方法得到的对象实例 是五个,而不是我们期望的一个。这是因为当一个线程执行了Thread.yield()后,就将CPU资源交给了另外一个线程。由于在线程之间切换时并未 执行到创建Singleton对象实例的语句,因此,这几个线程都通过了if判断,所以,就会产生了建立五个对象实例的情况(可能创建的是四个或三个对象 实例,这取决于有多少个线程在创建Singleton对象之前通过了if判断,每次运行时可能结果会不一样)。
要想使上面的单件模式变成线程安全的,只要为getInstance加上synchronized关键字即可。代码如下:
public static synchronized Singleton getInstance() |
当然,还有更简单的方法,就是在定义Singleton变量时就建立Singleton对象,代码如下: