java-API之多线程
初识多线程
进程:正在运行的程序。也就是代表了程序锁占用的内存区域。一个软件的运行,必须启动至少一个进程,也可以多个。
单线程:一个进程如果只启动了一个线程在干活,这个程序就是单线程程序
多线程:一个进程如果启动了多个线程在干活,这个程序就是多线程程序,执行效率高。
进程和线程的关系:一个软件的运行以来至少一个进程,进程的运行也是至少依赖一个线程,线程是调度运算的最小单位。(面试题)
并发:是指同一时刻,多个程序在抢占共享资源,同时抢占CPU来执行程序
并行:是指同一时刻,有多个CPU在干活,但是一个CPU只干一件事
线程的状态:新建状态、可运行状态、运行状态、终止状态、阻塞状态。
多线程编程
构造方法
- Thread() 分配新的 Thread 对象。
- Thread(Runnable target) 分配新的 Thread 对象。
- Thread(Runnable target, String name) 分配新的 Thread 对象。
- Thread(String name) 分配新的 Thread 对象。
常用方法
- static Thread currentThread() 返回对当前正在执行的线程对象的引用。
- long getId() 返回该线程的标识符。
- String getName() 返回该线程的名称。
- void run() 如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。
- static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
- void setName(String name) 改变线程名称,使之与参数 name 相同。
- void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
- void stop() 已过时。 终止线程
实现多线程方法一——继承Thread类
1 public class Test2_Thread { 2 3 public static void main(String[] args) { 4 5 // 创建线程 6 MyThread thread = new MyThread("线程1"); 7 // 启动线程 8 // start()从新建状态变成可执行状态,自动调用run方法 9 thread.start(); 10 11 MyThread thread2 = new MyThread("线程2"); 12 thread2.start(); 13 14 } 15 } 16 17 18 // 创建多线程类 继承于 Thread 类 19 class MyThread extends Thread { 20 public MyThread() { 21 super(); 22 } 23 24 public MyThread(String name) { 25 super(name); 26 } 27 28 // 把多线程业务写到run方法内 29 @Override 30 public void run() { 31 for (int i = 0; i < 10; i++) { 32 // getName() 获取当前线程的名称 33 System.out.println(getName()+ "--"+ i); 34 } 35 } 36 }
实现多线程方法一——实现Runnable接口
public class Test3_Runnable { public static void main(String[] args) { // 创建多个线程对象执行任务 MyRunnable runable = new MyRunnable(); // 把接口类型的对象,转成Thread类型,因为想调用Thread的start()启动线程——Thread(Runable target) Thread thread = new Thread(runable,"线程1"); // 调用start()启动线程 thread.start(); Thread thread2 = new Thread(runable,"线程2"); thread2.start(); } } // 创建实现类,实现Runnable接口 class MyRunnable implements Runnable { // 把多线程业务放在run方法内 @Override public void run() { for (int i = 0; i < 10; i++) { // Thread.currentThread()获取当前正在执行任务的线程对象 System.out.println(Thread.currentThread().getName()+"=="+i); } } }
Thread和Runable实现多线的比较
方式 | 优点 | 缺点 |
Thread | 编写简单,如果需要访问当前线程,则无需使用Thread.currentThread()方法,直接使用this即可获得当前线程。 | 线程类已经继承了Thread类,所以不能再继承其他父类 |
Runnable |
线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。 在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。 |
编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法。 |
多线程售票案例
1 public class Test1_MyTicket { 2 public static void main(String[] args) { 3 Runnable mt = new MyTicket(); 4 5 // 创建线程 6 Thread t1 = new Thread(mt,"窗口1"); 7 // 启动线程 8 t1.start(); 9 Thread t2 = new Thread(mt,"窗口2"); 10 t2.start(); 11 Thread t3 = new Thread(mt,"窗口3"); 12 t3.start(); 13 Thread t4 = new Thread(mt,"窗口4"); 14 t4.start(); 15 } 16 } 17 18 19 class MyTicket implements Runnable { 20 int count = 100; // 记录当前票的数量 21 22 @Override 23 public void run() { 24 while(true) { 25 if(count > 0) { 26 try { 27 Thread.sleep(100); //让线程休息一会 28 // Thread.currentThread().getName() 获取当前线程名称 29 System.out.println(Thread.currentThread().getName() + "==" + count--); 30 } catch (InterruptedException e) { 31 e.printStackTrace(); 32 } 33 }else { 34 break; 35 } 36 } 37 38 } 39 }
经过测试,我们发现以上代码出现以下问题:
- 问题1: 重复卖票,同一张票卖给了多人
- 问题2:超卖,也就是票数出现了负数
同步锁解决方案
异步:多个线程可以同时操作共享数据,互相不需要等待。效率较高,但是数据存在安全性问题。
同步:同一时刻只能有一个线程操作共享资源,相当于独占资源(其它线程必须等待其完成才能继续)。牺牲了程序的执行效率,提高了资源的安全性。
同步锁synchronized使用场景:将那些有多线程并发数据存在安全隐患的代码用同步包起来。它可以修饰代码块,也可以修饰方法,一般情况下使用代码块;如果方法里都是同步代码,可以把synchronized直接写在方法上。在保证解决数据安全的前提下,锁定的范围越小,程序的效率越高。多个线程,必须使用同一个锁(也就是关键字synchronized后面对象参数所指的对象,多个线程要使用同一对象,这样锁住共享资源,否则仍有安全隐患)。代码演示如下:
1 public class Test2_Synchronized { 2 public static void main(String[] args) { 3 Runnable run = new MyTicket2(); 4 5 Thread t1 = new Thread(run, "窗口1"); 6 t1.start(); 7 Thread t2 = new Thread(run, "窗口2"); 8 t2.start(); 9 Thread t3 = new Thread(run, "窗口3"); 10 t3.start(); 11 Thread t4 = new Thread(run, "窗口4"); 12 t4.start(); 13 } 14 } 15 16 class MyTicket2 implements Runnable { 17 int count = 100; 18 19 @Override 20 public void run() { 21 while (true) { 22 // synchronized同步代码块,把所有的问题的代码包起来,来实现同步代码 23 // 必须是多个线程使用的是同一个对象 24 25 synchronized (this) { // synchronized后面参数中的对象,必须是多个线程使用的是同一个对象 26 if (count > 0) { 27 try { 28 Thread.sleep(10); 29 } catch (InterruptedException e) { 30 e.printStackTrace(); 31 } 32 System.out.println(Thread.currentThread().getName() + "===" + count--); 33 34 } else { 35 break; 36 } 37 } 38 } 39 } 40 }