Java基础学习——多线程
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java基础学习——多线程相关的知识,希望对你有一定的参考价值。
一.进程与线程
进程:一个计算机程序的运行实例,包含了需要执行的指令;有自己的独立地址空间,包含程序内容和数据;不同进程的地址空间是互相隔离的;进程拥有各种资源和状态信息,包括打开的文件、子进程和信号处理。一个进程可以包含1-n个线程。
线程:表示程序的执行流程,是CPU调度执行的基本单位;线程有自己的程序计数器、寄存器、堆栈和帧。同一进程中的线程共用相同的地址空间,同时共享进进程锁拥有的内存和其他资源。
二.多线程带来的好处与坏处
好处
- 资源利用率更好
-
程序设计在某些情况下更简单
- 程序响应更快
坏处
- 对线程进行管理要求额外的 CPU开销。线程的使用会给系统带来上下文切换的额外负担
- 可能引起线程安全问题和死锁现象
- 降低了一个进程里面的线程的执行频率
总的来说,在现在硬件配置充裕的情况下,肯定是好处大于坏处的。
三.如何创建多线程
方式一 : 自定义一个类继承Thread类。
1. 重写Thread类的run方法,把自定义线程的任务代码写在run方法上。
2. 创建Thread的子类对象,并且调用start方法启动一个线程。
1 //自定义线程 2 class MyThread extends Thread { 3 @Override 4 public void run() { 5 for (int i = 0; i <= 20; i++) { 6 System.out.println("自定义线程:" + i); 7 } 8 } 9 } 10 11 public class CreateThread { 12 public static void main(String[] args) { 13 14 MyThread thread = new MyThread(); 15 thread.start(); 16 for (int i = 0; i <= 20; i++) { 17 System.out.println("main线程:" + i); 18 } 19 } 20 }
方式二:自定义一个类实现Runnable接口。
1. 实现Runnable接口 的run方法,把自定义线程的任务定义在run方法上。
2. 创建Runnable实现类对象。
3. 创建Thread类 的对象,并且把Runnable实现类的对象作为实参传递。
4. 调用Thread对象 的start方法开启一个线程。
注意:千万不要直接调用run方法,调用start方法的时候线程就会开启,线程一旦开启就会执行run方法中代码,如果直接调用
run方法,那么就 相当于调用了一个普通的方法而已。
1 //实现runnable接口 2 class MyThread implements Runnable { 3 4 @Override 5 public void run() { 6 for (int i = 0; i <= 20; i++) { 7 System.out.println("自定义线程:" + i); 8 } 9 } 10 } 11 12 public class CreateThread { 13 public static void main(String[] args) { 14 15 MyThread mt = new MyThread(); 16 Thread thread = new Thread(mt); 17 thread.start(); 18 for (int i = 0; i <= 20; i++) { 19 System.out.println("main线程:" + i); 20 } 21 } 22 }
问题1: 请问Runnable实现类的对象是线程对象吗?
Runnable实现类的对象并 不是一个线程对象,只不过是实现了Runnable接口 的对象而已。
只有是Thread或者是Thread的子类才是线程 对象。
问题2: 为什么要把Runnable实现类的对象作为实参传递给Thread对象呢?作用是什么?
作用就是把Runnable实现类的对象的run方法作为了线程的任务代码去执行了
四.线程的生命周期
五.线程安全问题
一个需求:模拟三个售票窗口售出50张票
1 class MyTicket implements Runnable { 2 int tickets = 50; 3 public void run() { 4 while (true) { 5 if (tickets > 0) { 6 try { 7 Thread.sleep(100); 8 } catch (InterruptedException e) { 9 e.printStackTrace(); 10 } 11 System.out.println(Thread.currentThread().getName() + "窗口@销售:" 12 + tickets + "号票"); 13 tickets--; 14 15 } else { 16 System.out.println("票已卖完。。。"); 17 break; 18 } 19 } 20 } 21 } 22 public class Demo { 23 public static void main(String[] args) { 24 MyTicket mt = new MyTicket(); 25 Thread t1 = new Thread(mt); 26 Thread t2 = new Thread(mt); 27 Thread t3 = new Thread(mt); 28 29 t1.start(); 30 t2.start(); 31 t3.start(); 32 } 33 }
运行上述代码你会发现某些时候在同一张票卖了几次,票以显示卖完的情况下,仍然还有窗口售出了票,这是不符合常理和需求的,这时候也就出现了线程安全的问题。
出现线程安全的原因:
-
存在两个或者两个以上 的线程对象共享同一个资源。
-
多线程操作共享资源的代码 有多句。
线程安全问题的解决方案:
方式一: 可以使用同步代码块去解决。
格式:
synchronized(锁对象){
需要被同步的代码
}
同步代码块要注意的事项:
- 锁对象可以是任意的一个对象。
- 一个线程在同步代码块中sleep了,并不会释放锁对象。
- 如果不存在着线程安全问题,千万不要使用同步代码块,因为会降低效率。
- 锁对象必须是多线程共享的一个资源,否则锁不住。
一个需求:模拟三个售票窗口售出50张票
1 class SaleTicket extends Thread 2 { 3 public static int num=50; 4 5 public SaleTicket(String name) 6 { 7 super(name); 8 } 9 @Override 10 public void run() 11 { 12 while(true) 13 { //同步代码块锁 14 synchronized("锁") 15 { 16 if(num>0) 17 { 18 System.out.println(Thread.currentThread().getName()+"售出第"+num+"张票"); 19 num--; 20 }else{ 21 System.out.println("售罄了..."); 22 break; 23 } 24 } 25 26 } 27 } 28 29 30 } 31 public class TheradSafe { 32 33 public static void main(String[] args) 34 { 35 new SaleTicket("窗口1").start(); 36 new SaleTicket("窗口2").start(); 37 new SaleTicket("窗口3").start(); 38 39 } 40 }
方式二:同步函数 : 同步函数就是使用synchronized修饰一个函数。
同步函数要注意的事项 :
- 如果是一个非静态的同步函数的锁 对象是this对象,如果是静态的同步函数的锁 对象是当前函数所属的类的字节码文件(class对象)。
- 同步函数的锁对象是固定的,不能由你来指定 的。
一个需求:银行账户内5000元,夫妻开始取钱比赛,一次只允许取一千元。
1 class BankThread extends Thread{ 2 3 static int count = 5000; 4 5 public BankThread(String name){ 6 super(name); 7 } 8 9 @Override // 10 public synchronized void run() { 11 while(true){ 12 synchronized ("锁") { 13 if(count>0){ 14 System.out.println(Thread.currentThread().getName()+"取走了1000块,还剩余"+(count-1000)+"元"); 15 count= count - 1000; 16 }else{ 17 System.out.println("取光了..."); 18 break; 19 } 20 } 21 } 22 } 23 } 24 25 public class TheradSafe { 26 27 public static void main(String[] args) 28 { 29 30 new BankThread("老公").start(); 31 new BankThread("老婆").start(); 32 } 33 }
六.死锁现象
经典的“哲学家就餐问题”,5个哲学家吃中餐,坐在圆卓子旁。每人有5根筷子(不是5双),每两个人中间放一根,哲学家时而思考,时而进餐。每个人都需要一双筷子才能吃到东西,吃完后将筷子放回原处继续思考,如果每个人都立刻抓住自己左边的筷子,然后等待右边的筷子空出来,同时又不放下已经拿到的筷子,这样每个人都无法得到1双筷子,无法吃饭都会饿死,这种情况就会产生死锁:每个人都拥有其他人需要的资源,同时又等待其他人拥有的资源,并且每个人在获得所有需要的资源之前都不会放弃已经拥有的资源。
当多个线程完成功能需要同时获取多个共享资源的时候可能会导致死锁。
1:两个任务以相反的顺序申请两个锁,死锁就可能出现
2:线程T1获得锁L1,线程T2获得锁L2,然后T1申请获得锁L2,同时T2申请获得锁L1,此时两个线程将要永久阻塞,死锁出现
如果一个类可能发生死锁,那么并不意味着每次都会发生死锁,只是表示有可能。
要避免程序中出现死锁。
1 public class DeadLock { 2 public static void main(String[] args) { 3 new Thread(new Runnable() { // 创建线程, 代表中国人 4 public void run() { 5 synchronized ("刀叉") { // 中国人拿到了刀叉 6 System.out.println(Thread.currentThread().getName() 7 + ": 你不给我筷子, 我就不给你刀叉"); 8 try { 9 Thread.sleep(10); 10 } catch (InterruptedException e) { 11 e.printStackTrace(); 12 } 13 synchronized ("筷子") { 14 System.out.println(Thread.currentThread() 15 .getName() + ": 给你刀叉"); 16 } 17 } 18 } 19 }, "中国人").start(); 20 new Thread(new Runnable() { // 美国人 21 public void run() { 22 synchronized ("筷子") { // 美国人拿到了筷子 23 System.out.println(Thread.currentThread().getName() 24 + ": 你先给我刀叉, 我再给你筷子"); 25 try { 26 Thread.sleep(10); 27 } catch (InterruptedException e) { 28 e.printStackTrace(); 29 } 30 synchronized ("刀叉") { 31 System.out.println(Thread.currentThread() 32 .getName() + ": 好吧, 把筷子给你."); 33 } 34 } 35 } 36 }, "美国人").start(); 37 } 38 }
7.线程间的通讯
线程通讯: 一个线程完成了自己的任务时,要通知另外一个线程去完成另外一个任务.
wait(): 等待 如果线程执行了wait方法,那么该线程会进入等待的状态,等待状态下的线程必须要被其他线程调用notify方法才能唤醒。
notify(): 唤醒 唤醒线程池等待线程其中的一个。
notifyAll() : 唤醒线程池所有等待 线程。
wait与notify方法要注意的事项:
1. wait方法与notify方法是属于Object对象 的。
2. wait方法与notify方法必须要在同步代码块或者是同步函数中才能 使用。
3. wait方法与notify方法必需要由锁对象调用
需求:生产者与消费者:生产一个产品后,消费者消费一个,再生产
1 //产品类 2 class Product 3 { 4 String name; //名字 5 6 double price; //价格 7 8 boolean flag = false; //产品是否生产完毕的标识,默认情况是没有生产完成。 9 10 } 11 12 //生产者 13 class Producer extends Thread 14 { 15 Product p ; //产品 16 17 public Producer(Product p) 18 { 19 this.p = p ; 20 } 21 22 @Override 23 public void run() 24 { 25 int i = 0 ; 26 while(true) 27 { 28 synchronized (p) 29 { 30 if(p.flag==false) 31 { 32 if(i%2==0) 33 { 34 p.name = "苹果"; 35 p.price = 6.5; 36 }else 37 { 38 p.name="香蕉"; 39 p.price = 2.0; 40 } 41 System.out.println("生产者生产出了:"+ p.name+" 价格是:"+ p.price); 42 p.flag = true; 43 i++; 44 p.notifyAll(); //唤醒消费者去消费 45 } 46 else 47 { 48 //已经生产 完毕,等待消费者先去消费 49 try 50 { 51 p.wait(); //生产者等待 52 } catch (InterruptedException e) 53 { 54 e.printStackTrace(); 55 } 56 } 57 58 } 59 } 60 } 61 } 62 63 //消费者 64 class Customer extends Thread 65 { 66 Product p; 67 public Customer(Product p) 68 { 69 this.p = p; 70 } 71 72 73 @Override 74 public void run() 75 { 76 while(true) 77 { 78 synchronized (p) 79 { 80 if(p.flag==true) 81 { //产品已经生产完毕 82 System.out.println("消费者消费了"+p.name+" 价格:"+ p.price); 83 p.flag = false; 84 p.notifyAll(); // 唤醒生产者去生产 85 }else 86 { 87 //产品还没有生产,应该 等待生产者先生产。 88 try 89 { 90 p.wait(); //消费者也等待了... 91 } catch (InterruptedException e) 92 { 93 e.printStackTrace(); 94 } 95 } 96 } 97 } 98 } 99 } 100 101 public class ThreadWait { 102 public static void main(String[] args) 103 { 104 Product p = new Product(); //产品 105 //创建生产对象 106 Producer producer = new Producer(p); 107 //创建消费者 108 Customer customer = new Customer(p); 109 //调用start方法开启线程 110 producer.start(); 111 customer.start(); 112 113 } 114 115 }
8.守护(后台)线程
特点:当所有的非后台线程结束时,程序也就终止了同时还会杀死进程中的所有后台线程,也就是说,只要有非后台线程还在运行,程序就不会终止。
需求: 模拟QQ下载更新包。
1 public class DaemonThread extends Thread { 2 3 public DaemonThread(String name){ 4 super(name); 5 } 6 7 @Override 8 public void run() { 9 for(int i = 1 ; i<=100 ; i++){ 10 System.out.println("更新包目前下载"+i+"%"); 11 if(i==100){ 12 System.out.println("更新包下载完毕,准备安装.."); 13 } 14 try { 15 Thread.sleep(100); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 } 20 } 21 22 public static void main(String[] args) { 23 DaemonThread d = new DaemonThread("后台线程"); 24 d.setDaemon(true); //setDaemon() 设置线程是否为守护线程,true为守护线程, false为非守护线程。 25 System.out.println("是守护线程吗?"+ d.isDaemon()); //判断线程是否为守护线程。 26 d.start(); 27 28 for(int i = 1 ; i<=100 ; i++){ 29 System.out.println(Thread.currentThread().getName()+":"+i); 30 } 31 } 32 }
以上是关于Java基础学习——多线程的主要内容,如果未能解决你的问题,请参考以下文章