黑马程序员————java多线程及同步机制
------<a href="http://www.itheima.com" target="blank">Java培训、Android培训、iOS培训、.Net培训</a>、期待与您交流! -------
1.理解程序、进程、线程的概念
程序可以理解为静态的代码,计算机指令的集合,它以文件的形式存储在磁盘上。
进程可以理解为执行中的程序,一个单独程序打开了单独的一段地址空间进行单独的工作。
线程可以理解为进程的进一步细分,程序的一条执行路径。
多线程并非是指许多个线程同时运行,而是cpu的快速切换。
线程大致的粗分为五个状态:
创建 通过 new Thread及其子类
运行 正在执行的线程,占据cpu
阻塞 拥有执行资格,只是没有抢到cpu,这是随机的有cpu决定
冻结 无执行资格,处于休眠状态
消亡 run方法执行完毕(1.5以前可以通过调用stop方法来强制结束线程)
线程实质是由Windows来创建,而java已经将创建线程这一功能封装好了,只需要用就可以了,继承java.lang包中的Thread类即可。大致分为两种方法
方法一:直接继承Thread类
class PrintNum extends Thread{//继承Thread类 public void run(){ //复写run方法,子线程执行的代码 for(int i = 1;i <= 100;i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } public PrintNum(String name){ super(name); } } public class TestThread { public static void main(String[] args) { PrintNum p1 = new PrintNum("线程1"); PrintNum p2 = new PrintNum("线程2"); p1.setPriority(Thread.MAX_PRIORITY);//优先级10 p2.setPriority(Thread.MIN_PRIORITY);//优先级1 p1.start();//启动线程并调用run方法 p2.start(); } }
分为三个步骤:1.创建线程 2.复写Tread中的run方法,也就是把要执行的代码块放其中 3.调用stat方法,启动线程并调用run方法
方法二:实现Runnable接口
class SubThread implements Runnable{ public void run(){//2.复写run方法 //子线程执行的代码 for(int i = 1;i <= 100;i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class TestThread{ public static void main(String[] args){ SubThread s = new SubThread(); Thread t1 = new Thread(s);//1.创建线程 Thread t2 = new Thread(s); t1.setName("线程1"); t2.setName("线程2"); t1.start();//3.启动并调用run方法 t2.start(); } }
两种方法的比较:
如果没有该类没有继承其他类则用继承方法,run方法是写在Thread类中,而且对于一大段代码中出现过的多次循环体也直接采用匿名内部类将其包装起来实现多线程比较简洁
new Thread(){ public void run(){ for(int i=0;i<100;i++){ System.out.println(i); } } }.start();
但是单继承具有局限性,如果多个线程有共享数据的话,建议使用实现方式,同时,共享数据所在的类可以作为Runnable接口的实现类,run方法是写在Runnable接口中。比如,两个窗口同时买100张票。
class gxzy implements Runnable{//实现Runnable接口 private int piao=100; public void run(){//重写run方法 while(true){ if(piao>0) System.out.println(Thread.currentThread().getName()+" 第"+piao--+"票"); } } } public class maipiao { public static void main(String[] args){ gxzy gx = new gxzy(); Thread xz1 =new Thread(gx);//创建线程 Thread xz2 =new Thread(gx); xz1.setName("第1号买票窗口"); xz2.setName("第2号买票窗口"); xz1.start();//启动线程并调用run方法 xz2.start(); } }
但是上面的程序有个安全问题存在,即如果在线程运行到if语句之下还未执行输出语句时,cpu被其他程序(是程序哦)给占了,那么此线程处于阻塞状态,而其他线程排在后面,当这个线程恢复为运行状态时刚好卖出的是最后一张票,则有可能后面的线程会卖出第0张票,写一个sleep方法就可以清晰的看到问题所在了
class gxzy implements Runnable{ private int piao=100; Object obj = new Object(); public void run(){ while(true){ if(piao>0) try{ Thread.sleep(3);//停3毫秒 } catch(Exception e){ } System.out.println(Thread.currentThread().getName()+" 第"+piao--+"票"); } } }
那么这个时候就需要采用同步来解决了,同步提供一个锁,在这个线程执行run方法的时候锁会关上,其他线程是无法进去的,直到这个线程执行完毕锁打开,其他线程才能呢进来。
synchronized关键字为同步,有两种写法可以写出同步代码块,里面的锁可以是任意对象,一般采用object类或现有资源类
synchronized(对象){执行的代码块}
还有一种是用synchronized修饰在函数上的称为同步函数,也就是把那想要同步的代码块单独拿出来写在一个方法里,再用synchronized修饰。这里的锁是this,如果函数经static修饰,因为静态里不能有this也先于对象存在所以他的锁为java.class字节码文件对象,格式为 类名.class。
无论是同步代码块还是同步代码函数,使用时都有两个前提,有两个或以上的线程,多个线程使用同一个锁。
看看单例模式中的懒汉式,用同步解决他的线程问题
class aa{ private static aa bb =null; private aa(){} public static aa lei(){ if(bb==null);//双重判断,提高效率 synchronized(aa.class){//创建了对象之后,将拒绝所有线程的访问 if(bb==null){ bb=new aa(); } return bb; } } }
我们没有用同步函数而是用的代码块,这样更灵活一些,如果直接采用同步函数,将十分低效,即使是当创建了对象也会不断有线程进来访问锁,而锁的一大弊端是很耗费资源。而采用代码块使用双重判断,在锁前面来一个判断,则不需要再对锁进行访问了。还有一点值得注意的还返回对象的函数是静态的,而静态的锁为类名.class。