首页 > 代码库 > 【Java基础总结】多线程
【Java基础总结】多线程
1. 实现多线程的两种方式
1 //第一种:继承Thread类,重写run()方法 2 class ThreadTest1 extends Thread{ 3 public void run(){ 4 String threadName = Thread.currentThread().getName(); 5 for(int i=0;i<10;i++){ 6 System.out.println("ThreadTest1 "+threadName+" running ... "+i); 7 } 8 } 9 }10 11 //第二种:实现Runnable接口,重写run()方法12 class ThreadTest2 implements Runnable{13 public void run(){14 String threadName = Thread.currentThread().getName();15 for(int i=0;i<10;i++){16 System.out.println("ThreadTest2 "+threadName+" running ... "+i);17 }18 }19 }
实现方式不同,使用方式也不同
public class Demo1{ public static void main(String[] args){ ThreadTest1 t1 = new ThreadTest1(); ThreadTest1 t2 = new ThreadTest1(); Thread t3 = new Thread(new ThreadTest2()); Thread t4 = new Thread(new ThreadTest2()); t1.start(); t2.start(); t3.start(); t4.start(); }}
运行结果大致如下:
ThreadTest1 Thread-0 running ... 0ThreadTest2 Thread-3 running ... 0ThreadTest2 Thread-2 running ... 0ThreadTest1 Thread-1 running ... 0ThreadTest2 Thread-2 running ... 1ThreadTest2 Thread-3 running ... 1ThreadTest1 Thread-0 running ... 1ThreadTest2 Thread-3 running ... 2ThreadTest2 Thread-2 running ... 2ThreadTest1 Thread-1 running ... 1ThreadTest2 Thread-2 running ... 3ThreadTest2 Thread-3 running ... 3ThreadTest1 Thread-0 running ... 2ThreadTest2 Thread-3 running ... 4ThreadTest2 Thread-2 running ... 4ThreadTest1 Thread-1 running ... 2ThreadTest2 Thread-2 running ... 5ThreadTest2 Thread-3 running ... 5ThreadTest1 Thread-0 running ... 3ThreadTest2 Thread-3 running ... 6ThreadTest2 Thread-2 running ... 6ThreadTest1 Thread-1 running ... 3ThreadTest2 Thread-2 running ... 7ThreadTest2 Thread-3 running ... 7ThreadTest2 Thread-3 running ... 8ThreadTest2 Thread-3 running ... 9ThreadTest1 Thread-0 running ... 4ThreadTest1 Thread-0 running ... 5ThreadTest2 Thread-2 running ... 8ThreadTest2 Thread-2 running ... 9ThreadTest1 Thread-1 running ... 4ThreadTest1 Thread-0 running ... 6ThreadTest1 Thread-0 running ... 7ThreadTest1 Thread-0 running ... 8ThreadTest1 Thread-1 running ... 5ThreadTest1 Thread-0 running ... 9ThreadTest1 Thread-1 running ... 6ThreadTest1 Thread-1 running ... 7ThreadTest1 Thread-1 running ... 8ThreadTest1 Thread-1 running ... 9
2. 线程共享资源
建议使用 实现Runnable接口,重写run方法 的方式来实现多线程,它有如下优点:
1. 线程和代码分离,多线程间可以共享资源
2. 避免了单继承带来的局限性
3. 多线程之间可以共享资源
tip
Thread.currentThread().getName() 获得当前线程的名称threadObj.setName() 设置线程名称
案例:售票
class ThreadTest4 implements Runnable{ private int ticket=20; public void run(){ String threadName = Thread.currentThread().getName(); while(ticket>0){ System.out.println("ThreadTest4 "+threadName+" 售出 "+ticket+" 号票"); ticket--; } }}
使用情况1:
(new Thread(new ThreadTest4(), "窗口a")).start(); (new Thread(new ThreadTest4(), "窗口b")).start(); (new Thread(new ThreadTest4(), "窗口c")).start(); (new Thread(new ThreadTest4(), "窗口d")).start();
运行情况说明A、B、C、D四个窗口也没用共享count这个资源
主线程名称:mainThreadTest4 窗口a 售出 20 号票ThreadTest4 窗口a 售出 19 号票ThreadTest4 窗口b 售出 20 号票ThreadTest4 窗口b 售出 19 号票ThreadTest4 窗口b 售出 18 号票ThreadTest4 窗口b 售出 17 号票ThreadTest4 窗口b 售出 16 号票ThreadTest4 窗口b 售出 15 号票ThreadTest4 窗口a 售出 18 号票ThreadTest4 窗口b 售出 14 号票ThreadTest4 窗口d 售出 20 号票ThreadTest4 窗口c 售出 20 号票ThreadTest4 窗口d 售出 19 号票ThreadTest4 窗口b 售出 13 号票ThreadTest4 窗口a 售出 17 号票ThreadTest4 窗口b 售出 12 号票ThreadTest4 窗口d 售出 18 号票ThreadTest4 窗口c 售出 19 号票ThreadTest4 窗口d 售出 17 号票ThreadTest4 窗口b 售出 11 号票ThreadTest4 窗口a 售出 16 号票ThreadTest4 窗口b 售出 10 号票ThreadTest4 窗口d 售出 16 号票ThreadTest4 窗口c 售出 18 号票ThreadTest4 窗口d 售出 15 号票ThreadTest4 窗口b 售出 9 号票ThreadTest4 窗口a 售出 15 号票ThreadTest4 窗口b 售出 8 号票ThreadTest4 窗口d 售出 14 号票ThreadTest4 窗口c 售出 17 号票ThreadTest4 窗口d 售出 13 号票ThreadTest4 窗口b 售出 7 号票ThreadTest4 窗口a 售出 14 号票ThreadTest4 窗口b 售出 6 号票ThreadTest4 窗口d 售出 12 号票ThreadTest4 窗口c 售出 16 号票ThreadTest4 窗口d 售出 11 号票ThreadTest4 窗口b 售出 5 号票ThreadTest4 窗口a 售出 13 号票ThreadTest4 窗口b 售出 4 号票ThreadTest4 窗口d 售出 10 号票ThreadTest4 窗口c 售出 15 号票ThreadTest4 窗口d 售出 9 号票ThreadTest4 窗口b 售出 3 号票ThreadTest4 窗口a 售出 12 号票ThreadTest4 窗口b 售出 2 号票ThreadTest4 窗口d 售出 8 号票ThreadTest4 窗口c 售出 14 号票ThreadTest4 窗口d 售出 7 号票ThreadTest4 窗口b 售出 1 号票ThreadTest4 窗口a 售出 11 号票ThreadTest4 窗口a 售出 10 号票ThreadTest4 窗口d 售出 6 号票ThreadTest4 窗口c 售出 13 号票ThreadTest4 窗口d 售出 5 号票ThreadTest4 窗口a 售出 9 号票ThreadTest4 窗口d 售出 4 号票ThreadTest4 窗口c 售出 12 号票ThreadTest4 窗口d 售出 3 号票ThreadTest4 窗口a 售出 8 号票ThreadTest4 窗口d 售出 2 号票ThreadTest4 窗口c 售出 11 号票ThreadTest4 窗口d 售出 1 号票ThreadTest4 窗口a 售出 7 号票ThreadTest4 窗口c 售出 10 号票ThreadTest4 窗口a 售出 6 号票ThreadTest4 窗口a 售出 5 号票ThreadTest4 窗口a 售出 4 号票ThreadTest4 窗口a 售出 3 号票ThreadTest4 窗口a 售出 2 号票ThreadTest4 窗口a 售出 1 号票ThreadTest4 窗口c 售出 9 号票ThreadTest4 窗口c 售出 8 号票ThreadTest4 窗口c 售出 7 号票ThreadTest4 窗口c 售出 6 号票ThreadTest4 窗口c 售出 5 号票ThreadTest4 窗口c 售出 4 号票ThreadTest4 窗口c 售出 3 号票ThreadTest4 窗口c 售出 2 号票ThreadTest4 窗口c 售出 1 号票
使用情况2:
ThreadTest4 t2 = new ThreadTest4(); (new Thread(t2,"窗口1")).start(); (new Thread(t2,"窗口2")).start(); (new Thread(t2,"窗口3")).start(); (new Thread(t2,"窗口4")).start();
运行情况说明A、B、C、D四个窗口共享count这个资源(但发生了访问冲突)
主线程名称:mainThreadTest4 窗口1 售出 20 号票ThreadTest4 窗口2 售出 20 号票ThreadTest4 窗口2 售出 19 号票ThreadTest4 窗口2 售出 18 号票ThreadTest4 窗口2 售出 17 号票ThreadTest4 窗口2 售出 16 号票ThreadTest4 窗口2 售出 15 号票ThreadTest4 窗口2 售出 14 号票ThreadTest4 窗口2 售出 13 号票ThreadTest4 窗口2 售出 12 号票ThreadTest4 窗口2 售出 11 号票ThreadTest4 窗口2 售出 10 号票ThreadTest4 窗口2 售出 9 号票ThreadTest4 窗口2 售出 8 号票ThreadTest4 窗口2 售出 7 号票ThreadTest4 窗口3 售出 7 号票ThreadTest4 窗口1 售出 5 号票ThreadTest4 窗口2 售出 6 号票ThreadTest4 窗口1 售出 3 号票ThreadTest4 窗口4 售出 4 号票ThreadTest4 窗口3 售出 4 号票ThreadTest4 窗口1 售出 1 号票ThreadTest4 窗口2 售出 2 号票
3. 线程同步
多线程中涉及到共享数据时,会出现线程安全问题。就上面的售票案例来说,若没有加 synchronized 关键字,在多个线程同时使用ticket这个共享数据时,会出现同一个ticket被使用两次这样的看似不可能的情况。另外还有一种情况,事实上ticket这个共享数据是类ThreadTest4对象t2中的变量,所以若是在主线程中添加t2.run();语句的话,也是会发生线程安全问题的。
在Java里面,同步锁的概念就是这样的。任何一个Object Reference都可以作为同步锁。我们可以把Object Reference理解为对象在内存分配系统中的内存地址。
1 class ThreadTest5 implements Runnable{ 2 private int ticket=20; 3 public void run(){ 4 while(ticket>0){ 5 String threadName = Thread.currentThread().getName(); 6 //同步代码块(越小越好) 7 synchronized(this){ 8 if(ticket>0){ 9 System.out.println(threadName + " sales ticket "+ticket);10 ticket--;11 }12 }13 }14 }15 }16 17 public class Demo3{18 public static void main(String[] args){19 ThreadTest5 t = new ThreadTest5();20 (new Thread(t, "窗口A")).start();21 (new Thread(t, "窗口B")).start();22 (new Thread(t, "窗口C")).start();23 (new Thread(t, "窗口D")).start();24 }25 }
运行结果
窗口A sales ticket 20窗口A sales ticket 19窗口A sales ticket 18窗口A sales ticket 17窗口A sales ticket 16窗口A sales ticket 15窗口A sales ticket 14窗口A sales ticket 13窗口A sales ticket 12窗口A sales ticket 11窗口A sales ticket 10窗口D sales ticket 9窗口D sales ticket 8窗口D sales ticket 7窗口D sales ticket 6窗口D sales ticket 5窗口D sales ticket 4窗口D sales ticket 3窗口C sales ticket 2窗口B sales ticket 1
(1)同步代码块
synchronized(类或对象){ //需要同步的代码段}
(2)同步函数
(非static的情况)
public synchronized void fun(){ //代码段}
等价于(调用此同步函数的对象作为此同步函数的同步锁)
public void fun() { synchronized(this) { //代码段 }}
(static的情况)
public static synchronized void fun() { //代码段}
静态变量或静态方法加载到内存中时,内存中没有本类对象,但一定有了该类对应的字节码文件(类名.class),该对象的类型是class。静态同步函数使用的同步锁是所在类的字节码文件。
线程同步的前提:
1)2个或2个以上的线程
2)使用同一把锁
保证同步中只有一个线程在运行。
好处:解决线程安全问题
弊端:多个线程需要判断锁,消耗资源
4. 线程通信
经典的生产者和消费者问题
假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费。如果仓库中没有产品,则生产者可以将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。显然,这是一个同步问题,生产者和消费者共享同一资源,并且,生产者和消费者之间彼此依赖,互为条件向前推进
4.1 synchronized和wait、notify、notifyAll
wait() 使得当前线程必须要等待,并释放对锁的拥有权,等到另外一个线程调用notify()或者notifyAll()方法
notify() 会唤醒一个等待当前对象的锁的线程
notifyAll() 唤醒所有一个等待当前对象的锁的线程
一个小比较
当线程调用了wait()方法时,它会释放掉对象的锁。
另一个会导致线程暂停的方法:Thread.sleep(millisecond),它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的。
2 class Repository{ 3 private int count=0; //当前仓库存放商品数量 4 private int capacity=5; //仓库容量 5 private String goodsName; //商品名称 6 public Repository(String goodsName){ 7 this.goodsName = goodsName; 8 } 9 public void store(String threadName){10 synchronized(this){11 while(this.count>=this.capacity){12 System.out.println("[" + threadName + "]仓库已达到最大容量 " + this.capacity + " 个 !!");13 try{this.wait();}14 catch(Exception e){}15 }16 this.count++;17 System.out.println("[" + threadName + "]仓库增加了一个"+this.goodsName+",现有"+this.goodsName+"["+this.count+"] 个");18 this.notifyAll();19 }20 }21 public void fetch(String threadName){22 synchronized(this){23 while(this.count<1){24 System.out.println("[" + threadName + "]仓库没有"+this.goodsName+"!!");25 try{26 this.wait();27 }catch(Exception e){28 29 }30 }31 this.count--;32 System.out.println("[" + threadName + "]仓库减少了一个"+this.goodsName+",现有"+this.goodsName+"["+this.count+"] 个");33 this.notifyAll();34 }35 }36 }37 //生产者38 class Producter implements Runnable{39 private Repository repository ;40 public Producter(Repository repository){41 this.repository = repository;42 }43 public void run(){44 String threadName = Thread.currentThread().getName();45 while(true){46 try{ 47 //sleep 2秒模拟生产过程48 Thread.sleep(1000); 49 }catch(Exception e){50 51 }52 //把生产的商品存放到仓库53 repository.store(threadName);54 }55 }56 }57 //消费者58 class Consumer implements Runnable{59 private Repository repository ;60 public Consumer(Repository repository){61 this.repository = repository;62 }63 public void run(){64 String threadName = Thread.currentThread().getName();65 while(true){66 //将商品从仓库取出来67 repository.fetch(threadName);68 try{ 69 //sleep 4秒模拟消费过程70 Thread.sleep(2000); 71 }catch(Exception e){72 73 }74 } 75 }76 }77 public class Thread06{78 public static void main(String[] args){79 Repository repository = new Repository("馒头");80 81 Producter pro1 = new Producter(repository);82 Consumer con1 = new Consumer(repository);83 //生产者和消费者各2个84 (new Thread(pro1, "生产者A")).start(); 85 (new Thread(pro1, "生产者B")).start();86 (new Thread(con1, "消费者1")).start();87 (new Thread(con1, "消费者2")).start();88 }89 }
其中一种运行结果:
1 [消费者1]仓库没有馒头!! 2 [消费者2]仓库没有馒头!! 3 [生产者A]仓库增加了一个馒头,现有馒头[1] 个 4 [消费者2]仓库减少了一个馒头,现有馒头[0] 个 5 [消费者1]仓库没有馒头!! 6 [生产者B]仓库增加了一个馒头,现有馒头[1] 个 7 [消费者1]仓库减少了一个馒头,现有馒头[0] 个 8 [生产者A]仓库增加了一个馒头,现有馒头[1] 个 9 [生产者B]仓库增加了一个馒头,现有馒头[2] 个10 [生产者A]仓库增加了一个馒头,现有馒头[3] 个11 [消费者2]仓库减少了一个馒头,现有馒头[2] 个12 [生产者B]仓库增加了一个馒头,现有馒头[3] 个13 [消费者1]仓库减少了一个馒头,现有馒头[2] 个14 [生产者A]仓库增加了一个馒头,现有馒头[3] 个15 [生产者B]仓库增加了一个馒头,现有馒头[4] 个16 [消费者2]仓库减少了一个馒头,现有馒头[3] 个17 [生产者A]仓库增加了一个馒头,现有馒头[4] 个18 [消费者1]仓库减少了一个馒头,现有馒头[3] 个19 [生产者B]仓库增加了一个馒头,现有馒头[4] 个20 [生产者A]仓库增加了一个馒头,现有馒头[5] 个21 [生产者B]仓库已达到最大容量 5 个 !!22 [消费者2]仓库减少了一个馒头,现有馒头[4] 个23 [生产者B]仓库增加了一个馒头,现有馒头[5] 个24 [生产者A]仓库已达到最大容量 5 个 !!25 [消费者1]仓库减少了一个馒头,现有馒头[4] 个26 [生产者A]仓库增加了一个馒头,现有馒头[5] 个27 [生产者B]仓库已达到最大容量 5 个 !!28 [生产者A]仓库已达到最大容量 5 个 !!
(运行结果分析)
1)线程“消费者1”调用repositoy.fetch()方法去仓库取馒头。进入synchronized块,刚一执行while语句,结果 this.count<1 为真,直接就wait()进入等待队列,最后释放了锁(就绪队列[消费者2,生产者A,生产者B],等待队列[消费者1])2)线程“消费者2”同“消费者1”的遭遇是相同的(对此,我们深表同情)(就绪队列[生产者A,生产者B], 等待队列[消费者1,消费者2])3)线程“生产者A”生产完商品后,调用repository.store()方法把商品存到了仓库中。在store方法里,使用notifyAll方法唤醒了所有在沉睡wait的线程,最后释放了锁(就绪队列[生产者A,生产者B,消费者1,消费者2],等待队列[])4)线程“消费者2”从上次wait的地方开始执行(从哪里跌倒,就从哪里爬起来)。同样是while语句,但这次 this.count<1 不为真了,于是乎顺利脱坑,接着执行 this.count--,把仓库仅有的一个馒头给拿走了,走之前还不忘大喊一句“仓库减少了一个馒头,现有馒头[0] 个”。同样是notifyAll方法唤醒所有沉睡wait的线程,释放锁(就绪队列[生产者A,生产者B,消费者1,消费者2],等待队列[])5)线程“消费者1”辛辛苦苦抢到了锁,终于能执行syschronized块代码了。和“消费者2”一样,也是从上次wait的地方接着执行,也是while语句,但不同的是 this.count<1 为真,线程“生产者A”生产存放到仓库的仅有的一个馒头被“消费者2”给吃了,可以想象此时的“消费者1”心里是万念俱灰的。啥也别说了,接着沉睡wait吧。(释放了锁)(就绪队列[生产者A,生产者B,消费者2],等待队列[消费者1])
**** 在接下来的几十个回合中,时而生产者线程夺得仓库锁,称霸武林,时而消费者线程夺得,笑傲江湖,仓库商品也是时增时减,但总体上还是生产者线程抢到的次数多,因为生产者够快,当消费者还在花2秒钟费劲的消化时,生产者早就1秒生产完毕,参与仓库锁的再次争夺了,可见“天下武功,唯快不破” *****“仓库里没有馒头为什么不通知我?”,线程“消费者2”不满“消费者1”对仓库情况的隐瞒不报,“你要是早告诉我,我也就不用争抢仓库锁,抢到了也没用,里面根本就没有馒头,去了也是wait”。(第1、2步)“你还有脸说我,你把生产者A生产存放在仓库仅有的一个馒头吃了,你明知道仓库再也没有馒头了,也不告诉我,还让我傻了吧唧抢到仓库锁,去了也白搭”。“消费者1”反驳道,它同样也很委屈。(第4、5步)“管我什么事,抢仓库锁的有不只你一个,我也去抢了,白费了劲,还没抢到....”,线程“消费者2”道。“要是仓库没有馒头的时侯,只唤醒那帮生产者就好了”,线程“消费者1”和“消费者2”异口同声的说道。
处理线程通信必须遵循一种原则:对于生产者,在生产者没有生产之前,要通知消费者等待;在生产者生产之后,马上又通知消费者消费;对于消费者,在消费者消费之后,要通知生产者已经消费结束,需要继续生产新的产品以供消费。
4.2 Lock和Condition
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用
5. 停止线程的方法
interrupt() //停止线程isInterrupt() //判断线程是否停止
6. 守护线程和join方法
守护线程是为其他线程提供便利服务的,当全部的用户线程结束后,守护线程才会随JVM结束工作。 thread.setDaemon(true);
public static void main(String[] args){ Thread t3 = new Thread(test2, "线程t3"); t3.start(); t3.join(); //主线程就此陷入等待,直到t3线程结束。}
7. 线程优先级和yield方法
线程的优先级从低到高:1-10,优先级高的的优先执行,每个新线程都继承了父线程的优先级,常量:Thread.MIN_PRIORITY 值为1,Thread.MAX_PRIORITY 值为10,Thread.NORM_PRIORITY 值为5
void setPriority(priority) //设置线程优先级int getPriority() //获取线程优先级
yield() 线程从执行状态变成就绪状态。
【Java基础总结】多线程