7 多线程基础
7.1 程序、进程、线程
- 程序(program):为达目的而编写的一组静态代码;
- 进程(process):资源分配的基本单位;
- 线程(thread):资源调度的基本单位。
7.2 创建线程的三种方法
7.2.1 继承Thread类
继承Thread类,并重写run()方法
public class test {
public static class BuyTicketThread extends Thread {
public BuyTicketThread(String name){
super(name);
}
//一共10张票:
static int ticketNum = 10;//多个对象共享10张票
@Override
public void run() {
//每个窗口后面有100个人在抢票:
for (int i = 1; i <= 100 ; i++) {
if(ticketNum > 0){//对票数进行判断,票数大于零我们才抢票
System.out.println("我在"+this.getName()+"买到了从北京到哈尔滨的第" + ticketNum-- + "张车票");
}
}
}
}
public static void main(String[] args) {
//多个窗口抢票:三个窗口三个线程对象:
BuyTicketThread t1 = new BuyTicketThread("窗口1");
t1.start();
BuyTicketThread t2 = new BuyTicketThread("窗口2");
t2.start();
BuyTicketThread t3 = new BuyTicketThread("窗口3");
t3.start();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y1C1pGcb-1677247441577)(images/cZxFgeP-WYxZQ5DkpuwGyzuExItBmXuEczxPTeSI_XQ.png)]
7.2.2 实现Runnable接口
implements Runnable接口,并实现run()方法
package test;
public class test {
public static class BuyTicketThread implements Runnable {
int ticketNum = 10;
@Override
public void run() {
for (int i = 1; i <= 100 ; i++) {
if(ticketNum > 0){
System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
}
}
}
}
public static void main(String[] args) {
BuyTicketThread t = new BuyTicketThread();
Thread t1 = new Thread(t,"窗口1");
t1.start();
Thread t2 = new Thread(t,"窗口2");
t2.start();
Thread t3 = new Thread(t,"窗口3");
t3.start();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lYrbztoL-1677247441578)(images/2WuheXY5K6o2YF6EyXdoXPQe3RLkWtHQijK-D5FKUjk.png)]
7.2.3 实现Callable接口
JDK 1.5之后提出。
它可以抛出异常,并能返回值。
public class test {
public static class TestRandomNum implements Callable<Integer> {
/*
1.实现Callable接口,可以不带泛型,如果不带泛型,那么call方式的返回值就是Object类型
2.如果带泛型,那么call的返回值就是泛型对应的类型
*/
@Override
public Integer call() throws Exception {
return new Random().nextInt(10);//返回10以内的随机数
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//定义一个线程对象:
TestRandomNum trn = new TestRandomNum();
FutureTask ft = new FutureTask(trn);
Thread t = new Thread(ft);
t.start();
//获取线程得到的返回值:
Object obj = ft.get();
System.out.println(obj);
}
}
7.2.4 三种方式小结
- 实际开发中,Runnable比Thread方式用的更多,因为只能extends一个,但可以implements多个;
- 在需要返回值时使用Callable接口;
- 本质上只有一种方法:new Tread对象,调用run()方法。
7.3 线程的生命周期
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GolkOr9m-1677247441581)(images/obdWY4qnO2itdzGKjbVtYm15VhzbPKd1o4eHzV4-RlE.png)]
sleep和join的区别:
- sleep期间不会释放对象锁;
- join期间会释放对象锁。
7.4 线程安全问题
同步代码块
synchronized(object) {…}
- 锁住的必须是引用数据类型,不能是基本数据类型;
- 可以创建一个专门的同步监视器private static final Object object = new Object(),没有任何业务含义;
同步方法
- public static synchronized void buyTicket(){…}
- public synchronized void buyTicket(){…}
Lock锁
package test;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class test2 {
public static class BuyTicketThread implements Runnable {
int ticketNum = 10;
//拿来一把锁:
Lock lock = new ReentrantLock();//多态 接口=实现类 可以使用不同的实现类
@Override
public void run() {
//此处有1000行代码
for (int i = 1; i <= 100 ; i++) {
//打开锁:
lock.lock();
try{
if(ticketNum > 0){
System.out.println("我在"+Thread.currentThread().getName()+"买到了北京到哈尔滨的第" + ticketNum-- + "张车票");
}
}catch (Exception ex){
ex.printStackTrace();
}finally {
//关闭锁:--->即使有异常,这个锁也可以得到释放
lock.unlock();
}
}
//此处有1000行代码
}
}
public static void main(String[] args) {
BuyTicketThread t = new BuyTicketThread();
Thread t1 = new Thread(t,"窗口1");
t1.start();
Thread t2 = new Thread(t,"窗口2");
t2.start();
Thread t3 = new Thread(t,"窗口3");
t3.start();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XGjdw94t-1677247441582)(images/OOo2fZ-msxuuN5QMOb4Ir167dn5C3rdT5ferXPd07fI.png)]
Thread 3 在执行完第三次for循环后,关闭了锁。
此时,Thread 1 抢占了锁。
Lock和synchronized的区别
- Lock是显式锁(手动开启和关闭锁),synchronized是隐式锁;
- Lock只有代码块锁,synchronized有代码块锁和方法锁;
- 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。
线程同步的优缺点
-
是否进行线程同步?
线程安全,但效率低
线程不安全,但效率高 -
可能造成死锁:不同线程分别占用对方需求的资源不放弃,造成了死锁。
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new Thread() {
public void run() {
synchronized (o1) {
try {
sleep(1000);//等待t2线程上好他的o2锁
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println(1);
}
}
}
};
Thread t2 = new Thread() {
public void run() {
synchronized (o2) {
try {
sleep(1000);//等待t1线程上好他的o1锁
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println(2);
}
}
}
};
t1.start();
t2.start();
}