Java基础篇---多线程

内容导航:

1、多线程的实现方式

2、线程安全问题

3、线程间通信

4、生产者消费者模式

第一部分多线程的实现方式

在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 (num > 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(小孩儿)是消费者

原文地址:https://www.cnblogs.com/baixiaoguang/p/11912953.html