JAVA 多线程

多线程

在看多线程之前先来看看必要的一些东西:

线程与进程

进程:
是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间。
线程:
是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少有一个线程。
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分成若干个线程。

同步与异步

同步: 排队执行 , 效率低但是安全.
异步: 同时执行 , 效率高但是数据不安全.

并发与并行

并发: 指两个或多个事件在同一个时间段内发生。
并行: 指两个或多个事件在同一时刻发生(同时发生)。

好了这些相关的知识了解一下就可以看看多线程了。

线程的定义

1.继承Thread
继承java.lang.Thread类,重写run()方法 。
格式如下:

//这是继承格式,记住一定要重写run()方法 public class MyThread extends Thread {      //run方法就是线程要执行的任务方法      @Override     public void run() {         //这里的代码就是一条新的执行路径         //这个执行路径是触发方式,不是调用run方法,而是通过thread对象的start方法来启动任务     } } 
//这个是创建格式,和之前类的对象的创建是一样的 //但是一定要记住要用"对象.start()"来启动线程,要不然就是线程创建了但是不会运行 public class Demo1 {     public static void main(String[] args) {          MyThread m = new MyThread();         m.start();     } } 

上述就完成并启动了一个线程。
2.实现Runnable
实现java.lang.Runnable接口,并将其传入到Thread类对象中
格式如下:

//与上述Thread格式一模一样,就是一个是继承类,一个是实现接口 public class MyRunnable implements Runnable{     @Override     public void run() {         //线程的任务     } } 
//创建格式 public class Demo1 {     public static void main(String[] args) {          //实现runnable         //1 创建一个任务对象         MyRunnable r = new MyRunnable();         //创建一个线程并给他一个任务         Thread t = new Thread(r);         //启动线程         t.start();     } } //这里就是比Tread多了一步,就是给他一个任务。 

那么这两种方法到底那种好呢,有人觉得1好应为1比2少一步,不用分配任务,但是实际上是2比较好。
来看看,实现Runnable与继承Thread相比有如下优势:
1、通过创建任务,然后给线程分配任务的方式实现多线程,更适合多个线程同时执行任务的情况
2、可以避免单继承所带来的局限性
3、任务与线程是分离的,提高了程序的健壮性
4、后期学习的线程池技术,接受Runnable类型的任务,不接受Thread类型的线程。

线程安全问题

先来看看什么是线程安全问题

package thread;   public class Demo7 {     public static void main(String[] args) {         //线程不安全         Runnable run = new Ticket();         new Thread(run).start();         new Thread(run).start();         new Thread(run).start();     }      static class Ticket implements Runnable{         //总票数         private int count = 10;         @Override         public void run() {             while (count>0){                 //卖票                 System.out.println("正在准备卖票");                 try {                     Thread.sleep(1000);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }                 count--;                 System.out.println("卖票结束,余票:"+count);             }         }     } }  

读者可以试试这个代码,有时候也要多运行几次,它会出现以下情况:
JAVA 多线程
我们可以看到在运行结果中出现了负数,也就是说票买到了负的,你想想这肯定是不符合逻辑的,但是程序也没有出错,也没有异常,这就是线程不安全,也叫叫做线程安全问题。下面就看看线程不安全的解决方法。
解决方案1 同步代码块
格式如下:

synchronized(锁对象){  } 

也就是将上述代码修改为如下:

package thread;   //线程同步synchronized  public class Demo8 {     public static void main(String[] args) {         Object o = new Object();          Runnable run = new Ticket();         new Thread(run).start();         new Thread(run).start();         new Thread(run).start();     }      static class Ticket implements Runnable{         //总票数         private int count = 10;         //这个锁是公共的         private Object o = new Object();         @Override         public void run() {             //Object o = new Object();    //这里不是同一把锁,所以锁不住                 while (true) {                     synchronized (o) {                         if (count > 0) {                          //卖票                             System.out.println("正在准备卖票");                             try {                             Thread.sleep(1000);                             } catch (InterruptedException e) {                             e.printStackTrace();                             }                             count--;                             //打印线程名和票的余数                             System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);                         }else {                             break;                         }                  }             }         }     } }  

有的读者会问,上面怎么有两个地方有“Object o = new Object(); ”这个语句,在上面的一个叫做公共锁,下面的一个叫做自己的锁,只用公共锁才能锁的住线程,打个比方,你去试衣间试衣服,门上肯定有一把锁,这个可以叫做公共锁,要是是锁住的别人就不会进来,如果试衣服的人要进试衣间,进去之前不是看门上的锁,而是看自己口袋的锁(也就是自己的锁)的话,那么自己的锁肯定一开始是开的,那么来一个人看一下自己的锁(是开的可以进试衣间),就进去了,那不是里面得打起来?
解决方案2 同步方法
将上述代码修改为如下:

package thread;    //线程同步synchronized  public class Demo9 {     public static void main(String[] args) {         Object o = new Object();          Runnable run = new Ticket();         new Thread(run).start();         new Thread(run).start();         new Thread(run).start();     }      static class Ticket implements Runnable{         //总票数         private int count = 10;         @Override         public void run() {              while (true) {                 boolean flag = sale();                 if(!flag){                     break;                 }             }         }         public synchronized boolean sale(){             if (count > 0) {                 //卖票                 System.out.println("正在准备卖票");                 try {                     Thread.sleep(1000);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }                 count--;                 System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);                 return true;             }                 return false;          }     } }  

也很容易理解,就是写方法来限制。
解决方案3 显示锁 Lock 子类 ReentrantLock
将上述代码修改为如下:

package thread;  import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock;   //同步代码块和同步方法都属于隐式锁 //线程同步lock  public class Demo10 {     public static void main(String[] args) {         Object o = new Object();          Runnable run = new Ticket();         new Thread(run).start();         new Thread(run).start();         new Thread(run).start();     }      static class Ticket implements Runnable{         //总票数         private int count = 10;         //参数为true表示公平锁    默认是false 不是公平锁         private Lock l = new ReentrantLock(true);         @Override         public void run() {             while (true) {             //锁死                 l.lock();                     if (count > 0) {                         //卖票                         System.out.println("正在准备卖票");                         try {                             Thread.sleep(1000);                         } catch (InterruptedException e) {                             e.printStackTrace();                         }                         count--;                         System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);                     }else {                         break;                     }                     //开锁                     l.unlock();             }         }     } }  

就是将程序执行的时候将线程锁死,一个线程执行完后又打开,就可以将其理解为生活当中正在的试衣间,一个人来试衣服,试衣服的时候就将门锁住,试完衣服就开锁,就是这样。

多线程通信问题

现在想想有这样一个问题,一个厨师和一个服务员,初始做好一个菜,服务员就端一个菜,顺序和味道不能弄混淆,来看下面的程序:

package thread;   public class Demo12 {     public static void main(String[] args) {         //多线程通信    生产者与消费者问题         Food f = new Food();         new Cook(f).start();         new Waiter(f).start();     }     //厨师     static class Cook extends Thread{         private Food f;          public Cook(Food f) {             this.f = f;         }          @Override         public void run() {             for (int i = 0; i < 100; i++) {                 if(i%2==0){                     f.setNameAndTaste("老干妈小米粥","香辣味");                 }else {                     f.setNameAndTaste("煎饼果子","甜辣味");                 }             }         }     }     //服务员     static class Waiter extends Thread{         private Food f;          public Waiter(Food f) {             this.f = f;         }          @Override         public void run() {             for (int i = 0; i < 100; i++) {                 try {                     Thread.sleep(100);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }                 f.get();             }         }     }     //食物     static class Food{         private String name;         private String taste;         public void setNameAndTaste(String name,String taste){             this.name = name;             try {                 Thread.sleep(100);             } catch (InterruptedException e) {                 e.printStackTrace();             }             this.taste = taste;         }         public void get(){             System.out.println("服务员端走的菜的名称是:" + name + "味道是:" + taste);         }     } }  

施行结果会出现这样的情况:
JAVA 多线程
我们看到了这样的语句:
服务员端走的菜的名称是:煎饼果子味道是:香辣味
服务员端走的菜的名称是:煎饼果子味道是:甜辣味
欸,出现了语句错误,因为两个线程在运行时,可能一个线程运行的时候另一个没反应过来,或者说是反应慢了的原因就导致了这种情况。
解决方法,如下代码:

package thread;   public class Demo12 {     public static void main(String[] args) {         //多线程通信    生产者与消费者问题         Food f = new Food();         new Cook(f).start();         new Waiter(f).start();     }     //厨师     static class Cook extends Thread{         private Food f;          public Cook(Food f) {             this.f = f;         }          @Override         public void run() {             for (int i = 0; i < 100; i++) {                 if(i%2==0){                     f.setNameAndTaste("老干妈小米粥","香辣味");                 }else {                     f.setNameAndTaste("煎饼果子","甜辣味");                 }             }         }     }     //服务员     static class Waiter extends Thread{         private Food f;          public Waiter(Food f) {             this.f = f;         }          @Override         public void run() {             for (int i = 0; i < 100; i++) {                 try {                     Thread.sleep(100);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }                 f.get();             }         }     }     //食物     static class Food{         private String name;         private String taste;         //true表示可以生产         boolean flag = true;         public synchronized void setNameAndTaste(String name,String taste){             if(flag){                 this.name = name;                 try {                     Thread.sleep(100);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }                 this.taste = taste;                 flag = false;                 this.notifyAll();                 try {                     this.wait();                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }          }         public synchronized void get(){             if(!flag){                 System.out.println("服务员端走的菜的名称是:"+name+",味道是:"+taste);                 flag = true;                 this.notifyAll();                 try {                     this.wait();                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }         }     } }  

和前面的线程安全一样添加同步方法一样,将get方法改为同步方法,就可以了。

版权声明:玥玥 发表于 2021-04-07 5:15:10。
转载请注明:JAVA 多线程 | 女黑客导航