Java中多线程重点总结

进程:

应用程序的执行实例

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

线程:

CPU调度和分派的基本单位

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

 

什么是多线程:

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

多线程的好处:

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

 

在JAVA中实现多线程编程:

Thread类:

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

主线程:

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

public static void main(String args[]) {

              Thread t= Thread.currentThread();

              t.setName("MyJavaThread");

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

Thread.currentThread():是获得主线程对象

.setName(……):设置线程名

.getName():获取线程名

 

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

一、继承java.lang.Thread类

  1. 定义MyThread类继承Thread
  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(); }

extends Thread:继承Thread类

run():run()方法中编写线程执行的代码

thread.start():启动线程调用了父类的start方法

 

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

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

例如:

MyThread t1 = new MyThread();

MyThread t2 = new MyThread();

 t1.start();

       t2.start();

 

启动线程是否可以直接调用run()方法?

不可以!

 

二、实现java.lang.Runnable接口

  1. 定义MyRunnable类实现Runnable接口
  2. 实现run()方法,编写线程执行体
  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();

       Thread myThread = new Thread(myRunnable);      

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

implements Runnable:实现Runnable接口

run():run()方法中编写线程执行的代码

myRunnable:创建线程对象

 

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

  1. 继承Thread类

编写简单,可直接操作线程

适用于单继承

  1. 实现Runnable接口

避免单继承局限性

便于共享资源

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

 

总结:

一、创建线程有哪2种方式?

  1. 继承Thread类
  2. 实现Runnable接口

二、启动线程

start()方法

三、线程对象调用start()方法和调用run()方法的区别

  1. run():只有主线程一条执行路径
  2. start():多条执行路径,主线程和子线程并行交替执行

 

线程的状态:

 

线程调度:

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

方法

说明

setPriority(int newPriority)

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

static void sleep(long millis)

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

void join()

等待该线程终止

static void yield()

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

void interrupt()

中断线程

boolean isAlive()

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

 

线程的优先级:

  1. 线程优先级由1~10表示,1最低,默认优先级为5
  2. 优先级高的线程获得CPU资源的概率较大

例如:

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);:

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

 

线程的休眠:

  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秒

 

线程的强制运行:

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

public final void join()

public final void join(long mills)

public final void join(long mills,int nanos)

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

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

【需处理InterruptedException异常】

例如:

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+"张票!"):第二步:显示出票信息

 

线程同步:

方法一:

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

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

或者

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

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

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

// 同步方法:售票

public synchronized void sale() {

              if (count <= 0) {

                     flag = true;

                     return;

        }

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

              count--;

              num++;

              try {

                     Thread.sleep(500);

              } catch (InterruptedException e) {

                     e.printStackTrace();

              }

       //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){

                     synchronized(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

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

 

死锁:

死锁产生的原因:

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

例如:

//模拟死锁

public class Test1 {

       public static void main(String[] args) {

              Object bobby = new Object();

              Object duck = new Object();

              //糖糖和豆豆拿到的是同一个bobby和同一个duck

              Thread Tangtang = new Thread(new Tangtang(bobby,duck));

              Thread Doudou = new Thread(new Doudou(bobby,duck));

              Tangtang.start();

              Doudou.start();

       }

}

//糖糖的构造和run方法

class Tangtang implements Runnable{

       Object bobby;//芭比

       Object duck;//玩具鸭

       //通过测试类传入bobby和duck的参数

       public Tangtang(Object bobby, Object duck) {

              this.bobby = bobby;

              this.duck = duck;

       }

       public void run() {

              synchronized(bobby){  //用synchronized锁定bobby

                     synchronized(duck){ //在用synchronized锁定bobby的情况下还想锁定duck

                     }

                     System.out.println("糖糖把芭比给豆豆玩!");

              }

       }

}

//糖糖的构造和run方法

class Doudou implements Runnable{

       Object bobby;

       Object duck;

       //通过测试类传入bobby和duck的参数

       public Doudou(Object bobby, Object duck) {

              this.bobby = bobby;

              this.duck = duck;

       }

       public void run() {

              synchronized(duck){  //用synchronized锁定duck

                     synchronized(bobby){   ////在用synchronized锁定duck的情况下还想锁定bobby

                     }

                     System.out.println("豆豆把玩具鸭给糖糖玩玩!");

              }

       }

}

 

死锁的条件:

  1. 两个或两个以上的线程共享同一份资源
  2. 某个线程拿到一个锁以后,还想拿第二个锁(但是这个锁已经被其他人独占了),造成锁的嵌套

 

避免

  1. 当前线程先释放自己的锁,使得代码可以继续运行。
  2. 尽量减少同步方法或者同步代码块的嵌套,避免死锁

 

总结:

猜你喜欢

转载自blog.csdn.net/baidu_29343517/article/details/81459917
今日推荐