首页 > 代码库 > 黑马程序员——总结——多线程——改12
黑马程序员——总结——多线程——改12
多线程
一、线程的概念
线程时程序执行的控制单元,一个进程必须有一个以上的线程;多线程并发执行可以提高程序的效率,起到同时执行的效果!
比如:电脑同时执行qq,迅雷;迅雷启动多个线程下载多个文件;窗口卖票
二、开启线程的方法
两种创建线程的方法:Extends Thread和implements Runnable
(1)、继承方法
a,定义类继承Thread。
b,复写Thread中的run方法。
目的:将自定义代码存储在run方法中,让线程运行。
c,创建定义类的实例对象。相当于创建一个线程。
d,用该对象调用线程的start方法。该方法的作用是:启动线程,调用run方法。
1 public class Demo2 { 2 3 public static void main(String[] args) { 4 MyThread mt = new MyThread(); //4,创建自定义类的对象 5 mt.start(); //5,开启线程 6 7 for(int i = 0; i < 3000; i++) { 8 System.out.println(Thread.currentThread().getName()+"bb"); 9 }//这个打印线程名字是main10 }11 }12 class MyThread extends Thread { //1,定义类继承Thread13 public void run() { //2,重写run方法14 for(int i = 0; i < 3000; i++) { //3,将要执行的代码,写在run方法中15 System.out.println(Thread.currentThread().getName()+"aaaaaaaaaaaaaaa");16 }17 }18 }
(2)、实现方法
a,定义类实现Runnable的接口。
b,覆盖Runnable接口中的run方法。目的也是为了将线程要运行的代码存放在该run方法中。
c,通过Thread类创建线程对象。
d,将Runnable接口的子类对象作为实参传递给Thread类的构造方法
1 public static void main(String[] args) { 2 MyThread mt = new MyThread(); //4,创建自定义类的对象 3 Thread th=new Thread(mt); 4 th.start(); //5,开启线程 5 6 for(int i = 0; i < 3000; i++) { 7 System.out.println(Thread.currentThread().getName()+"bb"); 8 }//这个打印线程名字是main 9 }10 }11 class MyThread implements Runnable { //1,定义类继承Thread12 public void run() { //2,重写run方法13 for(int i = 0; i < 3000; i++) { //3,将要执行的代码,写在run方法中14 System.out.println(Thread.currentThread().getName()+"aaaaaaaaaaaaaaa");15 }16 }17 }
三、两种方法的区别
1、使用方法的区别:
继承法:继承Thread,由于子类重写了Thread()类中run(),所以直接调用start()即可;
实现法:实现Runnable接口,构造函数中传入Runnable的引用,start()调用run ()方法是,判断成员变量Runnable的引用是否为空,
不为空编译时看的是Runnable的run(),运行时执行的是子类的run();
一般使用实现法:虽然代码较复杂,但避免了单继承的弊端,可以多实现,
2、线程的几种状态 (毕姥爷这里貌似有误,看别的资料获得)
在Java当中,线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。
第一是创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
第二是就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
第三是运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
第四是阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
第五是死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。
3、使用匿名内部类实现线程;
继承匿名内部类法:
1 new Thread() { //1,new 类(){}继承这个类2 public void run() { //2,重写run方法3 for(int i = 0; i < 3000; i++) { //3,将要执行的代码,写在run方法中4 System.out.println("aaaaaaaaaaaaaaaaaaaaaaaaaaaa");5 }6 }7 }.start();
实现匿名内部类法:
1 new Thread(new Runnable(){ //1,new 接口(){}实现这个接口2 public void run() { //2,重写run方法3 for(int i = 0; i < 3000; i++) { //3,将要执行的代码,写在run方法中4 System.out.println("bb");5 }6 }7 }).start();
四、同步
1、需要同步解决
a、当多线程并发,有多段代码同时执行时,我们不希望在某一段代码执行时CPU执行权更换到其他线程
b、如果两段代码是同步的,那么同一时间只能执行一段,完全执行完才能执行另一个线程
方法:
2、同步代码块法;关键字:synchronized加上一个所对象来定义这一段代码
多个同步代码块使用相同的锁为对象,那么这些线程就同步了
举个栗子:
卖票:当多线程操作共享数据时,可能发生安全问题,这时我们就要把操作数据的代码同步
1 package TEST; 2 2 /*编写三各类Ticket、SealWindow、TicketSealCenter分别代表票信息、售票窗口、售票中心。 3 3 * 售票中心分配一定数量的票,由若干个售票窗口进行出售,利用你所学的线程知识来模拟此售票过程。 4 4 */ 5 5 6 6 public class test_1Ticket { 7 7 8 8 public static void main(String[] args) { 9 9 10 10 Ticket tk = new Ticket();//建立票信息对象11 11 TicketSealCenter tsc = new TicketSealCenter(tk);// 创建售票中心。12 12 tsc.set(200);//售票中心分配一定数量的票13 13 14 14 new Thread(new SealWindow(tk,"一号窗口")).start();// 创建、启动线程,开始卖票。15 15 new Thread(new SealWindow(tk,"二号窗口")).start();16 16 new Thread(new SealWindow(tk,"三号窗口")).start();17 17 new Thread(new SealWindow(tk,"四号窗口")).start();18 18 19 19 }20 20 }21 21 class Ticket{22 22 private static int ticket;23 23 public static int getTicket() {24 24 return ticket;25 25 }26 26 public static void setTicket(int ticket) {27 27 Ticket.ticket = ticket;28 28 } 29 29 }30 30 31 31 32 32 class TicketSealCenter{33 33 Ticket tk=null;34 34 TicketSealCenter(Ticket tk){35 35 this.tk=tk;36 36 }37 37 38 38 public void set(int t){//它可以设置票数39 39 Ticket.setTicket(t);40 40 }41 41 }42 42 43 43 class SealWindow implements Runnable{44 44 private String name=null;45 45 private Ticket ticket;46 46 SealWindow(Ticket ticket,String name){47 47 this.name=name;48 48 this.ticket=ticket;49 49 }50 50 public void run(){51 51 while(true){52 52 synchronized (ticket) {53 53 int t=ticket.getTicket();54 54 if(t>0){55 55 System.out.println(name+": 第"+(tsex="+sex);29 30 flag=false;31 notify();32 } 33 }34 //存线程35 class Input implements Runnable{36 private Resource r;37 Input(Resource r){38 this.r=r;39 }40 @Override41 public void run() {42 int x=0;43 if(x==0){44 r.setInput("张三","waman");48 }49 x=(x+1)%2;//if和else交叉打印50 } 51 }52 //取线程53 class Output implements Runnable{54 private Resource r;55 Output(Resource r){56 this.r=r;57 }58 public void run(){59 while(true){60 r.getOutput();61 }62 }63 }64 65 public class SourceDemo {66 public static void main(String[] args){67 Resource r=new Resource();//操作同一个资源68 new Thread(new Input(r)).start();//存69 new Thread(new Output(r)).start();//取70 }71 72 }
1)wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
a,这些方法存在与同步中。
b,使用这些方法时必须要标识所属的同步的锁。同一个锁上wait的线程,只可以被同一个锁上的notify唤醒。
c,锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
2)wait(),sleep()有什么区别?
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
3)为甚么要定义notifyAll?
因为在需要唤醒对方线程时。如果只用notify,容易出现只唤醒本方线程的情况。导致程序中的所以线程都等待。
三.线程之间的通信
- 1.什么时候需要通信
- 多个线程并发执行时, 在默认情况下CPU是随机切换线程的
- 如果我们希望他们有规律的执行, 就可以使用通信, 例如每个线程执行一次打印
- 2.怎么通信
- 调用wait(),使线程等待让出CPU
- 调用notify(),唤醒等待的线程
- 这两个方法必须在同步代码中执行, 并且使用同步锁对象来调用
- 3.多个线程通信的问题
- notify()方法是随机唤醒一个线程
- notifyAll()方法是唤醒所有线程
- JDK5之前无法唤醒指定的一个线程
- 如果多个线程之间通信, 需要使用notifyAll()通知所有线程, 用while来反复判断条件
四.JDK5之后的线程控制
- 1.同步
- 使用ReentrantLock类的lock()和unlock()方法进行同步
- 2.通信
- 使用ReentrantLock类的newCondition()方法可以获取Condition对象
- 需要等待的时候使用Condition的await()方法, 唤醒的时候用signal()方法
- 不同的线程使用不同的Condition, 这样就能区分唤醒的时候找哪个线程了
五.同步与非同步类的
同步安全效率低下 不同步不安全效率高
StringBuffer StringBuilder
Vector ArrayList
Hashtable HashMap
黑马程序员——总结——多线程——改12