线程

多线程

 

进程:

应用程序的执行实例

有独立的内存空间和系统资源

进程是程序的一次动态执行过程,他对应了从代码加载 执行至执行完毕的一整个过程

特点:

并发执行的,动态创建、动态消亡

进程是系统运行程序的基本单位

每一个进程都有自己独立的一块空间 一组系统资源

每一个进程的内部数据和状态都是完全独立的

进程至少包含一个线程 这个线程叫做主线程

线程:

CPU调度和分派的基本单位

进程中执行运算的最小单位,可完成一个独立的顺序控制流程

真正在CPU上运行的是线程

一个线程可以创建一个线程也可以删除一个线程 -- 线程调度

多个线程完成不同工作就是多线程 抢占资源

 

什么是多线程:

  1. 如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程”。
  2. 多个线程交替占用CPU资源,而非真正的并行执行

 

多线程好处:

  1. 充分利用CPU的资源
  2. 简化编程模型
  3. 带来良好的用户体验

 

多线程的分类

核心级线程:

与系统任务相关的线程 负责处理不同进程之间的多个线程

用户级线程:

用户自主编写的线程即为用户级线程 这些线程的创建、运行和消亡都是在编写应用程序时进行控制的

 

线程和进程之间的区别!

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

资源分配给进程 同一进程的所有线程共享该进程的所有资源

处理机分配给线程 即真正在处理机上运行的是线程

 

 

线程的创建和启动:

在Java中创建线程的两种方式

 继承java.lang.Thread类

 实现java.lang.Runnable接口

定义线程-创建线程对象--启动线程--终止线程

 

在JAVA中实现多线程编程:

Thread类

Java提供了java.lang.Thread类支持多线程编程

主线程:

  1. main()方法即为主线程入口
  2. 产生其他子线程的线程
  3. 必须最后完成执行,因为它执行各种关闭动作
public static void main(String[] args) {

Thread t=Thread.currentThread();           //获得主线程对象

System.out.println("当前线程是:"+t.getName());

t.setName("MyJavaThread");                 //设置新的线程名

System.out.println("当前线程名是:"+t.getName());//获取线程名

}

 

Java中创建线程的两种方式 

  1. 继承java.lang.Thread类
  1. 定义MyThread类继承Thread类----extends
  2. 重写run()方法,编写线程执行体======什么执行的条件和执行体
  3. 在测试类里面,创建线程对象,调用start()方法启动线程

例如:

public class MyThread extends Thread{

    //重写run()方法

public void run(){

for(int i=1;i<100;i++){    //执行体

System.out.println(Thread.currentThread().getName()+":"+i);   }}}

public static void main(String[] args) {

MyThread thread = new MyThread();

thread.start();   }

多个线程交替执行,不是真正的“并行”

线程每次执行时长由分配的CPU时间片长度决定

例如:

MyThread t1 = new MyThread();

MyThread t2 = new MyThread();

t1.start();

t2.start();  //注意是start方法,而不是run方法!

应该是调用start方法:1、多条执行(输出)路径;   2、主线程和子线程并行交替执行。

如果调用run()方法:1、只有主线程一条执行(输出)路径;2、依次调用了两次run()方法。

  1. 实现java.lang.Runnable接口--implements
  1. 定义MyRunnable类实现Runnable接口
  2. 实现run()方法,编写线程执行体---for()
  3. 在测试类中,创建线程对象,调用start()方法启动线程

例如:

public class MyRunnable implements Runnable {

public void run() {

for(int i=1;i<100;i++) {

System.out.println(Thread.currentThread().getName()+":"+i);

}}}
public static void main(String[] args) {

MyRunnable myRunnable = new MyRunnable(); //myRunnable:创建线程对象

Thread myThread = new Thread(myRunnable);

thread.start(); //启动线程}

implements Runnable:实现Runnable接口
public static void main(String[] args) {

ThreadRunnable tr1=new ThreadRunnable();

ThreadRunnable tr2=new ThreadRunnable();

Thread t1=new Thread(tr1,"tr1");

Thread t2=new Thread(tr2,"tr2");   //抢同一个红包

t1.start();

t2.start();

}

两种创建线程方式的优缺点:

  1. 继承Thread类

编写简单,可直接操作线程,适用于单继承

  1. 实现Runnable接口

避免单继承局限性,便于共享资源

因为没有start()方法,只能先new Thread

所以:推荐使用实现Runnable接口方式创建线程

 

总结:

  1. 创建线程有哪2种方式?
  1. 继承Thread类
  2. 实现Runnable接
  1. 启动线程

start()方法

  1. 线程对象调用start()方法和调用run()方法的区别
  1. run():只有主线程一条执行路径
  2. start():多条执行路径,主线程和子线程并行交替执行

 

线程的状态:

 

线程的状态

线程的生命周期包括四个状态:新生状态,可运行状态,阻塞状态,死亡状态

新生状态:new出来之后没有start

可运行状态:调了start之后只是有了可运行的机会,准备抢占资源

阻塞状态:当一个运行时的线程出现某种原因不能继续运行时,则该线程进入阻塞状态,解决问题之后 又变成就绪状态 等待调度

死亡状态:一个线程的run方法执行完毕之后或者运行中出现异常时线程进入死亡状态

线程调度:

线程调度指按照特定机制为多个线程分配CPU的使用权setPriority

当同一时刻有多个线程处于可运行状态时,他们需要排队等待CPU资源 这时候每个线程会自动获得一个线程的优先级

方法

说明

setPriority(int newPriority)

更改线程的优先级(1-10之间的整数)

static void sleep(long millis)

在指定的毫秒数内让当前正在执行的线程休眠

void join()

等待该线程终止

static void yield()

暂停当前正在执行的线程对象,并执行其他线程

void interrupt()

中断线程

boolean isAlive()

测试线程是否处于活动状态

 

线程的优先级:setPriority关键字

  1. 线程优先级由1~10表示,1最低,默认优先级为5

优先级高的线程获得CPU资源的概率较大

例如:setPriority

public static void main(String[] args) {

Thread t1 = new Thread(new MyThread(),"线程A");

        Thread t2 = new Thread(new MyThread(),"线程B");

t1.setPriority(Thread.MAX_PRIORITY);

t2.setPriority(Thread.MIN_PRIORITY);

}}

Thread t1 = new Thread(new MyThread(),"线程A");:创建线程对象并指定线程名

t1.setPriority(Thread.MAX_PRIORITY);

t2.setPriority(Thread.MIN_PRIORITY);

 

两个线程对象分别设置为最高优先级和最低优先级

 

线程强制执行

线程名.join()

 .join(long mills)毫秒数

 .join(long mills,int nanos)毫秒数 纳秒数

线程的礼让(只提供了一种可能 不保证一定礼让)

 暂停当前线程 允许其他具有相同优先级单位线程获得运行机会

 该线程处于就绪状态 不进入阻塞状态。

线程同步问题

当多个线程操作同一资源时(多个并发线程访问同一资源的同步代码块时) 将引发数据不安全问题,这时候就需要使用线程同步。

如何实现?

同一时刻只能有一个线程进入synchronized(this)代码块

当一个线程访问一个synchronized(this)代码块时 其他synchronized(this)同步代码块同样被锁定

当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码

 

 

线程的休眠:

  1. 让线程暂时睡眠:指定时长,线程进入阻塞状态
  2. 睡眠时间过后线程会再进入可运行状态

millis为休眠时长,以毫秒为单位】

【调用sleep()方法需处理InterruptedException异常】

例如:

public class Wait {

public static void bySec(long s) {

for (int i = 0; i < s; i++) {

System.out.println(i + 1 + "秒");

try {

Thread.sleep(1000); 

} catch (InterruptedException e) {

e.printStackTrace();

}}}}
public static void main(String[] args) {

System.out.println("*****主线程开始休眠*****");

Wait.bysec(5); //让主线程休眠5秒

System.out.println("*****主线程休眠结束*****"); }

Thread.sleep(1000):线程休眠1秒

 

线程安全的类型

线程安全 方法同步 效率低 适合多线程并发共享资源

非线程安全 方法不同步 效率高 适合单线程

实现线程间通信

wait();调用wait方法会挂起当前线程 并释放共享资源的锁

notify():调用任意对象的notify方法会在因调用该对象的wait方法而阻塞的线程中随机选择一个线程解除阻塞,但要等到获得锁后才可真正的执行线程

notifyAll() 调用了该方法会将因调用该对象的wait方法而阻塞的所有线程一次性全部接触阻塞。

 

线程池!!!

为什么需要使用线程池?

线程缺乏统一管理 占用过多系统资源

缺乏更多功能

使用线程池的好处

重用存在的线程 减少对象创建 消亡的开销

有效控制最大并发数 提高系统资源使用率

定时执行 定期执行

线程池所在包 java.util.concurrent

顶级借口是Excutor 真正的线程池借口是ExcutorService

java.util.concurrent.Excutors类提供创建线程池的方法

newCachedThreadPool()创建一个可缓存的线程池 有任务时菜创建新任务

newSingleThreadExcutor()创建一个单线程池

newFixedThreadPool(int nThreads)创建一个固定长度的线程池

newScheduledThreadPool(int corePoolSize)创建一个固定长度的线程池而且以延迟或者定时的方式来执行任务

 

死锁产生的原因

两个线程都在等待对方先完成,造成程序的停滞

 

死锁的条件

两个或者两个以上的线程在活动

某个线程拿到一个锁之后 还想拿第二个锁 造成锁的嵌套

 

避免死锁的有效方法是

线程因某个条件未满足而受阻,不能让其继续占有资源

如果有多个对象需要互斥访问,应确定线程获得锁的顺序 并保证整个程序以相反的 序释放锁

构造器中各个参数的含义

corePoolSize 核心池的大小

maximumPoolSize 线程池最大线程数

keepAliveTime 表示线程没有任务执行时最多保持多久

unit 参数keepAliveTime的时间单位

workQueue 一个阻塞队列 用来存储等待执行的任务

threadFactory 线程工厂 主要用来创建线程

handler 表示当拒绝处理任务时的策略

 

线程的强制运行

使当前线程暂停执行,等待其他线程结束后再继续执行本线程

public final void join()

public final void join(long mills)

public final void join(long mills,int nanos)

【millis:以毫秒为单位的等待时长】

【nanos:要等待的附加纳秒时长】

【需处理InterruptedException异常】

 

线程的强制执行是再for()里面先用if(){  xx.Join()  }后输出;

  礼让是先输出,后if(){ Thread.yield  }

例如:

public static void main(String[] args) {

Thread temp = new Thread(new MyThread());

temp.start();

for(int i=0;i<20;i++){

if(i==5){

try {

    temp.join();

} catch (InterruptedException e) {

e.printStackTrace();

                     }

              }

System.out.println(Thread.currentThread().getName()+"运行:"+i); } }

temp.join():阻塞主线程,子线程强制执行

 

线程的礼让:

  1. 暂停当前线程,允许其他具有相同优先级的线程获得运行机会
  2. 该线程处于就绪状态,不转为阻塞状态

public static void yield()

【只是提供一种可能,但是不能保证一定会实现礼让】

例如:

public class MyThread implements Runnable{

     public void run(){

      for(int i=0;i<5;i++){

      System.out.println(Thread.currentThread().

              getName()+"正在运行:"+i);

      if(i==3){

     System.out.print("线程礼让:");

Thread.yield();  } } }}
public static void main(String[] args) {

MyThread my = new MyThread();

Thread t1 = new Thread(my,"线程A");

Thread t2 = new Thread(my,"线程B");

t1.start();

t2.start();}

Thread.yield():当i=3时,当前线程礼让

 

使用线程的步骤:

  1. 定义线程
  2. 创建线程对象
  3. 启动线程
  4. 终止线程

 

总结:

  1. 线程的五个状态

创建、就绪、阻塞、运行、死亡

  1. 线程调度的方法
setPriority(int grade)

sleep(long millis)

Join()

yield()

 

 

当多个线程操作同一共享资源时,一个线程未完成全部操作的时候,其他线程修改的数据,造成数据不安全问题

例如:

public void  run(){

while(true){

            //省略代码:判断是否余票

            num++;

count--;

try {

Thread.sleep(500); //模拟网络延时

} catch (InterruptedException e) {//…}

System.out.println(Thread.currentThread().getName()+"抢到第"+num+"张票,剩余"+count+"张票!"); }}



num++;

count--; :第一步:修改网站数据(出票号和剩余票数)

System.out.println(Thread.currentThread().getName()+"抢到第"+num+"张票,剩余"+count+"张票!"):第二步:显示出票信息

 

线程同步:

多个线程操作同一个共享资源时,将引发数据不安全的问题

 

  1. 同一时刻只能有一个线程进入synchronized(this)同步代码块;
  2. 当一个线程访问一个synchronized(this) 同步代码块,其他synchronized(this)

同步代码块同样被锁定;

  1. 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码块。

 

方法一:

使用synchronized修饰的方法控制对类成员变量的访问

 

访问修饰符 synchronized 返回类型 方法名(参数列表){……}

或者

synchronized 访问修饰符 返回类型 方法名(参数列表){……}

 

【synchronized就是为当前的线程声明一个锁】方法二:

 

使用synchronized关键字修饰的代码块

synchronized(syncObject){ //需要同步的代码}

 

syncObject为需同步的对象,通常为this

【效果与同步方法相同】

方法一:

例如:(修改之前的抢票)

// 同步方法:售票

public synchronized void sale() {

if (count <= 0) {

flag = true;

return;

 }

//1、修改数据(剩余票数,抢到第几张票)

count--;

num++;

try {

Thread.sleep(500);

} catch (InterruptedException e) {

e.printStackTrace();

}

//调用同步方法

public void run(){

while(!flag){

sale();

}

}
//2、显示信息,反馈用户抢到第几张票

System.out.println(Thread.currentThread().getName()+"抢到第"+num+"张票,剩余"+count+"张票"); } }

public void run() {

//循环当剩余票数为0时结束

while(!flag){

sale();

}

 

方法二:

使用synchronized关键字修饰的代码块

synchronized(syncObject){ //需要同步的代码}

syncObject为需同步的对象,通常为this

【效果与同步方法相同】

例如:(修改之前的抢票)

public void run() {

//循环当剩余票数为0时结束

while(true){

synchron`ized(this){

//同步方法,实现售票

if(count<=0){

break;

}

//1、修改数据(剩余票数,抢到第几张票)

count--;

num++;

try {

Thread.sleep(500);

} catch (InterruptedException e) {

e.printStackTrace();

}

//2、显示信息,反馈用户抢到第几张票

System.out.println(Thread.currentThread().getName()+"抢到第"+num+"张票,剩余"+count+"张票");  } } } }

 

多个并发线程访问同一资源的同步代码块时:

  1. 同一时刻只能有一个线程进入synchronized(this)同步代码块
  2. 当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定
  3. 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码

 

查看ArrayList类的add()方法定义:

public boolean add(E e) {

       ensureCapacityInternal(size + 1);

elementData[size++] = e;

        return true; }

ensureCapacityInternal(size + 1):集合扩容,确保能新增数据

elementData[size++] = e:在新增位置存放数据

 

ArrayList类的add()方法为非同步方法

当多个线程向同一个ArrayList对象添加数据时,可能出现数据不一致问题

【ArrayList为非线程安全的类型】

 

 

方法是否同步

 效率比较

适合场景

线程安全

多线程并发共享资源

非线程安全

单线程

 

Hashtable  &&  HashMap

  1. Hashtable
  1. 继承关系【实现了Map接口,Hashtable继承Dictionary类】
  2. 线程安全,效率较低
  3. 键和值都不允许为null
  1. HashMap
  1. 继承关系【实现了Map接口,继承AbstractMap类】
  2. 非线程安全,效率较高
  3. 键和值都允许为null

 

StringBuffer  &&  StringBuilder

前者线程安全,后者非线程安全

 

  1. 继承java.lang.Thread类
  1. 定义MyThread类继承Thread类
  2. 重写run()方法,编写线程执行体
  3. 创建线程对象,调用start()方法启动线程
  1. 实现java.lang.Runnable接口
  1. 定义MyRunnable类实现Runnable接口
  2. 实现run()方法,编写线程执行体
  3. 创建线程对象,调用start()方法启动线程

实现线程间通信

wait();调用wait方法会挂起当前线程 并释放共享资源的锁

notify():调用任意对象的notify方法会在因调用该对象的wait方法而阻塞的线程中随机选择一个线程解除阻塞,但要等到获得锁后才可真正的执行线程

notifyAll() 调用了该方法会将因调用该对象的wait方法而阻塞的所有线程一次性全部接触阻塞

 

死锁产生的原因

两个线程都在等待对方先完成,造成程序的停滞

死锁的条件

两个或者两个以上的线程在活动

某个线程拿到一个锁之后 还想拿第二个锁 造成锁的嵌套

避免死锁的有效方法是

线程因某个条件未满足而受阻,不能让其继续占有资源

如果有多个对象需要互斥访问,应确定线程获得锁的顺序 并保证整个程序以相反的顺序释放锁

 

线程池

为什么需要使用线程池

线程缺乏统一管理 占用过多系统资源

缺乏更多功能

使用线程池的好处

重用存在的线程 减少对象创建 消亡的开销

有效控制最大并发数 提高系统资源使用率

定时执行 定期执行

 

线程池所在包 java.util.concurrent

顶级借口是Excutor 真正的线程池借口是ExcutorService

java.util.concurrent.Excutors类提供创建线程池的方法

newCachedThreadPool()创建一个可缓存的线程池 有任务时菜创建新任务

newSingleThreadExcutor()创建一个单线程池

newFixedThreadPool(int nThreads)创建一个固定长度的线程池

newScheduledThreadPool(int corePoolSize)创建一个固定长度的线程池而且以延迟或者定时的方式来执行任务

 

构造器中各个参数的含义

corePoolSize 核心池的大小

maximumPoolSize 线程池最大线程数

keepAliveTime 表示线程没有任务执行时最多保持多久

unit 参数keepAliveTime的时间单位

workQueue 一个阻塞队列 用来存储等待执行的任务

threadFactory 线程工厂 主要用来创建线程

handler 表示当拒绝处理任务时的策略

 

                                            

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值