java学习笔记10——多线程的学习

这一段主要写一下关于java多线程的一些知识。这里仅仅简单的介绍一下,java多线程一直是编程的重点,有很多知识点讲都讲不完,需要很深入的学习和总结。

一、线程和进程

这里摘抄一下概念:

进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。

  上述概念其实也就是一个大概,可以很抽象的理解,大家有没有见过网线,一根网线里面有8根颜色不同的铜丝,这里就可以把里面的8根理解成线程,而那根网线就是一个进程,是一个包含的关系。
  多线程就是在宏观上具有不止一个线程在程序中运行。这里需要注意,这里我们以单核处理器为依据,在宏观时间上来分析,如果是微观时间上,那么还是一个个进程来处理的,就看谁可以抢到运行资源了。大家看图:
这里写图片描述
横着表示多线程,并发
斜线表示多进程,并行
  这里如果要深入理解的话,建议大家可以去看一下操作系统的书,里面讲的很详细,这里不做叙述。

二、实现多线程

在java中,实现多线程主要有两种方法,一种,继承Thread类,还有一种是实现runable接口

1、继承Thread类

继承Thread类,然后重写其中的run()方法。

public class ThreadDemo extends Thread {
  private String name;
  public ThreadDemo(String name) {
    this.name = name;
  }

  public void run() {
    for (int i = 0; i < 5; i++) {
      System.out.println(name + "线程" + i);
      try {
        sleep(100); //不让当前线程一直霸占资源
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

public class Test {
  public static void main(String[] args) {
    ThreadDemo threadDemo1 = new ThreadDemo("A");
    ThreadDemo threadDemo2 = new ThreadDemo("B");
    threadDemo1.start();
    threadDemo2.start();
  }
}

运行结果:

A线程0
B线程0
A线程1
B线程1
A线程2
B线程2
A线程3
B线程3
A线程4
B线程4

  大家看,这里建立了两个线程,然后对其进行start()操作,就是让线程就绪,当抢占到资源的时候就会开始运行。看输出结果也是A和B穿插着的,这就是谁抢到资源就会谁运行。

2、实现runable接口

实现runable接口,实现run()方法。

public class RunableDemo implements Runnable {

  private String name;
  public RunableDemo(String name) {
    this.name = name;
  }

  @Override
  public void run() {
    for (int i = 0; i < 5; i++) {
      System.out.println(name + "线程" + i);
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

public class RunableTest {
  public static void main(String[] args) {
    RunableDemo runableDemo1 = new RunableDemo("C");
    RunableDemo runableDemo2 = new RunableDemo("D");
    new Thread(runableDemo1).start();
    new Thread(runableDemo2).start();
  }
}

结果:

C线程0
D线程0
D线程1
C线程1
C线程2
D线程2
D线程3
C线程3
D线程4
C线程4

  这也是一种实现多线程的方法。在这里需要注意,在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread对象的start()方法来运行多线程代码。实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是扩展Thread类还是实现Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程编程的基础。

三、thread和runable的区别

这里看两个例子:

public class ThreadDemo extends Thread {

  private String name;
  private int count = 10;

  public ThreadDemo(String name) {
    this.name = name;
  }

  public void run() {
    for (int i = 0; i < 5; i++) {
      System.out.println(count--);
      try {
        sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

public class Test {
  public static void main(String[] args) {
    ThreadDemo threadDemo1 = new ThreadDemo("A");
    ThreadDemo threadDemo2 = threadDemo1;
    threadDemo1.start();
    threadDemo2.start();
  }
}

结果:

Exception in thread "main" 10
java.lang.IllegalThreadStateException
    at java.lang.Thread.start(Thread.java:705)
    at com.songqijie.demo.thread.Test.main(Test.java:28)
9
8
7
6

用runable来实现:

public class RunableDemo implements Runnable {

  private String name;
  private int count = 10;

  public RunableDemo(String name) {
    this.name = name;
  }

  @Override
  public void run() {
    for (int i = 0; i < 5; i++) {
      System.out.println(name + "线程" + (count--));
      try {
        Thread.sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}

public class RunableTest {
  public static void main(String[] args) {
    RunableDemo runableDemo1 = new RunableDemo("C");
    RunableDemo runableDemo2 = runableDemo1;
    new Thread(runableDemo1).start();
    new Thread(runableDemo2).start();
  }
}

运行结果:

C线程10
C线程9
C线程8
C线程7
C线程6
C线程5
C线程4
C线程3
C线程2
C线程1

  可见,对于相同的一个实例,使用runable接口可以生成多个线程运行,而Thread相当于对相同的线程进行操作,这样就报错了,这里如果是一个购票系统或者其他的有固定的数量的系统,那么肯定还是runable更加的安全和实用。
可见,runable实现多线程更具有优势
1. 适合多个相同的程序代码的线程去处理同一个资源
2. 可以避免java中的单继承的限制
3. 增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

四、线程中的一些常用方法

线程分为5个阶段:新建,就绪,运行,阻塞,死亡
这里写图片描述
1. 在线程中,可以设置其优先级,优先级高的线程可以获得较多的运行资源。
- Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
- static int MAX_PRIORITY
线程可以具有的最高优先级,取值为10。
- static int MIN_PRIORITY
线程可以具有的最低优先级,取值为1。
- static int NORM_PRIORITY
分配给线程的默认优先级,取值为5。
  Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
每个线程都有默认的优先级。主线程的默认优先级为Thread.NORM_PRIORITY。
  线程的优先级有继承关系,比如A线程中创建了B线程,那么B将和A具有相同的优先级。
  JVM提供了10个线程优先级,但与常见的操作系统都不能很好的映射。如果希望程序能移植到各个操作系统中,应该仅仅使用Thread类有以下三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。
2. sleep(long millis):
指在制定的时间内让线程进行休眠,也就是暂停执行操作
3. join():
join是Thread类的一个方法,启动线程后直接调用,即join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行。
看下面的代码:
不加join():

public class ThreadDemo extends Thread {

  private String name;
  private int count = 10;

  public ThreadDemo(String name) {
    this.name = name;
  }

  public void run() {
    for (int i = 0; i < 5; i++) {
      System.out.println(name + "线程" + count--);
      try {
        sleep(100);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }
}
public class Test {
  public static void main(String[] args) {
    System.out.println("主线程开始");
    ThreadDemo threadDemo1 = new ThreadDemo("A");
    ThreadDemo threadDemo2 = new ThreadDemo("B");
    threadDemo1.start();
    threadDemo2.start();
    System.out.println("主线程结束");
  }
}

运行结果:

主线程开始
主线程结束
A线程10
B线程10
B线程9
A线程9
B线程8
A线程8
A线程7
B线程7
B线程6
A线程6

加join():这里只修改test类

public class Test {
  public static void main(String[] args) {
    System.out.println("主线程开始");
    ThreadDemo threadDemo1 = new ThreadDemo("A");
    ThreadDemo threadDemo2 = new ThreadDemo("B");
    threadDemo1.start();
    threadDemo2.start();
    try {
      threadDemo1.join();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    try {
      threadDemo2.join();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("主线程结束");
  }
}

运行结果:

主线程开始
A线程10
B线程10
A线程9
B线程9
A线程8
B线程8
B线程7
A线程7
A线程6
B线程6
主线程结束

  可以发现,加了join,主线程会等待join的结束再运行,而不是和join的线程增多资源。
这里需要注意!join之前需要先把线程start(),而不能先join再start
4. yield():
  Thread.yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
  yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
  yield()从未导致线程转到等待/睡眠/阻塞状态。在大多数情况下,yield()将导致线程从运行状态转到可运行状态,但有可能没有效果。可看上面的图。

sleep()和yield()的区别

  sleep()和yield()的区别):sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
   sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程
   另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I\O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。
5. interrupt():
  中断某个线程,这种结束方式比较粗暴,如果t线程打开了某个资源还没来得及关闭也就是run方法还没有执行完就强制结束线程,会导致资源无法关闭
6. wait()
  Obj.wait(),与Obj.notify()必须要与synchronized(Obj)一起使用,也就是wait,与notify是针对已经获取了Obj锁进行操作,从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。从功能上来说wait就是说线程在获取对象锁后,主动释放对象锁,同时本线程休眠。直到有其它线程调用对象的notify()唤醒该线程,才能继续获取对象锁,并继续执行。相应的notify()就是对对象锁的唤醒操作。但有一点需要注意的是notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续执行。这样就提供了在线程间同步、唤醒的操作。Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。

五、线程同步

这里首先需要说一下关于线程安全的概念:
线程安全:
  当多个线程类并发操作某类的某个方法,(在该方法内部)来修改这个类的某个成员变量的值,不会出错,则我们就说,该的这个方法是线程安全的。
某类的某方法是否线程安全的关键是:
- 该方法是否修改该类的成员变量;
- 是否给该方法加锁(是否用synchronized关键字修饰)。
线程不安全:
  当多个线程类并发操作某类的某个方法,(在该方法内部)来修改这个类的某个成员变量的值,很容易就会发生错误,故我们就说,这个方法是线程不安全的。如果要把这个方法变成线程安全的,则用 synchronized关键字来修饰该方法即可。
  注:用 synchronized关键字修饰方法,会导致加锁,虽然可以使该方法线程安全,但是会极大的降低该方法的执行效率,故要慎用该关键字。
  
1. synchronized关键字的作用域有二种:

  • 是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
  • 是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。

2.除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/区块/},它的作用域是当前对象;

3.synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;

  总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
在进一步阐述之前,我们需要明确几点:
A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁――而且同步方法很可能还会被其他线程的对象访问。
B.每个对象只有一个锁(lock)与之相关联。
C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。

这里举一个例子:
不加synchronized:

public class ThreadDemo extends Thread {
  public static void main(String[] args) {
    HasSelfPrivateNum numRef = new HasSelfPrivateNum();
    ThreadA athread = new ThreadA(numRef);
    athread.start();
    ThreadB bthread = new ThreadB(numRef);
    bthread.start();
  }
}

class HasSelfPrivateNum {
  private int num = 0;
  public void addI(String username) {
    try {
      if (username.equals("a")) {
        num = 100;
        System.out.println("a set over!");
        Thread.sleep(2000);
      } else {
        num = 200;
        System.out.println("b set over!");
      }
      System.out.println(username + " num=" + num);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

class ThreadA extends Thread {
  private HasSelfPrivateNum numRef;
  public ThreadA(HasSelfPrivateNum numRef) {
    super();
    this.numRef = numRef;
  }

  @Override
  public void run() {
    super.run();
    numRef.addI("a");
  }
}

class ThreadB extends Thread {
  private HasSelfPrivateNum numRef;
  public ThreadB(HasSelfPrivateNum numRef) {
    super();
    this.numRef = numRef;
  }

  @Override
  public void run() {
    super.run();
    numRef.addI("b");
  }
}

运行结果:

a set over!
b set over!
b num=200
a num=200

加了synchronized:只修改一下部分:

synchronized public void addI(String username) {
    try {
      if (username.equals("a")) {
        num = 100;
        System.out.println("a set over!");
        Thread.sleep(2000);
      } else {
        num = 200;
        System.out.println("b set over!");
      }
      System.out.println(username + " num=" + num);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

运行结果:

a set over!
a num=100
b set over!
b num=200

  从运行结果可以发现,加了锁和没加锁是不通的,加了锁以后就编程线程安全了,不会进行资源的互相争抢。
  线程锁博大精深,这里我也就说了一个大概,大家有兴趣可以去买本java多线程编程看看。

参考资料:

http://blog.csdn.net/gf771115/article/details/51682561
http://www.importnew.com/20444.html

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值