13 多线程
进程:正在进行中的程序(直译).
线程:就是进程中的一个负责程序执行的控制单元(执行路径).
一个进程中可以有多个执行路径,称之为多线程.
一个进程中至少要有一个线程.
开启多个线程是为了同时运行多部分代码,每一个线程都有自己运行的内容,
这个内容可以称为线程要执行的任务.
多线程的好处:解决了多部分同时运行的问题.
多线程的弊端:线程太多回到效率的降低.
其实应用程序的执行都是cpu在做着快速的切换完成的,这个切换是随机的.
JVM启动时就启动了多个线程,至少有两个线程可以分析的出来.
1.执行main函数的线程
该线程的任务代码都定义在main函数中.
2.负责垃圾回收的线程.
如何创建一个线程呢?
步骤:
1.定义一个类继承Thread类.
2.覆盖Thread类中的run方法.
创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码实现同时运行.
而运行的指定代码就是这个执行路径的任务.
jvm创建的主线程的任务都定义在了主函数中.
而自定义的线程他的任务在哪儿呢?
Thread类用于描述线程,线程是需要任务的,所以Thread类也有对任务的描述.
这个任务就通过Thread类中的run方法来体现.也就是说,
run方法就是封装自定义线程运行任务的函数.
run方法中定义就是线程要运行的任务代码.
开启线程是为了运行指定代码,所以只有继承Thread类,并复写run方法.
将运行的代码定义在run方法中即可.
3.直接创建Thread的子类对象创建线程.
4.调用start方法开启线程并调用线程的任务run方法执行.
class Test extends Thread { private String name; Test(String name) { this.name =name; } public void run() { for(int x=0;x<10;x++) { System.out.println(name+"x="+x); } } } class Demo { public static void main(String[] args) { Test d1 =new Test("旺财"); Test d2 = new Test("xiaoqiang"); d1.start();//开启线程,调用run方法. d2.start(); System.out.println("over"); } }
上述代码有三个线程:主线程,d1和d2开启的两个线程.
可以通过Thread的getName获取线程的名称,Thread-编号(从0开始).
currentThread返回当前正在进行的线程.
Thread.currentThread().getName();
即使主线程先运行结束弹栈了,虚拟机也不会结束.要等所有线程都结束.
线程的四种状态:
cpu的执行资格:可以被cpu处理,在处理队列中排队.
cpu的执行权:正在被cpu处理
运行状态:具备着执行资格,具备着执行权
冻结状态:释放执行权的同时,释放执行资格.
临时阻塞状态:具备着执行资格,不具备执行权,正在等待着执行权.
创建线程的第一种方式:继承Thread类.
缺点:一旦继承了Thread就不能再继承别的类,因为java中不支持多继承.
可以扩展功能,让其中的内容可以作为线程的任务执行.
通过接口的形式完成.
创建线程的第二种方式:实现Runnable接口.
1.定义类实现runnable接口
2.覆盖接口中的run方法,将线程的任务代码封装到run方法中.
3.通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类
的构造函数的参数进行传递.为什么?
因为线程的任务都封装在Runnable接口子类对象的run方法中.
所以要在线程对象创建时就必须明确要运行的任务.
4.调用线程对象的start方法开启线程.
class Test implements Runnable//extends Thread { private String name; Test(String name) { this.name =name; } public void run() { for(int x=0;x<10;x++) { System.out.println(name+"x="+x+".."+Thread.currentThread().getName()); } } } class Demo { public static void main(String[] args) { Test d1 =new Test("旺财"); Test d2 = new Test("xiaoqiang"); Thread t1 = new Thread(d1); Thread t2 = new Thread(d2); t1.start(); t2.start(); //d1.start();//开启线程,调用run方法. //d2.start(); } }
底层大致的实现方式:
class Thread { private Runnable r; Thread() { } Thread(Runnable r) { this.r=r; } public void run() { if(r!=null) r.run(); } public void start() { run(); } } class ThreadTmpl implements Runnable { public void run() { System.out.println("runnable run"); } } ThreadImpl i = new ThreadTmpl(); Thread t = new Thread(i); t.start(); class SubThread extends Thread { public void run() { System.out.println("hhah"); } } SubThread s =new SubThread(); s.start();
实现Runnable接口的好处:
1.将线程的任务从线程的子类中分离出来,进行了单独的封装.
按照面向对象的思想将任务封装成对象.
2.避免了java单继承的局限性.
所以,创建线程的第二种方式较为常用.
/* 需求:买票. */ class Ticket implements Runnable//extends Thread { private int num=100; public void run() { while(true) { if(num>0) System.out.println(Thread.currentThread().getName()+"..sale."+num--); } } } class Demo { public static void main(String[] args) { Ticket t = new Ticket();//创建一个线程任务对象 Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); Thread t4 = new Thread(t); t1.start(); t2.start(); t3.start(); t4.start(); /* Ticket t1 = new Ticket(); Ticket t2 = new Ticket(); Ticket t3 = new Ticket(); Ticket t4 = new Ticket(); */ } }
线程安全问题产生的原因:
1.多个线程在操作共享的数据.
2.操作共享数据的线程代码有多条.
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的
产生.
解决思路:就是将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,
其他线程不可以参与运算的.
必须要当前线程把这些代码都执行完毕后,其他线程才可以参与运算.
在java中,用同步代码块就可以解决这个问题.
同步代码块的格式是:
synchronized(对象)
{
需要被同步的代码;
}
class Ticket implements Runnable//extends Thread { private int num=100; Object obj = new Object(); public void run() { while(true) { synchronized(obj) { if(num>0) { try{Thread.sleep(10);} catch(InterruptedException e) { } System.out.println(Thread.currentThread().getName()+"..sale."+num--); } } } } } class Demo { public static void main(String[] args) { Ticket t = new Ticket();//创建一个线程任务对象 Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); Thread t4 = new Thread(t); t1.start(); t2.start(); t3.start(); t4.start(); /* Ticket t1 = new Ticket(); Ticket t2 = new Ticket(); Ticket t3 = new Ticket(); Ticket t4 = new Ticket(); */ } }
同步的好处:解决了线程的安全问题.
同步的弊端:相对降低了效率,因为同步外的线程都会判断同步锁.
同步的前提:必须有多个线程并使用同一个锁.
/* 需求:储户两个,每个都到银行存钱,每次存100,共存三次. */ class Bank { private int sum; private Object obj = new Object(); public void add(int num) { synchronized(obj) { sum = sum+num; //线程可能在此处中断. try{Thread.sleep(10);}catch(InterruptedException e){}; System.out.println("sum="+sum); } } } class Cus implements Runnable { Bank b = new Bank(); public void run() { for(int x=0;x<3;x++) { b.add(100); } } } class Demo { public static void main(String[] args) { Cus c = new Cus(); Thread t1 = new Thread(c); Thread t2 = new Thread(c); t1.start(); t2.start(); } }
class Bank { private int sum; //private Object obj = new Object(); public synchronized void add(int num)//同步函数 { sum = sum+num; //线程可能在此处中断. try{Thread.sleep(10);}catch(InterruptedException e){}; System.out.println("sum="+sum); } }
同步函数使用的锁是this;
同步函数和同步代码块的区别是:
同步函数的锁是固定的this,就是当前的对象,同步代码块的锁是任意的对象.
建议使用同步代码块.
class Ticket implements Runnable//extends Thread { private int num=100; // Object obj = new Object(); boolean flag = true; public void run() { if(flag) while(true) { synchronized(this) { if(num>0) { try{Thread.sleep(10);} catch(InterruptedException e) { } System.out.println(Thread.currentThread().getName()+"..obj."+num--); } } } else while(true) this.show(); } public synchronized void show() { if(num>0) { try{Thread.sleep(10);} catch(InterruptedException e) { } System.out.println(Thread.currentThread().getName()+"..function."+num--); } } } class Demo { public static void main(String[] args) { Ticket t = new Ticket();//创建一个线程任务对象 Thread t1 = new Thread(t); Thread t2 = new Thread(t); t1.start(); try{ Thread.sleep(10); } catch(InterruptedException e){} t.flag = false; t2.start(); } }
class Ticket implements Runnable//extends Thread { private static int num=100; // Object obj = new Object(); boolean flag = true; public void run() { if(flag) while(true) { synchronized(this.getClass())//Ticket.class { if(num>0) { try{Thread.sleep(10);} catch(InterruptedException e) { } System.out.println(Thread.currentThread().getName()+"..obj."+num--); } } } else while(true) this.show(); } public static synchronized void show() { if(num>0) { try{Thread.sleep(10);} catch(InterruptedException e) { } System.out.println(Thread.currentThread().getName()+"..function."+num--); } } } class Demo { public static void main(String[] args) { Ticket t = new Ticket();//创建一个线程任务对象 //class clazz = t.getClass(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t1.start(); try{ Thread.sleep(10); } catch(InterruptedException e){} t.flag = false; t2.start(); } }
静态的同步函数使用的锁是:该函数所属字节码文件对象,可以用getClass获取
,也可以用当前类名.class表示.
多线程下的单例:
1.饿汉式(没问题)
2.懒汉式(同步有问题)
//饿汉式 class Single { private static final Single s = new Single(); private Single(){} public static Single getInstance() { return s; } } //懒汉式 class Single { private static Single s= null; private Single(){} public static Single getInstance()//synchronized可以同步函数 { if(s==null)//提高效率 { synchronized(Single.class)//getClass()方法是非静态的.不能用在这里 { if(s==null) //-->0 -->1 s=new Single(); } } return s; } } class Demo { public static void main(String[] args) { } }