多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行 。
二、使用多线程的情况 程序需要同时执行两个或多个任务程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。比如用户输入,当用户输入部分占据CPU时,如果一直没有输入,不可能让这一部分一直占据CPU,这个时候会让别的线程上CPU运行需要一些后台运行的程序时 二、多线程的优缺点(重在缺点) 优点:提高程序的响应
提高CPU的利用率
改善程序结构,将复杂任务分为对个线程,独立运行
缺点: 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多 第一点容易改善,我们不断提高电脑的性能就可以很好的改善占用内存的问题
多线程需要协调和管理,所以需要CPU时间跟踪线程
线程之间对共享资源的访问会相互影响,必须解决竞用共享资源的问题(最主要的问题)
比如我们春节期间买电影票,班里30个人买同一电影院的同一场次,电影票作为一个共享的资源,张三想买7排8号,恰巧李四也想买7排8号。张三和李四他俩购票的时候相当于创建了两个线程(多线程),如果不加以控制,他俩竞争共享资源,张三刚买完票,票数计数器还没来得及减一就进入阻塞状态,而李四看到的这张票还在,他也买了。这个时候就会出现事故。这是多线程存在的最大问题。
三、并行和并发理解并行和并发要注意到两个词:同一时刻和一个时间段
并行:并行是在同一时刻发生多件事情。例如:多个运动员听到枪响那一时刻同时起跑、多个CPU同一时刻开始执行多个线程
并发:并发是在一个时间段内,多个事情发生,这几个事件在时间上很紧凑,但还是有先后顺序的。例如:一个单核CPU一次只能执行一个线程,但是我们一边放音乐,一边用QQ聊天看起来是同时发生的,但其实是不断交换着进行的。由于这些线程上下处理机的时间非常短暂,在我们看来他们是同时发生的,这就是并发。
线程同步 一、多线程同步*** 多个线程同时读写同一份共享资源时,可能会引起冲突(上面买电影票的例子)。所以引入线程“同步”机制,即各线程间要有先来后到;
同步就是排队+锁:
几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作
为了保证数据在方法中被访问时的正确性,在访问时加入锁机制
二、模拟买票为例讲解线程同步有两个窗口卖票,当前的总票数是10张,分别用继承Thread和实现Runnable两种方式实现:
一、继承Thread方式创建包:package com.ffyc.javathread.demo6;
创建类:TicketThread类
创建类:Test类
public class TicketThread extends Thread{ int num = 10; //设定有10张票 static Object obj = new Object(); @Override public void run(){ while(true){ synchronized(obj) { //进入同步代码块就会枷锁-->将对象头中的锁状态改为枷锁 if (num > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "买到了票:" + num); num--; } else { break; } } //同步代码块执行完后会自动释放锁 } }}
上面的代码让我详细讲解,看不懂,不可能!
public class TicketThread extends Thread{ int num = 10;//num表示车票 作为类的成员属性==整体的变量 @Override public void run(){ } }
先重写出run()方法的轮廓就可以,具体实现完后看
***我们要在main方法中创建出题目要求的两个窗口(线程)
创建包:package com.ffyc.javathread.demo6;
创建类:Test类
package com.ffyc.javathread.demo6;public class Test{ public static void main(String [] args){ TicketThread th1 = new TicketThread(); th1.setName("窗口1"); //Thread中给线程命名的方发:setName() th1.start(); //创建第二个线程 TicketThread th2 = new TicketThread(); th2.setName("窗口2"); th2.start(); }}
接下来我们来实现run()方法:
@Override public void run(){ while(true){ if(num>0){ //买票方法体 System.out.println(Thread.currentThread().getName()+"买到票:"+mun); num--; }else{ break;//当mun票数小于等于0就说明没票了,break结束 } } }
现在,整合起来就是:
package com.ffyc.javathread.demo6;public class TicketThread extends Thread{ int num = 10;//num表示车票 作为类的成员属性==整体的变量 @Override public void run(){ while(true){ if(num>0){ //买票方法体 System.out.println(Thread.currentThread().getName()+"买到票:"+mun); //sleep()方法需要try/catch异常 //sleep()方法是线程休眠参数100指的是休眠100ms,即让当前正在执行的线程休眠(暂停执行) try{ Thread.sleep(100); }catch(InterruptedException e){ e.printStackTrace(); } num--; }else{ break;//当mun票数小于等于0就说明没票了,break结束 } } } } package com.ffyc.javathread.demo6;public class Test{ public static void main(String [] args){ TicketThread th1 = new TicketThread(); th1.setName("窗口1"); //Thread中给线程命名的方发:setName() th1.start(); //创建第二个线程 TicketThread th2 = new TicketThread(); th2.setName("窗口2"); th2.start(); }}
上面这个卖票程序会出错,不是编译期的错误,是程序运行时会出现上面举的买电影票例子的情况:窗口1卖出去了7排8号的票,窗口2也卖出去了这张票。
同时买到了第10张票,同时买到了第8张票…总共10张票却卖出去了12张。
多线程存在的这个问题我们可以通过排队+加锁来处理,即线程同步机制。
加锁的两种方式(这里先说给代码块加锁,给方法加锁见下一篇) 用synchronized(同步对象)关键字加锁用synchronized(同步对象)可以给方法加锁,也可以给代码块加锁:
(1)给代码块加锁:
package com.ffyc.javathread.demo6;public class TicketThread extends Thread{ int num = 10;//num表示车票 作为类的成员属性==整体的变量 Object obj = new Object(); @Override public void run(){ while(true){ synchronized(obj){ if(num>0){ //买票方法体 System.out.println(Thread.currentThread().getName()+"买到票:"+mun); //sleep()方法需要try/catch异常 //sleep()方法是线程休眠参数100指的是休眠100ms,即让当前正在执行的线程休眠(暂停执行) try{ Thread.sleep(100); }catch(InterruptedException e){ e.printStackTrace(); } num--; }else{ break;//当mun票数小于等于0就说明没票了,break结束 } } } } }
synchronized(同步对象) 中的同步对象 可以是任意类的对象,但是只能是唯一的,只有一份的。
想明白synchronized关键字怎么加锁,需要了解一点对象的底层:
其实,对象中有一个区域叫对象头,对象头中有一个锁状态的记录,我们在TicketThread类中,创建了一个Object对象,当两个线程中的一个进入锁之后obj对象的锁状态会标记锁里面有线程,此时别的线程就无法进来,直到synchronized(obj){}执行完,释放标志位 。一次只允许一个线程进入,是安全的。
这种情况不能用this,因为我们创建了两个线程对象(th1和th2),在访问锁里面的代码时:哪一个线程访问,this就表示 哪一个线程的对象,两个线程,就有两个this对象,不唯一,导致都可以进入锁。
以下是运行结果:
这篇文章就是理解线程同步,后面部分请见下一篇~