您的位置:首页 > 职场人生

黑马程序员_java多线程

2015-12-16 16:00 423 查看
——- android培训java培训、期待与您交流! ———-

概述

线程是进程中的内容,而每一个应用程序里面至少有一个线程。

1.什么是线程?

线程:是进程中用于控制程序执行的控制单元(执行路径,执行情景)。

2.什么是进程?

进程:是正在执行中的程序,每一个进程在执行时都有一个执行顺序,该顺序是一个执行路径,或叫做一个控制单元。

示例:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

public static void main(String[] args){

for(int x = 1; x<4000;x++)

System.out.println(“Hello Word!”);}



在我们启动JVM执行这个程序时,会有一个叫java.exe的进程。

因为进程中至少有一个线程,所以说明该进程中至少有一个线程在负责java程序的执行。

而这个线程运行的代码存放在main方法中,所以该线程也称为主线程。

3.多线程存在的意义

多线程的出现可以让我们程序中的部分能产生同时运行的效果。

例如:

其实如果更细节的说明,在JVM启动时是不止一个线程的,还有负责垃圾回收机制的线程。

如果JVM只有一个线程,没有垃圾回收线程,会有什么后果呢?

主线程在一直运行,在执行过程中堆内存中会存放很多很多垃圾,在主线程执行完后在做垃圾回收动作。可是,如果主线程在执行过程中垃圾太多堆内存放不下了,主线程就会先停下把垃圾先回收,解决之后在往下继续执行,而它在解决垃圾时我们的程序就会停止,这就很不合理。

如果JVM有多个线程,主线程在运行程序,还有一个线程在负责垃圾回收,这样程序就会更优化。

4.如何在程序中自定义线程?

Java给我们提供了对像线程这类事物的描述,该类是Thread类。

该类中定义了,创建线程对象的方法(构造函数),提供了要被线程执行的代码存储的位置(run()方法),还定义了开启线程运行的方法(start())。

Java还给我们提供了一个规则,实现Runnable接口。如果自定义类不继承Thread,也可以实现Runnable接口。并将多线程要运行的代码存放在Runnable的run方法中。

创建线程

方式一:继承Thread类

步骤:

1.定义一个类,继承Thread类

2.复写Thread类中的run方法,方法中写需要在新线程中执行的代码

3.创建Thread类的子类对象,调用线程的start方法

代码实现:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

public class Demo extends Thread{

public void run(){//复写Thread类中的run方法

//这里用来存放我们要线程执行的代码

System.out.println(“我的线程1”);

}

}

public class DemoTest{

public static void main(String[] args){

Demo d = new Demo();//创建一个线程

d.start();//开启线程并执行该线程的run方法

//d.run();//仅仅是对象调用方法,而线程创建了并没有运行

//也可以用匿名的方式

//new Demo().start();

}

}

1).为什么要覆盖run方法呢?

我们为什么要开启线程,就是为了要让线程去执行我们定义的某些代码,让这些代码起 到同时运行的效果。

Thread类用于描述线程,该类定义了一个功能,用于存储线程要运行的代码,该存储 功能就是run方法。也就是说Thread类中的run方法是用于存储线程要运行的代码。

所以我们覆盖父类中run方法,在run方法中存放我们要执行的代码。

2).start方法的作用

该方法有两个作用:启动线程,调用并执行run方法。

注意:如直接调用run()方法,则是主线程执行其中代码,不会开启新线程。

方式二:实现Runnable

步骤:

1. 定义类实现Runnable接口

2.覆盖Runnable接口中的run方法,将多线程要运行的代码存入其中。

3.通过Thread类建立线程对象,并将Runnable接口的子类对象作为参数传递给Thread的构造函数。

4.调用Thread对象的start方法开启线程。

代码实现:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

public class Demo implements Runnable{

public void run(){

//将线程要运行的代码存放在该run方法中

Sy
4000
stem.out.println(“我的线程2”);

}

}

public class DemoTest{

public static void main(String[] args){

Demo d = new Demo();//这里不是创建线程

Thread t = new Thread(d);//这里是创建线程

t.start();//启动线程

//匿名的方式

//new Thread(new Demo()).start();

}

}

注意:实现Runnable时这个类还不是线程,创建线程的是Thread类对象或Thread子类对象。

1).为什么要设计Runnable接口?

假设我们有两个类A和B,它们有一些共性代码,我们通过向上的不断抽取,出现了一个父类。可是A类中有一部分代码是需要被多线程所执行,因为java支持的是单继承,它已经继承了一个类了,所以不能在继承Thread类。java支持多实现所以就设计了Runnable接口。

2).Runnable接口应该由哪些类来实现?

Runnable接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个名称为run的无参数方法。

3).为什么要将Runnable接口的子类对象传递给Thread的构造函数?

因为线程要运行的代码都在Runnable子类的run方法中存储。所以要将该run方法所属的对象传递给Thread。让Thread线程去使用该对象调用其run方法。

4).继承Thread类和实现Runnable接口建立线程有什么区别?

1.它们线程代码存放位置不同

继承Thread类:线程代码存放在Thread子类run方法中。

实现Runnable接口:线程代码存放在接口的子类run方法中。

2.局限性

实现Runnable接口避免了单继承的局限性。而且实现Runnable接口这个对象中的数据共享(如果不用静态修饰共享数据是在堆内存中)。

注意:在定义线程时,建议使用实现Runnable接口方式。

线程运行状态



临时状态:有执行资格,没有执行权。

冻结状态:没有执行资格的情况下。

运行状态:有执行资格,有执行权。

sleep方法与wait方法的区别?

sleep():不会释放对象锁,有执行权,时间结束后,自动恢复线程。

wait():释放对象锁,进入等待此对象线程池中,只有针对此对象发出notify方法或notifyAll方法后此线程才进入临时状态准备获得执行权进入运行状态。

注意:其中的stop方法现在已经过时用不了,他被新方法interrupt方法所代替。冻结状态的线程都会在线程池中。

同步

我们在运行多线程代码时发现运行结果每一次都不同,说明多线程具备随机性,因为这是由CPU不断的快速切换造成的。这就有可能会产生问题。

问题产生的原因:

1,多线程代码中有操作共享数据。

2,多条语句操作该共享数据。

当具备这两点时:有一个线程对多条操作共享数据的代码只执行了一部分时,另一个线程就开始参与执行,这就会发生数据错误。

解决方法:当一个线程在执行多条操作共享数据代码时,其它线程即使获取了执行权,也不可以参与操作。

Java对这种解决方式提供了专业的代码——同步。

同步的原理:就是将部分操作功能数据的代码进行加锁(也叫监视器)。

同步的前提:

1.必须要有两个或两个以上的线程。

2.必须是多个线程使用同一个锁。

同步的好处与弊端:

好处:解决了线程的安全问题。

弊端:较为消耗资源,同步嵌套后容易死锁。

什么是死锁?

代码示例:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

class Test implements Runnable

{

private boolean flag;//定义一个布尔型变量

Test(boolean flag)

{

this.flag = flag;

}

public void run(){
if(flag){
//同步代码块的嵌套
synchronized(DuiXiang.t1){//锁1
System.out.println("if ----1");

synchronized (DuiXiang.t2){//锁2
System.out.println("if----2");
}
}

}
else{
synchronized(DuiXiang.t2){//锁2
System.out.println("else----2");
synchronized (DuiXiang.t1){//锁1
System.out.println("else----1");
}
}
}
}


}

class DuiXiang{//为了方便测试,所以把对象单独提取出来

static Object t1 = new Object();//定义一个静态的Object对象

static Object t2 = new Object();

}

class TestDemo{

public static void main(String[] args){

Thread h1 = new Tread(new Tesst(true));//创建线程

Thread h2 = new Tread(new Tesst(false));

h1.start();//启动线程

h2.start();

}

}

注意:我们在开发时一定要注意避免死锁。

同步的表现形式:

1. 同步代码块

[java] view plaincopy在CODE上查看代码片派生到我的代码片

synchronized(对象){

需要被同步的代码



2. 同步函数

1).一般同步函数

[java] view plaincopy在CODE上查看代码片派生到我的代码片

public synchronized void 方法名(){

需要被同步的代码

}

2).静态同步函数

[java] view plaincopy在CODE上查看代码片派生到我的代码片

public synchronized static void 方法名(){//静态同步函数

需要被同步的代码

}

同步代码快与同步函数有什么不同?

它们的锁不同:

同步代码块:锁是任意对象。

同步函数:一般同步函数是this。静态同步函数是类名.class,是该类的字节码文件对象(涉及示例:单例设计模式的懒汉式)。

如何找安全问题?

1.明确哪些代码是多线程运行代码。

2.明确共享数据。

3.明确多线程运行代码中哪些语句是操作共享数据的。

注意:一定要明白哪里可以用同步哪里不可以用,也不可以把run方法都放到同步里,那样就相当成了单线程。

多线程的一些方法

currentThread():获取当前正在执行的线程对象

getName():获取线程名称

Thread.currentThread().getName():线程名称

设置线程名称:

1. setName();

2. 构造函数

类名(String name){

super(name);//父类有这个构造函数,所以直接调用就行

}

设置线程名称的意义

我们想要知道当前到底是哪一个线程在运行,我们可以获取其名称并进行判断。

wait():等待。//这里会发生异常

notify():唤醒一个。

notifyAll():唤醒线程池中的所有。

上面这三个都使用在同步中,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。

为什么这三个操作线程的方法要定义Object类中呢?

因为这些方法在操作同步中线程时,都必须要标识它们所操作线程持有的锁。

只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒。

不可以对不同锁中的线程进行唤醒。

而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。

ThreadGroup():线程组。一般情况下,是谁开启这个线程的,这个线程就属于哪个组。

toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组。一般用的不多。

多线程示例

线程间通信:就是多个线程在操作同一个资源,但操作的动作不同,如:我们有一些数据,a在往里存,b在往出取。

等待唤醒机制示例:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

class XianCheng {

public static void main(String[] args) {

Res r = new Res();

new Thread(new Input(r)).start();//创建线程并启动

new Thread(new Output(r)).start();

}

}

class Res{

private String name;//姓名

private String sex;//性别

private boolean fal;//默认是false

public synchronized void set(String name,String sex){

if(fal)//判断

try {

this.wait();//这里会出现异常,所以只能try或抛

} catch (InterruptedException e) {

e.printStackTrace();

}

this.name = name;

this.sex = sex;

fal = true;

this.notify();//唤醒一个线程

}

public synchronized void out(){
if(!fal)//这里是不等于ture,因为上面给付了true
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+"----------"+sex);
fal = false;
this.notify();
}


}

//输入姓名 性别

class Input implements Runnable{

private Res r;//建立Res的引用,确保拿到的是同一个对象

Input(Res r){

this.r = r;

}

public void run() {

int x = 0;

while(true){

if(x==0)

r.set(“小丽”, “女”);

else

r.set(“Jack”, “man”);

x=(x+1)%2;

}

}

}

//输出姓名 性别

class Output implements Runnable{

private Res r;

Output(Res r){

this.r = r;

}

public void run() {

while(true)

r.out();

}

}

等待的线程放在哪里呢?

在线程运行时内存中会建立一个线程池,等待线程都存在这个线程池中,当我们notify时唤醒的都是线程池中的线程,通常唤醒第一个被等待的,因为它是按顺序往里存的。

停止线程与守护线程

如何停止线程

1. 定义循环结束标记,让run方法结束,因为线程运行代码一般都是循环,只要控制了循环就可以让run方法结束,也就是线程结束。

2. 使用interrupt方法,该方法是结束线程的冻结状态,使线程回到运行状态中来。

特殊情况:

当线程处于了冻结状态,就不会读取到标记,那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除。强制让线程恢复到运行状态,这样就可以操作标记让线程结束。

守护线程

setDaemon(boolean on):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,java虚拟机退出。该方法必须在启动线程前调用。

守护线程相当于后台线程,前台线程如果结束,后台线程自动结束。

守护线程代码示例:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

class StopThread implements Runnable

{

private boolean flag =true;//定义布尔型变量,赋初值true

public void run()

{

while(flag)

{

/*

try{

wait();//等待

} catch (InterruptedException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

*/

System.out.println(Thread.currentThread().getName()+”….run”);

}

}

public void changeFlag()

{

flag = false;

}

}

class StopThreadDemo

{

public static void main(String[] args)

{

StopThread st = new StopThread();

Thread t1 = new Thread(st);//创建线程
Thread t2 = new Thread(st);

t1.setDaemon(true);//定义守护线程
//t2.setDaemon(true);
t1.start();
t2.start();

int num = 0;
while(true)
{
if(num++ == 60)
{
st.changeFlag();
//t1.interrupt();//停止线程
//t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}


}

Join与yield方法示例:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

class Demo implements Runnable

{

public void run()

{

for(int x=0; x<70; x++)

{

//返回该线程的字符串表示形式,包括线程名称、优先级和线程组

System.out.println(Thread.currentThread().toString()+”…..”+x);

Thread.yield();//调用yield()方法释放执行权

}

}

}

class JoinDemo

{

public static void main(String[] args) throws Exception

{

Demo d = new Demo();

Thread t1 = new Thread(d);//创建线程

Thread t2 = new Thread(d);

//t1.join();//这里会有一个异常,可以try或抛

t1.start();

//t1.setPriority(Thread.MAX_PRIORITY);//定义线程优先级
t2.start();
for(int x=0; x<80; x++)
{
//System.out.println("main....."+x);
}
System.out.println("over");
}


}

join方法:等待该线程终止。

特点:当A线程执行到了B线程的.join()方法时,A线程就会等待。等B线程都执行完,A线程才会执行。Join方法可以用来临时加入线程执行。

yield方法:暂停当前正在执行的线程对象,并执行其他线程。

特点:线程一读到Thread.yield();就会释放执行权,临时释放用到。可以稍微减缓一下线程的运行。

优先级

setPriority():更改线程的优先级。

优先级是1—10。

默认优先级是:5。

MAX_PRIORITY:线程可以具有的最高优先级。10

MIN_PRIORITY:线程可以具有的最低优先级。1

NORM_PRIORITY:分配给线程的默认优先级。5

注意:凡是数据是固定的就定义成常量,凡是数据是共享的就定义成静态。

生产者消费者示例:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

class XianCheng1 {

public static void main(String[] args) {

Ress r = new Ress();

/*

new hread(new Sheng(r)).start();//创建线程并启动线程

new Thread(new Xiao(r)).start();

*/

Sheng s = new Sheng(r);

Xiao x = new Xiao(r);

Thread t1 = new Thread(s); //创建线程
Thread t2 = new Thread(s);
Thread t3 = new Thread(x);
Thread t4 = new Thread(x);

t1.start();//启动线程<p align="left"><span style="color:black;"><span style="font-family:Times New Roman;">                  t2.start();</span></span></p><p align="left"><span style="color:black;"><span style="font-family:Times New Roman;">     t3.start();</span></span></p><p align="left"><span style="color:black;"><span style="font-family:Times New Roman;">     t4
a333
.start();</span></span></p> }


}

class Ress{

private String name;

private int count = 1;

private boolean fa = false;

public synchronized void set(String name){
while(fa)
try {
wait();//等待
} catch (InterruptedException e) {
e.printStackTrace();
}
this.name = name+"---"+count++;//让count自增
System.out.println(Thread.currentThread().getName()+"---生产者--"+this.name);
fa = true;
notifyAll();//唤醒所有
}

public synchronized void out(){
while(!fa)
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"----------消费者--"+this.name);
fa = false;
notifyAll();
}


}

class Sheng implements Runnable{

private Ress r;//定义对象引用

Sheng(Ress r){

this.r = r;

}

public void run() {

while(true)

r.set(“==商品==”);

}

}

class Xiao implements Runnable{

private Ress r;

Xiao(Ress r){

this.r = r;

}

public void run() {

while(true)

r.out();

}

}

对于这个示例我们不可以定义if语句了只能定义while语句,为什么呢?

因为是多个消费者和多个生产者,需要让被唤醒的线程再一次判断标记。

为什么定义notifyAll()?

因为需要唤醒对方线程。只用notify,容易出现只唤醒本方线程的情况。导致程序中的所有线程都等待。

if语句只适用在只有一个生成者和消费者的时候。

这个问题怎么解决呢?

在JDK1.5中提供了多线程升级解决方案

将同步synchronized替换成显示了Lock操作。

将Object中的wait,notify,notifyAll,替换成了Condition对象。

该对象可以将Lock锁进行获取。

该示例中,实现了本方只唤醒对方的操作。

新特性示例:

[java] view plaincopy在CODE上查看代码片派生到我的代码片

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

//一定要导包,否则有错误

class XianCheng2 {

public static void main(String[] args) {

Ress1 r = new Ress1();

Sheng1 s = new Sheng1(r);
Xiao1 x = new Xiao1(r);

Thread t1 = new Thread(s); //创建线程
Thread t2 = new Thread(s);
Thread t3 = new Thread(x);
Thread t4 = new Thread(x);

t1.start();//启动线程
t2.start();
t3.start();
t4.start();

}


}

class Ress1 {

private String name;

private int count = 1;

private boolean fa = false;

private Lock lock = new ReentrantLock();//创建Lock的实现类对象。
private Condition condition_sheng = lock.newCondition();//新Condition实例。
private Condition condition_xiao = lock.newCondition();

public void set(String name) throws InterruptedException {
lock.lock();//获取锁
try {
while (fa)
condition_sheng.await();//生产者等待
this.name = name + "---" + count++;
System.out.println(Thread.currentThread().getName()+ "---生产者--" + this.name);
fa = true;
condition_xiao.signal();//唤醒消费者
} finally {//一定执行语句
lock.unlock();//释放锁
}
}

public void out() throws InterruptedException {
lock.lock();

try {
while (!fa)
condition_xiao.await();
System.out.println(Thread.currentThread().getName()+ "----------消费者--" + this.name);
fa = false;
condition_sheng.signal();
} finally {
lock.unlock();
}
}


}

class Sheng1 implements Runnable {

private Ress1 r;

Sheng1(Ress1 r) {

this.r = r;

}

public void run() {

while (true)

try {

r.set(“==商品==”);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

class Xiao1 implements Runnable {

private Ress1 r;

Xiao1(Ress1 r) {

this.r = r;

}

public void run() {
while (true)
try {
r.out();
} catch (InterruptedException e) {
e.printStackTrace();
}
}


}

Lock:它是一个接口,实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操

作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的Condition对象。

ReentrantLock:是Lock的实现类。

Lock:替代了synchronized方法和语句的使用。

lock ():获取锁。

unlock():释放锁。

newCondition():返回绑定到此Lock实例的新Condition实例。

Condition:替代了Object监视器方法的使用(wait notify notifyAll),它也是一个接口。

await();等待,会抛异常。

signal();唤醒一个等待线程。

signalAll();唤醒所有等待线程。

注意:要把lock.unlock();放到finally里,释放锁的资源。

新特性好处:一个锁上可以有多个相关的Condition。

总结:

run方法是将自定义代码存储的地方,不可以直接调用。

我们在开发时建议使用创建线程的实现Runnable接口方法,因为Java里面只允许单一继承,但允许实现多个接口。实现Runnable接口方法更加灵活。

多线程的安全问题与多线程的随机性有关,解决方法就是同步。

线程在我们不自定义起名时有默认的名称,Thread-编号,该编号从0开始。

finally里一般是用来释放资源的,join就是在要主线程的CPU执行权。Join有加入的意思。

等待和唤醒必须是同一个锁。

——- android培训java培训、期待与您交流! ———-
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: