JavaSE之多线程
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JavaSE之多线程相关的知识,希望对你有一定的参考价值。
今天打算重新学习一遍多线程,通过条理的的梳理,更加巩固基础知识。
谈起多线程,我们需要分清楚一些概念,什么是程序、进程和线程?
- 程序(program):是为了完成特定任务,用某种语言编写的一组指令的集合,指的是一段静态的代码,静态对象
- 进程(process):是程序的一段执行过程,或者是正在运行的一个程序。动态过程:有它自身的产生,存在和消亡的过程
- 线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径
线程与进程的关系:
这里一个进程可以分为单线程进程和多线程进程,它们之间的关系如图所示:
什么时候需要多线程?
- 程序需要同时执行两个或多个任务时
- 程序需要实现一些需要等待的任务时
- 需要一些后台运行的程序时
多线程程序的优点:
- 提高程序相应速度,增强用户体验
- 提高CPU的利用率
- 改善程序结构,将既长又复杂的进程分成多个线程,有助于修改和理解
如何创建线程呢,创建线程的方法有两种:
- 一是将该类声明为Thread的子类,该子类要重写Thread的run()方法
- 二是将该类声明实现Runnable接口,然后实现接口的run()方法
1 //1.创建一个继承于Thread类的子类 2 class SubThread extends Thread { 3 4 // 2.重写Thread类的run()方法,此方法内为线程要执行的任务 5 public void run() { 6 for (int i = 0; i < 100; i++) { 7 System.out.println(Thread.currentThread().getName() + ":" + i); 8 } 9 } 10 } 11 12 // 3.实现Runnable接口 13 class InstRunnable implements Runnable { 14 15 // 4. 实现Runnable接口的run()方法 16 public void run() { 17 for (int i = 0; i < 100; i++) { 18 System.out.println(Thread.currentThread().getName() + ":" + i); 19 } 20 } 21 } 22 23 public class ThreadMain { 24 25 public static void main(String[] args) { 26 27 // 5.实例化一个线程实例 28 SubThread st = new SubThread(); 29 30 // 6.调用start()方法启动线程,然后调用相应的run()方法 31 st.start(); 32 33 // 线程只能执行一次start(),多次执行报错 34 // st.start(); 35 36 // run()方法不能直接启动线程,调用run()方法无变化 37 // st.run(); 38 39 // 7.实例化InstRunnable类 40 InstRunnable ir = new InstRunnable(); 41 42 // 8.启动线程,并调用run()方法 43 new Thread(ir).start(); 44 } 45 46 }
实现Runnable接口创建线程的优点:
- Thread本质上也是实现Runnable接口 class Thread implements Runnable{……}
- 使用Runnable接口可以避免Java的单继承的局限性
- 若多个线程操作同一份数据,只需实现一次,然后多次生成Thread实例即可,所以实现更适合
-
1 class Window implements Runnable { 2 int ticket = 100; 3 public void run() { 4 while(true) { 5 if(ticket>0) { 6 System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket--); 7 }else { 8 break; 9 } 10 } 11 } 12 } 13 14 public class TestWindow1 { 15 16 public static void main(String[] args) { 17 Window w = new Window(); 18 19 Thread w1 = new Thread(w); 20 Thread w2 = new Thread(w); 21 Thread w3 = new Thread(w); 22 23 w1.setName("窗口1"); 24 w2.setName("窗口2"); 25 w3.setName("窗口3"); 26 27 w1.start(); 28 w2.start(); 29 w3.start(); 30 } 31 32 }
Thread类常用的几个方法:
- start():启动线程并执行相应的run()方法
- run():子线程要执行的代码放入run()方法中
- currentThread():静态方法,调取当前的线程
- getName():获取此线程的名字
- setName():设置此线程的名字
- yield():调用此方法的线程释放当前CPU的执行权
- join():在A线程中调用B线程的join()方法,表示执行到B.join()方法时,A停止执行,直到B执行完毕,A继续执行
- sleep(long x):显式的让线程睡眠x毫秒
- getPriority():获取线程的优先级
- setPriority(int n):设置线程的优先级(n的范围是1到10)
1 package cn.lyog.javase; 2 3 class SubThread extends Thread { 4 5 // 子线程要执行的代码放入run()方法中 6 public void run() { 7 for (int i = 0; i < 100; i++) { 8 try { 9 // 静态方法,调取当前的线程 10 Thread.currentThread(); 11 12 // 显式的让线程睡眠100毫秒 13 Thread.sleep(100); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 18 // getName()用来获取此线程的名字 19 System.out.println(Thread.currentThread().getName() + ":" + i); 20 } 21 } 22 } 23 24 public class ThreadMain { 25 26 public static void main(String[] args) { 27 28 SubThread st = new SubThread(); 29 30 // 设置st线程的名字 31 st.setName("子线程"); 32 33 // 启动线程并执行相应的run()方法 34 st.start(); 35 36 // 设置此线程的名字 37 Thread.currentThread().setName("主线程"); 38 39 /* 40 * 设置线程的优先级(n的范围是1到10) 41 * MAX_PRIORITY的值为10 42 * NORM_PRIORITY的值为5 43 * MIN_PRIORITY的值为1 44 */ 45 Thread.currentThread().setPriority(Thread.MAX_PRIORITY); 46 47 for (int i = 0; i < 100; i++) { 48 49 // 获取线程的名字 50 System.out.println(Thread.currentThread().getName() + ":" + i); 51 52 // 获取线程的优先级 53 System.out.println(Thread.currentThread().getPriority() + ":" + i); 54 55 if (i > 10) { 56 try { 57 // 当i>10时,执行st,直到st执行完毕,继续执行main 58 st.join(10); 59 } catch (InterruptedException e) { 60 e.printStackTrace(); 61 } 62 } 63 } 64 } 65 }
线程分为两类:守护线程和用户线程
- 守护线程与用户线程本质上是相同的,区别区别就是判断JVM何时离开。
- 守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)方法,可以把用户线程变成守护线程。
- Java垃圾回收就是一个守护线程。
- 若JVM中全为守护线程,意味着用户线程都没了,所以当前JVM退出。
线程的生命周期:
- 新建:当一个Thread类或其子类被声明创建时
- 就绪:具备CPU的执行资格,但没有CPU执行权
- 运行:既具备CPU的执行资格,同时具备着CPU的执行权
- 阻塞:释放了CPU执行权,同时释放了CPU执行资格
- 消亡:当线程完成全部工作或被提前终止
线程安全问题的原因:由于一个线程在操作共享数据的过程中,未执行完毕的情况下,另外一个线程参与进来,导致共享数据存在安全问题。
线程安全问题的解决方案:让一个线程执行完毕后,其他线程才能参与进来。(线程同步机制)
- 同步代码块
- 同步方法
-
1 class Windows implements Runnable { 2 int ticket = 100; 3 Object obj = new Object(); 4 5 @Override 6 public void run() { 7 // 同步代码块 8 synchronized (obj) { 9 // 操作共享数据ticket的代码 10 } 11 } 12 13 } 14 15 class Windows1 implements Runnable { 16 int ticket = 100; 17 18 public synchronized void show() { 19 // 操作共享数据ticket的代码 20 } 21 22 @Override 23 public void run() { 24 // 同步方法 25 show(); 26 } 27 28 }
释放锁的操作有:
- 同步方法、同步代码块执行结束
- 同步方法、同步代码块中遇到break或return终止执行
- 同步方法、同步代码块中遇到Error或Exception异常结束
- 同步方法、同步代码块中执行wait()方法释放锁
不会释放锁的操作有:
- sleep()方法
- yield()方法
线程的死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
解决方法:
- 专门的算法、原则
- 尽量减少同步资源的定义
1 package cn.lyog.javase; 2 3 public class TestDeadLock { 4 static StringBuffer sb1 = new StringBuffer(); 5 static StringBuffer sb2 = new StringBuffer(); 6 7 public static void main(String[] args) { 8 new Thread() { 9 public void run() { 10 synchronized (sb1) { 11 try { 12 Thread.currentThread(); 13 Thread.sleep(10); 14 } catch (InterruptedException e) { 15 e.printStackTrace(); 16 } 17 sb1.append("A"); 18 synchronized (sb2) { 19 sb2.append("B"); 20 System.out.println(sb1); 21 System.out.println(sb2); 22 } 23 } 24 } 25 }.start(); 26 new Thread() { 27 public void run() { 28 synchronized (sb2) { 29 try { 30 Thread.currentThread(); 31 Thread.sleep(100); 32 } catch (InterruptedException e) { 33 e.printStackTrace(); 34 } 35 sb1.append("C"); 36 synchronized (sb1) { 37 sb2.append("D"); 38 System.out.println(sb1); 39 System.out.println(sb2); 40 } 41 } 42 } 43 }.start(); 44 } 45 46 }
线程通讯,在java.lang.Object包下面定义了三个方法:wait()、notify()和notifyAll(),它们只能在synchronized方法或者synchronized代码块中才能执行
- wait():令当前线程挂起放弃CPU、同步资源,使别的线程可以访问并修改同步资源,而当前线程排队等候在此对资源的访问
- notify():唤醒正在排队等待同步资源的线程中优先级最高的结束等待
- notifyAll():唤醒所有正在排队等待同步资源的线程结束等待
1 // 线程通讯之交替打印数字 2 class PrintNum implements Runnable { 3 4 int num = 1; 5 6 @Override 7 public void run() { 8 while (true) { 9 synchronized (this) { 10 notify(); 11 if (num <= 100) { 12 System.out.println(Thread.currentThread().getName() + ":" + num); 13 num++; 14 } else { 15 break; 16 } 17 18 try { 19 wait(); 20 } catch (InterruptedException e) { 21 e.printStackTrace(); 22 } 23 } 24 25 } 26 27 } 28 29 } 30 31 public class TestCommunication { 32 33 public static void main(String[] args) { 34 PrintNum pn = new PrintNum(); 35 36 Thread t1 = new Thread(pn); 37 Thread t2 = new Thread(pn); 38 39 t1.setName("甲"); 40 t2.setName("乙"); 41 42 t1.start(); 43 t2.start(); 44 45 } 46 }
以上是关于JavaSE之多线程的主要内容,如果未能解决你的问题,请参考以下文章