多线程
- 1.进程与线程的区别
1.根本区别
进程是作为资源分配的单位,而线程是调度与执行的单位
2.开销区别
每个进程都有独立的代码和数据空间,进程间的切换会有较大的开销。
线程可以看做轻量级的进程,同一类线程共享代码和数据空间。
每个线程有独立运行栈和程序计数器(PC),线程切换的开销小
3.包含关系
一个进程可以包含多个线程,线程是进程的一部分。
- 2.多线程的优缺点
优点:资源利用率更好;程序设计在某些情况下更简单;程序响应更快
缺点:设计更为复杂
- 3.线程的创建
一共有四种方式
1.继承Thread类+子类对象+start() 2.实现Runnable接口 + 静态代理+start() -->推荐 3.juc 包下Callable接口 了解 4.使用线程池创建
3.1 继承Thread类 + 子类对象+start()
创建 Thread 子类的一个实例并重写 run 方法, run方***在调用 start() 方法之后自动被执行
public class TestThread { public static void main(String[] args) { // 创建线程类对象 SomeThread oneThread = new SomeThread(); // 启动线程 oneThread.start(); } } // 创建线程类 class SomeThead extends Thread{ @Override public void run() { //do something here } }
缺点:我们的类已经从一个类继承(如小程序必须继承自 Applet 类),则无法再继承Thread 类,异常只能捕获。
3.2 实现Runnable接口 + 静态代理+start() -->推荐
步骤:
1.创建实现 Runnable 接口的实现类 + 重写 run() 方法
2.创建一个实现类对象
3.利用实现类对象创建Thread类对象
4.启动线程
实现资源共享 接口多实现,类只能单继承 run()方法中定义线程体 如果出现异常只能捕获不能抛出,因为run方法的父类没有抛出异常,子类方法也就不能抛出异常 public class TestThread2 implements Runnable { SomeRunnable r1 = new SomeRunnable(); Thread thread1 = new Thread(r1); thread1.start(); Thread thread2 = new Thread(new SomeRunnable()); thread2.start(); } // 创建Runnable子类 class SomeRunnable implements Runnable{ @Override public void run() { //do something here } }
3.3 实现Callable接口实现(了解)
步骤:
创建实现 Callable 接口的实现类 + 重写 call() 方法
创建一个实现类对象
由 Callable 创建一个 FutureTask 对象
由 FutureTask 创建一个 Thread 对象
启动线程
public class CallAbleTest { public static void main(String[] args) throws Exception{ MyCallable callable = new MyCallable(); // 将Callable包装成FutureTask, FutureTask也是一种Runnable FutureTask<Integer> futureTask = new FutureTask<>(callable); // 将FutureTask包装成Thread new Thread(futureTask).start(); System.out.println(futureTask.isDone()); System.out.println(futureTask.get()); } } class MyCallable implements Callable<Integer>{ @Override public Integer call() throws Exception {int sum = 0; for (int i = 0; i <= 100000; i++) { sum += i; } return sum; } }
call方法可以抛出异常
call方法可以得到返回值
3.4 使用线程池创建
使用 Executors 类中的newFixedThreadPool(int num) 方法创建一个线程数量为num的线程池
调用线程池中的 execute() 方法执行由实现Runnable 接口创建的线程;调用 submit() 方法执行由实现 Callable 接口创建的线程
调用线程池中的 shutdown() 方法关闭线程池
public class ThreadPoolTest {
public static void main(String[]args)throws Exception {
Thread.currentThread().setName("主线程");
System.out.println(Thread.currentThread().getName() + ": 输出的结果" );
// 通过线程池工厂创建线程数量为2的线程池
ExecutorService service =Executors.newFixedThreadPool(2);
//执行线程,execute()适用于实现Runnable接口创建的线程
service.execute(new MyThread());
//submit()适用于实现Callable接口创建的线程
Future<integer> task =service.submit(new MyCallable()); </integer>
System.out.println(task.get());
// 关闭线程池
service.shutdown();
}
}
- 4.线程的状态
线程的五种状态
- 新建状态
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
- 就绪状态
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
- 运行状态
如果就绪状态的线程获取 CPU 资源,就可以执行run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
- 阻塞状态
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。
可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出
了 I/O请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪态。
- 死亡状态
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
如何控制线程进入终止状态: 1.正常运行结束 2.stop ,destroy() 过时 3.通过标识判断 如何进入就绪状态: 1.start 2.线程切换 3.yield 礼让线程 4.阻塞解除 如何进入阻塞状态 1.sleep 2.join 插队 3.wait 4.IO操作 注意: 一个线程如果进入阻塞状态,阻塞解除没有办法直接恢复到运行,会直接恢复就绪。 一个线程如果一旦终止,没有办法恢复。
4.1 Sleep方法:抱着资源睡觉
sleep() 方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态。当一个执行到sleep方法,当前线程就会休眠指定时间,在休眠过程中,让出cpu的资源。
Thread.sleep(1000);//里面放一个int型的整数 表示休眠多少ms 1s=1000ms
4.2 yield方法:礼让线程
yield() 方法和 sleep() 方法类似,也不会释放“锁标志”,区别在于,它没有参数,即 yield() 方法只是使当前线程重新回到可执行状态,所以执行yield() 的线程有可能在进入到可执行状态后马上又被执行。让出CPU的使用权,从运行态直接进入就绪态。让CPU重新挑选哪一个线程进入运行状态
public class ThreadStateDemo08 implements Runnable{ public static void main(String[] args) { new Thread(new ThreadStateDemo08(),"A").start(); new Thread(new ThreadStateDemo08(),"B").start(); } @Override public void run() { System.out.println(Thread.currentThread().getName()+"开始了"); Thread.yield(); //礼让 System.out.println(Thread.currentThread().getName()+"结束了"); } }
4.3 join方法
join方法使只有当前调用的该方法线程结束后,其他线程才会继续往后执行,才会继续往后执行。
public class JoinDemo09 { public static void main(String[] args) { new Thread(new Father()).start(); } } class Father implements Runnable{ @Override public void run() { System.out.println("想抽烟了... 叫儿子去买烟.."); System.out.println("给儿子100块钱... 煊赫门"); //创建儿子线程 Thread th = new Thread(new Son()); th.start(); //插队 try { th.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("接过烟,抽一***过来了"); System.out.println("把零钱给儿子..."); } } class Son implements Runnable{ @Override public void run() { System.out.println("接过钱去买烟...."); System.out.println("路边看到一个游戏厅,进去玩10s钟"); System.out.println("赶紧去买烟..."); System.out.println("把烟给老爸..."); } }
如果没有加join方法,父亲可能还没等儿子把烟递回来就抽烟了,不符合逻辑
5.线程的基本信息
判断线程状态Thread.State getState() 获取线程状态 返回值是一个枚举类型
线程的优先级
线程的优先级用数字表示,范围是1-10。如果没有为线程赋予优先级,则线程的缺省值是5
void setPriority(int newPriority);
注意:优先级低只是意味着获得调度的概率低。并不是绝对先调用优先级高后调用优先级低的线程。
6.线程的同步与死锁
线程安全问题:
当多线程共享同一份资源的时候,才有可能存在线程不安全问题 同步锁: synchronized 同步方法 : 同步静态方法 同步成员方法 同步块{} : synchronized(this|类名.class|资源){ 代码段... } {} -> 排队执行的代码块 this|类名.class|资源 : 线程要求的执行{}中代码的执行条件 注意: 同步的代码范围如果 太大,效率低,如果太小,锁不住 锁不变的内容(this|资源..|类..)
6.1 同步类
类的class对象,相当于把整个类锁住了,效率相对较低
public class SynDemo11 implements Runnable{ int tickets = 100; @Override public void run() { //A B C while(true){ //停止循环的条件 //A B C synchronized (SynDemo11.class)//锁住这个类的所有资源 如果有另一个类的对象想拿,必须得等锁释放 { if(tickets<=0){ break; } //A B C //买票 System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets--); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { //资源 SynDemo11 web = new SynDemo11(); //创建线程 Thread th1 = new Thread(web,"张绍杰"); Thread th2 = new Thread(web,"何志豪"); Thread th3 = new Thread(web,"阮志志"); //线程的开启 th1.start(); th2.start(); th3.start(); } }
6.2同步对象this
this,相当于把调用成员方法的对象,相当于锁住了整个对象,整个对象的所有 资源都锁住了,如果只想锁住某一个资源,只锁住这个资源就可以 -->推荐锁资源
public class SynDemo12 implements Runnable{ int tickets = 100; @Override public void run() { //A B C while(true){ //停止循环的条件 //A B C synchronized (this){ //当前调用成员方法的对象 if(tickets<=0){ break; } //A B C //买票 System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets--); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { //资源 SynDemo12 web = new SynDemo12(); //创建线程 Thread th1 = new Thread(web,"张绍杰"); Thread th2 = new Thread(web,"何志豪"); Thread th3 = new Thread(web,"阮志志"); //线程的开启 th1.start(); th2.start(); th3.start(); } }
6.3同步资源
资源,一定要锁不变的东西,引用数据类型 -->自定义的 引用数据类型的地址
如果不是自定义的引用数据类型,可能会出现线程不安全
public class SynDemo13 implements Runnable{ //资源 100张 票 Ticket tickets = new Ticket(); @Override public void run() { //A B C while(true){ //停止循环的条件 //A B C synchronized (tickets){ //当前调用成员方法的对象 if(tickets.num<=0){ break; } //A B C //买票 System.out.println(Thread.currentThread().getName()+"正在购买第"+tickets.num--); try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { //资源 SynDemo13 web = new SynDemo13(); //创建线程 Thread th1 = new Thread(web,"张绍杰"); Thread th2 = new Thread(web,"何志豪"); Thread th3 = new Thread(web,"阮志志"); //线程的开启 th1.start(); th2.start(); th3.start(); } } //票 class Ticket{ int num = 100; }
- 7.死锁
死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁线程。
如何解决死锁问题:
- 往往是程序逻辑的问题。需要修改程序逻辑。
- 尽量不要同时持有两个对象锁。