多线程
进程:正在进行中的程序(直译);
线程:就是进程中的一个负责程序执行的控制单元(执行路径);一个进程中可以有多个执行路径,称为多线程。 一个进程中,至少要有一个线程。 多线程存在的意义:开启多个线程,是为了同时运行多部分代码; 每一个线程都有自己运行的内容,这个内容可以称为线程要执行的任务。 多线程优点:解决了多部分同时运行的问题。 多线程弊端:线程太多会导致效率的降低。 其实应用程序的执行都是cpu在做着快速的切换完成的,这个切换是随机的。JVM启动时就启动了多个线程,至少有两个线程可以分析得出来:
1.执行main函数的线程; 该线程的任务代码都定义在main函数中; 2.负责垃圾回收的线程。创建线程的目的是为了开启一条执行路径,去运行指定的代码和其他代码就,实现同时运行
而运行的指定代码就是这个执行路径的任务; JVM创建的主线程的任务都定义在了主函数中;而自定义的线程:Thread类用于描述线程, 线程是需要任务的,所以Thread类也有对任务的描述,这个任务就通过Thread类中的run方法来实现,也就是说, run方法就是封装自定义线程运行任务的函数。run方法中定义的就是线程要运行的代码。 开启线程是为了运行指定代码,所以只有继承Thread类,并覆写run方法,将运行的代码定义在run方法中。如何创建线程?
1.继承Thread类 步骤:1.定义一个类继承Thread类; 2.覆盖Thread类中的run方法; 3.直接创建Thread的子类对象创建线程; 4.调用start方法,开启线程并调用线程的任务run方法执行。可以通过Thread的getName获取线程的名称 Thread-(从0开始)。
继承Thread类示例:class Demo extends Thread { private String name; Demo(String name) { super(name); //this.name=name; } public void run() { for (int x=0;x<10 ;x++ ) { System.out.println(name+"...........x="+x+" name="+Thread.currentThread().getName()); } } } class ThreadDemo3 { public static void main(String[] args) { Demo d1=new Demo("旺财"); Demo d2=new Demo("xiaoqiang"); d1.start();//开启线程,调用run方法 d2.start(); System.out.println("over="+Thread.currentThread().getName()); } }
1.定义类,实现Runnable接口;
2.覆盖接口中的run方法,将线程的任务代码封装到run方法中;3.通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递; 原因:因为线程的任务都封装在Rnnable接口子类对象的Run方法中,所以要在线程对象创建时就必须明确要运行的任务。4.调用线程对象的start方法,开启线程。Runnable:它的出现,仅仅是将线程的任务进行了对象的封装。实现Runnable接口的优点:
1.将线程的任务从线程的子类中分离出来,进行了单独的封装; 按照面向对象的思想将任务封装成对象;2.避免了Java单继承的局限性;所以,创建线程的第二种方法较为常用。实现Runnable接口示例:
class Demo implements Runnable //准备扩展Demo类的功能,让其中的内容可以作为线程的任务执行 //通过接口的形式完成{ public void run() { show(); } public void show() { for (int x=0;x<10 ;x++ ) { System.out.println(Thread.currentThread().getName()+"......"+x); } }}class ThreadDemo{ public static void main(String[] args) { Demo d=new Demo(); Thread t1=new Thread(d); Thread t2=new Thread(d); t1.start(); t2.start(); }}
线程安全问题产生的原因:
1.多个线程在操作共享的数据;2.操作共享数据的线程代码有多条;当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算,就会导致线程安全问题的产生。解决思路:
将多条操作共享数据的线程代码封装起来,当有线程在执行这些代码的时候,其他线程是不可以参与运算的,必须要当前线程将这些代码执行完毕后,其他线程才可以参与运算。在Java中,用同步代码块可以解决这个问题,格式为:synchronized(对象){ 需要被同步的代码;}同步的好处:解决了线程的安全问题;
同步的弊端:相对降低了效率,因为同步外的线程都会判断同步锁。同步的前提:同步中必须有多个线程,并使用同一个锁。线程安全问题示例、同步代码块:class Ticket implements Runnable{ private int num=20; Object obj=new Object(); public void run() { sale(); } public void sale() { while (true) { synchronized(obj)//如果在此处使用 new Objcet(),则使用的不是同一个锁 { if (num>0) { System.out.println(Thread.currentThread().getName()+":"+num--); } } } }}class TicketDemo{ 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(); }}
同步函数使用的锁是this
同步函数示例:class Ticket implements Runnable{ private int num=100; //Object obj=new Object(); boolean flag=true; public void run() { sale(); } public void sale() { if(flag) while (true) { synchronized(this) { if (num>0) { System.out.println(Thread.currentThread().getName()+":"+num--); } } } else while(true) show(); } public synchronized void show() { if (num>0) { System.out.println(Thread.currentThread().getName()+" function :"+num--); } }}class SynFunctionLockDemo{ 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(); try { Thread.sleep(10); } catch (InterruptedException e) { } t.flag=false; t2.start(); }}
同步函数和同步代码块的区别:
同步函数的锁是固定的this;同步代码块的锁是任意的对象。建议使用同步代码块。静态的同步函数使用的锁是,该函数所属的字节码文件对象,可以用getClass() 方法获取,也可以用 当前 类名.class()获取Class cla=t.getClass();Class cla=Ticket.class;静态同步函数锁示例:class Ticket implements Runnable{ private static int num=100; boolean flag=true; public void run() { sale(); } public void sale() { if(flag) while (true) { synchronized(Ticket.class) { if (num>0) { try { Thread.sleep(10); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName()+":"+num--); } } } else while(true) show(); } public static synchronized void show() { if (num>0) { System.out.println(Thread.currentThread().getName()+" function :"+num--); } }}class SynFunctionLockDemo{ 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(); try { Thread.sleep(10); } catch (InterruptedException e) { } t.flag=false; t2.start(); }}
多线程下的单例示例:
//饿汉式(单例设计模式)class Single//类一加载,对象就已经存在{ private static Single s=new Single();//在本类中创建对象 private Single(){};//私有化构造函数 public static Single getInstance()//公有方法,返回对象 { //多线程中不存在安全隐患 return s; }}//懒汉式(延迟加载单例设计模式)class Single2//类加载进来,没有对象;只有调用了getInstance时,才会创建对象 //延迟加载形式{ private static Single2 s=null; private Single2(){}; public static Single2 getInstance() { if (s==null)//加入此行代码,当s不为空时,则不再判断锁,提高效率 { synchronized(Single.class)//静态函数,不能使用getClass()方法 { if(s==null) // -->0 -->1 线程0,1可能存在安全隐患,需要同步锁 /* *线程0经过判断语句进入,然后执行权切换; *线程1经过判断语句进入,执行权切换; *执行权切换回0,new一个对象,并返回; *执行权切换到2,由于已经经过判断,所以此处不需要再次判断,new一个对象,返回; *造成的后果是有了2个对象,不能保证单例。 */ s=new Single2(); } } return s; }}class SingleDemo { public static void main(String[] args) { Single s1= Single.getInstance(); Single s2= Single.getInstance(); System.out.println(s1==s2); }}
死锁示例:
class Ticket implements Runnable{ private static int num=100; boolean flag=true; public void run() { sale(); } public void sale() { if(flag) while (true) { synchronized(Ticket.class) { if (num>0) { try { Thread.sleep(10); } catch (InterruptedException e) { } System.out.println(Thread.currentThread().getName()+":"+num--); } } } else while(true) show();//此处线程0获得锁this } public static synchronized void show() { if (num>0) { System.out.println(Thread.currentThread().getName()+" function :"+num--); } }}class SynFunctionLockDemo{ 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(); try { Thread.sleep(10); } catch (InterruptedException e) { } t.flag=false; t2.start(); }}
死锁示例:
class Test implements Runnable{ private boolean flag; Test(boolean flag) { this.flag=flag; } public void run() { if(flag) { synchronized(MyLock.locka) {//1、线程0获得锁MyLock.locka,继续下一句;尝试获得MyLock.lockb,此时线程1占有MyLock.lockb,无法获得 System.out.println(Thread.currentThread().getName()+".....if......locka"); synchronized(MyLock.lockb) { System.out.println(Thread.currentThread().getName()+".....if.....lockb"); } } } else { synchronized(MyLock.lockb) {//2、线程1获得锁MyLock.lockb,继续下一句;尝试获得MyLock.locka,此时线程0占有MyLock.locka,无法获得 System.out.println(Thread.currentThread().getName()+".....else.....lockb"); synchronized(MyLock.locka) { System.out.println(Thread.currentThread().getName()+".....else.....locka"); } } } }}class MyLock{ public static final Object locka=new Object(); public static final Object lockb=new Object();}class DeadLockTest { public static void main(String[] args) { Test a=new Test(true); Test b=new Test(false); Thread t1=new Thread(a); Thread t2=new Thread(b); t1.start(); t2.start(); }}
1、线程0获得锁MyLock.locka,继续下一句;尝试获得MyLock.lockb,此时线程1占有MyLock.lockb,无法获得;
2、线程1获得锁MyLock.lockb,继续下一句;尝试获得MyLock.locka,此时线程0占有MyLock.locka,无法获得;此时,就可能发生死锁。线程间通信
/*线程间通信:多个线程在处理同一资源,但是任务却不同。*///资源class Resource{ String name; String sex;}//输入class Input implements Runnable{ Resource r; Object obj=new Object(); Input(Resource r) { this.r=r; } public void run() { int x=0; while (true) { synchronized(r) { if(x==0) { r.name="mike"; r.sex="male"; } else { r.name="lili"; r.sex="female"; } } x=(x+1)%2; } }}//输出class Output implements Runnable{ Resource r; Output(Resource r) { this.r=r; } public void run() { while(true) { synchronized(r) { System.out.println(r.name+"......."+r.sex); } } }}class ResourceDemo{ public static void main(String[] args) { Resource r=new Resource(); Input in=new Input(r); Output out=new Output(r); Thread t1=new Thread(in); Thread t2=new Thread(out); t1.start(); t2.start(); }}
等待/唤醒机制
涉及的方法:1.wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。2.notify():唤醒线程池中的一个线程(任意)。3.notifyAll():唤醒线程池中的所有线程。这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法,
必须要明确到底操作的是哪个锁上的线程。为什么操作线程的方法 wait、notify、notifyAll定义在了Object类中?
因为这些方法是监视器的方法,监视器其实就是锁。锁可以是任意的对象,任意的对象调用的方法一定定义在Object类中。等待唤醒机制示例:
//资源class Resource{ private String name; private String sex; private boolean flag=false; public synchronized void set(String name,String sex) { if (flag) { try { wait(); } catch (InterruptedException e) { } } this.name=name; this.sex=sex; flag=true; notify(); } public synchronized void get() { if(!flag) try { wait(); } catch (InterruptedException e) { } System.out.println(name+"..............."+sex); flag=false; notify(); }}//输入class Input implements Runnable{ Resource r; Object obj=new Object(); Input(Resource r) { this.r=r; } public void run() { int x=0; while (true) { if(x==0) { r.set("mike","male"); } else { r.set("lili","female"); } x=(x+1)%2; } }}//输出class Output implements Runnable{ Resource r; Output(Resource r) { this.r=r; } public void run() { while(true) { r.get(); } }}class ResourceDemo3{ public static void main(String[] args) { Resource r=new Resource(); Input in=new Input(r); Output out=new Output(r); Thread t1=new Thread(in); Thread t2=new Thread(out); t1.start(); t2.start(); }}
if判断标记只有一次,会导致不该运行的线程运行了,出现了数据错误的情况。
while判断标记,解决了线程获取执行权后,是否要运行的问题。notify只能唤醒一个线程,如果本方唤醒了本方,没有意义。而且while判断标记+nitify会导致死锁。notifyAll解决了本方线程一定会唤醒对方线程的问题。多生产者-多消费者示例:class Resource{ private String name; private int count=1; private boolean flag=false; public synchronized void set(String name) { while(flag) try{wait();}catch(InterruptedException e){} this.name=name+count; count++; System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name); flag=true; notifyAll(); } public synchronized void get() { while(!flag) try{wait();}catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+"...消费者......"+this.name); flag=false; notifyAll(); }}class Producer implements Runnable{ private Resource r; Producer(Resource r) { this.r=r; } public void run() { while (true) { r.set("烤鸭"); } }}class Consumer implements Runnable{ private Resource r; Consumer(Resource r) { this.r=r; } public void run() { while (true) { r.get(); } }}class ProducerConsumerDemo{ public static void main(String[] args) { Resource r=new Resource(); Producer pro=new Producer(r); Consumer con=new Consumer(r); Thread t0=new Thread(pro); Thread t1=new Thread(con); Thread t2=new Thread(pro); Thread t3=new Thread(con); t0.start(); t1.start(); t2.start(); t3.start(); }}
接口 Lock
同步代码块,对于锁的操作时隐式的jdk1.5以后,将同步和锁封装成了对象,并将操作锁的方式定义到了该对象中,将隐式动作变成了显式动作。Lock lock=new ReentrantLock();lock.lock();//获取锁code...;//throw Exception();lock.unlock();//释放锁try{ lock.lock();//获取锁}finally{ lock.unlock;//释放锁}Lock接口:它的出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成了显式锁操作;
同时更为灵活,可以一个锁上加上多组监视器。 lock();获取锁 unlock();释放锁,通常需要定义到finally代码块中。Condition接口:它的出现替代了Object中的wait、notify、notifyAll方法。
将这些监视器方法单独进行了封装,变成了Condition监视器对象, 可以和任意锁组合。 await();//等待 signal();//唤醒一个等待线程 signalAll();//唤醒所有等待线程接口Lock应用示例:
import java.util.concurrent.locks.*;class Resource{ private String name; private int count=1; private boolean flag=false; //创建一个锁对象 Lock lock=new ReentrantLock(); //通过已有的锁,获取该锁上的监视器对象 //Condition con=lock.newCondition(); //通过已有的锁,获取两组监视器;一组监视生产者,一组监视消费者 Condition producer_con=lock.newCondition(); Condition consumer_con=lock.newCondition(); public void set(String name) { lock.lock(); try { while(flag) try{producer_con.await();}catch(InterruptedException e){} this.name=name+count; count++; System.out.println(Thread.currentThread().getName()+"...生产者5.0..."+this.name); flag=true; consumer_con.signal(); } finally { lock.unlock(); } } public void get() { lock.lock(); try { while(!flag) try{consumer_con.await();}catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name); flag=false; producer_con.signal(); } finally { lock.unlock(); } }}class Producer implements Runnable{ private Resource r; Producer(Resource r) { this.r=r; } public void run() { while (true) { r.set("烤鸭"); } }}class Consumer implements Runnable{ private Resource r; Consumer(Resource r) { this.r=r; } public void run() { while (true) { r.get(); } }}class ProducerConsumerDemo{ public static void main(String[] args) { Resource r=new Resource(); Producer pro=new Producer(r); Consumer con=new Consumer(r); Thread t0=new Thread(pro); Thread t1=new Thread(con); Thread t2=new Thread(pro); Thread t3=new Thread(con); t0.start(); t1.start(); t2.start(); t3.start(); }}
wait和sleep的区别
1.wait可以指定时间,也可以不指定; sleep必须指定时间。2.在同步中时,对于cpu的执行权和锁的处理不同。 wait释放执行权,释放锁。 sleep释放执行权,不释放锁。停止线程
1.stop方法(已过时);2.run方法结束; 怎么控制线程的任务结束? 任务中都会有循环结构,只要控制住循环就可以结束任务。 控制循环:通常用定义标记来完成。如果线程处于冻结状态,无法读取标记,如何结束?
可以使用interrupt()方法,将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格。但是强制动作会发生InterruptedException,需要处理。class StopThread implements Runnable{ private boolean flag=true; public synchronized void run() { while (flag) { try { wait(); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+",,,,,,,"+e); flag=false; } System.out.println(Thread.currentThread().getName()+"......"); } } public void setFlag() { flag=false; }}class StopThreadDemo{ public static void main(String[] args) { StopThread st=new StopThread(); Thread t1=new Thread(st); Thread t2=new Thread(st); t1.start(); t2.setDaemon(true);//守护线程(后台线程) t2.start(); int num=1; for (;;) { if(++num==50) { //st.setFlag(); t1.interrupt(); //t2.interrupt(); break; } System.out.println("main"+num); } System.out.println("over"); }}