多线程
进程:正在运行的程序,一个进程中至少有一个线程
线程:线程就是进程中的一个控制单元,线程控制着进程的执行
多线程:每个线程在“同时”运行,是因为cpu在做着快读的切换,以达到同时运行的效果
体现:程序主线程在执行,垃圾回收也在执行,这是多线程的体现
1.Thread(继承类)
字段摘要 | |
---|---|
static int | MAX_PRIORITY 线程可以具有的最高优先级。10 |
static int | MIN_PRIORITY 线程可以具有的最低优先级。5 |
static int | NORM_PRIORITY 分配给线程的默认优先级。1 |
构造方法摘要 | |
---|---|
Thread() 分配新的 Thread 对象。 | |
Thread(Runnable target) 分配新的 Thread 对象。 | |
Thread(Runnable target, String name) 分配新的 Thread 对象。 | |
Thread(String name) 分配新的 Thread 对象。 |
方法摘要 |
---|
static Thread | currentThread() 返回对当前正在执行的线程对象的引用。 |
long | getId() 返回该线程的标识符。 |
String | getName() 返回该线程的名称。 |
int | getPriority() 返回线程的优先级。 |
Thread.State | getStat() 返回该线程的状态。 |
void | setName(String name) 改变线程名称,使之与参数 name 相同。 |
void | interrupt() 中断线程。对线程冻结状态进行清除,强制让线程恢复到状态中来,可以操作标记让线程结束 |
void | setDaemon(boolean on) 将该线程标记为守护线程或用户线程。当前台线程都结束后,守护线程自动结束(后台线程) |
void | join() 等待该线程终止。获得主线程执行权,等待该线程终止,主线程才执行。只有main方法叫主线程。 throws InterruptedException |
String | toString() 返回该线程的字符串表示形式,包括线程名称、优先级和线程组。 |
void | setPriority(int newPriority) 更改线程的优先级。所有线程优先级默认为5 |
static void | yield() 暂停当前正在执行的线程。临时释放线程的执行权 |
1.1 要点:
(1)步骤:Thread子类需要重写run()方法,运行run()方法可通过start()方法。此时Java进程下就有两个线程了,主线程和我们创建的线程
(2)run方法:其就是一个存储器,存储线程要运行的代码
(3)start方法:开启新线程并运行。不用start()方法运行run()方法,程序还是单线程
(4)分析:运行结果每一次都不一样,因为多个线程都在争抢cpu执行权,不过每一时刻只能有一个线程在执行(多核除外)
/**
*需求:站台售票实例,通过继承实现多线程
*思路:因为要多个对象共享票的数量,需要对票数进行静态修饰
*/
class ticket extends Thread
{
private static int tic = 100;//票数
public ticket(String stageName){//站台名
super(stageName);
}
public void run(){//线程的方法体
while(true)
if(tic>0)
System.out.println(Thread.currentThread().getName()+"....还剩"+tic--+"张票");
//System.out.println("票已经售完");
}
}
class java
{
public static void main(String[] args){
new ticket("站台1").start();
new ticket("站台2").start();
new ticket("站台3").start();
new ticket("站台4").start();
}
}
1.2 线程运行的状态
(1)被创建:待运行
(2)运行:拥有运行资格以及执行权
(3)冻结:放弃了运行资格
(4)阻塞:拥有运行资格却没有执行权
(5)消亡:没有运行资格
2.Runnable(实现接口)
方法摘要 | |
---|---|
void | run() 使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。 |
2.1 步骤
class MyThread implements Runnable
{
public void run(){
//多线程要执行的方法体;
System.out.println(Thread.currentThread().getName());
}
}
class java
{
public static void main(String[] args){
//建立自定义线程类对象
Runnable r = new MyThread();
//建立线程类,并通过Thread(Runnable r)来传入自定义线程类对象
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
//运行多线程
t1.start();
t2.start();
//简易写法
new Thread(r).start();
2.2 特点
(1)好处:Java只支持单继承,如果该类已经是一个体系中子类,那么不能够运用继承来实现该类方法的多线程形式
(2)现状:实现方式最常用
3. 线程的安全
3.1 问题产生原因
(1)重要:当多条语句在操作同一个线程的共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误
3.2 问题的解决(synchronized——同步)
(1)同步代码格式如下
//同步代码块格式
synchronized(对象){//对象为任意对象
需要被同步的代码块
}
//同步函数格式
synchronized 作为函数的修饰符
(2)同步的含义:同步就是确保同一时间段内只有一个线程能访问这段代码
(3)同步的前提
<1>.必须要有一个或两个以上的线程
<2>.必须是多个线程共同使用一把锁、监视器(锁就是对象,每一把锁都有自己的状态值,每进去一个线程此状态值都会变化)
(4)注意:同步较为消耗资源,所以尽可能减少判断的同步的次数,同步尽可能少的代码
(5)同步函数的要点
<1>.同步函数使用的锁是 this
<2>.静态同步函数使用的锁是 类名.class(字节码文件对象)
(6)死锁:同步嵌套同步,而且必须互相调用时,可能会发生。代码如下:
//死锁实现
//死锁类,自定义线程类
class DeadLock implements Runnable
{
private boolean flag;//标记
DeadLock(boolean flag){
this.flag = flag;
}
public void run(){
if(flag)
synchronized(MyLock.locka){//死锁嵌套形式
System.out.println(Thread.currentThread().getName()+"......locka");
synchronized(MyLock.lockb){
System.out.println(Thread.currentThread().getName()+"......lockb");
}
}
else
synchronized(MyLock.lockb){
System.out.println(Thread.currentThread().getName()+"......lockb");
synchronized(MyLock.locka){
System.out.println(Thread.currentThread().getName()+"......locka");
}
}
}
}
//提供锁的类
class MyLock
{
public static Object locka = new Object();
public static Object lockb = new Object();
}
class java
{
public static void main(String[] args){
new Thread(new DeadLock(true)).start();//多线程的简便写法,会创建两个定义一线程类对象
new Thread(new DeadLock(false)).start();
}
}
4.线程间通信
4.1 情况阐述:
(1)多个线程中每个线程运行的代码都不同
4.2 等待唤醒机制
(1)方法摘要(Object类)
方法摘要 | |
---|---|
void | wait() 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。throws InterruptedException |
void | notify() 唤醒在此对象监视器上等待的单个线程。 |
void | notifyAll() 唤醒在此对象监视器上等待的所有线程。 |
void | wait(long timeout) 在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。throws InterruptedException |
<1>.操作这些方法的对象是同步中的锁对象,因为可以通过监视器来确定线程。只有在同步中才能使用这些方法
<2>.等待和唤醒必须是同一把锁,只有同一把锁的等待线程能被同一把锁的notify唤醒
<3>.因为锁可以是任意对象,所以这些方法定义在Object类中
(3)输入和输出的代码体现(繁琐代码)————wait()、notify()的应用
/**
*需求:等待唤醒机制通过输入输出的形式体现。只适合两个线程,一个输入一个输出
*思路:1.建立资源类,输入类,输出类
* 2.输入类、输出类是多线程的实现类,并且分别用一个线程来控制其输入输出过程
* 3.同步解决线程完全问题、等待唤醒机制实现输入一次输出一次的形式
*/
class Res
{
String name;
String sex;
boolean b = false;//等待唤醒的判断依据
}
class Input implements Runnable
{
//资源类的引用
Res r;
//保证传入对象唯一
Input(Res r){
this.r = r;
}
public void run(){
int x = 0;//自定义的判断条件
while(true){
synchronized(r){
if(r.b)
try{r.wait();}catch(Exception e){}//等待机制
if(x==0){
r.name="张欣";
r.sex="女";//输入
}
else{
r.name="Tom";
r.sex="man";
}
x = (x+1)%2;//通过此运算x的值始终为0或1
r.notify();//唤醒机制
r.b = true;
}
}
}
}
class Output implements Runnable
{
//同上
Res r;
Output(Res r){
this.r = r;
}
public void run(){
while(true){
synchronized(r){
if(!r.b)
try{r.wait();}catch(Exception e){}//等待机制
System.out.println(r.name+"......"+r.sex);//输出
r.notify();
r.b = false;
}
}
}
}
class java
{
public static void main(String[] args){
Res r = new Res();//建立唯一的资源类对象
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();//运行多线程
}
}
(4)生产者和消费者的代码体现(简化代码)————while多次判断标记、nitifyAll()唤醒全部等待线程
/**
*需求:用生产者消费者的形式来实现线程通信(有超过两个线程操作程序)
*思路;1.建立资源类、生产者类、消费者类
* 2.资源类中建立生产消费的函数,来被生产者消费者调用
* 3.用同步解决线程安全问题,用等待唤醒全部机制保证生产一个消费一个
*/
class Resource
{
private String name;//商品名称
private int count;//商品序号
private boolean flag = false;//判断等待唤醒的标记
Resource(String name){
this.name = name;
}
//生产函数
public synchronized void pdt(){
while(flag)//多次判断标记
try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...生产者..."+(++count)+"."+name);//生产一个商品,每生产一个序号+1
flag = true;
notifyAll();//唤醒全部等待线程
}
//销售函数
public synchronized void sell(){
while(!flag)
try{wait();}catch(Exception e){}
System.out.println(Thread.currentThread().getName()+"...消费者........."+count+"."+name);//销售一个商品
flag = false;
notifyAll();
}
}
class Produce implements Runnable
{
private Resource res;//资源引用
Produce(Resource res){//保证资源对象唯一
this.res = res;
}
//多线程run方法,存放生产的过程
public void run(){
while(true){
res.pdt();
}
}
}
class Customer implements Runnable
{
private Resource res;//资源引用
Customer(Resource res){//保证对象唯一
this.res = res;
}
//多线程run方法,存放销售的过程
public void run(){
while(true){
res.sell();
}
}
}
class java
{
public static void main(String[] args){
//建立要用的唯一资源对象
Resource res = new Resource("手办");
//建立两个生产线程
new Thread(new Produce(res)).start();
new Thread(new Produce(res)).start();
//建立两个销售线程
new Thread(new Customer(res)).start();
new Thread(new Customer(res)).start();
}
}
类 ReentrantLock
5. JDK5.0升级
5.1 知识
(1)JDK5.0升级后多线程的同步与等待唤醒机制被替代,工具包位置为 java.util.concurrent.locks
(2)synchronized关键字的功能被lock接口子类ReentrantLock中的方法替代,wait()、nitify()、notifyAll()被Condition接口子类 中的方法所替代
5.2 类概要
Lock接口(常用子类——ReentrantLock)
方法摘要 | |
---|---|
void | lock() 获取锁。 |
void | unlock() 释放锁。 |
Condition | newCondition() 返回绑定到此 Lock 实例的新 Condition 实例。 |
Condition接口
方法摘要 | |
---|---|
void | await() 造成当前线程在接到信号或被中断之前一直处于等待状态。throws InterruptedException |
void | signal() 唤醒一个等待线程。 |
void | signalAll() 唤醒所有等待线程。 |
5.3 实现代码(实现本方只唤醒对方的操作,上边生产者消费者代码的转型)
/**
*需求:用JDK5.0新特性实现生产者和消费者线程通信
*思路;1.建立资源类、生产者类、消费者类
* 2.资源类中建立生产消费的函数,来被生产者消费者调用
* 3.用同步解决线程安全问题,用等待唤醒全部机制保证生产一个消费一个
*/
import java.util.concurrent.locks.*;
class Resource
{
private String name;//商品名称
private int count;//商品序号
private boolean flag = false;//判断等待唤醒的标记
private Lock lock = new ReentrantLock();//同步对象
private Condition conditionPro = lock.newCondition();//锁对象
private Condition conditionCus = lock.newCondition();
Resource(String name){
this.name = name;
}
//生产函数
public void pdt(){
lock.lock();//锁住
try{
while(flag)//多次判断标记
conditionPro.await();
System.out.println(Thread.currentThread().getName()+"...生产者..."+(++count)+"."+name);//生产一个商品,每生产一个序号+1
flag = true;
conditionCus.signalAll();//指定唤醒哪些线程
}catch(Exception e){}
finally{
lock.unlock();//释放锁
}
}
//销售函数
public void sell(){
lock.lock();
try{
while(!flag)
conditionCus.await();
System.out.println(Thread.currentThread().getName()+"...消费者........."+count+"."+name);//销售一个商品
flag = false;
conditionPro.signalAll();
}catch(Exception e){}
finally{
lock.unlock();//释放锁
}
}
}
class Produce implements Runnable
{
private Resource res;//资源引用
Produce(Resource res){//保证资源对象唯一
this.res = res;
}
//多线程run方法,存放生产的过程
public void run(){
while(true){
res.pdt();
}
}
}
class Customer implements Runnable
{
private Resource res;//资源引用
Customer(Resource res){//保证对象唯一
this.res = res;
}
//多线程run方法,存放销售的过程
public void run(){
while(true){
res.sell();
}
}
}
class java
{
public static void main(String[] args){
//建立要用的唯一资源对象
Resource res = new Resource("手办");
//建立两个生产线程
new Thread(new Produce(res)).start();
new Thread(new Produce(res)).start();
//建立两个销售线程
new Thread(new Customer(res)).start();
new Thread(new Customer(res)).start();
}
}