Java—— 多线程 第一期
什么是多线程
线程是应用软件中互相独立,可以同时运行的功能
有了多线程,我们就可以让程序同时做多件事情
多线程的作用
提高效率
多线程的应用场景
想让多个事情同时运行就需要用到多线程
比如:软件中的耗时操作、所有的聊天软件、所有的服务器
并发和并行
并发:在同一时刻,有多个指令在单个CPU上交替执行
并行:在同一时刻,有多个指令在多个CPU上同时执行
多线程的实现方式
继承Thread类的方式进行实现
步骤:
1.自己定义一个类继承线程Thread
2.重写run方法
3.创建子类的对象,并启动线程
代码演示
哪条线程抢占到CPU,哪条线程执行run中的代码,所有会出现交替执行
输出部分展示
实现Runnable接口的方式进行实现
步骤:
1.自己定义一个类实现Runnable接口
2.重写里面的run方法
3.创建自己的类的对象
4.创建一个Thread类的对象,并开启线程
输出部分展示
利用Callable接口和Future接口方式实现
特点:可以获取到多线程运行的结果
步骤:
1.创建一个类MyCallable实现Callable接口
2.重写call (是有返回值的,表示多线程运行的结果)
3.创建MyCallable的对象(表示多线程要执行的任务)
4.创建Future接口的实现类FutureTask的对象(用于管理多线程运行的结果)
5.创建Thread类的对象,并启动(表示线程)
三种实现方式的比较
实现方式 | 优点 | 缺点 |
继承Thread类 | 编程比较简单,可以直接使用Thread类中的方法 | 可以扩展性较差,继承Thread类不能再继承其他的类 |
实现Runnable接口 | 扩展性强,实现该接口的同时还可以继承其他的类 | 编程相对复杂,不能直接使用Thread类中的方法 |
实现Callable接口 |
线程对象Thread的常见成员方法
方法名称 | 说明 |
String getName() | 返回此线程的名称 |
void setName(String name) | 设置线程的名字(构造方法也可以设置名字) |
static Thread currentThread() | 获取当前线程的对象 |
static void sleep(long time) | 让线程休眠指定的时间,单位为毫秒 |
setPriority(int newPriority) | 设置线程的优先级 |
final int getPriority() | 获取线程的优先级 |
final void setDaemon(boolean on) | 设置为守护线程 |
public static void yield() | 出让线程/礼让线程 |
public static void join() | 插入线程/插队线程 |
前6个方法的细节及演示
public class Test {public static void main(String[] args) throws InterruptedException {/*String getName()返回此线程的名称void setName(String name)设置线程的名字(构造方法也可以设置名字)细节:1、如果没有给线程设置名字,线程默认名字的格式:Thread-X(X从0开始的序号)2、如果要给线程设置名字,可以用set方法进行设置,也可以构造方法设置*/MyRun mr = new MyRun();Thread t1 = new Thread(mr, "线程1");Thread t2 = new Thread(mr, "线程2");/*static Thread currentThread()获取当前线程的对象细节:当JVM虚拟机启动之后,会自动的启动多条线程其中有一条线程就叫做main线程他的作用就是去调用main方法,并执行里面的代码在main方法中获取当前的线程就是main线程*/System.out.println(Thread.currentThread().getName());//main/*static void sleep(long time)让线程休眠指定的时间,单位为毫秒细节:1、哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间2、方法的参数:就表示睡眠的时间,单位毫秒3、当时间到了之后,线程会自动的醒来,继续执行下面的其他代码*/for (int i = 1; i <= 5; i++) {System.out.println(i);Thread.sleep(1000);//每隔1秒输出一个数字}/*setPriority(int newPriority) 设置线程的优先级final int getPriority() 获取线程的优先级细节:1、Java默认线程是抢占式调度,哪条线程抢占到CPU,哪条线程执行2、线程的优先级越高,越容易抢占到CPU3、线程的优先级从1到10,默认为5*/int p1 = Thread.currentThread().getPriority();System.out.println(p1);//5Thread.currentThread().setPriority(10);int p2 = Thread.currentThread().getPriority();System.out.println(p2);//10}
}
设置为守护线程的细节及演示
线程1的任务:打印1-10
public class MyRun1 implements Runnable{@Overridepublic void run() {for (int i = 1; i <= 10; i++) {System.out.println(Thread.currentThread().getName()+":"+i);}}
}
线程2的任务:打印1-100
public class MyRun2 implements Runnable{@Overridepublic void run() {for (int i = 1; i <= 100; i++) {System.out.println(Thread.currentThread().getName()+":"+i);}}
}
测试类
public class Test2 {public static void main(String[] args) {//final void setDaemon(boolean on) 设置为守护线程//public static void yield() 出让线程/礼让线程//public static void join() 插入线程/插队线程MyRun1 mr1 = new MyRun1();MyRun2 mr2 = new MyRun2();Thread t1 = new Thread(mr1,"线程1");Thread t2 = new Thread(mr2,"线程2");//将t2设置为守护线程//t1结束时,t2也要陆续结束,不管是否执行完毕t2.setDaemon(true);t1.start();t2.start();}
}
线程1打印完10后结束,线程2是守护线程,也要陆续结束,所以没有打印完100程序就结束了
输出部分展示
出让线程细节及演示
继承Thread类
public class MyThread extends Thread{public MyThread() {}//构造方法不能继承,要使用父类的构造方法需要利用super关键字public MyThread(String name) {super(name);}@Overridepublic void run() {for (int i = 1; i <= 100; i++) {System.out.println(getName()+":"+i);//出让线程,使别的线程容易抢占到CPU//可以让线程尽可以的交替进行Thread.yield();}}
}
测试类
public class Test3 {public static void main(String[] args) {//public static void yield() 出让线程/礼让线程MyThread t1 = new MyThread("线程1");MyThread t2 = new MyThread("线程2");t1.start();t2.start();}
}
让线程尽可以的交替进行
输出部分展示
插入线程细节及演示
继承Thread类
public class MyThread extends Thread{@Overridepublic void run() {for (int i = 1; i <= 5; i++) {System.out.println(i);}}
}
测试类
public class Test4 {public static void main(String[] args) throws InterruptedException {//public static void join() 插入线程/插队线程MyThread t = new MyThread();t.start();//将t线程插入到当前线程前,当前线程为main线程//只有t线程执行完毕,main线程才开始执行t.join();for (int i = 10; i <=15 ; i++) {System.out.println(i);}}
}
只有t线程执行完毕,main线程才开始执行
线程的生命周期
多线程的安全问题
有多个线程操作一段有共享数据的代码,A线程还没有把操作后得到的数据输出出来,其他线程可能就修改了共享数据的值,导致A线程输出的数据被改变,破坏了数据
保证多线程安全的方式
同步代码块
把操作共享数据的代码锁起来,在一个线程执行这些代码时,别的线程在代码外等待。只有这个线程执行完毕后,锁打开,再让抢占到CPU的线程执行。
格式:
synchronized(锁){操作共享数据的代码
}
注意:锁对象只要唯一就行,一般使用该类所在文件的字节码作为锁对象(类名.class)
特点1:锁默认打开,有一个线程进去了,锁自动关闭
特点2:里面的代码全部执行完毕,线程出来,锁自动打开
代码演示
ABC三个窗口卖100张电影票
MyThread类
public class MyThread extends Thread {//定义票数,static共享static int ticket = 0;public MyThread() {}public MyThread(String name) {super(name);}@Overridepublic void run() {while (true) {synchronized (MyThread.class) {try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}if (ticket == 100) {break;}ticket++;System.out.println(getName() + "卖出第" + ticket + "张票");}}}
}
测试类
public class Test1 {public static void main(String[] args) {MyThread t1 = new MyThread("窗口A");MyThread t2 = new MyThread("窗口B");MyThread t3 = new MyThread("窗口C");t1.start();t2.start();t3.start();}
}
输出部分展示
同步方法
就是把锁中的代码抽取成一个方法,再把关键字synchronized加到方法上
格式:
修饰符 synchronized 返回值类型 方法名(方法参数) {...
}
特点1:同步方法的方法体是锁里面所有的代码
特点2:锁对象不能自己指定,自动设定为如下形式
非静态:this
静态:当前类的字节码文件对象
代码演示
ABC三个窗口卖100张电影票
MyThread类
public class MyThread extends Thread {//定义票数,static共享static int ticket = 0;public MyThread() {}public MyThread(String name) {super(name);}@Overridepublic void run() {while (true) {if (method()) break;}}//同步方法private synchronized boolean method() {try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}if (ticket == 100) {return true;}ticket++;System.out.println(getName() + "卖出第" + ticket + "张票");return false;}
}
测试类
public class Test1 {public static void main(String[] args) {MyThread t1 = new MyThread("窗口A");MyThread t2 = new MyThread("窗口B");MyThread t3 = new MyThread("窗口C");t1.start();t2.start();t3.start();}
}
输出部分展示
Lock锁
同步代码块和同步方法中不能直观的看到在哪里加上了锁,在哪里释放了锁
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock提供比使用synchronized方法和语句更广泛的锁定操作
Lock是接口,采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法ReentrantLock():创建一个ReentrantLock的实例
Lock中提供了获得锁和释放锁的方法,可以手动上锁、手动释放锁
void lock():获得锁
void unlock():释放锁
代码演示
ABC三个窗口卖100张电影票
MyThread类
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class MyThread extends Thread {//定义票数,static共享static int ticket = 0;//获得锁对象,static共享(唯一)static Lock l = new ReentrantLock();public MyThread() {}public MyThread(String name) {super(name);}@Overridepublic void run() {while (true) {try {//手动上锁l.lock();Thread.sleep(100);if (ticket == 100) {break;}ticket++;System.out.println(getName() + "卖出第" + ticket + "张票");} catch (InterruptedException e) {throw new RuntimeException(e);} finally {//即使break跳出循环,也会执行finally中的代码//手动解锁l.unlock();}}}
}
测试类
public class Test1 {public static void main(String[] args) {MyThread t1 = new MyThread("窗口A");MyThread t2 = new MyThread("窗口B");MyThread t3 = new MyThread("窗口C");t1.start();t2.start();t3.start();}
}
输出部分展示