【学习笔记】Java 多线程

Java多线程

一. 基本概念

程序

是为完成特定任务,用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。

进程

是程序的一次执行过程,或是正在运行的一个程序。是一个 动态的过程,有它自身的产生、存在和消亡的过程。(进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域)

线程

进程可进一步细化为线程,是一个线程内部的一条执行路径。

若一个线程同一时间并行执行多个线程,就是支持多线程的。

线程作为调度和执行的单位,每个线程拥有和独立的运行栈和程序计数器(pc),线程切换的开销小

一个进程中的多个线程共享相同的内存单元/内存地址空间->他们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更渐变、高效。但多个线程操作共享的系统资源可能就会多带来安全的隐患(解决方法在线程的同步)。

每个线程独占一份虚拟机栈和程序计数器,每个进程只占一份方法区和堆区,静态在方法去,new出来的在堆区

 

单核CPU和多核CPU的理解:

单核CPU

其实是一种假的多线程,因为在一个时间单月内,也只能执行一个线程的任务。例如:虽然有好多车道,但是收费站只有一个工作人员在收费,周游收了费才能够通过,那么CPU就好比收费人员,如果有某个人不想交钱,那么收费人员可以把他“挂起”,但是因为CPU时间单元特别短,因此感觉不出来

多核CPU

如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)

一个Java应用程序Java.exe,其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。

并行与并发:

并行:

多个CPU同时执行多个任务,比如:多个人同时做不同的事。

并发:

一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事

使用多线程的优点:

何时需要多线程

线程的分类

二.线程的创建和使用(两种)【重点】

面试常问:有几种?

答:四种

/**  * 多线程的创建,方式1:继承与thread类  * 1.创建一个继承于thread类的子类  * 2.重写超类的run方法  -->将此线程执行的操作声明在run()中  * 3.创建Thread的子例对象  * 4.通过此对象调用start()  *  * 例子:遍历100以内的所有的偶数  */ public class thread1 extends Thread{     public void run(){        for (int i=0;i<=100;i+=2)         System.out.printf("i = " + i+"\t");//            System.out.printf(Thread.currentThread().getName()+"  ");         System.out.println();     }     public static void main(String[] args) {         thread1 th1=new thread1();         th1.start();         th1.run();         System.out.println("输出完毕!");     } } 

输出

 

线程的调度

调度策略

Java的调度方法

线程优先级:

 

 

方法一:

/**  * 测试Thread中的常用方法  * start 启动当前线程,调用当前线程的run  * run 通常需要重写Thread类中的此方法,讲创建的线程要执行的操作声明在此方法中  * currentThread 静态方法,返回执行当前代码的线程  * getName();获取当前线程的线程名  * setName();设置当前线程的线程名  * yield();释放当前CPU的执行权  * join();进入已经开始的线程,如果线程未start,则不进入  *        在线程a中调用线程b的join,线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态  *        注意:在线程a阻塞是,线程b yeild是不会进入线程a的  * stop();强制线程生命期结束  * sleep(long millis) 阻塞(延迟)millis毫秒  * 子类抛出的异常不能比父类大  * isAlive() 判断当前线程是否存活  */ public class thread1 extends Thread{     @Override     public void run(){         for (int i=0;i<=100;i+=2) {             System.out.println(Thread.currentThread().getName() + i);             if(i%1==0) this.yield();         } System.out.println();     }     public thread1() {     }     public thread1(String name) {         super(name);     }     public static void main(String[] args) {         thread1 th1=new thread1("卧槽"); //        thread1 th2=new thread1("牛逼");         th1.start(); //        th1.setName("牛逼");         for (int i = 0; i < 100; i++) {             System.out.println("输出完毕!");             if(i==20)try {                 th1.join();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }     } } 

方法二:

public class runable1 implements Runnable{     @Override     public void run() {         for (int i = 0; i < 100; i++) {             System.out.println(i);         }     }     public static void main(String[] args) {         runable1 w=new runable1();         new Thread(w).start();//构造函数中可以放继承Runnable接口的类的实例         new Thread(w).start();     } } 

由此可见,每一个run用start开始后都是一个单独的线程名不同的线程

而Thread也是继承与Runnable接口的

项目:

一.多窗口买票(不实现线程锁,有BUG的那种,较为简单,并且更能够直观的体现出BUG)

方式一
import java.util.Locale; import java.util.Scanner; class Windows extends Thread{     public static int book=30;     int LocalNumber=0;     public Windows() {     }     public Windows(String name) {         super(name);     }     @Override     public void run() {         for (int i = 0; i < 10; i++) {             if(book<=0)this.stop();//就算放到这里也不安全,有一个时间差             System.out.println(this.getName() + "当前已抢到" + ++LocalNumber + "张票!仓库中还剩" + (book -1)+ "张!");             book--;         }     } } public class WindowsTest{     public static void main(String[] args) {         Windows XM=new Windows("小明");         Windows XH=new Windows("小红");         Windows XG=new Windows("小刚");         System.out.println("双十一抢票开始!目前库存"+Windows.book+"张票");         XM.start();         XH.start();         XG.start();     } } 
方式二
import java.util.Locale; import java.util.Scanner; class Windows implements Runnable{     public static int book=30;     int LocalNumber=0;     public Windows() {     }     @Override     public void run() {         for (int i = 0; i < 10; i++) {             if(book<=0)break;//就算放到这里也不安全,有一个时间差             System.out.println(Thread.currentThread().getName() + "当前已抢到" + ++LocalNumber + "张票!仓库中还剩" + (book -1)+ "张!");             book--;         }     } } public class WindowsTest{     public static void main(String[] args) {         Thread XM=new Thread(new Windows(),"小明");         Thread XH=new Thread(new Windows(),"小红");         Thread XG=new Thread(new Windows(),"小刚");         System.out.println("双十一抢票开始!目前库存"+Windows.book+"张票");         XM.start();         XH.start();         XG.start();     } } 

两种创建线程的方法的比较:

Thread

有多种方法,比如释放CPU,截断当前线程,而Runnable方法没有

Runnable

如果有相同属性,使用Runnable接口口更好

三.线程的生命周期

Thread.State定义了几种生命状态

四.线程的同步【重点】

解决线程安全问题

方式一:同步代码块

synchronized(同步监视器){     //需要被同步的代码 } 

说明:操作共享数据的代码,即为需要被同步的代码 --范围不可过大

共享数据:多个线程共同操作的变量

同步监视器(俗称:锁):任何一个类的对象,都可以充当锁(隐藏要求:多个线程共用同一把锁)

采用同步的方式,解决了线程的安全问题。——好处

在同步代码块中只有一个线程参与,其余线程等待(有点像单线),效率较低

在实现Runnable接口创建多线程的方式中,我们可以考虑使用this来充当同步监视器,在继承Thread类创建多线程的方式中,慎用this充当同步监视器,可以考虑使用当前类充当同步监视器(保证唯一性)

方式二:同步方法

如果操作共享数据的代码完整的声明在一个方法中,不妨讲此方法声明为同步的。

线程死锁

当进入线程1时,A被锁上,线程2如果在线程1运行还被进入B锁时也同时运行,那么线程1就会在B之前死锁,同样,A也就会被一直锁上,然后线程2也无法进入A,线程2也会参数死锁

方式三:Lock(以银行账户转账为例)

实现Runnable

import java.util.concurrent.locks.ReentrantLock; class User implements Runnable{     private double count=0;     private ReentrantLock Lock=new ReentrantLock(false);     @Override     public void run() {             for (int i=0;i<3;i++){                 Lock.lock();                 try {                 System.out.println("当前账户原剩"+count+"元,用户"+Thread.currentThread().getName()+"向当前账户存入1000元,目前账户剩余"+(count+=1000)+"元");             }finally {                 Lock.unlock();             }         }     } } public class bank {     public static void main(String[] args) {         User user = new User();         Thread th1= new Thread(user,"用户1");         Thread th2= new Thread(user,"用户2");         Thread th3= new Thread(user,"用户3");         th1.start();         th2.start();         th3.start();     } } 

继承Thread

import java.security.Provider; import java.util.concurrent.locks.ReentrantLock; //class User implements Runnable{ //    private double count=0; //    private ReentrantLock Lock=new ReentrantLock(false); //    @Override //    public void run() { //        for (int i=0;i<3;i++){ //            Lock.lock(); //            try { //                System.out.println("当前账户原剩"+count+"元,用户"+Thread.currentThread().getName()+"向当前账户存入1000元,目前账户剩余"+(count+=1000)+"元"); //            }finally { //                Lock.unlock(); //            } //        } // //    } //} class User extends Thread{     public static double count=0;     private static ReentrantLock Lock=new ReentrantLock(false);     public User(String name) {         super(name);     }     @Override     public void run() {         for (int i=0;i<3;i++){             Lock.lock();             try {                 System.out.println("当前账户原剩"+count+"元,用户"+Thread.currentThread().getName()+"向当前账户存入1000元,目前账户剩余"+(count+=1000)+"元");             }finally {                 Lock.unlock();             }         }     } } public class bank {     public static void main(String[] args) {         //采用实现Runnable         /**                 User user = new User();                 Thread th1= new Thread(user,"Runnable用户1");                 Thread th2= new Thread(user,"Runnable用户2");                 Thread th3= new Thread(user,"Runnable用户3");                 th1.start();                 th2.start();                 th3.start();          */         //采用继承Thread         User peaple1 =new User("Thead用户1");         User peaple2 =new User("Thead用户2");         User peaple3 =new User("Thead用户3");         peaple1.start();         peaple2.start();         peaple3.start();     } } 

关于Lock,在这里提一点,由于锁是共用的,所以若是继承,则要改为静态,如果是实现,但是每一个都是用的同一个对象,所以不用静态。

五.线程的通信

import java.util.concurrent.locks.ReentrantLock; /**  * 线程通信:  * 1.wait notify notifyAll 三个方法必须在同步代码块或者同步方法中使用,且调用者必须是同步监视器(锁),这三个方法定义在java.lang.Object类中  */ class Number implements Runnable{     private int number=0;     private ReentrantLock Lock = new ReentrantLock(true);     @Override     public void run() {         while (true){             synchronized (this){                 notifyAll();                 if (number<10) {                     System.out.println(Thread.currentThread().getName()+(++number));                     try {                         wait();                     } catch (InterruptedException e) {                         e.printStackTrace();                     }                 } else {                     break;                 }             }         }     } } public class communication {     public static void main(String[] args) {         Number w= new Number();         new Thread(w,"A").start();         new Thread(w,"B").start();     } } 
import java.util.concurrent.locks.ReentrantLock; class Number implements Runnable{     private int number=0;     private ReentrantLock Lock = new ReentrantLock(true);     @Override     public void run() {             while (true){                 Lock.lock();                 try {                     if (number<=10) {                         System.out.println(Thread.currentThread().getName()+(++number));                     } else {                         break;                     }                 } finally {                     Lock.unlock();                 }             }         }     } public class communication {     public static void main(String[] args) {         Number w= new Number();         new Thread(w,"A").start();         new Thread(w,"B").start();     } } 

项目一:生产者与消费者的极限拉扯:

import java.util.concurrent.locks.ReentrantLock; class Clark{     private int count=0;     private static Clark me=null;     private void Clark(){     }     public static Clark getIntence(){         ReentrantLock lock=new ReentrantLock();         lock.lock();             if (me==null)  me=new Clark();             lock.unlock();         return me;     }     public  void make(){         while (true){             synchronized(this){                 if (count < 20) {                     System.out.println(Thread.currentThread().getName() + "正在生产第" + ++count + "个货物");                     notify();                 }                 else {                     try {                         wait();                     } catch (InterruptedException e) {                         e.printStackTrace();                     }                 }             }                 try {                 Thread.sleep(10);             } catch (InterruptedException e) {                 e.printStackTrace();             }         }     }          public void seil(){          while (true){              synchronized(this) {                  if (count > 0) {                      System.out.println(Thread.currentThread().getName() + "正在购买产第" + count-- + "个货物");                  } else {                          try {                              wait();                          } catch (InterruptedException e) {                              e.printStackTrace();                          }                  }              }                  try {                               Thread.sleep(10);                        } catch (InterruptedException e) {                     e.printStackTrace();                        }          }         }     } class Programmer implements Runnable{     private Clark clark;     public Programmer() {     }     public Programmer(Clark clark) {         this.clark = clark;     }     @Override     public void run() {         clark.make();     } } class Ceiler implements Runnable{     private Clark clark;     public Ceiler() {     }     public Ceiler(Clark clark) {         this.clark = clark;     }     @Override     public void run() {         clark.seil();     } } public class Prodocter {     public static void main(String[] args) {         Clark c=Clark.getIntence();         Programmer A=new Programmer(c);         Ceiler B=new Ceiler(c);         Ceiler C=new Ceiler(c);         new Thread(A,"程序员").start();         new Thread(B,"小白").start();         new Thread(C,"小黑").start();     } } 

六.JDK5.0新增线程创建方式(两种)

面试题:

##

##

##

##

##

##

sleep和wait方法的异同

相同点:执行到时进入阻塞状态

不同点:

1.两个方法声明的位置不同(Thread和Object)

2.调用范围和要求不同,sleep无要求(想用就用),wait只能在同步代码块或者同步方法中使用

3.是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep不释放锁,wait释放锁

yeild是强行让步,且不释放锁

实现Callable接口的方式创建多线程为何比实现Runnable方式创建多线程要强大?

  1. call()可以返回值
  2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
  3. 支持泛型

  • 微信或QQ扫一扫

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

目录