【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默认是最高优先级10NORM_PRIORITY默认是普通优先级5MIN_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()取出元素空则阻塞;


附注:

        本文如有错漏之处,烦请不吝指正,谢谢!

0 0