多线程
1、概念
- 进程是一个应用程序。
- 线程是一个进程中的一个执行单元
- 一个进程可以启动多个线程
- 进程之间内存独立不共享
- 在java语言中,线程之间 ,堆内存和方法区是共享的。栈内存是独立的,一个线程一个栈。
实现多线程
1、方式一,编写一个类,继承java.lang.Thread,重写run方法
例如:
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println("MyThread"+i);
}
}
}
public class Test01{
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程"+i);
}
}
}
运行结果:
2、方式二,实现Runable接口
public class MyThread02 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("MyThread02"+i);
}
}
}
@Test
public void t2() {
MyThread02 myThread02 = new MyThread02();
Thread t1 = new Thread(myThread02);
t1.start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程"+i);
}
}
结果:
3、采用 匿名内部类方式
@Test
public void t3() {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("匿名内部类"+i);
}
}
});
t1.start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程"+i);
}
}
结果:
线程生命周期
常用操作
1、获取当前线程对象的信息
- 获取线程的名字:getName()
- 设置线程的名字:setName()
- 获取当前线程:Thread.currentThrend();
2、sleep()方法
- 静态方法:Thread.sleep(1000);
- 参数是毫秒
- 作用:让当前线程进入休眠,进入“阻塞状态”,放弃占用CPU时间片,让给其他线程使用
面试题:
public class MyThread03 {
public static void main(String[] args) {
Thread t1 = new T1();
t1.start();
try {
//因为sleep是静态方法所以此处其实是Thread.sleep是当前主线程睡眠进入阻塞状态
t1.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello world!");
}
}
class T1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
Thread thread = Thread.currentThread();
System.out.println(thread+"-->"+i);
}
}
}
结果:hello world! 隔了5秒后输出
3、interrupt()方法:中断线程的睡眠,唤醒sleep的线程,利用异常的方式。
4、终止线程推荐方法:
public class MyThread04 {
public static void main(String[] args) {
T2 t2 = new T2();
t2.setName("t2");
t2.start();
try {
//主线程sleep5秒
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒之后就会停止T2线程
t2.run = false;
}
}
class T2 extends Thread {
boolean run = true;
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if(run) {
System.out.println(Thread.currentThread() + "-->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//这里会终止线程,比如可以在终止的时候保存没保存的文件
return;
}
}
}
}
5、线程调度
1、常见的线程调度模型
- 抢占式调度模型:线程的优先级越高,抢到的CPU时间的概率就搞一些。(Java采用的就是)
- 均分式调度模型:平均分配CPU时间片,每个线程占用的时间片长度一样。
2、线程调度的方法
- setPriority(int newPriority) 更改线程优先级
- getPriority():获取线程优先级
- 最低1,最高10,默认5
- 静态方法:yield():暂停当前执行的线程,回到就绪状态,让出时间片,执行其他线程。不是阻塞
- 实例方法:void join() 合并线程
6、线程安全
1、数据在多线程并发的环境下存在安全问题的条件
- 多线程并发
- 存在数据共享
- 共享数据有修改的行为
2、解决线程安全
- 线程同步机制synchronized
3、死锁
public class MyThread05 {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
T3 t3 = new T3(o1,o2);
T4 t4 = new T4(o1,o2);
t3.start();
t4.start();
}
}
class T3 extends Thread {
private Object o1;
private Object o2;
public T3(Object o1,Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
}
}
}
}
class T4 extends Thread {
private Object o1;
private Object o2;
public T4(Object o1,Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
}
}
}
}
开发中避免使用synchronzed嵌套
在开发中如何解决线程安全问题
1、尽量使用局部变量代替“实例变量和静态变量”
2、如果必须是实例变量,那么可以考虑创建多个对象,这样的话实例变量的内存就不是共享的。
3、如果不能使用局部变量,那就只能选择synchronized。
守护线程
1、Java中线程分为两大类
- 用户线程
- 守护线程(后台线程),比如垃圾回收线程(守护线程)
- 特点:一般守护线程就是一个死循环,所有的用户线程只要结束,守护线程自动结束;(主线程main方法是一个用户线程)
2、实现
public final void setDaemon(boolean on)
将此线程标记为daemon线程或用户线程。 当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。
线程启动前必须调用此方法。
参数
on - 如果 true ,将此线程标记为守护线程
定时器
1、作用
- 间隔特定的时间,执行特定的程序
2、实现
- 利用sleep方法
- java.util.Timer
- 实际开发中,spring框架提供SpringTask框架
3、coding
public class MyTimeTask {
public static void main(String[] args) throws ParseException {
//设置起始时间,将给定的时间转成Date对象
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse("2020-11-26 16:30:50");
//创建定时器对象
Timer timer = new Timer();
/*
schedule(TimerTask task, Date firstTime, long period)
从指定的时间开始 ,对指定的任务执行重复的固定延迟执行 。
*/
timer.schedule(new TimerTask() {
@Override
public void run() {
Date now = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String format = sdf.format(now);
System.out.println(format+" 备份了");
}
},date,1000*5);
}
}
实现多线程的第三种方式实现Callable接口(java8新特性)
该方式可以获取线程的返回值
public class MyCallableThread {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
创建一个FutureTask,该任务将在运行时执行给定的Callable。 参数: 可调用–可调用任务
*/
FutureTask futureTask = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread()+"执行");
Thread.sleep(5000);
System.out.println(Thread.currentThread()+"结束");
return "线程返回的结果";
}
});
Thread t = new Thread(futureTask);
t.start();
//这一步会导致主线程阻塞,因为主线程要获取线程的返回结果,要等该线程执行完毕才能接着执行自己的内容;
futureTask.get();
System.out.println("主线程。。。");
}
}
Object中的wait和notify方法
1、不是线程中的方法,是Java中任何一个对象都有的
2、wait()方法作用
- Object o = new Object(); o. wait();
- 让正在o对象活动的线程进入等待状态,无限期等直到被唤醒。(会让当前线程进入等待状态)
- 调用notify()方法,将进入等待状态的线程唤醒。
生产者消费者代码演示:
public class ProductConsumer {
public static void main(String[] args) {
List list = new ArrayList();
Product product = new Product(list);
Consumer consumer = new Consumer(list);
Thread t1 = new Thread(product);
Thread t2 = new Thread(consumer);
t1.start();
t2.start();
}
}
//生产者
class Product implements Runnable {
//仓库
private List list;
public Product(List list) {
this.list = list;
}
@Override
public void run() {
//不停的生产对象
while (true) {
//将共享对象锁住
synchronized (list) {
if (list.size() > 0) {
try {
//如果已经有产品了,就停止生产,等待消费者消费
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没有产品就 生产
Object o = new Object();
list.add(o);
System.out.println("生产者生产中。。。"+o);
list.notifyAll(); //唤醒所有线程,但并不释放锁
}
}
}
}
//消费者
class Consumer implements Runnable {
//仓库
private List list;
public Consumer(List list) {
this.list = list;
}
@Override
public void run() {
//不停的消费产品
while (true) {
synchronized (list) {
if (list.size() == 0) {
//如果没有产品了,就等待生产
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果有产品就消费
Object remove = list.remove(0);
System.out.println("消费者消费中。。。"+remove);
//唤醒所有线程,不释放锁
list.notifyAll();
}
}
}
}
练习
public class Product implements Runnable{
private MyEx num;
public Product(MyEx num) {
this.num = num;
}
@Override
public void run() {
while (num.num < 100) {
synchronized (num) {
if (num.num % 2 == 0) {
try {
num.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"-->"+num.num++);
num.notifyAll();
}
}
}
}
public class Consumer implements Runnable{
private MyEx num;
public Consumer(MyEx num) {
this.num = num;
}
@Override
public void run() {
while (num.num < 100) {
synchronized (num) {
if (num.num % 2 != 0) {
try {
num.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"-->"+num.num++);
num.notifyAll();
}
}
}
}
public class MyEx {
int num = 1;
public static void main(String[] args) {
MyEx myEx = new MyEx();
Product product = new Product(myEx);
Consumer consumer = new Consumer(myEx);
Thread t1 = new Thread(product);
t1.setName("t1");
Thread t2 = new Thread(consumer);
t2.setName("t2");
t1.start();
t2.start();
}
}