线程(java课堂笔记)

1.两种方式的差异

2.线程的生命周期

3.线程控制(线程的方法)

4.线程同步

5.线程同步锁

一、 两种方式的差异

A extends Thread :简单

不能再继承其他类了(Java单继承)同份资源不共享

B implements Runnable:( 推荐) )多个线程共享一个目标资源,适合多线程处理同一份资源。

该类还可以继承其他类,也可以实现其他接口。

二、 线程的生命周期

新建:当程序使用new创建一个线程后,该线程处于新建状态,此时他和其他java对象一样,仅仅由Java虚拟机为其分配内存并初始化成员变量值。【 Thread r = new Thread()】

就绪:当线程对象调用start()方法后,该线程处于就绪状态,线程计入线程队列排队,此时该状态线程并未开始执行,它仅表示可以运行了。至于该线程何时运行,取决于JVM线程调度器的调度。【 r.start() 】

运行:若处于就绪状态的线程获得了CPU,开始执行run()线程执行体,该线程处于执行状态。

阻塞:线程运行过程中需要被中断,目的是是其他的线程获得执行的机会。该状态就会进入阻塞状态。

注意:阻塞状态不能直接转成运行状态,阻塞状态只能重新进入就绪状态。

死亡:run()执行完成,线程正常结束;

线程抛出未捕获的Exception或Error;

调用线程的stop()。(易导致死锁,不推荐)

注意:

主线程结束后,其他线程不受其影响,不会随之结束;一旦子线程启动起来后,就拥有和主线程相等地位,不受主线程影响。

例子:

class MyThread implements Runnable {

  public void run() {

    for (int i = 0; i < 200;i++) {

      System.out.println(i);

    }

  }

}

public class MyThreadDemo {

  public static void main(String[] args) {

    for (int i = 0; i < 10;i++) {

      if(i == 5){

        MyThread t = new MyThread();

        new Thread(t).start();

      }

      System.out.println("main" + i);

    }

  }

}

测试线程是否活着,可用线程对象的isAlive()方法。当线程处于就绪,运行,阻塞状态返回true。

当线程处于新建和死亡状态,返回false。

已死亡的线程是不可能通过start()方法唤醒线程的。

否则引发IllegalThreadStateException异常;

public class DieThreadDemo {

  public static void main(String[]args) {

    DeadThread dt = new DeadThread();

    for (int i = 0; i < 100; i++) {

      Thread mainThread =Thread.currentThread();//获得当前线程(Main线程)

      System.out.println(mainThread.getName()+ "--" + i);

      System.out.println("------>"+mainThread.isAlive());

      if (i == 10) {

        dt.start();

      }

    }

    if (!dt.isAlive()) {

      dt.start();

    }

  }

}

class DeadThread extends Thread {

  public void run() {

    for (int i = 0; i < 50; i++) {

      System.out.println(getName() +"--" + i);

    }

  }

}

三、线程控制

Join()方法:等待该线程终止。

join 方法:调用join方法的线程对象强制运行,该线程强制运行期间,其他线程无法运行,必须等到该线程结束后其他线程才可以运行。

有人也把这种方式成为联合线程

join方法的重载方法:

join(long millis):

join(long millis,int nanos):

通常很少使用第三个方法:

程序无须精确到一纳秒;

计算机硬件和操作系统也无法精确到一纳秒;

public class JoinDemo {

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

    Thread join = new Thread(newJoin(),"Join线程");

    join.start();

    int i = 0;

    while(i < 500){

      if(i == 100){

        join.join();

      }

      System.out.println("Main -- > "+ i ++);

    }

  }

}

class Join implements Runnable {

  public void run() {

    int i = 0;

    while(i< 200){

      System.out.println(Thread.currentThread().getName() + "--" + i ++ );

    }

  }

}

setDaemon()方法

后台线程:处于后台运行,任务是为其他线程提供服务。也称为“守护线程”或“精灵线程”。

JVM的垃圾回收就是典型的后台线程。

特点:若所有的前台线程都死亡,后台线程自动死亡。

设置后台线程:Thread对象setDaemon(true);

setDaemon(true)必须在start()调用前。

否则出现IllegalThreadStateException异常;

前台线程创建的线程默认是前台线程;

判断是否是后台线程:使用Thread对象的isDaemon()方法;

并且当且仅当创建线程是后台线程时,新线程才是后台线程。

class Daemon extends Thread {

  public void run() {

    for (int i = 0; i < 10000; i++) {

      System.out.println(getName() +"-" + i);

    }

  }

}

public class DaemonDemo {

  public static void main(String[]args) {

    Daemon d = new Daemon();

    d.setDaemon(true);//把 d 线程设置成后台线程

    d.start();

    for (int i = 0; i < 5; i++) {

      System.out.println(Thread.currentThread().getName()+"--" + i);

    }

  }

}

Sleep()

线程休眠:

让执行的线程暂停一段时间,进入阻塞状态。

sleep(long milllis) throws InterruptedException:毫秒

sleep(long millis,int nanos) throws InterruptedException:毫秒,纳秒

调用sleep()后,在指定时间段之内,该线程不会获得执行的机会。

public class SleepDemo {

  public static void main(String[]args) {

    for (int i = 10; i > 0; i--) {

      System.out.println("还剩" + i);

      try {

        Thread.sleep(1000);

      } catch (InterruptedException e){

        e.printStackTrace();

      }

    }

  }

}

四、线程优先级

每个线程都有优先级,优先级的高低只和线程获得执行机会的次数多少有关。

并非线程优先级越高的就一定先执行,哪个线程的先运行取决于CPU的调度;

默认情况下main线程具有普通的优先级,而它创建的线程也具有普通优先级。

Thread对象的setPriority(int x)和getPriority()来设置和获得优先级。

MAX_PRIORITY : 值是10

MIN_PRIORITY : 值是1

NORM_PRIORITY : 值是5(主方法默认优先级)

class Priority extends Thread{

  public void run() {

    for (int i = 0; i < 100; i++) {

      System.out.println(getName()+",优先级=" + getPriority() +"--"+ i);

    }

  }

}

public class PriorityDemo {

  public static void main(String[]args) {

    Thread.currentThread().setPriority(7);

    for (int i = 0; i < 100; i++) {

      if(i == 10){

        Priority p1 = new Priority();

        p1.setPriority(Thread.MAX_PRIORITY);

        p1.start();

      }

      if(i == 15){

        Priority p2 = new Priority();

        p2.setPriority(Thread.MIN_PRIORITY);

        p2.start();

      }

      System.out.println("main" + i);

    }

  }

}

Yield()方法

线程礼让:

暂停当前正在执行的线程对象,并执行其他线程;

Thread的静态方法,可以是当前线程暂停,但是不会阻塞该线程,而是进入就绪状态。

所以完全有可能:某个线程调用了yield()之后,线程调度器又把他调度出来重新执行。

class Yield implements Runnable {

  public void run() {

    for (int i = 0; i < 100; i++) {

      System.out.println(Thread.currentThread().getName() + "--> " + i);

      if (i % 2 == 0) {

        Thread.yield();// 礼让

      }

    }

  }

}

public class YieldDemo {

  public static void main(String[]args) {

    Yield y = new Yield();

    new Thread(y, "A").start();

    new Thread(y, "C").start();

  }

}

API 过时方法 -- 易死锁, , 不推荐使用

stop:终止线程

马上让线程停止运行,并释放该线程所持有的锁,该操作无法保证对象的内部状态正确;

suspend:挂起线程

使线程进入“阻塞”状态,该状态下CPU不会分给线程时间片,进入这个状态可以用来暂停一个线程的运行,在被resume方法调用前,不可用.

如果要suspend的目标线程对一个重要的系统资源持有锁,那么没任何线程可以使用这个资源直到要suspend的目标线程被resumed。

resume:恢复线程

恢复被suspend方法挂起的线程Java2开始已经废弃了suspend()和resume()方法,因为使用这两个方法可能会产生死锁,所以应该使用同步对象调用wait()和notify()的机制来代替suspend()和resume()进行线程控制。

五、线程的安全性问题

导致安全问题的出现的原因:

多个线程访问出现延迟。

线程随机性。

注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。

我们可以通过Thread.sleep(long time)方法来简单模拟延迟情况。

六、线程同步

当多个线程访问同一份数据的时候,很容易出现线程安全的问题。

让多个操作在同一个时间段内只能有一个线程进行,其它线程要等到该线程完成后才可以继续执行。

经典的事例: 银行取钱

用户输入取款金额;

系统判断余额是否大于取款金额

若余额大于取款金额,取款成功;

若余额小于取款金额,取款失败。

例子分析:

账户类:Account

字段balance表示余额

取钱类:DrawMoney 线程类

字段a表示账户对象,money表示需要取的金额;

把取钱行为放在线程体里面;

提示:

public void run(){

  if(c.getBlance() >= money){

    System.out.println(getName()+",取走" + money);

    Thread.sleep(100);

    c.setBlance(c.getBlance()- money);

    System.out.println("余额= " +c.getBlance());

  }else{

    System.out.println(getName()+ "余额不足,余额"+ c.getBlance() +",需要取走" +money );

  }

}

解决问题:

方法一:

当发生以上情况时,java给我们提供了同步代码块

synchronized(obj){

//obj表示同步监视器,是同一个同步对象

/**.....

TODO SOMETHING

*/

}

方法二:

synchronized 返回值类型 方法名(参数列表){

/**.....

TODO SOMETHING

*/

}

同步方法的同步监听器其实的是 this

/**

* 账号

*

*/

public class Account {

/**

* 余额

*/

  private double blance;

  public Account(double blance) {

    this.blance = blance;

  }

  public double getBlance() {

    return blance;

  }

  public void setBlance(double blance){

    this.blance = blance;

  }

  public synchronized void draw(doubledrawMoney){

    if(getBlance() >= drawMoney) {

      System.out.println(Thread.currentThread().getName()+",取出" + drawMoney);

      try {

        Thread.sleep(1);

      } catch (InterruptedException e){

        e.printStackTrace();

      }

//修改余额

      setBlance(getBlance() -drawMoney);

      System.out.println("余额= " +getBlance());

    }else{

      System.out.println("对不起余额不足");

    }

  }

}

/**

* 取钱操作

*/

public class DrawThread extendsThread{

/**

* 账号

*/

  private Account a;

/**

* 取的钱

*/

  public double drawMoney;

  public DrawThread(String name,Account a,double drawMoney){

    super(name);

    this.a = a;

    this.drawMoney = drawMoney;

  }

/**

* 当多条线程修改同一个共享资源数据的时候,将会导致数据安全问题

*/

  public void run(){

/*   synchronized (a) {

      if(a.getBlance() >= drawMoney) {

        System.out.println(getName()+",取出" +drawMoney);

        try {

          Thread.sleep(1);

        } catch (InterruptedExceptione) {

          e.printStackTrace();

        }

//修改余额

        a.setBlance(a.getBlance() -drawMoney);

        System.out.println("余额= " +a.getBlance());

      }else{

        System.out.println("对不起余额不足");

      }

    }*/

    a.draw(drawMoney);

  }

}

以上内容是课堂笔记之后是今天作业:做一个售票的程序

要求三个窗口,一共50张票,随机售票,售完后求小明在每个窗口购票的几率:

public class Dome {                         //主类
  public static void main(String[] args) {             //主方法
    Chuangkou c1 = new Chuangkou("窗口一");
    Chuangkou c2 = new Chuangkou("窗口二");
    Chuangkou c3 = new Chuangkou("窗口三");
    Thread t1 = new Thread(c1);               //新建三个线程
    Thread t2 = new Thread(c2);
    Thread t3 = new Thread(c3);
    t1.start();                         //三个线程同时运行
    t2.start();
    t3.start();
  }
}

public class Piao{                        //用来统计票数  
  public static String name = "目前只有这一种票";
  public static int piao = 1;
}

public class Chuangkou implements Runnable{           //窗口类
  public String name;
  static int a = 0;                        //定义三个静态变量用来统计每个窗口一共卖的票
  static int b = 0;
  static int c = 0;
  public Chuangkou(String name) {               //有参构造器
    this.name = name;
  }
  public void run(){                        //run方法
    while(Piao.piao<49){                    //如果票数等于49就停止
      try {
        Thread.sleep(1/10);                //经测试睡眠1/10的时间最有利于每个窗口的售票率
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      synchronized(Piao.name){                //同步,防止多个线程同时运行
        System.out.println(name+"卖出了第 "+Piao.piao+++"票");
      }
      switch(name){                      //统计窗口票数
        case "窗口一":a+=1;break;
        case "窗口二":b+=1;break;
        case "窗口三":c+=1;break;
      }
    }
    System.out.println(name+"售罄");
    if(Piao.piao == 50){                      //计算出小明购票几率
      System.out.println("小明买窗口一的票的概率为"+(a/50.0));
      System.out.println("小明买窗口二的票的概率为"+(b/50.0));
      System.out.println("小明买窗口三的票的概率为"+(c/50.0));
    }

  }
}

目前这个作业还有问题,不知为什么小明的效率有时会出现两次,有时不出现,希望大神指导一下。

时间: 2024-12-26 12:13:47

线程(java课堂笔记)的相关文章

java课堂笔记------日期API

* 默认创建出来的Date实例表示当前系统时间 Date date = new Date(); * 获取Date内部维护的long值 long l = date.getTime(); * 支持传入long值的构造方法 * 创建的Date就表示该long值所表示的时间 Date now = new Date(1000*60*60*24); * 也可以在现有的Date对象上调用setTime方法 * 将long值传入,使其表示这个时间 date.setTime(1000*60*60*24); * j

java课堂笔记------集合api

Collection c = new ArrayList(); * boolean add(E e) * 将当前集合中添加给定的元素 * 若成功添加则返回true c.add("one"); c.add("two"); * int size() * 获取当前集合中的元素个数 c.size(); * boolean isEmpty() * 判断当前集合是否不包含任何元素 * 当集合中没有元素时返回true boolean isEmpty = c.isEmpty();

java课堂笔记------Map,队列,栈

* java.util.Map * Map看起来像是一个多行两列的表格 * 每条记录分为两部分:key,value * 其中在一个Map中key是不允许重复的.(equals比较) * * 常用的实现类:java.util.HashMap(散列算法实现) * TreeMap(二叉树实现) Map<String,Integer> map = new HashMap<String,Integer>(); * V put(K k,V v) * 将给定的key与value存入到Map中.

java课堂笔记------字符串API

* int length() * 获取当前字符串中的字符个数(长度),无论中英文 * 每个字符都算一个长度 str.length(); * int indexOf(String str) * 查找给定的字符串在当前字符串中第一次出现的位置. * 返回值为给定的字符串第一个字符在当前字符串的下标 * 由于java大小写敏感,所以必须全部匹配上才可以返回具体的下标. index = str.indexOf("in"); * int indexOf(String str ,int from)

我做的第一个程序(菜鸟的java课堂笔记)

内存: 堆区,栈区,常量区,计数区,代码区: 数据类型: 基本数据类型:整形,浮点型,布尔型 boolean 只能返回真假值,字符型: 引用数据类型:类,数组: 基本数据类型-->直接存放到栈区: 引用数据类型-->将引用放到栈区,将内容放到堆区: 全栈工程师:全都会,最牛逼: 数组声明: int [] a = new int [7]: 方法: 修饰符 返回值类型 方法名(参数列表){ 方法体... [return] } 方法的唯一性: 方法名+参数列表(顺序,个数,类型): 方法的重载: 两

java课堂笔记------toString方法和equals方法

* 重写toString方法 * 当我们需要使用当前类的toString方法时,通常我们 * 就需要重写该方法.具体返回字符串的格式没有严格 * 要求,可根据将来实际开发中的需求而定.但通常反 * 回的字符串中应当包含当前类的属性值,以便了解当前 * 对象的信息. * 格式:(x,y) public String toString(){ return "(" + x + "," + y + ")"; } * 通常我们会重写一个类的equals方法

Java课堂笔记317

JAVA的面向对象编程--------课堂笔记

JAVA的面向对象编程--------课堂笔记 面向对象主要针对面向过程. 面向过程的基本单元是函数.   什么是对象:EVERYTHING IS OBJECT(万物皆对象)   所有的事物都有两个方面: 有什么(属性):用来描述对象. 能够做什么(方法):告诉外界对象有那些功能. 后者以前者为基础. 大的对象的属性也可以是一个对象.   为什么要使用面向对象: 首先,面向对象符合人类看待事物的一般规律. 对象的方法的实现细节是屏蔽的,只有对象方法的实现者了解细节. 方法的定义非常重要.方法有参

java学习笔记14--多线程编程基础1

本文地址:http://www.cnblogs.com/archimedes/p/java-study-note14.html,转载请注明源地址. 多线程编程基础 多进程 一个独立程序的每一次运行称为一个进程,例如:用字处理软件编辑文稿时,同时打开mp3播放程序听音乐,这两个独立的程序在同时运行,称为两个进程 进程要占用相当一部分处理器时间和内存资源 进程具有独立的内存空间 通信很不方便,编程模型比较复杂 多线程 一个程序中多段代码同时并发执行,称为多线程,线程比进程开销小,协作和数据交换容易