多线程知识点总结
第一部分当然是多线程的实现方式,在java中多线程实现方式有2种
一、自定义一个类A,继承Thread类
1 public class ThreadA extends Thread { 2 public void run(){ 3 4 } 5 } 6 7 }
此时ThreadA是一个线程类,在ThreadA中重写Thread类中的run方法
调用方式如下
ThreadA A = new ThreadA(); A.start();
二、自定义一个类B,实现Runable接口
1 public class ThreadB implements Runnable { 2 public void run() { 3 4 } 5 }
此时ThreadB不是一个线程类,在ThreadB中重写Thread类中的run方法
ThreadB b = new ThreadB(); Thread t1 = new Thread(b); t1.start();
需要在初始化Thread的时候,把Runnable接口的实现类的对象b作为参数传入
第二部分是线程安全问题
线程安全问题产生的场景:
1 public class SaleTickets implements Runnable { 2 3 private int num = 100; //表示100张火车票 这是共享资源 4 5 //买100张火车票 6 public void run() { 7 for (int i = 0; i < num; i++) { 8 if (id > 0) { 9 System.out.println(Thread.currentThread().getName() + "买了编号为" + num + "的火车票"); 10 num--; 11 try { 12 Thread.sleep(500); 13 } catch (InterruptedException e) { 14 } 15 } 16 } 17 } 18 }
SaleTickets st=new SaleTickets (); Thread tA=new Thread(st); //用户A Thread tB=new Thread(st); //用户B tA.setName("用户A线程"); tB.setName("用户B线程"); tA.start(); tB.start();
进入12306购买火车票的时候,为了解决多个人同时进行抢票问题,需要使用多线程来增加系统效率,假如票共有100张,用户A和用户B同时进行买票,如果是单线程,每次用户买票的时候,首先会打印用户买到了哪张票,然后票的总数num减1。
但是由于是多线程,多线程有一个特性就是线程的执行顺序由cpu来分配,所以执行顺序是不确定的,假如用户A执行SaleTickets方法中的第10行的时候,会打印出用户A线程买了编号为100的火车票,此时用户B线程也开始执行SaleTickets方法,用户B也会打印出用户B线程买了编号为100的火车票
这种结果不是合理的,所以就引发线程安全问题
解决线程安全问题的方式有2种
1、同步代码块
1 public class SaleTickets implements Runnable { 2 3 private int num = 100; //表示100张火车票 这是共享资源 4 5 //买100张火车票 6 public void run() { 7 for (int i = 0; i < num; i++) { 8 synchronized (this){ 9 if (num > 0) { 10 System.out.println(Thread.currentThread().getName() + "买了编号为" + num + "的火车票"); 11 num--; 12 try { 13 Thread.sleep(500); 14 } catch (InterruptedException e) { 15 } 16 } 17 } 18 } 19 } 20 }
2、同步方法
1 public class SaleTickets implements Runnable { 2 3 private int num = 100; //表示100张火车票 这是共享资源 4 5 public synchronized void saleOne(){ 6 if (num > 0) { 7 System.out.println(Thread.currentThread().getName() + "买了编号为" + num + "的火车票"); 8 num--; 9 try { 10 Thread.sleep(500); 11 } catch (InterruptedException e) { 12 } 13 } 14 } 15 16 //买100张火车票 17 public void run() { 18 for (int i = 0; i < 10; i++) { 19 saleOne(); 20 } 21 } 22 }
此时同步方法中的synchronized的锁默认为this
第三部分是线程间通信
线程间的通信机制为等待唤醒机制,多个线程通信的前提是共用同一把锁
wait(long timeout) 当前线程释放锁,并等待timeout毫秒
notify 唤醒持有同一锁的某个线程
1、定义一个静态object对象,作为线程间通信的锁
1 public class MyLock { 2 public static Object obj = new Object(); 3 }
2、定义线程A
1 //定义一个线程类 输出1 2 public class ThreadA extends Thread { 3 4 public void run(){ 5 for(int j=0;j<10;j++){ 6 synchronized (MyLock.obj) { 7 System.out.println(1); 8 MyLock.obj.notify(); 9 try { 10 MyLock.obj.wait(); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 } 15 } 16 } 17 18 }
3、定义线程B
1 //定义一个线程类 输出2 2 public class ThreadB extends Thread { 3 4 public void run(){ 5 for(int j=0;j<10;j++){ 6 synchronized (MyLock.obj) { 7 System.out.println(2); 8 MyLock.obj.notify(); 9 try { 10 MyLock.obj.wait(); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } 14 } 15 } 16 } 17 18 }
4、定义主函数执行线程A和线程B
1 public class TestThreadForSignal { 2 public static void main(String[] args){ 3 new ThreadA().start(); 4 new ThreadB().start(); 5 } 6 }
执行结果:交替打印1和2
执行原理:先执行线程A,会被同步代码块synchronized锁住,打印1,然后唤醒共用MyLock.obj锁的线程B,然后线程A进入等待,并释放锁。然后执行线程B,会被同步代码块synchronized锁住,打印2,然后唤醒共用MyLock.obj锁的线程A,然后线程B进入等待,并释放锁。接下来继续执行线程A循环往复…….
第四部分是生产者消费者模式
利用线程间通信可以很好的实现生产者消费者模式
因为这个代码跟上面线程通信的代码很类似,这里博主不打算继续码代码了,用文字做一个简要说明
线程A (农夫) 往篮子里放苹果 如果篮子满了(苹果数量=10),线程A wait,如果没满(苹果数量<10),往篮子里放苹果,同时告诉B(小孩儿) notify唤醒
线程B (小孩儿)从篮子里拿苹果吃 如果篮子空了(苹果数量=0),线程B wait 如果篮子里还有苹果(苹果数量>0),从篮子里拿苹果吃 同时告诉A(农夫)notify唤醒
在这个场景里线程A(农夫)是生产者,线程B(小孩儿)是消费者