Java语言高编——多线程(并发)

来源:互联网 发布:tcl液晶电视网络连接 编辑:程序博客网 时间:2024/06/05 15:13

线程的并发和并行

并发:同一个CPU在同一时间模拟对不同的代码进行运行。
并行:不同的CPU在同一时间对不同的代码进行运行。

进程(一个Java程序是独立的进程)

进程:指一个程序,在内存占有一块空间(资源),一个进程至少会包含一个线程(主线程main)。

线程(一个执行单元)

线程:是进程执行的单位,多个线程共享进程资源。

多线程

多线程:是指这个程序(一个进程)运行时产生了不止一个线程。

线程的创建和启动

线程的创建启动的方法有两种:

1.继承Thread类,Java类是支持多线程的,通过java.lang.Thread类来实现,Thread中的相关函数可以启动线程,终止线程、线程挂起等;

创建:继承Thread类,并重写run方法。
启动:创建一个该类的对象,调用start方法。

实现代码:

创建

public class MyThread extends Thread{    public void run(){    }}

启动

MyThread thread = new MyThread();thread.start();

2.提供一个实现Runnable接口的类作为一个线程的目标对象,在初始化一个Thread类或Threed子类的线程对象时,把目标对象从Runnable得到run()方法。这个定义方法仍然可以继承其它的类。

创建:实现Runnable或者Callable接口
启动:创建Runnable或者Callable接口的实例,创建一个Thread对象,将Runnable对象传入Thread对象,调用start方法。

实现代码(包含两种):
方式A(常用):

创建

public class MyThread implements Runnable{    public void run(){        for(int i=0;i<100;i++;){            System.out.println(Thread.currentThread().getName()+i);        try{            Thread.sleep(50)        }catch(InterruptedException e){            e.printStackTarce();        }    }  } }

启动

Thread thread =new Thread(new MyRunnable());thread.start();

方式B(可以拿到线程的执行结果的返回值和抛异常):

创建

import java.util.concurrent.Callable;public class MyCallable implements Callable<String>{public String call() throws Exception{    for(int i=0;i<100;i++;){        System.out.println(Thread.currentThread().getName()+i);        Thread.sleep(50);        }        return "fanhui";    }}

启动

MyCallable callable = new MyCallable();FutureTask<String> futureTask =new FutureTask<String>(callable);new Thread(futureTask,"childThread").start();String str =futureTask.get();

run()和start()方法的使用

把需要处理的代码放到run()方法中,start()方法启动线程将调用run()方法,这个由java的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。不要直接调用run()方法,否则并没有开启新的线程去执行。

线程生命周期这里写图片描述

新建状态

使用new关键字和Thread类及其子类建立一个线程对象后,该对象就处于新建状态。它保持这个状态直到start()方法开启这个线程。
当一个线程处于新建状态时,它仅仅是一个空的线程对象,系统不为它分配资源。

就绪状态

当线程对象调用start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程线程调度器的调度。

thread.start();//调用start()方法

运行状态

如果就绪状态的线程获取CPU资源,就可以执行run(),此时线程便处于运行状态。此处运行状态的线程最为复杂,它可以变为阻塞状态,就绪状态和死亡状态。

阻塞状态

如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行进入阻塞状态。在睡眠时间已到或者获得设备资源后可以进入就绪状态。

死亡状态

一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。

线程控制

线程睡眠

Thread.sleep(millis);//静态方法,使用类名调用。使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行。

线程加入

thread.join();//让当前线程等待thread线程结束之后才能继续运行thread.join(millis);//让当前线程等待thead线程millis毫秒后运行

线程让步

thread.yield();//该方法与sleep()类似,只是不能由用户指定暂停多长时间,并且yield()方法只是让同优先级的线程有执行的机会。

后台程序

thread.setDaemon(boolean on);//设置后台线程

后台线程会随着主程序的结束而结束,但是前台进程则不会;或者说只要一个前台线程未退出,进程就不会终止。
默认情况下,程序员创建的线程的是用户线程:用setDaemon(ture)可以设置线程为后台程序;而用isDemon()则可以判断一个线程是前台线程还是后台线程;

注意:setDemon函数必须在start函数之前设定,否则会抛出IllgalThreadStateException异常。

线程优先级

在Java中,每一个线程都有一个优先级。默认情况,一个线程将继承其父线程的优先级。线程用数字来表示,范围从1到10,一个线程的缺省优先级是5

Thread.MIN_PRIORITY = 1Thread.MAX_PRIORITY = 10Thread.NORM_PRIORITY = 5

使用下述方法获得或设置线程对象的优先级

int  getPriority();void  setPriority(int newPriority);

可以通过setPriority方法(final的,不能被子类重载)更改优先级。优先级不能超出1-10的取值范围,否则抛出IllageArgumentException异常。

注意:不要假定高优先级线程一定先于低优先级的线程执行,不要有逻辑依赖于线程优先级,否则可能产生意外结果。

线程组

有些程序包含了相当数量的线程。这时,如果按照线程的功能将他们分成不同的类别将很有用。
线程组可以用来同时对一组线程进行操作。

创建线程组

hreadGroup  g=new ThreadGroup(groupName);

将各个线程添加给该线程组:

Thread  t=new  Thread(g, threadName);

要中断某个线程组中所有线程的运行:

g. interrupt();

要确定某个线程组中的任一线程是否仍处于可运行状态:

if (g. activeCount()==0)

线程组有一个很好的特性,那就是如果某个线程由于异常事件而成为死线程,你将会得到通知

对象监视器(锁)

在java中,每个对象都包含了一把锁(也叫作“监视器”),它自动成为对象的一部分(不必为此写任何特殊的代码)。在给定时刻,只有一个线程可以拥有一个对象的监视器。

同步(synchronized)

为了确保在任何时刻一个共享对象只被一个线程使用,必须使用“同步(synchronized)”

有两种方式实现同步:

使用同步方法

synchronized void methodA() { }

使用同步代码块

synchronized(obj){//obj是被锁定的对象    //要同步的语句}

用synchronized来标识的块或方法即为监视器监视的部分。只有使用synchronized ,才能利用对象的监视器功能。

调用任何synchronized方法时,对象就会被锁定,不可再调用那个对象的其他任何synchronized方法,除非第一个方法完成了自己的工作,并解除锁定。因此,一个特定对象的所有synchronized方法都共享着一把锁,而且这把锁能防止多个方法对通用内存同时进行写操作(比如同时有多个线程)。
每个类也有自己的一把锁(作为类的Class对象的一部分),所以synchronized static方法可在一个类的范围内被相互间锁定起来,防止与static数据的接触。一般情况下,只在方法的层次上使用同步

生产者-消费者问题

在多线程程序中,可能出现生产者-消费者问题,即等待同步数据的问题。
这里写图片描述

可能出现的问题是:

生产者比消费者快时,消费者会漏掉一些数据没有取到
消费者比生产者快时,消费者取相同的数据

notify()和wait ()方法用来协调读取的关系

notify()和wait ()都只能从同步方法中调用

wait-notify 机制

  • 当synchronized方法中的wait方法被调用时,当前线程将被中断运行,并且放弃该对象的锁。
  • 一旦线程调用了wait方法,它便进入该对象的等待列表。要从等待列表中删除该线程,使它有机会继续运行,其它线程必须调用同一个对象上的notify或者notifyAll方法。
  • 当线程再次成为可运行的线程后,它们便试图重新进入该对象。一旦可以使用该对象锁时,其中的一个线程将锁定该对象,并且从它上次调用wait方法后的位置开始继续运行。

使用同步机制

1.如果两个或多个线程修改一个对象,请将执行修改的方法声明为synchronized方法。受到对象修改影响的只读方法也必须实现同步。
2.如果一个线程必须等待某个对象的状态出现变更,那么它应该在对象的内部等待而不是在外边等待。这可以通过进入一个synchronized方法,并且调用wait方法来实现。
3.不要在synchronized方法中花费大量的时间。大多数操作只是更新数据,然后很快返回。
4.每当一个方法改变某个对象的状态时,它就应该调用notifyAll方法。这样可以给等待线程一个机会,以便查看环境有没有发生变化
5.记住,wait和notifyAll/notify方法都属于Object类的方法,而不是Thread类的方法。反复检查你对wait方法的调用与同一对象上的通知是否匹配。

死锁

定义:当所有的线程都在等待得到某个资源后才能继续运行下去时,整个程序将被挂起,这种情况就叫做死锁。
这里写图片描述

原创粉丝点击