------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------
多线程
多线程的运行出现了安全问题的原因:
当多条语句在操作同一个线程共享数据的时候,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据错误。
解决的办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可参与执行。
Java对于多线程的安全问题,提供了专业的解决方式。
就是同步代码块。
- Synchronized(对象)
- {
- //需要被同步的代码。
- }
这里的对象如同一个锁,持有锁的线程可以在同步中执行,没有持有锁的线程即使获取了cpu的执行权,也进不去,因为没有锁。
线程同步的书写前提:(也就是当用了同步之后依然出现线程安全问题)
1. 必须要有两个或者两个以上的线程。
2. 必须是多个线程使用同一个锁。(十分的重要)
如何找一个程序中是否会发生多线程安全的问题。
1. 明确哪些代码是多线程运行的代码。(run方法中的都是)
2. 明确哪些数据是共享数据。(公共资源的数据)
3. 明确多线程运行代码中哪些语句是操作共享数据的(是否是多条数据操作)。
同步函数的锁是this。
静态进内存时候,内存中没有本类的对象,但是一定有该类对应的字节码文件对象。
所以静态同步函数的锁是这个函数所对应的类的字节码文件对象(类名.class对象)。该对象的类型是Class。
单例模式:使一个类只能有一个实例对象。
有懒汉式和饿汉式两种方式:
懒汉式:实例的延迟加载。
在多线程中有一定的问题,可以加同步来解决,而同步的方式,用同步代码块和同步函数都可以,但是稍微有些低效。使用双重否定的方式可以避免每次得到对象的时候都去验证锁,解决这个效率问题。加同步的时候,使用的锁是该类所属的字节码文件对象
示例如下:
//懒汉式
- //懒汉式
- class SingleClass
- {
- private SingleClass()
- {
- System.out.println("create class");
- }
- private static SingleClass singleClass;
- public static SingleClass getSingle()
- {
- if (singleClass == null)
- {
- synchronized (SingleTest.class)
- {
- if (singleClass == null)
- {
- singleClass = new SingleClass();
- }
- }
- }
- return singleClass;
- }
- }
- //饿汉式
- class SingleClass
- {
- private SingleClass()
- {
- }
- private static final SingleClass singleClass = new SingleClass();
- public static SingleClass getSingle()
- {
- return singleClass;
- }
- }
线程中通信的等待和唤醒机制。
系统中等待的线程在哪呢?
线程在运行的时候内存中会建立一个线程池,等待的线程(调用wait()方法)都存放在这个线程池中,当使用notify()唤醒的都是线程池中的线程,通常唤醒第一个被等待的线程,使用notifyAll()唤醒线程池中所有等待的线程
十分的注意:在使用wait,notify,notifyAll的时候,必须用在同步中,因为调用它们的前提是获得锁。而且必须要标志这些方法在哪个锁上调用。
为什么这些操作线程的方法要定义在Object类中呢?
因为这些方法在操作同步线程时候,都必须要标识它们所操作线程持有的锁,
只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,也就是等待和唤醒是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。
实例代码:
- package day23;
- public class ThreadConnection
- {
- public static void main(String[] args)
- {
- Res res = new Res();
- Thread inputThread = new Thread(new InputRun(res));
- Thread outputThread = new Thread(new OutputRun(res));
- inputThread.start();
- outputThread.start();
- }
- }
- class Res
- {
- private String name;
- private String sex;
- boolean flagType = true;
- private boolean flagCondiction = true;
- public synchronized void show()
- {
- if(flagCondiction)
- {
- try
- {
- this.wait();
- }
- catch (InterruptedException e)
- {
- e.printStackTrace();
- }
- }
- System.out.println(name+".."+sex);
- flagCondiction = true;
- this.notifyAll();
- }
- public synchronized void set(String name,String sex)
- {
- if(!flagCondiction)
- {
- try
- {
- this.wait();
- }
- catch (InterruptedException e)
- {
- e.printStackTrace();
- }
- }
- this.name = name;
- this.sex = sex;
- flagCondiction = false;
- this.notifyAll();
- }
- }
- class InputRun implements Runnable
- {
- public InputRun(Res res)
- {
- super();
- this.res = res;
- }
- private Res res;
- public void run()
- {
- while(true)
- {
- if(res.flagType)
- {
- res.set("wangdabin","nan");
- }
- else
- {
- res.set("丽丽","女");
- }
- res.flagType = !res.flagType;
- }
- }
- }
- class OutputRun implements Runnable
- {
- private Res res;
- public OutputRun(Res res)
- {
- super();
- this.res = res;
- }
- public void run()
- {
- while(true)
- {
- res.show();
- }
- }
- }
当出现多个生产者和消费者时候,上述的代码会出现错误。
原因:
在一个对象调用wait()方法进入阻塞状态的时候,当另一个线程使用notify唤醒的时候,是从wait()方法之后继续执行,而不是去继续验证if中的条件。
所以必须使用while(条件),而这样又会导致所有的线程全部阻塞等待。必须使用notifyAll将所有的线程全部唤醒才能避免。
在jdk1.5中提供了多线程升级的解决方案:
将同步Synchronized替换成了Lock操作。
将Object中的wait,notify,notifyAll,替换成了Condition对象。
该对象用来管理那些已经获得了一个锁但是却不能做有用工作的线程,或者完成线程之间的通讯。它用来实现了因为某些条件而导致某些线程进入阻塞状态。在另一个线程中可以使用这个条件唤醒因为某一特定条件被阻塞的线程,而不是所有的线程(像Object)。
特别注意,在使用Lock锁的时候,释放锁这个动作必须要执行,无论是否抛出异常,所以有了以下的代码格式。
- Lock l = ...;
- l.lock();
- try {
- // access the resource protected by this lock
- } finally {
- l.unlock();//释放锁的动作一定要执行。
- }
停止线程:怎么让一个处于while(true)中的线程停止呢?
只有一种,run方法结束。
开启多线程运行,运行代码通常都是循环结构。
只要控制住循环,就可以让run方法结束,也就是线程结束。
实例:
- public class StopThreadDemo
- {
- public static void main(String[] args)
- {
- StopThread st = new StopThread();
- Thread thread = new Thread(st);
- thread.start();
- int num = 0;
- while(true)
- {
- try
- {
- Thread.sleep(1);
- }
- catch (InterruptedException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- if(num == 60)
- {
- st.changeFlag();
- break;
- }
- num++;
- System.out.println(num);
- }
- }
- }
- class StopThread implements Runnable
- {
- private boolean flag = true;
- public void changeFlag()
- {
- flag = false;
- }
- public void run()
- {
- while(flag)
- {
- System.out.println("132");
- }
- }
- }
但是上边这种方式仅仅只能用来对于没有在代码块中出现等待使一个线程进入阻塞状态时候,就会使之不能结束,例如将上述代码块改为
- public void run()
- {
- synchronized(this)
- {
- while(flag)
- {
- try
- {
- this.wait();
- }
- catch(Exception e)
- {
- }
- System.out.println("132");
- }
- }
- }
就会使程序不能结束,必须采取其他方法使线程结束,而不仅仅改变标记。
在Thread中有一个方法叫做interrupt(),如果在调用形如wait方法使线程进入等待状态。使用interrupt可以将其等待状态清除,重新恢复到就绪态。但是会抛出InterrupetedException. 也就是在处理这个异常的时候,将while的判断位改为false即可。
实例如下:
- public class StopThreadDemo
- {
- public static void main(String[] args)
- {
- StopThread st = new StopThread();
- Thread thread = new Thread(st);
- thread.start();
- int num = 0;
- while(true)
- {
- try
- {
- Thread.sleep(1);
- }
- catch (InterruptedException e)
- {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- if(num == 60)
- {
- // st.changeFlag();
- thread.interrupt();
- break;
- }
- num++;
- System.out.println(num);
- }
- }
- }
- class StopThread implements Runnable
- {
- private boolean flag = true;
- public void changeFlag()
- {
- flag = false;
- }
- public void run()
- {
- synchronized(this)
- {
- while(flag)
- {
- try
- {
- this.wait();
- }
- catch(Exception e)
- {
- flag = false;
- }
- System.out.println("132");
- }
- }
- }
- }
Thread类中的常见方法。
void setDaemon(boolean on)
将该线程标记为守护线程或用户线程。
当正在运行的线程都是守护线程的时候,jvm退出执行。
该方法必须在启动线程前调用。
也就是当一个线程是守护线程的时候,当用户线程没有结束时候,守护线程和它一起抢夺cpu的执行权,一旦用户线程结束,守护线程也就结束。
public final void join()
throws InterruptedException等待该线程终止。
也就是当在一个线程中调用了另一个线程的join方法时候,这个线程必须在另一个线程运行完成后才能执行。
static void yield()
暂停当前正在执行的线程对象,并执行其他线程。
也就是临时释放这个线程的cpu执行权,使其它线程有机会执行。避免了其它线程抢不到资源而被饿死。