多线程

本文详细介绍了Java中多线程的基础知识,包括进程与线程的概念、线程的创建方式、线程的安全问题及其解决方法、线程间的通信机制以及线程的高级应用等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

------- android培训java培训、期待与您交流!

多线程

进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元,一般运行一个应用程序,就生成了一个进程, 这个进程拥有自己的内存空间。

线程:就是进程中的一个独立的控制单元

进程  一个进程中至少有一个线程

l  线程(例:FlashGet)

l  多线程存在的意义和特性。

l  线程的创建方式  thread  run()  start()

 

java VM  启动的时候就会有一个进程java.exe

该进程中至少有一个线程负责java程序的执行。而这个线程运行的代码存在于main()方法中,该线程称之为主线程。

扩展:其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程

补充:

进程概念

  进程是表示资源分配的基本单位,又是调度运行的基本单位。例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等。然后,把该进程放人进程的就绪队列。进程调度程序选中它,为它分配CPU以及其它有关资源,该进程才真正运行。所以,进程是系统中的并发执行的单位。

线程概念

  线程是进程中执行运算的最小单位,亦即执行处理机调度的基本单位。如果把进程理解为在逻辑上操作系统所完成的任务,那么线程表示完成该任务的许多可能的子任务之一。

 

1,如何在自定义代码中 自定义一个线程??

  创建线程方式一 继承Thread

继承Thread类

  1. 继承Thread类的子类覆盖父类中的run方法,将线程运行的代码存放在run
  2. 建立子类对象的同时线程也被创建。
  3. 通过调用start方法开启线程,同时调用run方法。

public class ThreadTest extends Thread {
public voidrun() {
     // 在这里编写线程执行的主体
     // dosomething
}

线程命名

继承Thread线程 可以通过构造函数 直接为线程命名  而实现Runnable的线程必须通过new Thread(Runnable target, String name)为线程命名,,当没有显示为线程命名的时候。线程就使用自己默认的名称。Thread-编号,该编号默认从0开始

继承Thread线程 可以通过this.getName()或者Thread.currentThread().getName()来获取当前线程名字  而实现Runnable的线程必须通过Thread.currentThread().getName()来获取当前线程名字

一个线程只可以开启一次一个线程只可以一次start()

 

 Cpu在某一时刻只能执行一个程序cpu一直在切换执行每一个进程中的线程多个cpu可以同时执行多个进程多线程随机获取执行权 cpu随机选取线程执行 出现了多线程随机执行

明确一点,在某一时刻,只能有一个线程在运行(多核除外)cpu在做着快速的切换,已达到看上去是同时运行的效果,我们可以形象的把多线程的运行行为理解为在互相抢夺cpu的执行权。这就是多线程的一个特性:随机性。谁抢到就执行谁,至于执行多久,cpu说的算

可是为什么 要覆盖run方法呢??

Thread类用于描述 线程,该类就定义了一个功能,用于存储线程要运行的代码,具备该存储功能的就是run方法,也就是说Thread类中的run方法用于存储 要运行的代码

 

虚拟机定义 主线程的代码存储在main函数里 所以虚拟机必须调用main方法 其他函数只要main方法中没调用就不会被调用  main函数就是一个线程而且是主线程其他线程要通过thread类的子类开启


创建线程方式二     Runnable

1.  创建子类实现Rubbable接口

2.  子类覆盖接口中的run方法

3.  通过Thread类创建线程对象

4.  将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。

5.  Thread类对象调用start方法开启线程。Thread通过start()函数开启线程   线程内通过调用Runnable子类的对象来调用run()方法,,,所以说调用run()方法的对象是Runnable子类的对象

public class RunnableTest implements Runnable {
    public void run() {
        // 在这里编写线程执行的主体
        // do something
    }
}


main函数中开启线程的方法   new Thread(newRunnableTest()).start();

思考:为什么要给Thread类的构造函数传递Runnable的子类对象?

因为,自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定指定对象的run方法,就必须明确该run方法所属对对象

 

实现方式和继承方式有什么区别

    这两种实现方式的区别并不大。继承Thread类的方式实现起来较为简单,但是继承它的类就不能再继承别的类了,因此也就不能继承别的类的有用的方法了。而使用是想Runnable接口的方式就不存在这个问题了,而且这种实现方式将线程主体和线程对象本身分离开来,逻辑上也较为清晰,所以推荐大家更多地采用这种实现方式。

通过继承Thread开启的线程 开启了几个线程 就有几个各自的run()方法 无法实现资源共享              

 例子:多个窗口买票问题,每个窗口买票1000张点击查看代码

 通过实现Runnable开启的线程 开启几个线程 这些线程都共享一个run()方法 操作的是同一个资源 

 例子:多个窗口买票问题,所有窗口一共买票1000张      点击查看代码

继承Thread:线程代码存放在Thread子类的run方法中

实现Runnable:线程代码存放在接口的子类对象的run方法中

实现方式的好处:避免了单继承的局限性,在定义线程时,建议使用实现方式。Java只支持单继承

 

线程安全问题

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

多个线程访问出现延迟。线程执行的随机性。当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程就参与进来执行,导致共享数据错误。

解决方法:

    对多条线程操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以参与执行

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

 

sleep方法需要指定睡眠时间,单位是毫秒。  需要内部try

一个特殊的状态:就绪。具备了执行资格,但是还没有获取资源。

 

同歨(sychronized)

格式:                                                   上厕所例子

synchronized(对象)  锁是一个对象 Object

{

    需要同步的代码;

}

同步可以解决安全问题的根本原因就在那个对象上

该对象如同锁的功能。持有锁的线程可以在同步中执行,没有持有锁的线程技术获取cpu的执行权,也进不去,因为没有获取锁

同步的特点

同步的前提:

1,  同步需要两个或者两个以上的线程。

2,  多个线程使用的是同一个锁。

未满足这两个条件,不能称其为同步。

同步的弊端:

当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。             对象A执行完后 对象B才能执行 A出了B才能进

如何找问题:

1, 明确哪些代码是多线程运行代码

2, 明确共享数据

3, 明确多线程运行代码中哪些语句是操作共享数据的

 

同步函数

格式:

在函数上加上synchronized修饰符即可。

思考:同步函数用的是哪个锁呢?  this

    函数需要被对象调用,那么函数都有一个所属对象的引用,就是this。所以同步函数使用的锁是this

如果同步函数被静态修饰,使用的锁匙什么呢??

通过验证,发现并不是this,因为静态方法也不不可以定义this。类名.class

静态进内存,内存中没有本类对象,但是一定有该类对应的字节码文件对象。类名.class 该对象类型Class

静态 函数 的同步方法:使用的锁匙该方法所在类的字节码文件对象,;类名.class



执行资格   &&&    执行权   &&&&     注意临界状态     

 

性别问题引出的线程通信

当没使用同步的时候 输出的人对应的性别出现错位 张冠李戴          点击查看代码

当使用到同步的时候 输出的人对应的性别正确 但是输出的是一段男一段女     点击查看代码

如何让输出为男女交替呢??此时涉及到线程间的通信,等待唤醒机制           点击查看代码


sleep()和wait()的区别

sleep()方法和wait()方法都成产生让当前运行的线程停止运行的效果,这是它们的共同点。

这两个方法都可以让调用它的线程沉睡(停止运行)指定的时间,到了这个时间,线程就会自动醒来,变为可运行状态,但这并不表示它马上就会被运行,因为线程调度机制恢复线程的运行也需要时间。调用sleep()方法并不会让线程释放它所持有的同步锁;而且在这期间它也不会阻碍其它线程的运行。上面的连个方法都声明抛出一个InterruptedException类型的异常,这是因为线程在sleep()期间,有可能被持有它的引用的其它线程调用它的interrupt()方法而中断。中断一个线程会导致一个InterruptedException异常的产生,如果你的程序不捕获这个异常,线程就会异常终止,进入TERMINATED状态,如果你的程序捕获了这个异常,那么程序就会继续执行catch语句块(可能还有finally语句块)以及以后的代码。

当调用了某个对象的wait()方法时,当前运行的线程就会转入等待状态,等待别的线程再次调用这个对象的notify()或者notifyAll()方法(这两个方法也是本地方法)唤醒它,或者到了指定的最大等待时间,线程自动醒来。如果线程拥有某个或某些对象的同步锁,那么在调用了wait()后,这个线程就会释放它持有的所有同步资源,而不限于这个被调用了wait()方法的对象。wait()方法同样会被Thread类的interrupt()方法中断,并产生一个InterruptedException异常,效果同sleep()方法被中断一样。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

wait():导致当前线程等待当前线程必须拥有此对象监视器。等待线程都存在线程池中

notify():唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程选择是任意性的,并在对实现做出决定时发生。

notifyAll() :唤醒在此对象监视器上等待的所有线程

Wait() notify()全部用在同步当中,同时还必须明确锁,因为要对持有监视器(锁)的线程操作。所以要使用在同步中,因为只有同步才具有锁。

线程运行的时候  内存中会创建一个线程池 等待线程都存在线程池中

为什么这些操作线程的方法要定义在Object类中呢?对象就是充当锁的角色

:因为这些方法在操作同步线程时,都必须要标识它们所操作的线程 持有的锁,因为只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,noty()不可以对不同锁监视的线程进行唤醒。

也就是说 等待和唤醒必须持有同一个锁对象只能被锁对象调用,,锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。

、、、、、···、、、、、、、、、、、

生产者消费者 需求:生产一个商品消费一个商品,两线程完成,一个线程负责生产一个负责消费     发现 如期输出 没有异常    点击查看代码

当有两个线程负责生产,两个线程负责消费的时候 ,发现输出有异常,会出现生产一个消费两次还有 生产两个 只有一个被消费

点击查看代码

解决方案:对生产和消费的两个线程 每次被唤醒都通过while进行flag判断,生产之后唤醒所有线程(否则会出现所有线程都等待)  结果输出正常

点击查看代码

但是以上的解决方案不合理,需求是唤醒对方线程  点击查看代码

生产者消费者  引出的1.5新特性:多线程的升级方案,将同步synochronized替换成显示的Lock操作。将Object中的wait,notify,notifyAll替换成了Condition 对象

该对象Condition  可以通过Lock锁获取(newCondition())    实现了本方只唤醒了对方线程的操作

======================================================================================

接口 Condition   java.util.concurrent.locks

通过Lock子类对象的newCondition()获取  一个线程一个Condition对象

void await()   与此 Condition 相关的锁以原子方式释放,并且出于线程调度的目的,将禁用当前线程,

boolean await(long time, TimeUnit unit)   time - 最长等待时间   unit - time 参数的时间单位

          造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。

void signal()           唤醒一个与此condition关联的等待线程。

void signalAll()        唤醒所有此condition关等待线程。

接口 Lock  java.util.concurrent.locks 通过其子类ReentrantLock获取Lock对象

void lock()          获取锁。

void unlock()           释放锁。

Condition newCondition()           返回绑定到此 Lock 实例的新Condition 实例。

Lock l =new ReentrantLock();

     l.lock();

     try {

         // access theresource protected by this lock

     } finally {

         l.unlock();

     }

 锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或try-catch 加以保护,以确保在必要时释放锁。


停止线程

由于stop方法已经过时。但是如何停止线程呢:::

只有一种方法:run()方法结束,线程自然结束

开启多线程运行,运行代码通常是循环结构、只要控制住循环,就可以让run()方法结束,也是线程结束

特殊情况

当线程处于冻结状态,就不会读取到标记,那么线程就不会结束

当没有指定的方式让冻结的线程恢复到运行状态时,这时就需呀对冻结进行清除,强制让线程恢复到运行状态中来,这样就可以操作标记线程结束。。

interrupt()该方法就是 结束线程冻结状态 让线程恢复到运行状态中

 

join

当A线程执行到了B线程的join()方法时,A就会等待。等B线程都执行完,A才会执行

join方法可以用来临时加入线程执行

调用join()会抛异常 所以函数 要声明抛异常 throws Exception

 

Thread.currentThread().toString() 输出的是 线程名称 线程优先级 线程组

 

由主函数开启的线程 此线程的线程组就是主函数

 

线程对象.setDaemon(true)此线程设置为后台线程或者说守护线程 主线程运行结束 虚拟机退出 导致 此线程也结束  此方法必须在线程开启前调用 才能够说明线程是守护线程

主线程结束(虚拟机退出)守护线程结束

============================================================================================

线程同步中的单例模式

//单例 饿汉模式
class single {
	private static single s = new single();

	private single() {
	}

	public static single getinstance() {
		return s;
	}
}

// 单例 懒汉模式
class single2 {
	private static single2 s = null;
	private single2() {
	}

	public static synchronized single2 getinstance()
	{
		if (s == null)
				s = new single2();
			return s;
			
	}
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值