多线程的作用
(1):计算机的操作系统大多采用多任务和分时设计,多任务是指在一个操作系统中可以同时运行多个程序,例如,在你使用csdn写博客的时候,你还可以打开网易云听音乐。即有多个独立运行的任务,每一个任务对应一个进程,每个进程又可以产生多个线程。
(2):多线程程序可以带来更好的用户体验,避免因程序执行过慢而导致出现计算机死机或者白屏的情况。多线程程序可以最大限度地提高计算机系统的利用效率,如迅雷的多线程下载。
什么是进程
(1):进程(Process)是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展至消亡的过程。操作系统同时管理一个计算机系统中的多个进程,让计算机系统中的多个进程轮流使用CPU资源,或者共享操作系统的其他资源。
(2):进程的特点
1:进程都是系统运行程序的基本单位;
2:每一个进程都有自己独立的一块内存空间,一组系统资源;
3:每一个进程的内部数据和状态都是完全独立的;
4:当一个应用程序运行的时候会产生一个进程,如图所示为当前电脑的程序运行图。
(3):进程的组成
什么是线程
(1)概述:
线程是进程中执行运算的最小单位,一个进程在其执行过程中可以产生多个线程,而线程必须在某个进程内执行。
(2):多线程
线程是进程内部的一个执行单元,是可完成一个独立任务的顺序控制流程,如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程。
(3):线程按处理级别可以分为核心级线程和用户级线程。
1:核心级线程---核心级线程是和系统任务相关的线程,它负责处理不同进程之间的多个线程。允许不同进程中的线程按照同一相对优先调度方法对线程进行调度,使它们有条不紊地工作,可以发挥多处理器的并发优势,以充分利用计算机的软/硬件资源
2:用户级线程----在开发程序时,由于程序的需要而编写的线程即用户级线程,这些线程的创建、执行和消亡都是在编写应用程序时进行控制的。对于用户级线程的切换,通常发生在一个应用程序的诸多线程之间,如迅雷中的多线程下载就属于用户线程。
(4):线程的特点
1:进程中负责程序执行的最小执行单元;
2:依靠程序执行的顺序控制流,只能使用程序的资源和环境,线程间可以共享进程的全部资源;
3:线程有自己的堆栈和局部变量,没有单独的地址空间;
4:CPU调度和分派的基本单位,持有程序计数器,寄存器 ,堆栈。
(5):线程的组成
同一个进程中线程访问资源的方式和线程与线程之间的关系。
进程和线程的关系
(1):一个进程中至少要求一个线程,资源分配个给进程,同一进程的所有线程共享该进程的所有资源。
(2):处理机分配给线程,即真正在处理机上运行的是线程。
使用Java语言来编写线程
(1):主线程
每个程序至少自动拥有一个线程,称为主线程。当程序加载到内存时启动主线程。Java程序中的public static void main()方法是主线程的入口,运行Java程序时,会先执行这个方法。
(2):线程的使用步骤
【1】:定义一个线程,同时指明这个线程所要执行的代码,即期望完成的功能;
【2】:创建线程对象;
【3】:启动线程;
【4】:终止线程。
(3):案例1,使用定义线程的两种方式来定义线程,并完成介绍线程的使用步骤。
【注意】1:在主线程mian()方法和新线程中的run()方法并发执行的,多进程,多线程在单核上 的执行方式是时分复用,操作系统 或线程库负责把时间片分给进程或线程,进程或线程实际上是交织执行的。因此微观上来说是不能的,但是从用户的角度上看起来是多个线程并发在做,因为用户感受到的时间尺度远大于时间片,所以对用来会认为在这段时间内多个线程同时执行了。
【注意】2:start()方法: 它会启动一个新线程,并将其添加到线程池中,待其获得CPU资源时会执行run()方法,start()不能被重复调用。
【注意】3:run()方法:它和普通的方法调用一样,不会启动新线程。只有等到该方法执行完毕,其它线程才能获得CPU资源。
package Process_and_Thread;
public class M_thread {
public static void main(String[] args)
{
//实列化线程
Thread_1 mt = new Thread_1();
//获取当前正在执行的线程
System.out.println(Thread.currentThread().getName()+" is running");
//启动线程
mt.start();
System.out.println("线程执行开始");
System.out.println(Thread.currentThread().getName()+" is running");
for(int s = 0;s<100;s++)
{
System.out.println(s);
System.out.println(Thread.currentThread().getName()+" is running");
}
}
}
//方式1----这是一个通过继承Thread类创建的线程
class Thread_1 extends Thread
{
private int count = 0;
//重写父类的run()方法
@Override
public void run()
{
while(count<100)
{
count++;
System.out.println(Thread.currentThread().getName()+" is running");
System.out.println("Count=="+count);
}
}
}
//方式2---这是通过继承runnable接口的方式来实现线程
【执行结果】
方式2—这是通过继承runnable接口的方式来实现线程
【结论】:
java种创建线程的方式有各自的特点和应用领域:直接继承Thread类的方式编写简单,可以直接操作线程,适用于单重继承的情况;实现Runnable接口的方式,当一个线程继承了另一个类时,就只能用实现Runnable接口的方法来创建线程,而且这种方式还可以使多个线程之间使用同一个Runnable对象。
package Process_and_Thread;
public class runnable {
public static void main(String[] args) {
Thread thread = new Thread(new runnable_thread());
//获取当前正在执行的线程
System.out.println(Thread.currentThread().getName()+" is running");
thread.start();
System.out.println("线程执行开始");
System.out.println(Thread.currentThread().getName()+" is running");
for(int s = 0;s<100;s++)
{
System.out.println(s);
System.out.println(Thread.currentThread().getName()+" is running");
}
}
}
class runnable_thread implements Runnable
{
private int count = 0;
//实现run()方法
public void run()
{
while(count<100)
{
count++;
System.out.println(Thread.currentThread().getName()+" is running");
System.out.println(count+"run()方法");
}
}
}
(4):线程的状态
线程的声明周期可以 分为四个阶段,即线程的4种状态,分别为新生状态,可以运行状态 ,阻塞状态和死亡状态。一个具有生命的线程,总是处于这四种状态之一。
1 新生状态:创建线程对象后,但是没有调用start()方法之前,此时的线程是有生命的,但是 系统没有为其分配资源。此时只能启动和终止线程,任何其他操作都会引发异常。
2 可运行状态:当调用start()方法启动线程之后,系统为该线程分配除CPU外的所需资源,这个线程就有了运行的机会,线程处于可以运行的状态,在这个状态当中,该线程对象可能正在运行,也可能尚未运行。对于只有一个CPU的机器而言,任何时刻只能有一个处于可以运行状态的线程占用CPU,至于并发执行,并不是每个线程在同一个CPU中同时运行,而是采用时分复用的机制来进行轮流执行,而其切换速度相当快,这样在我们看来就是同时运行。
3:阻塞状态:一个正在运行的程序,因为其他原因停止运行时,就进入阻塞状态,而处于这种状态的线程在得到一个特定的事件之后会转回可运行状态。其导致阻塞有如下几种方式:
1:调用了Thread类的静态方法sleep()方法。注意这是静态方法,在java中静态方法只能访问静态类型的变量和方法,不能访问非静态成员,静态方法不能以任何方式引用this和super关键字,因为静态方法在使用前不用创建任何实例对象,当静态方法调用时,this所使用的对象根本没有创建,静态方法中不可能再创建静态变量,不会导致方法里面的变量为静态变量 因为方法中的变量都是局部变量,不可能同时为局部变量又同时是静态变量,JVM对每种类型的变量都有自己的存储区域,static有专门的存储区。
2:一个线程执行到一个I/O操作时,如果当这个I/O操作尚未执行完成,则线程将被阻塞。
3:当一个线程的执行需要得到一个对象的锁,而这个对象的锁正在被别的线程占用,那么此线程会被阻塞。
4:线程的 suspend()方法被调用而是线程被挂起时 ,线程进入阻塞状态。但suspend()容易导致死锁,已经被JDK列为过期方法,基本不再使用。
4:死亡状态:一个线程的run()方法运行完毕,stop()方法被调用或者 在运行过程中出现为捕获的异常时,线程进入死亡状态 。
案例1:使用sleep()方法让线程进入睡眠状态,注意sleep()方法是一个静态方法,同时他并不是使用某个线程的名字来指定调用,在这里我在run()方法中调用了那么他代表的就是类runnable_thread的线程停止。
package Process_and_Thread;
public class runnable {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new runnable_thread());
// 获取当前正在执行的线程
System.out.println(Thread.currentThread().getName() + " is running");
thread.start();
System.out.println("线程执行开始");
System.out.println(Thread.currentThread().getName() + " is running");
int s = 0;
while (s < 100) {
System.out.println(s);
System.out.println(Thread.currentThread().getName() + " is running");
s = s + 1;
}
}
}
class runnable_thread implements Runnable {
private int count = 0;
// 实现run()方法
public void run() {
while (count < 100) {
count++;
System.out.println(Thread.currentThread().getName() + " is running");
System.out.println(count + "run()方法");
if (count == 30) {
try {
System.out.println("当count等于30的时候就进入睡眠状态");
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
案例2:在开启多个子线程的情况下使用sleep()方法,这个时候就需要知道线程的优先级了:
1:线程的优先级用1~10表示,10表示优先级最高,默认值是5。每个优先级对应一个Thread类的公用静态常量。
2:线程的优先级可以通过setPriority(int grade)方法更改,此方法的参数表示要设置的优先级,它必须是一个1~10的整数。
package Process_and_Thread;
public class runnable {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new runnable_thread());
Thread thread2 = new Thread(new runnable_thread());
// 获取当前正在执行的线程
System.out.println(Thread.currentThread().getName() + " is running");
thread.start();
thread2.setPriority(8);
thread2.start();
System.out.println("线程执行开始");
System.out.println(Thread.currentThread().getName() + " is running");
int s = 0;
while (s < 100) {
System.out.println(s);
System.out.println(Thread.currentThread().getName() + " is running");
s = s + 1;
}
}
}
class runnable_thread implements Runnable {
private int count = 0;
// 实现run()方法
public void run() {
while (count < 100) {
count++;
System.out.println(Thread.currentThread().getName() + " is running");
System.out.println(count + "run()方法");
if (count == 30) {
try {
System.out.println("当count等于30的时候就进入睡眠状态");
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
根据其输出来进行分析多线程的执行过程,
(5):实现线程调度的方法:
1:join()方法
join()方法使当前线程暂停执行,等待调用该方法的线程结束后再继续执行本线程。它有3种重载形式。
public final void join()
public final void join(long mills)
public final void join(long mills,int nanos)
package Process_and_Thread;
public class M_join {
public static void main(String[] args) {
for(int i=0;i<10;i++)
{
if(i==5)
{
//主线程运行次后,开始M_thread线程
M_thread tempjt = new M_thread("MyThread");
try{
tempjt.start();
//join()方法可以使当前正在执行的线程结束,等待调用该方法的线程执行结束
tempjt.join();
}catch(InterruptedException e)
{
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+""+i);
}
}
}
class M_thread extends Thread
{
public M_thread(String name)
{
super(name);
}
public void run()
{
for(int i = 0;i<5;i++)
{
System.out.println(Thread.currentThread().getName()+""+i);
}
}
}
(6):yield()方法:
yield()方法让当前线程暂停执行,允许其他线程执行,但是该线程任处于可以运行的状态,并不变为阻塞状态。此时,系统选择其他相同或更高优先级的线程来执行,若无其他相同或更高优先级线程,则该线程继续执行。
//其语法格式
public static void yield()
使用线程同步模拟银行取款
线程同步的必要性
上述的线程都是独立的,而且异步执行,也就是说每个线程都包含了运行时所需要的数据或方法,而不需要外部资源或方法,也不必关心其他线程的状态或行为。但是经常有一些同时运行的线程需要共享数据,此时就需要考虑其他线程的状态和行为,否则就不能保证程运行结果的正确性。
案例:小明和小红拥有同一个账户,模拟他们同时对账户的操作。
实现步骤:
(1):创建账户类
1:账户的初始money = 5000
(2):创建取款线程
1:在该类中创建一个私有类型的账户对象。
2:在run()方法中调用取款方法。并且判断当前剩下的钱.
(3):主线程类
1:实例化一个继承了Thread的TestAccount()类
2:以test对象作为参数类实例化出两个线程,注意这两个线程是共用同一个资源区。
3:两个共用相同内存区的线程,实现方式。
TestAccount test = new TestAccount();
//两个用户线程
Thread user_one = new Thread(test);
Thread user_two = new Thread(test);
user_one.setName("小红");
user_two.setName("小明");
//启动线程
user_one.start();
user_two.start();
4:不同的内存区
Thread thread = new Thread(new runnable_thread());
Thread thread2 = new Thread(new runnable_thread());
// 获取当前正在执行的线程
System.out.println(Thread.currentThread().getName() + " is running");
thread.start();
thread2.start();
(2)实现(两个线程异步执行)取款代码:
通过运行我们可以发现,小红查询余额时发现还有500块钱,正当他打算取钱但是还没有取时小明已经把这500块钱取走了,可小红并不知道,所以他也去取钱便发生了透支的情况。在开发中,要避免这种情况的发生,就要使用线程同步。所以为了改正这个缺点,将在这个案例的基础在来实现线程同步取款
package Process_and_Thread;
//该类为测试类
public class question {
public static void main(String[] args) {
//创建两个线程
TestAccount test = new TestAccount();
//两个用户线程
Thread user_one = new Thread(test);
Thread user_two = new Thread(test);
user_one.setName("小红");
user_two.setName("小明");
//启动线程
user_one.start();
user_two.start();
}
}
// 创建银行账户类
class Account {
private int money = 5000;
public int getMoney() {
return money;
}
// 取款
public void out_money(int amount) {
money = money - amount;
}
}
// =取款的线程类
class TestAccount implements Runnable {
// 将账户设立为私有成员,在这个线程中就只有该一个账户类,所有用TestAccount类创建的线程共享同一个账户对象
private Account account1 = new Account();
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 10; i++) {
makeWithdrawal(500);// 取款
if (account1.getMoney() < 0) {
System.out.println("你的账户已经透支了");
}
}
}
private void makeWithdrawal(int amt) {
if (account1.getMoney() >= amt) {
System.out.println(Thread.currentThread().getName() + "准备取款"+"当前剩下的钱==="+account1.getMoney());
try {
Thread.sleep(2464640739);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
account1.out_money(amt);
System.out.println(Thread.currentThread().getName()+"完成取款"+"当前剩下的钱==="+account1.getMoney());
}else{
//
System.out.println("你的钱已经不足了"+
Thread.currentThread().getName()+"的钱还有"+account1.getMoney());
}
}
}
(3)实现线程同步取款