黑马程序员_java多线程

来源:互联网 发布:mysql表分区上亿的数据 编辑:程序博客网 时间:2024/05/22 05:07

——- 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方法中
System.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.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培训、期待与您交流! ———-

0 0