标签:
一、多线程概述
要理解多线程,就必须理解线程。而要理解线程,就必须知道进程。
1、 进程
是一个正在执行的程序。例如,qq等
每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。//例如登录qq,你需要输入账号、密码、点击登录等
2、线程
就是进程中的一个独立的控制单元。线程在控制着进程的执行。只要进程中有一个线程在执行,进程就不会结束。//登录qq时,会验证密码账号等线程
一个进程中至少有一个线程。即 进程:线程=1:n(n为大于等于1的整数)
3、多线程
在java虚拟机启动的时候会有一个java.exe的执行程序,也就是一个进程。该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。JVM启动除了执行一个主线程,还有负责垃圾回收机制的线程。像这种在一个进程中有多个线程执行的方式,就叫做多线程。
4、多线程存在的意义
多线程的出现能让程序产生同时运行效果。可以提高程序执行效率。
例如:在java.exe进程执行主线程时,如果程序代码特别多,在堆内存中产生了很多对象,而同时对象调用完后,就成了垃圾。如果垃圾过多就有可能是堆内存出现内存不足的现象,只是如果只有一个线程工作的话,程序的执行将会很低效。而如果有另一个线程帮助处理的话,如垃圾回收机制线程来帮助回收垃圾的话,程序的运行将变得更有效率。现实生活中,你可以一边烧水,一边切菜,为了做汤。
5、计算机CPU的运行原理
我们电脑上有很多的程序在同时进行,就好像cpu在同时处理这所以程序一样。但是,在一个时刻,单核的cpu只能运行一个程序。而我们看到的同时运行效果,只是cpu在多个进程间做着快速切换动作。就好像灯泡一样,它是在不停的闪灭,而我们眼睛接受不全,便以为是一直亮。
而cpu执行哪个程序,是毫无规律性的。这也是多线程的一个特性:随机性。哪个线程被cpu执行,或者说抢到了cpu的执行权,哪个线程就执行。而cpu不会只执行一个,当执行一个一会后,又会去执行另一个,或者说另一个抢走了cpu的执行权。至于究竟是怎么样执行的,只能由cpu决定。就好像皇帝跟妃子的关系。
二、创建线程的方式
创建线程共有两种方式:继承方式和实现方式(简单的说)。
1、 继承方式
通过查找java的帮助文档API,我们发现java中已经提供了对线程这类事物的描述的类——Thread类。这第一种方式就是通过继承Thread类,然后复写其run方法的方式来创建线程。
创建步骤:
a,定义类继承Thread。
b,复写Thread中的run方法。
目的:将自定义代码存储在run方法中,让线程运行。
c,创建定义类的实例对象。相当于创建一个线程。
d,用该对象调用线程的start方法。该方法的作用是:启动线程,调用run方法。
注:如果对象直接调用run方法,等同于只有一个线程在执行,自定义的线程并没有启动。
覆盖run方法的原因:
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要执行的代码。该存储功能就run方法。也就是说,Thread类中的run方法,用于存储线程要运行的代码。
程序示例:
1 /* 2 3 创建两个线程 贡献一个 资源, 4 加入主线程 便有三个线程 5 */ 6 class Demo extends Thread//继承Thread类 7 { 8 public int x=60;//定义计数器 9 public Demo(String name) 10 { 11 super(name); 12 } 13 public void run()//复写run方法 14 { 15 16 while(x>0) 17 { 18 System.out.println(Thread.currentThread().getName()+".....sale:"+"...."+x);//获取线程名称以及对应的次数 19 x--; 20 } 21 } 22 } 23 class ThreadDemo 24 { 25 public static void main(String[] args) 26 { 27 Demo d=new Demo("--one--");//创建线程名称 28 Thread d1=new Thread(d);//共用一个资源的两个线程 29 Thread d2=new Thread(d); 30 31 d1.start();//start()方法可以调用run()方法 实现多线程 32 d2.start(); 33 34 35 for(int x=0;x<60;x++)//主线程 打印 main 36 { 37 System.out.println("---main---"+x); 38 } 39 40 } 41 }
程序运行结果每一次都可能出现不同种的情况,而下图出现的次序错乱问题,应该是计算机为多核,打印到显示台上才会如此。如图:
第二种方式:实现Runnable接口:
1 /* 2 需求:简单的卖票程序。 3 多个窗口同时买票。 4 5 6 创建线程的第二种方式:实现Runable接口 7 8 步骤: 9 1,定义类实现Runnable接口 10 2,覆盖Runnable接口中的run方法。 11 将线程要运行的代码存放在该run方法中。 12 13 3,通过Thread类建立线程对象。 14 4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。 15 为什么要将Runnable接口的子类对象传递给Thread的构造函数。 16 因为,自定义的run方法所属的对象是Runnable接口的子类对象。 17 所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。 18 19 20 5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。 21 22 23 24 实现方式和继承方式有什么区别呢? 25 26 实现方式好处:避免了单继承的局限性。 27 在定义线程时,建立使用实现方式。 28 29 两种方式区别: 30 继承Thread:线程代码存放Thread子类run方法中。 31 实现Runnable,线程代码存在接口的子类的run方法。 32 33 34 35 36 */ 37 38 class Ticket implements Runnable//extends Thread 39 { 40 private int tick = 100;//有一百张票 41 public void run() 42 { 43 while(true) 44 { 45 if(tick>0) 46 { 47 System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);//打印线程名称跟票数 48 } 49 else 50 break; 51 } 52 } 53 } 54 55 56 class TicketDemo 57 { 58 public static void main(String[] args) 59 { 60 61 Ticket t = new Ticket(); 62 63 Thread t1 = new Thread(t);//创建了一个线程; 64 Thread t2 = new Thread(t);//创建了一个线程; 65 Thread t3 = new Thread(t);//创建了一个线程; 66 Thread t4 = new Thread(t);//创建了一个线程; 67 t1.start();//运行该线程,调用run()方法 68 t2.start(); 69 t3.start(); 70 t4.start(); 71 72 73 /* 74 Ticket t1 = new Ticket(); 75 //Ticket t2 = new Ticket(); 76 //Ticket t3 = new Ticket(); 77 //Ticket t4 = new Ticket(); 78 79 t1.start(); 80 t1.start(); 81 t1.start(); 82 t1.start(); 83 */ 84 85 } 86 }
三、两种方式的区别和线程的几种状态
1、两种创建方式的区别
继承Thread:线程代码存放在Thread子类run方法中。
实现Runnable:线程代码存放在接口子类run方法中。
2、几种状态
被创建:等待启动,调用start启动。
运行状态:具有执行资格和执行权。
临时状态(阻塞):有执行资格,但是没有执行权。
冻结状态:遇到sleep(time)方法和wait()方法时,失去执行资格和执行权,sleep方法时间到或者调用notify()方法时,获得执行资格,变为临时状态。
消忙状态:stop()方法,或者run方法结束。
注:当已经从创建状态到了运行状态,再次调用start()方法时,就失去意义了,java运行时会提示线程状态异常。
四、线程安全问题
1 /* 2 通过分析,发现,打印出0,-1,-2等错票 3 4 多线程的运行出现了问题 5 问题的原因: 6 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完, 7 另一个线程参与进来执行。导致共享数据的错误 8 9 解决办法: 10 对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。 11 12 13 java对于多线程的安全问题提供了专业的解决方式。 14 就是同步代码块 15 synchronized(对象) 16 { 17 需要被同步的代码 18 } 19 对象如同锁。持有锁的线程可以在同步中执行。 20 没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有 获取锁 21 22 火车上的卫生间----经典 23 24 同步的前提: 25 1 必须要有两个或者两个以上的线程 26 2 必须是多个线程使用同一个锁 27 28 29 必须保证同步中只能有一个线程在运行 30 31 好处: 解决了多线程的安全问题 32 33 弊端: 多个线程需要判断锁,较为消耗资源 34 35 36 */ 37 38 class Ticket implements Runnable 39 { 40 private int tick=100; 41 Object obj =new Object(); 42 public void run() 43 { 44 while(true) 45 { 46 synchronized(obj)//加锁,锁可以是任意存在的对象 47 { 48 if(tick>0) 49 { 50 //try{Thread.sleep(10);}catch(Exception e){}//线程暂时休眠 51 System.out.println(Thread.currentThread().getName()+"....sale:"+tick--);//打印线程名称和票编号 52 } 53 else 54 break; 55 } 56 } 57 } 58 } 59 class TicketDemo2 60 { 61 public static void main(String[] args) 62 { 63 64 Ticket t = new Ticket(); 65 66 Thread t1 = new Thread(t); 67 Thread t2 = new Thread(t); 68 Thread t3 = new Thread(t); 69 Thread t4 = new Thread(t); 70 t1.start(); 71 t2.start(); 72 t3.start(); 73 t4.start(); 74 } 75 }
五、静态函数的同步方式
如果同步函数被静态修饰后,使用的锁是什么呢?
通过验证,发现不在是this。因为静态方法中也不可以定义this。静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。如:
类名.class 该对象的类型是Class
这就是静态函数所使用的锁。而静态的同步方法,使用的锁是该方法所在类的字节码文件对象。类名.class
经典示例:
1 /* 2 单例设计模式。 3 4 5 */ 6 //饿汉式 7 class Single 8 { 9 private static final Single s=new Single();//创建Single类并分配资源 10 private Single(){} 11 public static Single getInstance() 12 { 13 return s; 14 } 15 } 16 17 18 //懒汉式 19 class Single 20 { 21 private static Single s=null;//创建Single类,使其为空 22 private Single(){} 23 public static Single getInstance() 24 { 25 if (s==null) 26 { 27 synchronized(Single.class)//Single.class文件在类创建时,便存在 28 { 29 if(s==null) 30 s=new Single();//分配资源 31 } 32 } 33 return s; 34 } 35 }
六、死锁
当同步中嵌套同步时,就有可能出现死锁现象。
1 /* 2 死锁 3 同步中嵌套同步。A锁套B锁,B锁套A锁。 4 */ 5 class Test implements Runnable 6 { 7 private boolean flag; 8 Test(boolean flag) 9 { 10 this.flag = flag; 11 } 12 13 public void run() 14 { 15 if(flag) 16 { 17 while(true) 18 { 19 synchronized(MyLock.locka)//A锁 20 { 21 System.out.println(Thread.currentThread().getName()+"...if locka "); 22 synchronized(MyLock.lockb)//B锁 23 { 24 System.out.println(Thread.currentThread().getName()+"..if lockb"); 25 } 26 } 27 } 28 } 29 else 30 { 31 while(true) 32 { 33 synchronized(MyLock.lockb)//B锁 34 { 35 System.out.println(Thread.currentThread().getName()+"..else lockb"); 36 synchronized(MyLock.locka)//A锁 37 { 38 System.out.println(Thread.currentThread().getName()+".....else locka"); 39 } 40 } 41 } 42 } 43 } 44 } 45 46 47 class MyLock//创建两个锁 48 { 49 static Object locka = new Object(); 50 static Object lockb = new Object(); 51 } 52 53 class DeadLockTest 54 { 55 public static void main(String[] args) 56 { 57 Thread t1 = new Thread(new Test(true));//创建两个线程 58 Thread t2 = new Thread(new Test(false)); 59 t1.start();//运行线程,调用run()方法 60 t2.start(); 61 } 62 }
死锁运行程序 结果如下图:
七、线程间通信
其实就是多个线程在操作同一个资源,但是操作的动作不同。如一个 存入,一个输出。
1、使用同步操作同一资源的示例:
1 /* 2 线程间通讯: 3 其实就是多个线程在操作同一个资源, 4 但是操作的动作不同。 5 6 */ 7 class Res//定义资源 包括姓名 性别 以及否的初设定 8 { 9 String name; 10 String sex; 11 boolean flag = false; 12 } 13 14 class Input implements Runnable//存入 继承Runnable 15 { 16 private Res r ; 17 Input(Res r) 18 { 19 this.r = r; 20 } 21 public void run()//复写run方法 22 { 23 int x = 0;//定义变量,改变存入资源的种类 24 while(true) 25 { 26 synchronized(r)//锁 27 { 28 29 if(r.flag)//当有资源。flag为真时,先等待,避免多存 30 try{r.wait();}catch(Exception e){} 31 if(x==0) 32 { 33 r.name="mike"; 34 r.sex="man"; 35 } 36 else 37 { 38 r.name="丽丽"; 39 r.sex = "女女女女女"; 40 } 41 x = (x+1)%2;//改变x的奇偶 42 r.flag = true;//改变flag的值,说明已存 43 r.notify();//唤醒其他线程 44 } 45 } 46 } 47 } 48 49 class Output implements Runnable//输出 继承Runnable 50 { 51 private Res r ; 52 53 Output(Res r) 54 { 55 this.r = r; 56 } 57 public void run()//复写run方法 58 { 59 while(true) 60 { 61 synchronized(r)//锁 62 { 63 if(!r.flag)//当flag为假时,即没有资源时,先等待 64 try{r.wait();}catch(Exception e){} 65 System.out.println(r.name+"...."+r.sex);//打印资源 66 r.flag = false;//改变flag的值,说明已取出 67 r.notify();//唤醒其他线程 68 } 69 } 70 } 71 } 72 73 74 class InputOutputDemo 75 { 76 public static void main(String[] args) 77 { 78 Res r = new Res(); 79 80 Input in = new Input(r); 81 Output out = new Output(r); 82 83 Thread t1 = new Thread(in); 84 Thread t2 = new Thread(out); 85 86 t1.start(); 87 t2.start(); 88 } 89 } 90 91 92 //notifyAll(); 93 94 /* 95 wait: 96 notify(); 97 notifyAll(); 98 99 都使用在同步中,因为要对持有监视器(锁)的线程操作。 100 所以要使用在同步中,因为只有同步才具有锁。 101 102 为什么这些操作线程的方法要定义Object类中呢? 103 因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁, 104 只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒。 105 不可以对不同锁中的线程进行唤醒。 106 107 也就是说,等待和唤醒必须是同一个锁。 108 109 而锁可以是任意对象,所以可以被任意对象调用的方法定义Object类中。 110 111 112 */
程序运行部分截图如下:
几个小问题:
1)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
a,这些方法存在与同步中。
b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。
c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
2)wait(),sleep()有什么区别?
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
3)为甚么要定义notifyAll?
因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。
2、JDK1.5中提供了多线程升级解决方案。
将同步synchronized替换成显示的Lock操作。将Object中wait,notify,notifyAll,替换成了Condition对象。该Condition对象可以通过Lock锁进行获取,并支持多个相关的Condition对象。
升级解决方案的示例:
1 import java.util.concurrent.locks.*; 2 3 class ProducerConsumerDemo2 4 { 5 public static void main(String[] args) 6 { 7 Resource r = new Resource();//建立资源 8 9 Producer pro = new Producer(r);//创建生产者与消费者,操作的是同一个资源 10 Consumer con = new Consumer(r); 11 12 Thread t1 = new Thread(pro);//生产者两个线程 13 Thread t2 = new Thread(pro); 14 Thread t3 = new Thread(con);//消费者两个线程 15 Thread t4 = new Thread(con); 16 17 t1.start();//运行线程,调用run()方法 18 t2.start(); 19 t3.start(); 20 t4.start(); 21 22 } 23 } 24 25 /* 26 JDK1.5 中提供了多线程升级解决方案。 27 将同步Synchronized替换成现实Lock操作。 28 将Object中的wait,notify notifyAll,替换了Condition对象。 29 该对象可以Lock锁 进行获取。 30 该示例中,实现了本方只唤醒对方操作。 31 Lock:替代了Synchronized 32 lock 33 unlock 34 newCondition() 35 36 Condition:替代了Object wait notify notifyAll 37 await(); 38 signal(); 39 signalAll(); 40 */ 41 class Resource 42 { 43 private String name;//创建资源固有属性 44 private int count = 1; 45 private boolean flag = false; 46 // t1 t2 47 private Lock lock = new ReentrantLock();//创建锁 48 49 private Condition condition_pro = lock.newCondition();//将锁的情况 分为两份,线程使用的是自己派别的锁 50 private Condition condition_con = lock.newCondition(); 51 52 53 54 public void set(String name)throws InterruptedException 55 { 56 lock.lock(); 57 try 58 { 59 while(flag)//flag 为真时,代表有资源 60 condition_pro.await();//t1,t2 生产者等待 61 this.name = name+"--"+count++; 62 63 System.out.println(Thread.currentThread().getName()+"...生产者.."+this.name);//打印线程信息到控制台上 64 flag = true; 65 condition_con.signal();//唤醒消费者 66 } 67 finally 68 { 69 lock.unlock();//释放锁的动作一定要执行。 70 } 71 } 72 73 74 // t3 t4 75 public void out()throws InterruptedException 76 { 77 lock.lock(); 78 try 79 { 80 while(!flag)//flag 为假时, 代表没有资源 81 condition_con.await();//消费者等待 82 System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);//打印线程信息到控制台上 83 flag = false; 84 condition_pro.signal();//唤醒生产者 85 } 86 finally 87 { 88 lock.unlock();//释放锁 89 } 90 91 } 92 } 93 94 class Producer implements Runnable//生产者的类,继承Runnable 95 { 96 private Resource res; 97 98 Producer(Resource res) 99 { 100 this.res = res; 101 } 102 public void run() //复写run()方法 103 { 104 while(true) 105 { 106 try 107 { 108 res.set("+商品+"); //在资源中增加商品 109 } 110 catch (InterruptedException e) 111 { 112 } 113 114 } 115 } 116 } 117 118 class Consumer implements Runnable//消费者的类,继承Runnable 119 { 120 private Resource res; 121 122 Consumer(Resource res) 123 { 124 this.res = res; 125 } 126 public void run()//复写 run()方法 127 { 128 while(true) 129 { 130 try 131 { 132 res.out();//移除资源 133 } 134 catch (InterruptedException e) 135 { 136 } 137 } 138 } 139 }
改程序运行结果部分截图如下:
八、停止线程
1 /* 2 stop方法已经过时 3 4 如何停止线程? 5 只有一种,run方法结束 6 开启多线程运行,运行代码通常是循环结构 7 8 只要控制住循环,就可以让run方法结束,也就是线程结束 9 就是将while等判断语句中加入限制条件让其运行一段时间后停止运行 10 11 特殊情况: 12 当线程处于冻结状态就不会读取到标记。那么线程就不会结束 13 例如线程就如等待状态,没有其他唤醒线程,将会导致线程运行不到后面的结束 语句 14 15 当没有指定的方式让冻结的线程恢复到运行状态时,就需要对冻结了的线程进行清除 16 强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。 17 18 强制的方法为中断 interrupt(); 19 例如 20 当主函数运行将要结束时,使进入冻结状态的t1进程,强制将其终止 21 t1.interrupt(); 22 23 24 */ 25 26 27 /* 28 该函数有三个线程,主线程和t1和t2两个线程。 两个线程中while可能会进入死循环,需要强制结束, 29 第一种方法: 通过调用,是while中的条件不满足,这时程序会结束 30 第二种方法: 使用中断函数interrupt(); 31 */ 32 class StopThread implements Runnable 33 { 34 private boolean flag =true; 35 public void run() 36 { 37 while(flag) 38 { 39 40 System.out.println(Thread.currentThread().getName()+"....run"); 41 } 42 } 43 public void changeFlag() 44 { 45 flag = false; 46 } 47 } 48 49 50 51 52 class StopThreadDemo 53 { 54 public static void main(String[] args) 55 { 56 StopThread st = new StopThread();//新建停止线程 57 58 Thread t1 = new Thread(st); 59 Thread t2 = new Thread(st); 60 61 62 //t1.setDaemon(true);//设置为守护线程, 63 //t2.setDaemon(true); 64 t1.start(); 65 t2.start(); 66 67 int num = 0; 68 69 while(true) 70 { 71 if(num++ == 60) 72 { 73 st.changeFlag();//当满足条件时改变Flag状态,使线程结束 74 //t1.interrupt();////清除冻结状态 ,唤醒线程 75 //t2.interrupt(); 76 break; 77 } 78 System.out.println(Thread.currentThread().getName()+"......."+num); 79 } 80 System.out.println("over"); 81 } 82 }
程序运行结果部分截图如下:
扩展小知识:
1 /* 2 目的: 学习join的运用 3 join: 4 当A线程执行到了B线程的.join()方法时,A就会等待。等B线程都执行完,A才会执行 5 join可以 用来临时加入线程执行。 6 7 即在主函数中,出现t1.join()时,只能等t1线程执行完成后,才能执行主函数中的线程 8 如果在t1线程中加入了 t2.join()时,只能等t2线程执行完成后,才能执行主函数中的线程 9 10 注意: t1.join()如果在主函数中出现,只与t1和主线程有关 11 12 当t1有可能进入冻结状态时,可以使其中断,之后便可以执行主函数。 13 假使又使t1唤醒进入执行状态,将出现 异常 14 15 16 17 18 同步的线程中 cpu决定哪个线程将会执行,然而执行将会有一个频率,就是线程具有优先级 19 20 从1到10;默认线程的优先级为5;10为最高,1为最低; 21 因为是常量 为了阅读性 MAX_PRIORITY max_priority 22 MIN_PRIORITY min_priority 23 MORM_PRIORITY morm_priority 24 改变线程的优先级 t1.setPriority(Thread.MAX_PRIORITY); 25 26 MAX_PRIORITY 27 MIN_PRIORITY 28 MORM 29 30 Thread.yield(); 线程休息一下,将cpu的使用权,推给其他线程 31 Thread.yield(); Thread.yield thread.yield 32 */ 33 class Demo implements Runnable 34 { 35 public void run() 36 { 37 for(int x=0;x<70;x++) 38 { 39 System.out.println(Thread.currentThread+"........"+x);40 Thread.yield(); 41 42 } 43 } 44 } 45 class JoinDemo 46 { 47 public static void main(String[] args) throws Exception 48 { 49 Demo d=new Demo(); 50 Thread t1=new Thread(d); 51 Thread t2=new Thread(d); 52 t1.start(); 53 //t1.setPriority(Thread.MAX_PRIORITY); 54 t2.start(); 55 //t1.join(); 56 for(int x=0;x<80;x++) 57 { 58 //System.out.println("main...."+x); 59 } 60 System.out.println("over"); 61 } 62 63 }
自我总结:
线程的由来是继承Thread或者实现Runnable,单线程可以认为是要做完某事才能做另外一件事,因此很负责很安全,同样的会导致效率比较慢,而多线程,可以看做同时做很多事,因此效率会比较高,但同时可能会带来安全隐患,因此需要用到锁这样的工具。
如果是很多人来做一件事情时,就需要有标记即flag,需要告诉别人,自己完成到哪儿了。
而有时某些人会陷入困境中,做很多无用功,这时需要别人来制止它的行为。即,强制结束。
而某些人会陷入迷茫,无事可做。要么唤醒它,要么结束它。
甚至会有那么一些人陷入内乱,导致无法工作,即死锁。这是我们要避免的情况。
而有些人是为了某些人的存在而存在的。即守护线程。如果某些人不存在了,那么有些人也就没有戏了。
而有些毒瘤的人加入某组织,毒瘤不死,组织无法运行。即 join()
标签:
原文地址:http://www.cnblogs.com/ktlshy/p/4715197.html