【java】多线程
来源:互联网 发布:国家统计局网站数据库 编辑:程序博客网 时间:2024/04/29 03:53
一、程序、进程和线程
1. 进程是资源拥有的基本单位,线程是独立执行的基本单位,一个进程可以拥有多个线程至少拥有一个线程,线程共享进程的资源;
2. 进程=程序+资源+线程;
二、线程创建的三种方式
- 继承Thread
1. 继承Thread类,并重写run();方法,调用start();方法来启动线程,run();方法称为线程执行体;
2. 特点:只能继承Thread类,无法再继承其他类;每一个线程都是一个Thread类子类的实例,因为多个线程之间不能共享数据(子类中的属性);
3. 须知:通过getName();和Thread.currentThread().getName();方法来获取当前执行线程名称;
4. 为什么需要重写run方法?因为Thread类中的start方法会调用run方法来执行线程执行体,子类中重写父类的run方法之后,start方法中调用的run方法将会变成子类中的run方法,也就是我们希望线程真正执行的内容;
5. 为什么要通过start();方法来启动线程而不是run()方法?start方法启动线程主要做了两件事,分别是启动线程,然后调用run方法运行线程的执行体;直接调用run方法则不会启动新的线程,只有主线程一个线程,类似调用普通类中的方法一样;
package thread_demo;public class MyThread extends Thread {@Overridepublic void run() {// TODO Auto-generated method stub/* * 线程执行体 */}public static void main(String[] args) {/* * 线程启动方式 */new MyThread().start();}}
- 实现Runable
1. 实现Runnable接口并重写run方法,并以实现Runnable的类作为Thread的target来创建线程,该Thread才是真正的线程对象,并调用start方法来启动线程并调用线程执行体run方法;
2. 通过实现Runnable方法来创建线程,只能通过Thread.currentThread().getName();来获取线程名称;
3. 特点:通过实现Runnable接口来创建的多个线程,他们可以共享线程类的实例属性;
package thread_demo;public class MyThread implements Runnable {@Overridepublic void run() {// TODO Auto-generated method stub/* * 线程执行体,线程的运行内容写在这里 */}public static void main(String[] args) {/* * 线程启动,在Thread实例化中传入MyThread对象 */new Thread(new MyThread()).start();}}
- 实现Callable
1. 实现Callable接口,并重写线程执行体call();方法;新建实现类的对象并用FutureTask来包装,将包装后的FutureTask对象作为Thread的target来创建线程,调用start方法启动线程并调用线程执行体call方法;
2. 实现Callable建立线程和实现Runnable建立线程方法一致,但是利用Callable方法实现多线程,允许线程执行体call方法有返回值和抛出异常;
3. 使用FutureTask对象来包装自定义的线程实例并启动线程,即自定义线程作为target传入FutureTask;
4. 使用FutureTask的get方法来获取线程执行后的返回值;
package thread_demo;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;public class MyThread implements Callable<Integer> {@Overridepublic Integer call() throws Exception {// TODO Auto-generated method stub/* * 线程执行体,通过实现Callable方式来创建线程允许线程有返回值和抛出异常 */return null;}public static void main(String[] args) {/* * 自定义的线程体call实例化后作为target传入FutureTask * 最后FutureTask实例也作为target传入Thread中,调用start方法启动线程 * 通过FutureTask的get方法获取线程执行的返回值 */FutureTask<Integer> fTask = new FutureTask<>(new MyThread());new Thread(fTask).start();try {Integer result = fTask.get();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (ExecutionException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
- 三种方式比较
1. 继承Thread之后不能再继承其他类,并且多个线程之间不能共享数据;2. 实现Runnable接口,多个线程之间共享数据;
3. 实现Callable接口,允许线程执行体有返回值和抛出异常;
三、线程的生命周期
线程的状态:新建、就绪、运行、阻塞、消亡,其生命周期中的状态变幻如图所示。
四、线程同步
- 同步代码块
同步代码块:利用对象锁机制,对象显式传入,要确保资源的互斥访问,必须要确保传入的同步监视器为同一个。
package thread_demo;public class MyThread {public void readSource(){synchronized (this) {/* * 需要互斥访问资源的代码,要确保读写的同步监视器相同 */}}}
- 同步方法
同步方法,即在方法中加入关键字synchronized,同步方法默认的同步监视器为this。静态方法的同步监视器不再是this,而是该类的字节码对象,即Class的实例。
package thread_demo;public class MyThread {public synchronized void readSource(){/* * 整个方法都必须互斥调用,同步监视器为this,即谁调用则谁做为同步监视器 */}}
- Lock
Lock:lock方法和unlock方法一定要配对
package thread_demo;import java.util.concurrent.locks.ReentrantLock;public class MyThread {private final ReentrantLock lock = new ReentrantLock();public void readSource(){lock.lock();try {/* * 互斥访问的代码 */} finally {lock.unlock();}}}
五、线程控制
- join
当在某个执行流中调用其它线程的join方法,执行流将会被阻塞,直到调用join方法的线程执行完毕才能继续执行。如以下代码示例,共有三个线程,主线程、线程1和线程2,当线程2调用join方法以后,主线程被阻塞,只有当线程2执行完成之后,主线程才能继续往下执行。
public static void main(String[] args){<span style="white-space:pre"></span>new ThreadDemo("线程1").start();for(int i=0; i<100; i++){if(i==10){<span style="white-space:pre"></span>ThradDemo t = New ThreadDemo("线程2");t.start();t.join();}}}
- setDemo
将一个线程设置成后台线程,其特点为如果所有的前台线程都死亡,则后台线程也会自动死亡,需要关注的是必须先设置后台线程之后在启动线程!前台线程的子线程默认是前台线程,后台线程的子线程默认是后台线程。如果想要判断一个线程是否是后台线程,可以利用isDaemon();方法判断。
ThreadDemo t = new ThreadDemo(“后台线程”);T.setDaemon(true);//一定要在启动线程之前将纤尘设置T.start()
- sleep
该方法可以让执行程序暂停执行进入阻塞状态,但是sleep只释放了执行权而不是放锁。try {<span style="white-space:pre"></span>Thread.sleep(1);} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}
- yield
该方法和sleep一样,会让执行中的程序暂停执行,只是该方法不是让线程进入阻塞状态,而是进入就绪状态,如果该线程的优先级够高,执行该方法之后又会立即获得cpu资源进入执行状态。sleep和yield相比较如下。
1. sleep方法暂停当前线程转让cpu资源,不理会线程的优先级;yield方法只会给优先级相同或更高的线程执行机会;
2. sleep方法将线程转入阻塞状态,yield方法将线程转入就绪态;
3. sleep方法抛出中断异常,而yield方法不抛出异常;
4. sleep方法比yield有更好的移植性,因此优先选用sleep方法;
- 线程优先级
每个线程的优先级默认与其父类线程的优先级相同,在默认情况下,main具有普通优先级,因此main中创建的线程也都具有普通优先级。线程利用setPriority(intnewPriority);和getPriority();方法来设置和获取线程优先级,三个静态常量MAX_PRIORITY默认是最高优先级10,NORM_PRIORITY默认是普通优先级5,MIN_PRIORITY是最低优先级1。
六、线程通信
- 传统线程通信
传统线程通信(拥有同步监视器的同步,即synchronized修饰的同步)。
1. wait方法:导致当前线程等待,直到其他线程调用该同步监视器的notify或notifyAll方法来唤醒线程,会释放资源;
2. notify方法:唤醒该同步监视器上任意一个等待的线程;
3. notifyAll方法:唤醒该同步监视器上所有等待的线程;
- Condition
当程序不是使用synchronized来保证同步,即不存在同步监视器,而是直接使用lock对象来保证同步,则不能使用wati/notify/notifyall方法来进行线程通信;此时需要利用Condition类来协调。
1. 首先获取监视器:lock.newCondition();,此时监视器有如下三个方法
2. Await:类似wait方法,导致线程等待;
3. signal:唤醒任意一个lock对象上等待的线程;
4. signalAll:唤醒所有lock对象上等待的线程;
- BlockingQueue
Queue的子接口,当生产者放入元素的时候,如果队列已满,则线程被阻塞;当消费者取出元素的时候,如果队列已空,则线程被阻塞;其提供两个阻塞的方法。
1. Put(E e):放入元素,满则阻塞;
2. Take():取出元素,空则阻塞;
附注:
本文如有错漏之处,烦请不吝指正,谢谢!
- 【Java多线程】多线程死锁
- Java 多线程
- java 多线程
- java多线程
- JAVA多线程
- java多线程
- JAVA多线程
- java多线程
- JAVA 多线程
- Java多线程
- java多线程
- JAVA 多线程
- Java 多线程
- Java 多线程
- java多线程
- Java 多线程
- Java多线程
- java 多线程
- VMware虚拟机CentOS的网络设置
- 对大学的认识
- 关联规则基本方法
- iOS7 UINavigationBar设置白色但是出现半透明解决方法
- storm安装过程中遇到的问题解决办法
- 【java】多线程
- 使用CriticalSection(CRITICAL_SECTION*) 去创建临界区对象 应该在线程创建前创建
- dedecms采集侠破解完整版
- java处理高并发高负载类网站的优化方法
- 1蒙特卡洛法求PI
- restful的实例论证
- Eclipse中SVN的安装步骤(两种)和使用方法
- mklink和junction 详解
- 如何在VS 2010中使用 VS2013的解决方案