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方式创建多线程要强大?
- call()可以返回值
- call()可以抛出异常,被外面的操作捕获,获取异常的信息
- 支持泛型