多线程-学习笔记

程序:程序是一段静态的代码,它是应用程序执行的蓝本

进程:进程是指一种正在运行的程序,有自己的地址空间;

进程特点:1、动态性

       2、并发性

       3、独立性

线程的定义:进程内部的一个执行单元,它是程序中一个单一的顺序控制流。如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程。

      进程是系统资源分配的单位,可包括多个线程;

  线程是独立调度和分派的基本单位,共享进程资源;

  引入进程是为了多个程序并发执行,提高资源的利用率和系统吞吐量;

  引入线程是为了减少程序在并发执行时付出的时空开销;

线程分类:

  系统级线程(核心级线程):由操作系统内核进行管理,使用户程序可以创建、执行、撤销线程;

  用户级线程:管理过程全部由用户程序完成,操作系统内核只对进程进行管理;

多线程的优势:

  1、多线程使系统空转时间减少,提高CPU利用率;

  2、进程间不能共享内存,但线程之间共享内存非常容易;

  3、使用多线程实现多多任务并发比多进程的效率高;

  4、JAVA语言内置多线程功能支持,简化了JAVA的多线程编程

创建线程的两种方法:

  1、继承Java.lang.Thread类,并覆盖run()方法;

  

1 package test;
2
3 public class MyThreadTest1 extends Thread {
4
5 @Override
6 public void run() {
7 // 覆盖方法
8 }
9 }

  2、实现Java.lang.Runnable接口,并实现run()方法;

package test;

public class MyThreadTest2 implements Runnable {

@Override
public void run() {
//实现该方法
}

}

启动线程:

  新建的线程不会自动开始运行,必须通过start()方法启动(直接调用run()方法不是启动线程)

  1、启动继承Thread的线程

MyThreadTest1 mtt1=new MyThreadTest1();
mtt1.start();

  2、启动实现Runnable接口的线程

MyThreadTest2 mtt2=new MyThreadTest2();
Thread thread=new Thread(mtt2);//这里需要把实现Runnable接口的对象传入,再介由Threa对象启动
thread.start();

例子1:

 1 package test;
2
3 public class ThreadDemo1 {
4
5 /**
6 * @param args
7 */
8 public static void main(String[] args) {
9 //main方法也是一个线程
10 MyThread1 mt1=new MyThread1();
11 mt1.start();
12 while(true){
13 System.out.println("兔子领先了,加油啊!");
14 }
15
16 }
17
18 }
19 class MyThread1 extends Thread{
20
21 @Override
22 public void run() {
23 while(true){
24 System.out.println("乌龟超过了,再接再厉!");
25 }
26 }
27
28 }

例子2:

 1 package test;
2
3 public class ThreadDemo2 {
4
5 /**
6 * @param args
7 */
8 public static void main(String[] args) {
9 MyThread2 mt2=new MyThread2();
10 Thread thread=new Thread(mt2);
11 thread.start();
12 while(true){
13 System.out.println("兔子领先了,加油啊!");
14 }
15
16 }
17
18 }
19
20 class MyThread2 implements Runnable{
21
22 @Override
23 public void run() {
24 while(true){
25 System.out.println("乌龟超过了,再接再厉!");
26 }
27
28 }
29
30 }


两种线程创建方式比较:

继承Thread类方式的多线程:

  优势:编写简单

  劣势:无法继承其他父类

实现Runnble接口方式的多线程:

  优势:可以继承其它类,多线程可共享同一个Thread对象

  劣势:编程方式稍微复杂,如果要访问当前线程,需要调用Thread.cuurrentThread()方法

Thread类的常用方法

      方法              功能

  static Thread currentThread()         得到当前线程

  final String getName()       返回线程的名称

  final void setName(String name)   将线程的名称设置为由name指定的名称

  void start()             调用run()方法启动线程,开始线程的执行

  void run()             存放线程体代码

线程的状态

  新生:使用new 关键字创建一个线程后,尚未调用其start方法之前

  可运行:调用线程对象的start方法之后;

      这个状态当中,线程对象可能正在运行,也可能等待运行

  阻塞:一种“不可运行”的状态,在得到一个特定的事件之后会返回到可运行状态

  死亡:线程的run方法运行完毕或者在运行中出现未捕获的异常时

线程优先级概述

  每个线程执行时都具有一定的优先级。当调度线程时会优先考虑级别高的线程。

  默认情况下,一个线程继承其父线程的优先级。

  使用线程对象.setPriority(int p)来改变线程的优先级

  优先级影响CPU在线程间切换,切换的原则是:

      当一个线程通过显式放弃、睡眠、或者阻塞、自愿释放控制权时,所有线程均接受检查而优先级高线程将会优先执行

      一个线程可以被一个高优先级的线程抢占资源

      同级别的线程之间,则通过控制权释放,确保所有的线程均有机会运行。

线程调度:

  thread.join()阻塞主线程,该线程执行完毕再执行主线程,在start()方法之后调用有效

  Thread.sleep(int millisecond)阻塞当前线程一定时间,时间过后转为可运行状态。线程体内部调用

  Thread.yield()停止当前线程,线程状态变为可运行状态。线程体内部调用

  thread.setDaemon(true)可以将指定线程设置成后台线程,创建后台线程的线程结束时,后台线程也随之消亡。在start()方法之前调用有效

 

线程同步:

  当多个线程访问同一个数据时,容易出现线程安全问题。需要让线程同步,保证数据安全

  当两个或两个以上线程访问同一资源时,需要某种方法来确保资源在某一时刻只被一个线程使用

线程同步的实现方案

  1、同步代码块

  2、同步方法

例子:

  创建一个账户类

  

 1 package test;
2
3 /**
4 * 账户类
5 * @author Administrator
6 *
7 */
8 public class Account {
9
10 //余额
11 private int balance=1000;
12
13
14 public int getBalance() {
15 return balance;
16 }
17
18
19 /**
20 * 取款
21 * @param amount
22 */
23 public void withdraw(int amount){
24 balance = balance - amount;
25 }
26
27 }

创建一个取款的线程类

package test;

/**
* 取款线程
*
@author Administrator
*
*/
public class WithdrawThread implements Runnable {

private Account acct=new Account();

public void run(){
for (int i = 0; i < 10; i++) {
withdraw(100);
if(acct.getBalance()<0){
System.out.println("账户透支了!!");
}
}
}

private void withdraw(int amount){
if(acct.getBalance() >= amount){
System.out.println(Thread.currentThread().getName()+"准备取款。。。。。");
acct.withdraw(amount);
System.out.println(Thread.currentThread().getName()+"完成取款");
}else{
System.out.println("余额不足以支付"+Thread.currentThread().getName()+"的取款,余额为:"+acct.getBalance()+"元");
}
}

}

创建测试类,创建两个线程同时对同一个账号操作

package test;

public class TestWithdraw {

/**
*
@param args
*/
public static void main(String[] args) {
WithdrawThread wt=new WithdrawThread();
Thread zhangsan=new Thread(wt);
zhangsan.setName("张三");
Thread qizi=new Thread(wt);
qizi.setName("张三的妻子");
zhangsan.start();
qizi.start();

}

}

测试结果有可能出现透支的情况,为了避免这种情况对取款线程类进行修改:

第一种解决方式:同步代码块

 1 package test;
2
3 /**
4 * 取款线程
5 * @author Administrator
6 *
7 */
8 public class WithdrawThread implements Runnable {
9
10 private Account acct=new Account();
11
12 public void run(){
13 for (int i = 0; i < 10; i++) {
14 withdraw(100);
15 if(acct.getBalance()<0){
16 System.out.println("账户透支了!!");
17 }
18 }
19 }
20
21 private void withdraw(int amount){
22 synchronized (acct) {
23 if(acct.getBalance() >= amount){
24 System.out.println(Thread.currentThread().getName()+"准备取款。。。。。");
25 acct.withdraw(amount);
26 System.out.println(Thread.currentThread().getName()+"完成取款");
27 }else{
28 System.out.println("余额不足以支付"+Thread.currentThread().getName()+"的取款,余额为:"+acct.getBalance()+"元");
29 }
30 }
31 }
32
33 }


第二种解决方式:同步方法

 1 package test;
2
3 /**
4 * 取款线程
5 * @author Administrator
6 *
7 */
8 public class WithdrawThread implements Runnable {
9
10 private Account acct=new Account();
11
12 public void run(){
13 for (int i = 0; i < 10; i++) {
14 withdraw(100);
15 if(acct.getBalance()<0){
16 System.out.println("账户透支了!!");
17 }
18 }
19 }
20
21 private synchronized void withdraw(int amount){
22
23 if(acct.getBalance() >= amount){
24 System.out.println(Thread.currentThread().getName()+"准备取款。。。。。");
25 acct.withdraw(amount);
26 System.out.println(Thread.currentThread().getName()+"完成取款");
27 }else{
28 System.out.println("余额不足以支付"+Thread.currentThread().getName()+"的取款,余额为:"+acct.getBalance()+"元");
29 }
30 }
31
32 }

这样就不会出现透支的情况了。

线程同步的好处:

  解决了线程安全问题

线程同步的缺点:

  性能下降

  会带来死锁

死锁:

  当两个线程相互等待对方释放“锁”时就会发生死锁

  出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续

  多线程编程时应该注意避免死锁的发生

线程间通信的必要性

生产者和消费者问题

  假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中产品取走消费

  如果仓库中没有产品,则生产者将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止

  如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止

  这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者之间相互信赖,互为条件

  对于生产者,没有生产产品之前,要通知消费者等待。而生产了产品之后,又需要马上通知消费者消费

  对于消费者,在消费之后,要通知生产者已经消费结束,需要继续生产新产品以供消费

  在生产者消费者问题中,仅有synchronized是不够的

    synchronized可阻止并发更新同一个共享资源,实现了同步

    synchronized不能用来实现不同线程之间的消息传递(通信)


Java提供了3个方法解决线程之间的通信

方法名 作用
final void wait() 表示线程一直等待,直到其他线程通知
void wait(long timeout) 线程等待指定的毫秒数的时间
final void wait(long timeout,int nanos) 线程等待指定毫秒、微秒的时间
final void notify() 唤醒一个处于等待状态的线程
final void notifyAll() 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先运行

  以上几个方法均是java.lang.Object类的方法都只能在同步方法或者同步代码块中使用,否则会抛出异常

示例:

 1 package test;
2
3 /**
4 * 共享资源类
5 * @author Administrator
6 *
7 */
8 public class SharedDate {
9
10 private char c;
11
12 private boolean isProduced=false;
13
14 public synchronized void putSharChar(char c){
15 if(!isProduced){
16 this.c=c;
17 isProduced=true;
18 System.out.println("生产了产品"+c);
19 notify();//通知消费者,可以消费
20 }
21 try {
22 System.out.println("等待消费者消费。。。。。");
23 wait();
24 } catch (InterruptedException e) {
25 e.printStackTrace();
26 }
27
28 }
29
30 public synchronized char getSharChar(){
31 if(isProduced){
32 isProduced=false;
33 System.out.println("消费者消费了产品"+c);
34 notify();//通知生产者需要生产
35 }
36 try {
37 System.out.println("等待生产者生产。。。。");
38 wait();
39 } catch (InterruptedException e) {
40 e.printStackTrace();
41 }
42 return this.c;
43 }
44
45 }
 1 package test;
2
3 /**
4 * 生产者线程
5 * @author Administrator
6 *
7 */
8 public class ProducerThread extends Thread {
9
10 private SharedDate s;
11
12 public ProducerThread(SharedDate s){
13 this.s=s;
14 }
15
16 public void run(){
17 for(char ch='A';ch<='D';ch++){
18 s.putSharChar(ch);//生产产品将产品放入仓库
19 }
20 }
21 }

 

 1 package test;
2
3 /**
4 * 消费者线程
5 * @author Administrator
6 *
7 */
8 public class ConsumerThread extends Thread {
9
10 private SharedDate s;
11
12 public ConsumerThread(SharedDate s){
13 this.s=s;
14 }
15
16 public void run(){
17 char ch;
18 do{
19 ch=s.getSharChar();//消费
20 }while(ch!='D');
21 }
22
23 }

 

 

 1 package test;
2
3 /**
4 * 测试类
5 * @author Administrator
6 *
7 */
8 public class CommunicationDemo {
9
10 /**
11 * @param args
12 */
13 public static void main(String[] args) {
14 SharedDate s=new SharedDate();//共享同一个共享资源
15
16 new ProducerThread(s).start();//生产者线程
17 new ConsumerThread(s).start();//消费者线程
18
19
20 }
21
22 }









  

posted @ 2012-03-21 20:13  lhc、  阅读(218)  评论(0编辑  收藏  举报