JavaSE--面向对象-多线程--第一讲

本文深入探讨Java多线程的创建方式、线程状态、线程安全问题及解决方案,包括synchronized关键字的使用,线程同步的特点,以及线程间通信的方法。通过实例讲解如何避免线程安全问题,确保多线程程序的正确性和效率。

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

 

目录

创建方式一

 

线程的四种状态:

创建线程方式二

创建方式3:Callable   Future

 

线程安全问题

线程同步synchronized

线程同步的特点

同步函数

线程间通信

停止线程

对象怎么被回收的?

 垃圾回收器 gc 方法的调用及 可能出现的结果?

final   finally   finalize   三者区别?

 

  多线程主函数运行实例

线程安全问题的原因是什么?

线程同步代码块

同步锁

同步代码块和同步函数的区别?


线程:进程中一个负责程序运行的控制单元。执行路径

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

为了同时运行多部分代码,每个线程要执行的任务。

多线程程的好处:解决了多部分同时运行;

多线程的弊端:线程太多会导致效率降低;

应用程序的执行都是CPU 在做着快速切换,这种切换是随机的。

执行 main 函数的线程:

改线程的任务代码都定义在main 函数里面。

 

创建方式一

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

为什么要覆盖run 方法呢?

Thread 类用于描述线程,线程是需要任务的,所以 Thread 类也是需要任务的描述。

这个任务是通过run 方法实现的。 也就是run方法是封装自定义线程运行任务的函数。

run 方法里面是要运行的任务代码。

如果直接调用run 方法 跟普通函数调用没有区别不会开启新线程。

class  Demo extends  Thread{}

eg: Demo1  d1= new Demo();

   Demo2 d2 =new  Demo()

    d1.run();

    d2.run();

   *****需要调用 d1.start() 才会启动线程。  java  虚拟机调用该线程的run 方法。

  • start() 方法  只能调用一次。

  • 多线程的弊端:
new  Thread(){//创建线程  方法1
    @Override
    public void run() {
        for(int  i=0;i<10;i++){
            System.out.println("Thread="+Thread.currentThread().getName() +i);
        }
    }
}.start();

 

线程的四种状态:

创建  运行 冻结  消亡

创建线程方式二

实现Runnable接口
1. 子类覆盖接口中的run方法。
2. 通过Thread类创建线程,并将实现了
Runnable接口的子类对象作为参数传递给
Thread类的构造函数。
3. Thread类对象调用start方法开启线程。
思考:为什么要给Thread类的构造函数传递
Runnable的子类对象

Runnable  r= new Runnable() {//创建线程方法2
    @Override
    public void run() {
      for(int x=0;x<10;x++){
          System.out.println("Runable "+Thread.currentThread().getName());
      }
    }
};
new  Thread(r).start();

创建方式3:Callable   Future

https://www.cnblogs.com/felixzh/p/6044371.html  详细

 

 

线程安全问题

导致安全问题的出现的原因:
 多个线程访问出现延迟。
 线程随机性 。
注:线程安全问题在理想状态下,不容易出
现,但一旦出现对软件的影响是非常大
的。

线程同步synchronized


格式:
synchronized(对象)
{
需要同步的代码;
}
同步可以解决安全问题的根本原因就在那个对象上。
该对象如同锁的功能。

执行流程:0线程 首先 获得obj  ,并且将1置位为0   ,Thread.sleep 这时 0线程 放弃cpu执行权,线程1,2,3 判断 obj 对象为0  ,进不来,Thread.sleep结束后继续执行。 线程0 将obj  置位1  。

 

 

 

线程同步的特点

同步的前提:
 同步需要两个或者两个以上的线程。
 多个线程使用的是同一个锁
未满足这两个条件,不能称其为同步。
同步的弊端:
当线程相当多时,因为每个线程都会去判断
同步上的锁,这是很耗费资源的,无形
中会降低程序的运行效率。
 

如下使用不是同一个锁

package thread;
class   Ticket_3    implements   Runnable{
    private  int num = 100;
    public void run()
    {
        Object obj = new Object();// 每个线程都有一个锁  不是同一个锁
        while(true)
        {
            synchronized(obj)
            {
                if(num>0)
                {
                    try{Thread.sleep(10);}catch (InterruptedException e){}

                    System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
                }
            }
        }
    }
}

public class Ticket3    {
    public static void main(String[] args) {
        Ticket_3 t = new Ticket_3();//创建一个线程任务对象。
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        Thread t4 = new Thread(t);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }

}

依然存在线程安全问题:

 使用同一个锁;

package thread;
class   Ticket_3    implements   Runnable{
    private  int num = 100;
    Object obj = new Object();// 每个线程都有一个锁  不是同一个锁
    public void run()
    {

        while(true)
        {
            synchronized(obj)
            {
                if(num>0)
                {
                    try{Thread.sleep(10);}catch (InterruptedException e){}

                    System.out.println(Thread.currentThread().getName()+".....sale...."+num--);
                }
            }
        }
    }
}

public class Ticket3    {
    public static void main(String[] args) {
        Ticket_3 t = new Ticket_3();//创建一个线程任务对象。
        Thread t1 = new Thread(t);
        Thread t2 = new Thread(t);
        Thread t3 = new Thread(t);
        Thread t4 = new Thread(t);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }

}

/*
        Ticket t1 = new Ticket();
//        Ticket t2 = new Ticket();
//        Ticket t3 = new Ticket();
//        Ticket t4 = new Ticket();

        t1.start();
        t1.start();//一个线程不能开启两次,会抛出无效线程状态异常 
        t1.start();

        t1.start();
        */

  银行存钱: 两个人存钱  ,每个存100,存300次。

package thread;

class   Bank{
    private    int  sum;
    Object  obj =new Object();
    public   void   add(int num ){
       synchronized (obj) {
           sum += num;
           System.out.println("sum =" + sum);
       }
    }
}
class   Cus  implements   Runnable  {

  Bank   bank =new Bank();

    @Override
    public void run() {
          for(int  i=0;i<3;i++){
                  bank.add(100);
          }
    }
}
public class BankDemo {
    public static void main(String[] args) {
       Cus  cus  =new Cus();
       Thread  thread =new Thread(cus);
       Thread  thread1 =new Thread(cus);
       thread.start();
       thread1.start();
    }
}

 

 

同步函数
 

格式:
在函数上加上synchronized修饰符即可。
思考:同步函数用的是哪个锁呢?

线程间通信

思考1: wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
1,这些方法存在与同步中。
2,使用这些方法时必须要标识所属的同步的锁。
3,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
思考2: wait(),sleep()有什么区别?
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。

停止线程


1. 定义循环结束标记
 因为线程运行代码一般都是循环,只要控制了循环即
可。
2. 使用interrupt(中断)方法。
 该方法是结束线程的冻结状态,使线程回到
运行状态中来。
注: stop方法已经过时不再使用。

线程类的其他方法
 setPriority(int num)
 setDaemon(boolean b)
 join()
 自定义线程名称
 toString()

多线程的好处:解决了多部分同时运行

 

JVM 多线程解析:

 

 

  • 对象怎么被回收的?

定义在Object 类中的finalize ,当垃圾回收器确定不存在改对象的更多引用时,由对象的垃圾回收器做垃圾回收。

子类重写 finalize 方法以便配置系统资源或者执行其他清除。

什么是垃圾回收器?执行垃圾回收的程序 :

  System 类下的  gc  方法:

 垃圾回收器 gc 方法的调用及 可能出现的结果?

package thread;

/**
 * 调用 gc() 的时候 垃圾回收器会不定时开始
 * 垃圾回收,主线程继续执行。
 * Created by zengjx on 2019/4/29.
 */
class   Demo6{
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println(" Demo6  finalize");
    }
}
public class ThreadDemo6 {
    public   static   void   main(String[]  args){
        new Demo6();
        new Demo6();
        new Demo6();
        System.gc();//开启新线程  垃圾回收器
        System.out.println(" main ");
    }
}
/**
 *  main
 Demo6  finalize
 Demo6  finalize
 Demo6  finalize
 */

/**
 *  Demo6  finalize
 Demo6  finalize
 Demo6  finalize
 main
 */
// 垃圾回收器 收了一个后主线程结束 ,虚拟机准备结束退出了

//内存被强制清除
/**
 * 可能出现  垃圾回收器   回收一个后虚拟机准备结束强制清除所在内存区域
 * 还没等 运行下一个finalize 就已经结束了。
 * main
 *Demo6  finalize
 */
//主线程 结束了 虚拟机其他线程还在运行 ,虚拟机还没结束。
  • final   finally   finalize   三者区别?

一、final :

1、修饰符(关键字) 如果一个被声明为final,意味着它不能再派生新的子类,不能作为父类被继承。因此一个类不能既被声明为abstract,又被声明为final的。

2、将变量或方法声明为final,可以保证他们使用中不被改变。被声明为final的变量必须在声明时给定初值,而以后的引用中只能读取,不可修改,被声明为final的方法也同样只能使用,不能重载。

二、finally:

异常处理时提供finally块来执行清除操作。如果抛出一个异常,那么相匹配的catch语句就会执行,然后控制就会进入finally块,如果有的话。

三、finalize:

是方法名。java技术允许使用finalize()方法在垃圾收集器将对象从内存中清除之前做必要的清理工作。这个方法是在垃圾收集器在确定了,被清理对象没有被引用的情况下调用的。

finalize是在Object类中定义的,因此,所有的类都继承了它。子类可以覆盖finalize()方法,来整理系统资源或者执行其他清理工作。

 

  多线程主函数运行实例

 main 进栈------d1.show() 进栈---d1,show() 出栈---d2.show()进栈---d2.show()出栈-  main 函数出栈。

  •  多线程的弊端;线程的安全问题

多线程执行时随机的,例如卖票可能出现负数  。

package exception;
public class Ticket    extends   Thread {
    private  static    int  num =100;
    Object   object =new Object();
    @Override
    public void run() {
        super.run();
        while(true){
            synchronized (object){
                try{
                    Thread.sleep(10);
                }catch ( InterruptedException  e){
                    e.printStackTrace();
                }
                System.out.println(" name+ "  +Thread.currentThread()+" num="+num);
                num--;
                if(num<0){
                    System.out.println(" ");
                    break;
                }
            }
        }
    }
}
class   TicktetDemo{
    public static void main(String[] args) {
        Ticket  ticket =new Ticket();
        Ticket  ticket2 =new Ticket();
        Ticket  ticket3 =new Ticket();
        ticket.start();
        ticket2.start();
        ticket3.start();
    }
}
输出结果:

 name+ Thread[Thread-0,5,main] num=4
 name+ Thread[Thread-2,5,main] num=4
 name+ Thread[Thread-1,5,main] num=1
 name+ Thread[Thread-2,5,main] num=1
 
 name+ Thread[Thread-0,5,main] num=1
 
 name+ Thread[Thread-1,5,main] num=-2

 

线程安全问题的原因是什么?

多线程调用是随机的,当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算就会导致线程安全问题。

  • 线程同步代码块

  • 解决方法: 多个线程在操作共享数据的的线程代码封装起来,当有线程在执行这些代码的时候,其他线程不能够参与运算。

必须要当前线程把这些代码都执行完之后,其他线程才可以参与运算。

   

package thread;

class   Bank{
    private    int  sum;
    Object  obj =new Object();
    public synchronized   void   add(int num ){//同步函数 用 sychronized
     //  synchronized (obj)
       {
           sum += num;
           System.out.println("sum =" + sum);
       }
    }
}
class   Cus  implements   Runnable  {

  Bank   bank =new Bank();

    @Override
    public void run() {
          for(int  i=0;i<3;i++){
                  bank.add(100);
          }
    }
}
public class BankDemo {
    public static void main(String[] args) {
       Cus  cus  =new Cus();
       Thread  thread =new Thread(cus);
       Thread  thread1 =new Thread(cus);
       thread.start();
       thread1.start();
    }
}

同步锁

明白什么该同步,什么不该同步?

run  方法设置为同步函数。 会导致 只有一个线程获得锁直到票卖完。

应该要把需要同步的 封装起来

package thread;

class   Ticket    implements    Runnable{
    private   int  num =100;
    @Override
    public  synchronized  void run() {//   run  设置为同步函数
        while (true){
            if(num>0){
                try{
                    Thread.sleep(10);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(" thread  name"+Thread.currentThread()   +"num ="+num--);
            }
        }
    }
}

public class SynFunctionLockDemo {
    public static void main(String[] args) {
        Ticket   ticket  =new Ticket();
        Thread   thread1  =new Thread(ticket);
        Thread  thread2 =new Thread(ticket);
        Thread   thread3  =new Thread(ticket);
        Thread  thread4 =new Thread(ticket);
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}

封装需要同步的代码。

package thread;

class   Ticket    implements    Runnable{
    private   int  num =100;
    @Override
    public    void run() {
        while (true){
           int  ret=   show();
           if(ret>0){
               System.out.println(" game  over");
               return;
           }
        }
    }
    //封装函数
    public   synchronized   int   show(){

        if(num>0){
            try{
                Thread.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(" thread  name"+Thread.currentThread()   +"num ="+num--);
        }
        if(num==0){
            return  1;
        }
        return   0;
    }


}

public class SynFunctionLockDemo {
    public static void main(String[] args) {
        Ticket   ticket  =new Ticket();
        Thread   thread1  =new Thread(ticket);
        Thread  thread2 =new Thread(ticket);
        Thread   thread3  =new Thread(ticket);
        Thread  thread4 =new Thread(ticket);
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
    }
}

 同步代码块与同步函数的同步问题:

问题代码:

package thread;
class   Ticket    implements    Runnable{
    private   int  num =100;
    boolean  flag=true;
    Object  object =new Object();
    @Override
    public    void run() {
        if(flag==true){
            while (true){
               synchronized (object){
                   if(num>0){
                       try{
                        Thread.sleep(100);
                       }catch (InterruptedException  e){
                           e.printStackTrace();
                       }
                       System.out.println(" thread  name"+Thread.currentThread()   +"obj ="+num--);
                   }
               }
            }
        }else{
           while (true){
               this.show();
           }
        }
    }
    //封装函数
    public   synchronized   int   show(){
        if(num>0){
            try{
                Thread.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(" thread  name"+Thread.currentThread()   +"num ="+num--);
        }
        if(num==0){
            return  1;
        }
        return   0;
    }
}
public class SynFunctionLockDemo {
    public static void main(String[] args) {
        Ticket   ticket  =new Ticket();
        Thread   thread1  =new Thread(ticket);
        Thread  thread2 =new Thread(ticket);
        thread1.start();
        ticket.flag=false;
        thread2.start();
    }
}  

标志位flag初始化为 true  但是main 函数中里面的语句会一起 执行此时flag 会被置为flase, start()运行的时候只会   this.show();

main  函数添加  Thread.sleep(10) :

public class SynFunctionLockDemo {
    public static void main(String[] args) {
        Ticket   ticket  =new Ticket();
        Thread   thread1  =new Thread(ticket);
        Thread  thread2 =new Thread(ticket);
        thread1.start();

        try{
            Thread.sleep(100);
        }catch (InterruptedException  e){
            e.printStackTrace();
        }
        ticket.flag=false;
        thread2.start();
    }
}

此时:原因是:不是用的用一个锁

修改: 使用 synchronized (this),因为  public synchronized int show() 调用也是当前对象 同步函数使用的锁是this,这样可以保证使用同一个锁。

package thread;
class   Ticket    implements    Runnable{
    private   int  num =100;
    boolean  flag=true;
  //  Object  object =new Object();
    @Override
    public    void run() {
        if(flag==true){
            while (true){
               synchronized (this){//当前对象
                   if(num>0){
                       try{
                        Thread.sleep(100);
                       }catch (InterruptedException  e){
                           e.printStackTrace();
                       }
                       System.out.println(" thread  name"+Thread.currentThread()   +"obj ="+num--);
                   }
               }
            }
        }else{
           while (true){
               this.show();
           }
        }
    }
    //封装函数
    public   synchronized   int   show(){
        if(num>0){
            try{
                Thread.sleep(10);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println(" thread  name"+Thread.currentThread()   +"num ="+num--);
        }
        if(num==0){
            return  1;
        }
        return   0;
    }
}
public class SynFunctionLockDemo {
    public static void main(String[] args) {
        Ticket   ticket  =new Ticket();
        Thread   thread1  =new Thread(ticket);
        Thread  thread2 =new Thread(ticket);
        thread1.start();

        try{
            Thread.sleep(100);
        }catch (InterruptedException  e){
            e.printStackTrace();
        }
        ticket.flag=false;
        thread2.start();
    }
}

 

  • 同步函数与同步代码块有什么区别?

1.同函数的锁唯一的 是this ,而同步代码块的锁可以是任意对象,建议使用 同步代码块。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值