当前位置:网站首页 > Java基础 > 正文

java基础系列23



1、什么是进程?什么是线程?

  进程概念:进程是资源分配的基本单位,它是程序执行时的一个实例,在程序运行时创建。进程是容器,里面装的都是线程。

  线程概念:线程是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位。一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。

注意:
  1、一个进程中至少要有一个线程,该线程必须是主线程
  2、一个进程中可以有多个线程,至少要有一个主线程
  3、多线程之间按照自己的执行路径同时运行(在cpu中交替执行),互不影响。

为什么多线程可以同时运行?
  因为每个线程在CPU上执行的时间非常短,多线程执行的顺序高速的切换,是人类无法感受到的。

2、创建线程 的三种方式

创建线程方式一:继承Thread类 --------重写run()方法---------- 在主线程中启动(start())
  
    注意:启动线程是调用start()而不是run(), run()仅仅是线程的成员,不具有启动的功能。

创建线程方式二:定义 实现Runnable接口 实现类----------在类中重写run()-----------在主线程中先创建实现类的对象,传递给线程类对象的构造方法中
  
    eg MyRunnable my = new MyRunnable();

创建线程方式三:实现Callable接口和Future创建线程--------重写call()方法,并且有返回值-----------在主线程中启动(start())

Callable接口是一个带泛型的接口,泛型的类型就是线程返回值的类型。实现Callable接口中的call()方法,方法的返回类型与泛型的类型相同。

Callable<String> callable=new CallableThread(); FutureTask<String> futureTask=new FutureTask<String>(callable); Thread thread=new Thread(futureTask); thread.start();//开启一个线程

三种方式的区别:

  1、实现 接口的方式可以避免java单继承带来的 局限性。
  2、数据共享问题: 使用继承的方式 只能将属性修改为static, 而实现接口的方式可以自动做到数据共享(只创建一个Runnable接口实现类对象)
  
3、 Callable规定(重写)的方法是call(),有返回值,并且可以可以抛出异常。Runnable规定(重写)的方法是run(),没有返回值,不能抛出异常。

4、运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

3、Thread类介绍

Thread类位于java.lang包下,常见的构造方法和字段如下:

  1. Thread():无参的构造方法
  2. Thread(Runnable target):依赖实现Runnable接口对象创建线程对象
  3. Thread(String name):指定创建线程对象的名称
  4. Thread(Runnable target,String name):指定创建线程对象的Runnable接口对象和名字

    MAX_PRIORITY =10;
    MIN_PRIORITY =1;
    NORM_PRIORITY =5;

  这些字段给线程分配优先级,都有默认值。。

      获取当前线程:Thread.currentThread()

      获取线程的名字 getName()

      获取线程的IDgetId()

4、线程的优先级

概念: :如果一个线程的优先级越高,获得CPU资源的机会更大,不等于高优先级的就最先执行,

线程优先级的值为1-10,默认优先级为5

  1. 如何设置优先级呢? 调用方法

    线程对象.getPriority() 返回线程的优先级

    void setPriority(int newPriority) 更改线程的优先级

public static void main(String[] args) { Thread main = Thread.currentThread(); System.out.println("主线程默认优先级:"+main.getPriority());//主线程默认优先级:5 main.setPriority(Thread.MAX_PRIORITY); System.out.println("主线程修改后的优先级:"+main.getPriority());//主线程修改后的优先级:10 main.setPriority(7); System.out.println("主线程修改后的优先级:"+main.getPriority());//主线程修改后的优先级:7 }

5、守护线程

  1. 守护线程(精灵线程/后台线程)
  2. 跟普通线程(用户线程)没区别
    唯一区别是:当程序中没有普通线程时,守护线程也就没有用了,会自动死亡(死亡时间不确定),当程序中只剩下守护线程时,JVM可以关闭了
  1. 特点

    ① 一般来说后台线程是为前台线程服务的(例如最典型的守护线程:gc线程,垃圾回收器);

    ② 如果所有的前台线程都死了,那么后台线程也会自动的死亡;但是前台线程死了,后台线程不一定立即死亡(可能还需要收尸...

    ③ 一个线程的默认状态和创建它的环境线程状态一致(比如:已经设置一个线程为守护线程,那么在该线程里面创建的线程都是守护线程。)

常用方法:

  setDaemon(true): 设置值为true是守护线程。
  isDaemon(): 判断当前线程是否是守护线程,返回 true 和 false.
注意:当一个线程处于运行状态的时候,不可以去改变守护状态,否则发生异常

6、线程终止 join()方法

join方法有三个重载版本:

/code>

  

br> 对象名.join()

br>  调用此方法,当前线程会 让出cpu的使用权,优先执行其他线程,该线程也可能会处于被执行的对象。yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会更大而已。

  sleep方法有两个重载版本:

  

并行和并发的区别

线程安全:线程主体中的某段代码同时只有一个线程可以访问,其他线程处于等待状态

解决问题:为了解决多线程下访问资源共享时产生数据安全问题。

  一、使用同步代码块

如:卖票案例 出现了线程安全 重复的票不能出现

步骤:


成员位置建立锁对象;


synchronized(锁对象){


出现安全问题代码


}

  1、锁对象 任意对象


  2、 必须保证多个线程使用的是同一个锁对象


  3、把{} 只让一个线程进

例子:

public class RunnableImpl implements Runnable{ // 定义共享资源 线程不安全 private int ticket = 100; //在成员位置创建一个锁对象  Object obj = new Object(); // 线程任务 卖票 @Override public void run() { while(true){ //建立锁对象  synchronized (obj){ if(ticket>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //卖票操作 System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票"); ticket--; } } } } } 

二、使用同步方法解决多线程安全

步骤

1 、创建一个方法 修饰符添加synchronized


2、 把访问了 共享数据的代码放入到方法中


3 、调用同步方法

public class RunnableImpl implements Runnable{ // 定义共享资源 线程不安全 private int ticket = 100; // 线程任务 卖票 @Override public void run() { while(true){ payTicket();//调用下面synchronized修饰的方法 } }  public synchronized void payTicket(){ if(ticket>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } //卖票操作 System.out.println(Thread.currentThread().getName()+"正在卖第"+ticket+"张票"); ticket--; } } } 

  三、同步锁,不解锁会造成死锁

java.util.concurrent.locks.Lock;机制提供了比synchronized代码块和synchronized方法更广泛的锁操作,在类中定义一个锁对象: ReentrantLock lock = new ReentrantLock();


在实际业务代码中 先上锁 lock.lock() 执行完业务后 释放锁 lock.unlock()。

Lock lock = new ReentrantLock(true); //加锁 lock.lock(); try{ //业务逻辑 }finally{ //放锁 lock.unlock(); }

1、用法:synchronized (隐式锁)在需要同步的对象中加入此控制,synchronized可以加在方法和代码块上面,括号中表示要锁的对象。

lock(显示锁) 需要显示在指定的 起始和终止位置 。 一般使用 ReentrantLock 类作为锁。 加锁 lock(),解锁 unlock() . finally块一定要解锁,不然会造成死锁情况。

2、性能: synchronized 是Java的关键字,在

JVM层面

实现了对资源的同步互斥访问 . 而 lock 是一个接口。里面有许多方法。

比如:尝试获取锁包括立即返回是否成功的tryLock(),以及一直尝试获取的lock()方法和尝试等待指定时间长度获取的方法,比synchronized相对灵活了许多。

3、机制:synchronized(悲观锁CUP,怕别人抢吃的), 同一时刻不管是读还是写都只能有一个线程对共享资源操作,其他线程只能等待. 当锁的对象成功完成或者抛出异常时,会自动释放锁。

lock(乐观锁CAS)

Lock有多个锁获取的方式

,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待。

lock():获取锁,如果锁被暂用则一直等待 unlock():释放锁 tryLock(): 注意返回类型是boolean,如果获取锁的时候锁被占用就返回false,否则返回true tryLock(long time, TimeUnit unit):比起tryLock()就是给了一个时间期限,保证等待参数时间 lockInterruptibly():用该锁的获得方式,如果线程在获取锁的阶段进入了等待,那么可以中断此线程,先去做别的事   

补充重点精华:ReentrentLock 和ReentrentReadWriteLock两个类可重入的锁,即当同一线程获取到锁之后,他在不释放锁的情况下,可以再次获取到当前已经拿到的锁,只需标记获取到锁的次数加一即可;

ReentrantReadWriteLock是Lock的另一种实现方式,我们已经知道了ReentrantLock是一个排他锁,同一时间只允许一个线程访问,而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。

通过在读多,写少的高并发情况下,我们用

ReentrantReadWriteLock  

分别获取读锁和写锁来提高系统的性能,因为读锁是共享锁,即可以同时有多个线程读取共享资源,而写锁则保证了对共享资源的修改只能是单线程的。

线程的生命周期

新建

状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

就绪

状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

运行

状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

阻塞

状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。

死亡

状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程中的等待和通知机制

进入休眠状态 并 释放锁

当前线程不会马上释放该对象锁

,要等到程序退出同步块后,当前线程才会释放锁。

只能在同步方法或同步块中调用。

3. Condition的作用是对锁进行更精确的控制。

Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,

Condition中的signalAll()相当于Object的notifyAll()方法。

不同的是,Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。

15、

生产者和消费者模式

 * 1.当消费者消费的资源不够时,通知生产者生产,消费者处于等待状态 * 2.当生产者生产的资源饱和时,通知消费者消费,生产者处于等待状态 */ public class Resource { private int num; //表示当前资源量 private int size = 20;//表示资源池的大小(饱和状态) //表示消费了一个资源 public synchronized void remove(){ if(num>0){ num--; System.out.println(Thread.currentThread().getName()+"消费了一件资源,剩余"+num+"件");        this.notify(); }else{ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); }  }  } public synchronized void add(){ if(num<size){ num++; System.out.println(Thread.currentThread().getName()+"生产了一件资源,剩余"+num+"件"); this.notify();//通知消费者消费 }else{ try { this.wait();//当资源的数量处于饱和状态生产者处于等待状态 } catch (InterruptedException e) { e.printStackTrace(); } }  } }  

 public class CustomerRunnable implements Runnable { Resource res; public CustomerRunnable(Resource res){ this.res = res; } @Override public void run() { while(true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } res.remove(); } } }   //生产者 public class ProducerRunnable implements Runnable{ Resource res; public ProducerRunnable(Resource res){ this.res = res; } @Override public void run() { while(true){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } res.add();//表示让生产者生产 }  } }  

 public class Test {  public static void main(String[] args) { Resource res = new Resource();//创建资源类 ProducerRunnable pr = new ProducerRunnable(res);//生产者 CustomerRunnable cr = new CustomerRunnable(res);//消费者 Thread pr1 = new Thread(pr,"生产者A");//生产者线程 Thread pr2 = new Thread(pr,"生产者B");  Thread cr1 = new Thread(cr,"消费者1号");//消费者线程 Thread cr2 = new Thread(cr,"消费者2号");  pr1.start(); pr2.start(); cr1.start(); cr2.start(); }  } 

使用 juc锁 condition 来解决存钱和取钱案例

在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类,用于定义类似于线程的自定义子系统,包括线程池,异步 IO 和轻量级任务框架;还提供了设计用于多线程上下文中的 Collection 实现等。

 import java.util.concurrent.locks.ReentrantLock;  public class Account { private double money;//银行账户余额   private ReentrantLock lock = new ReentrantLock(); Condition con1 = lock.newCondition(); public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } public void saveMoney(double money){ lock.lock(); if(this.money > 0){ try { con1.await();//通知存钱线程处于等待状态 } catch (InterruptedException e) { e.printStackTrace(); } }else{ this.money+=money; System.out.println("存钱:"+money); }  con1.signal();//通知等待的取钱线程进行取钱 lock.unlock(); } public void fetchMoney(double money){ lock.lock(); if(this.money > 0){ this.money-=money; System.out.println("取钱"+money); con1.signal();//通知等待的存钱线程进行存钱 } try { con1.await();//当账户没钱了,让取钱线程处于等待状态 } catch (InterruptedException e) { e.printStackTrace(); } lock.unlock(); } }  

 Account acc;//银行账户对象 public FetchMoneyRunnable(Account acc) { this.acc = acc; } //模拟账户取钱10次 @Override public void run() { for (int i = 0; i < 10; i++) { acc.fetchMoney(500);//取钱操作 } } }  public class SaveMoneyRunnable implements Runnable{ Account acc; public SaveMoneyRunnable(Account acc) { this.acc = acc; } // 模拟存钱10次 @Override public void run() { for (int i = 0; i < 10; i++) { acc.saveMoney(1000); } } }  

 public static void main(String[] args) { Account acc = new Account();//创建一个账户 FetchMoneyRunnable fmr = new FetchMoneyRunnable(acc); SaveMoneyRunnable smr = new SaveMoneyRunnable(acc); Thread fetch = new Thread(fmr);//取钱线程 Thread save = new Thread(smr);//存钱线程  //启动线程 fetch.start(); save.start(); } }  

  

  • 上一篇: java基础的数组
  • 下一篇: java技术基础题
  • 版权声明


    相关文章:

  • java基础的数组2025-04-21 21:50:06
  • java基础测试2025-04-21 21:50:06
  • java基础20232025-04-21 21:50:06
  • 小学基础学java2025-04-21 21:50:06
  • 济南java基础培训2025-04-21 21:50:06
  • java技术基础题2025-04-21 21:50:06
  • java基础190讲2025-04-21 21:50:06
  • java二进制基础2025-04-21 21:50:06
  • 基础java面试2025-04-21 21:50:06
  • java基础判断2025-04-21 21:50:06