黑马程序员 六、线程技术

    xiaoxiao2025-09-14  45

    Java帮帮-IT资源分享网  六、黑马程序员—线程技术 第六篇   1、进程和线程 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中 可以有多个线程。比如在 Windows 系统中,一个运行的 xx.exe 就是一个进程。 Java 程序的进程里有几个线程:主线程, 垃圾回收线程(后台线程) 线程是指进程中的一个执行任务(控制单元),一个进程中可以运行多个线程,多个线程可共享 数据。 多进程:操作系统中同时运行的多个程序; 多线程:在同一个进程中同时运行的多个任务; 一个进程至少有一个线程,为了提高效率,可以在一个进程中开启多个控制单元。 并发运行。如:多线程下载软件。 可以完成同时运行,但是通过程序运行的结果发现,虽然同时运行,但是每一次结果都不一 致。 因为多线程存在一个特性:随机性。 造成的原因:CPU 在瞬间不断切换去处理各个线程而导致的。 可以理解成多个线程在抢 cpu 资源。 我的总结: 多线程下载:此时线程可以理解为下载的通道,一个线程就是一个文件的下载通道,多 线程也就是同时开起好几个下载通道.当服务器提供下载服务时,使用下载者是共享带宽的, 在优先级相同的情况下,总服务器会对总下载线程进行平均分配。不难理解,如果你线程 多的话,那下载的越快。现流行的下载软件都支持多线程。 多线程是为了同步完成多项任务,不是为了提供运行效率,通过提高资源使用效率来提 高系统的效率. 线程是在同一时间需要完成多项任务的时候实现的. 线程与进程的比较 线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程 元;而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。 在引入了线程的操作系统中,通常一个进程都有若干个线程,至少需要一个线程。 进程与线程的区别: 1.进程有独立的进程空间,进程中的数据存放空间(堆空间和栈空间)是独立的。 2.线程的堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可 以影响的。   2、创建线程方式 1、继承 Thread 类 子类覆写父类中的 run 方法,将线程运行的代码存放在 run 中。 建立子类对象的同时线程也被创建。 通过调用 start 方法开启线程。 2、实现 Runnable 接口 子类覆盖接口中的 run 方法。 通过 Thread 类创建线程,并将实现了 Runnable 接口的子类对象作为参数传递给 Thread 类的构造函数。 Thread 类对象调用 start 方法开启线程。 可使用匿名内部类来写 Eg: package july7; //线程的两种方法 class MyThread extends Thread{ private String name; public MyThread(String name) { super(); this.name = name; } public void run(){ System.out.println(name+"启动!"); } } class YourThread implements Runnable{ private String name; public YourThread(String name) { this.name = name; } @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(Thread.currentThread().getName()+" 第 "+i+"次启动!"); } } } public class Demo1 { public static void main(String[] args) { for (int i = 0; i < 100; i++) { if(i == 50){ new MyThread("刘昭").start(); new Thread(new YourThread(""),"章泽天").start(); } } } } 我的总结: Thread 类中 run()和 start()方法的区别如下: run()方法:在本线程内调用该 Runnable 对象的 run()方法,可以重复多次调用; start()方法:启动一个线程,调用该 Runnable 对象的 run()方法,不能多次启动一个 线程;   3、两种进程创建方式比较 A extends Thread: 简单 不能再继承其他类了(Java 单继承) 同份资源不共享 A implements Runnable:(推荐) 多个线程共享一个目标资源,适合多线程处理同一份资源。 该类还可以继承其他类,也可以实现其他接口。 我的总结: 实现方式,因为避免了单继承的局限性,所以创建线程建议使用第二种方式。 Eg: package july7; //线程卖票的例子 class SellTicket extends Thread{ private String name; private int num = 50; public SellTicket(String name) { super(); this.name = name; } public void run(){ for (int i = 1; i <= num; i++) { System.out.println(name+"卖出了第"+i+"张票!"); } } } class MySell implements Runnable{ private int num = 50; @Override public void run() { for (int i = 1; i <= num; i++) { System.out.println(Thread.currentThread().getName()+" 卖 出了第"+i+"张票!"); } } } public class Demo2 { public static void main(String[] args) throws Exception { new SellTicket("A").start(); new SellTicket("B").start(); new SellTicket("C").start(); new Thread(new MySell(),"D").start(); new Thread(new MySell(),"E").start(); new Thread(new MySell(),"F").start(); for (int i = 10; i > 0; i--) { System.out.println(i); Thread.sleep(1000); } } } 我的总结: 为什么要覆盖 run 方法呢? Thread 类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存 储功能就是 run 方法. 也就是说 Thread 类中的 run 方法,用于存储线程要运行的代码。   4、线程的生命周期 Thread 类内部有个 public 的枚举 Thread.State,里边将线程的状态分为: NEW-------新建状态,至今尚未启动的线程处于这种状态。 RUNNABLE-------运行状态,正在 Java 虚拟机中执行的线程处于这种状态。 BLOCKED-------阻塞状态,受阻塞并等待某个监视器锁的线程处于这种状态。 WAITING-------冻结状态,无限期地等待另一个线程来执行某一特定操作的线程处于 这种状态。 TIMED_WAITING-------等待状态,等待另一个线程来执行取决于指定等待时间的操作 的线程处于这种状态。 TERMINATED-------已退出的线程处于这种状态。   我的总结: 如何停止线程? 只有一种,run 方法结束。 开启多线程运行,运行代码通常是循环结构。 只要控制 住循环,就可以让 run 方法结束,也就是线程结束。   5、控制线程 join 方法:调用 join 方法的线程对象强制运行,该线程强制运行期间,其他线程无法运行, 必须等到该线程结束后其他线程才可以运行。 有人也把这种方式成为联合线程 join 方法的重载方法: join(long millis): join(long millis,int nanos): 通常很少使用第三个方法: 程序无须精确到一纳秒; 计算机硬件和操作系统也无法精确到一纳秒; Eg: package july7; class MyThreadDemo implements Runnable{ @Override public void run() { for (int i = 0; i < 50; i++) { System.out.println(Thread.currentThread().getName()+" 正 在运行!"+i); if(i == 25){ try { new Thread(new MyThreadDemo(),"刘昭").join(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } public class DemoRe10 { public static void main(String[] args) { new Thread(new MyThreadDemo(),"刘昭").start(); new Thread(new MyThreadDemo(),"章泽天").start(); } } Daemon 后台线程:处于后台运行,任务是为其他线程提供服务。也称为“守护线程”或“精灵线程”。 JVM 的垃圾回收就是典型的后台线程。 特点:若所有的前台线程都死亡,后台线程自动死亡。 设置后台线程:Thread 对象 setDaemon(true); setDaemon(true)必须在 start()调用前。否则出现 IllegalThreadStateException 异常; 前台线程创建的线程默认是前台线程; 判断是否是后台线程:使用 Thread 对象的 isDaemon()方法; 并且当且仅当创建线程是后台线程时,新线程才是后台线程。 sleep 线程休眠: 让执行的线程暂停一段时间,进入阻塞状态。 sleep(long milllis) throws InterruptedException:毫秒 sleep(long millis,int nanos) throws InterruptedException:毫秒,纳秒 调用 sleep()后,在指定时间段之内,该线程不会获得执行的机会。 控制线程之优先级 每个线程都有优先级,优先级的高低只和线程获得执行机会的次数多少有关。 并非线程优先级越高的就一定先执行,哪个线程的先运行取决于 CPU 的调度; 默认情况下 main 线程具有普通的优先级,而它创建的线程也具有普通优先级。 Thread 对象的 setPriority(int x)和 getPriority()来设置和获得优先级。 MAX_PRIORITY : 值是 10 MIN_PRIORITY : 值是 1 NORM_PRIORITY : 值是 5(主方法默认优先级) yield 线程礼让: 暂停当前正在执行的线程对象,并执行其他线程; Thread 的静态方法,可以是当前线程暂停,但是不会阻塞该线程,而是进入就绪状态。所 以完全有可能:某个线程调用了 yield()之后,线程调度器又把他调度出来重新执行。 我的总结:用到时查询 api!   6、多线程安全问题 导致安全问题的出现的原因: 多个线程访问出现延迟。 线程随机性。 注:线程安全问题在理想状态下,不容易出现,但一旦出现对软件的影响是非常大的。 我们可以通过 Thread.sleep(long time)方法来简单模拟延迟情况。 我的总结: 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还 没有执行完,另一个线程参与进来执行。导致共享数据的错误。 解决办法: 对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不 可以参与执行。 Eg:在前面的卖票例子上,在每卖票的前面加上模拟延时的语句! package july7; class SellDemo implements Runnable{ private int num = 50; @Override public void run() { for (int i = 0; i < 200; i++) { if(num > 0){ try { //因为它不可以直接调用getName()方法,所以必须要获取当前线 程。 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" 卖 出 第 "+num--+"张票!"); } } } } public class Demo3 { public static void main(String[] args) { SellDemo s = new SellDemo(); new Thread(s,"A").start(); new Thread(s,"B").start(); new Thread(s,"C").start(); } } 输出:这样的话,会出现买了第 0,甚至-1 张票的情况!   7、多线程安全问题的解决方法 三种方法: 同步代码块: synchronized(obj) { //obj 表示同步监视器,是同一个同步对象 /**..... TODO SOMETHING */ } 同步方法 格式: 在方法上加上 synchronized 修饰符即可。(一般不直接在 run 方法上加!) synchronized 返回值类型 方法名(参数列表) { /**..... TODO SOMETHING */ } 同步方法的同步监听器其实的是 this 静态方法的同步 同步方法 同步代码块 static 不能和 this 连用 静态方法的默认同步锁是当前方法所在类的.class 对象 同步锁 jkd1.5 后的另一种同步机制: 通过显示定义同步锁对象来实现同步,这种机制,同步锁应该使用 Lock 对象充当。 在实现线程安全控制中,通常使用 ReentrantLock(可重入锁)。使用该对象可以显示地加锁和 解锁。 具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义, 但功能更强大。 public class X { private final ReentrantLock lock = new ReentrantLock(); //定义需要保证线程安全的方法 public void m(){ //加锁 lock.lock(); try{ //... method body }finally{ //在 finally 释放锁 lock.unlock(); } } } 修改后的例子: //同步代码块 package july7; class SellDemo implements Runnable{ private int num = 50; @Override public void run() { for (int i = 0; i < 200; i++) { synchronized (this) { if(num > 0){ try { //因为它不可以直接调用getName()方法,所以必须要获取当前线 程。 Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" 卖 出 第 "+num--+"张票!"); } } } } } public class Demo3 { public static void main(String[] args) { SellDemo s = new SellDemo(); new Thread(s,"A").start(); new Thread(s,"B").start(); new Thread(s,"C").start(); } } //同步方法 package july7; //同步方法 class FinalDemo1 implements Runnable { private int num = 50; @Override public void run() { for (int i = 0; i < 100; i++) { gen(); } } public synchronized void gen() { for (int i = 0; i < 100; i++) { if (num > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖出了第" + num-- + "张票!"); } } } } public class Demo6 { public static void main(String[] args) { FinalDemo1 f = new FinalDemo1(); new Thread(f, "A").start(); new Thread(f, "B").start(); new Thread(f, "C").start(); } } //线程同步锁 package july7; import java.util.concurrent.locks.ReentrantLock; //同步锁 class FinalDemo2 implements Runnable { private int num = 50; private final ReentrantLock lock = new ReentrantLock(); @Override public void run() { for (int i = 0; i < 100; i++) { gen(); } } public void gen() { lock.lock(); try{ //for (int i = 0; i < 100; i++) { if (num > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "卖出了第" + num-- + "张票!"); } //} }finally{ lock.unlock(); } } } public class Demo7 { public static void main(String[] args) { FinalDemo2 f = new FinalDemo2(); new Thread(f, "A").start(); new Thread(f, "B").start(); new Thread(f, "C").start(); } }   8、线程通信 有一个数据存储空间,划分为两部分,一部分用于存储人的姓名,另一部分用于存储人的性别; 我们的应用包含两个线程,一个线程不停向数据存储空间添加数据(生产者),另一个线程从数 据空间取出数据(消费者); 因为线程的不确定性,存在于以下两种情况: 若生产者线程刚向存储空间添加了人的姓名还没添加人的性别,CPU 就切换到了消费者线 程,消费者线程把姓名和上一个人的性别联系到一起; 生产者放了若干数据,消费者才开始取数据,或者是消费者取完一个数据,还没等到生产者 放入新的数据,又重复的取出已取过的数据; 生产者和消费者 wait():让当前线程放弃监视器进入等待,直到其他线程调用同一个监视器并调用 notify()或 notifyAll()为止。 notify():唤醒在同一对象监听器中调用 wait 方法的第一个线程。 notifyAll():唤醒在同一对象监听器中调用 wait 方法的所有线程。 这三个方法只能让同步监听器调用: 在同步方法中: 谁调用 在同步代码块中: 谁调用 wait()、notify()、notifyAll(),这三个方法属于 Object 不属于 Thread,这三个方法必须由同 步监视对象来调用,两种情况: 1.synchronized 修饰的方法,因为该类的默认实例(this)就是同步监视器,所以可以 在同步方法中调用这三个方法; 2.synchronized 修饰的同步代码块,同步监视器是括号里的对象,所以必须使用该 对象调用这三个方法; 可要是我们使用的是 Lock 对象来保证同步的,系统中不存在隐式的同步监视器对象,那么就 不能使用者三个方法了,那该咋办呢? 此时,Lock 代替了同步方法或同步代码块,Condition 代替了同步监视器的功能; Condition 对象通过 Lock 对象的 newCondition()方法创建; 里面方法包括: await(): 等价于同步监听器的 wait()方法; signal(): 等价于同步监听器的 notify()方法; signalAll(): 等价于同步监听器的 notifyAll()方法; 例子:设置属性 容易出现的问题是: 名字和性别不对应! 线程通信,很好! package july7; class Person{ private String name; private String sex; private Boolean isimpty = Boolean.TRUE;//内存区为空! public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public void set(String name,String sex){ synchronized (this) { while(!isimpty.equals(Boolean.TRUE)){//不为空的话等待消费者 消费! try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } this.name = name;//为空的话生产者创造! this.sex = sex; isimpty = Boolean.FALSE;//创造结束后修改属性! this.notifyAll(); } } public void get(){ synchronized (this) { while(!isimpty.equals(Boolean.FALSE)){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(" 姓 名 "+getName()+ ", "+" 性 别 "+getSex()); isimpty = Boolean.TRUE; this.notifyAll(); } } } class Producer implements Runnable{ private Person p; public Producer(Person p) { super(); this.p = p; } @Override public void run() { for (int i = 0; i < 100; i++) { if( i % 2 == 0){ p.set("刘昭", "男"); }else{ p.set("章泽天", "女"); } } } } class Consumer implements Runnable{ private Person p; public Consumer(Person p) { super(); this.p = p; } @Override public void run() { for (int i = 0; i < 100; i++) { p.get(); } } } public class Demo9 { public static void main(String[] args) { Person p = new Person(); new Thread(new Producer(p)).start(); new Thread(new Consumer(p)).start(); } } Java帮帮-IT资源分享网
    最新回复(0)