文章目录
1.进程和线程
- 进程是一个应用程序,一个进程可以启动多个线程
- 线程是一个进程中的执行单元/执行场景
- JVM是一个进程,在java程序中至少有两个线程并发,一个主线程调用main方法,一个垃圾回收线程
- 进程间的内存不共享,线程间堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈(多线程并发)
- Java的多线程机制,目的是为了提高程序的处理效率
- 单核CPU无法实现真正的多线程并发(线程间执行互不干扰),但由于CPU处理速度快,可以在多个线程间频繁切换,给人感觉是并发的
2.实现线程
2.1继承Thread类
编写一个类,继承java.lang.Thread,重写run方法。创建分支对象,执行start
public class MyThread extends Thread {
public void run() {
//这段程序运行在分支线程中
for (int i = 0; i < 1000; i++) {System.out.println("分支线程---->"+i);}
}
}
public class ThreadTest02 {
public static void main(String[] args) {
MyThread myThread = new MyThread();//创建分支对象
//启动线程,在JVM中开启一个新的栈空间,开出来后方法结束
//启动成功的线程会自动调用run方法,run方法在分支栈的栈底部,与main平级
myThread.start();
//这段程序运行在主线程中
for (int i = 0; i < 1000; i++) {System.out.println("主线程---->"+i);}
}
}
2.2实现Runnable接口
编写一个类,实现java.lang.Runnavle接口,实现run方法。创建线程对象,启动线程
public class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 1000; i++) {System.out.println("分支线程---->"+i);}
}
}
ublic class ThreadTest04 {
public static void main(String[] args) {
//创建一个可运行对象
MyRunnable myRunnable = new MyRunnable();
//将可运行的对象封装成一个线程对象
Thread thread = new Thread(myRunnable);
//Thread thread1 = new Thread(new MyRunnable());
thread.start();
for (int i = 0; i < 1000; i++) {System.out.println("主线程---->" + i);}
}
}
使用匿名内部类
实际上还是第二种方法
public class ThreadTest05 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {System.out.println("分支线程---->"+i);}
}
});
t.start();
for (int i = 0; i < 1000; i++) { System.out.println("主线程---->" + i);}
}
}
2.3实现Callable接口
JDK8新特性,优点:该方法实现的线程可以获取线程的返回值
缺点:效率低,获取t线程执行效果,当前线程受阻
public class ThreadTest09 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建一个未来任务对象,参数为一个Callable接口实现类
FutureTask task = new FutureTask(new Callable() {
public Object call() throws Exception {//call()相当于run,执行之后可能会有一个执行结果
System.out.println("call method begin");
Thread.sleep(1000 * 10);
System.out.println("call method end");
int a = 100;
int b = 200;
return a + b;
}
});
Thread t = new Thread(task);
t.start();
Object object = task.get();//get()方法获取放回结果
System.out.println("执行结果是" + object);
System.out.println("HelloWorld");
}
}
3.线程的生命周期
- 新建状态
- 就绪状态
- 运行状态
- 阻塞状态
- 死亡状态
4.线程的常用方法
- 获取和修改线程的名字
public class ThreadTest06 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable2());
//t.setName("tttt");设置线程的名字
System.out.println(t.getName());//Thread-0
Thread t2 = new Thread(new MyRunnable2());
System.out.println(t2.getName());//Thread-1
}
}
- 获取当前线程对象
public class MyRunnable2 implements Runnable {
public void run() {
for (int i = 0; i < 1000; i++) {
Thread currentThread = Thread.currentThread();
System.out.println(currentThread.getName()+"---->"+i);
}
}
}
- sleep方法
static void sleep(Long mills)
静态方法,参数是毫秒Thread.sleep(1000);
作用:让当前对象进入阻塞状态(休眠),放弃占有CPU时间片,让给其他线程使用。
可以做到:间隔特定时间,去执行一段特定的代码(每隔多久执行一次)
public class ThreadTest07 {
public static void main(String[] args) {
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("HelloWorld");
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"---->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
sleep的面试题
public class ThreadTest07 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable2());
t.start();
try {
t.sleep(1000 * 5);//线程t不会休眠,sleep是静态方法,会让当前main线程休眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 终止程序的休眠
public class MyRunnable2 implements Runnable {
public void run() {
System.out.println(Thread.currentThread().getName() + "---->begin");
//run()方法中的异常不能throws,因为run方法在父类中没有抛出任何异常
//子类不允许比父类抛出更多的异常,只能try...catch
try {
Thread.sleep(1000 * 60 * 60);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---->end");
}
}
public class ThreadTest06 {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable2());
t.start();
try {
Thread.sleep(1000 * 5);//5秒后,希望他、线程醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();//终止线程的休眠,依靠java的异常处理机制
}
}
- 强制终止线程的执行(已过时,不建议使用)
容易丢失数据
try {Thread.sleep(1000 * 5);} catch (InterruptedException e) {e.printStackTrace();}
//5秒后强制终止t线程
t.stop();
- 合理的终止一个线程的执行
public class MyRunnable3 implements Runnable {
boolean run = true;
public void run() {
for (int i = 0; i < 10; i++) {
if (run) {
System.out.println(Thread.currentThread().getName() + "---->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
//终止当前程序
return;
}
}
}
}
public class ThreadTest06 {
public static void main(String[] args) {
MyRunnable3 r = new MyRunnable3();
Thread t = new Thread(r);
t.start();
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//模拟5秒后终止线程,将rn修改为false,t线程就结束了
r.run = false;
}
}
5.线程调度
5.1线程调度模型
- 抢占式调度模型:那个线程的优先级比较高,抢到的CPU时间片的概率就高一些。Java采用这种模型
- 均分式调度模型:平均分配CPU时间片,每个线程占有的CPU时间片时间长度一样
5.2线程调度相关的方法
- 获取与设置线程优先级
void setPriority(int newPriority)
int getPriority()
最低优先级1,默认5,最高10,优先级比较高的获取CPU时间片可能会多一些
public class ThreadTest07 {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getPriority());
Thread.currentThread().setPriority(10);
}
}
- 让位方法
static void yield()
暂停正在执行的线程对象,执行其他线程
yield()不是阻塞方法,让当前线程从运行状态回到就绪状态(回到就绪后,可能还会再次抢到)
public class MyRunnable4 implements Runnable {
public void run() {
for (int i = 0; i < 1000; i++) {
if(i%100 == 0) Thread.yield();//当前线程暂停,让给主线程
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
- 合并线程
t.join();
当前线程进入阻塞,让t线程执行,知道t线程结束,当前线程才可以执行
public class ThreadTest07 {
public static void main(String[] args) {
System.out.println("main begin");
Thread t = new Thread(new MyRunnable6());
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main over");
}
}
class MyRunnable6 implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
}
}
}
6.线程安全
在以后的开发中,项目运行在服务器中,而服务器已经将线程定义了,线程对象的创建、启动等都不需要编写。需要注意程序再多线程并发环境下运行,数据是否安全
数组在多线程情况下存在安全问题的三个条件:
- 多线程并发
- 有数据共享
- 共享数据有修改行为
6.1怎么解决线程安全问题
- 第一种方案:尽量使用局部变量代替“实例变量和静态变量”
- 第二种方案:如果必须是实例变量,可以考虑创建多个对象,这样实例变量的内存就不共享了
- 第三种方案:若果不能使用局部变量,也不能创建多个对象,只能选择synchronized,线程同步机制。会让程序的执行效率降低,用户体验不好,系统的用户吞吐量降低,在不得已的情况下再选择线程同步机制
线程同步机制:线程排队执行(不能并发),会牺牲一部分效率,数据安全第一位
两个专业术语: - 异步编程模型:线程t1和t2各自执行各自的,谁也不需要等谁,多线程并发,效率较高
- 同步编程模型:线程t1和t2之间发生了等待关系,需要等另一个线程执行完,自己才能执行,排队,效率较低
6.2多线程取款示例
取款时,线程需排队,不能并发
线程同步机制的语法是
sychronized(){
//线程同步代码块
}
()中填写需要排队的线程共享的对象
这里的共享对象为账户对象,填写this
public class Account {
private String actno;
private double balance;
public Account() {}
public Account(String actno, double balance) {
this.actno = actno;
this.balance = balance;
}
public String getActno() {return actno;}
public void setActno(String actno) {this.actno = actno;}
public double getBalance() {return balance;}
public void setBalance(double balance) {this.balance = balance;}
public void withdraw(double money) {
synchronized (this) {
try {
Thread.sleep(1000 * 2);//模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(this.getBalance() - money);
}
}
}
public class AccountThread extends Thread {
private Account act;
public AccountThread(Account act) {this.act = act;}
public void run() {
double money = 5000;
act.withdraw(money);
System.out.println(Thread.currentThread().getName() + "对" + act.getActno() + "取款" + money + ",余额为" + act.getBalance());
}
}
public class Test2 {
public static void main(String[] args) {
Account act2 = new Account("act-002",10000);
AccountThread t1 = new AccountThread(act2);
AccountThread t2 = new AccountThread(act2);
t1.start();
t2.start();
}
}
6.3共享对象的标记“锁”
java语言中任何一个对象都有一把锁,即为标记。
在取款的例子中,t1与t2并发,坑定有先后,假设t1先执行,遇到了sychronized,自动找()中共享对象的对象锁,找到后占有,然后执行同步代码块中的程序,同步代码快中代码执行结束,锁才会释放。t1占有锁后,t2也遇到sychronized,如果共享对象的锁被t1占有,t2只能等待t1把同步代码快执行结束,t2占有锁后,才能进入同步代码块执行
注意:共享对象需要是你需要排队执行的这些线程对象所共享的
//Object obj2 = new Object2();
//synchronized (obj2) { 定义的局部变量,不是共享的
//synchronized (obj) { 可以,act的实例变量obj对象只有一个,可以共享
//synchronized ("abc") { 在方法区的字符串常量池中,导致所有的栈都排队
//synchronized (null) { 空指针异常
synchronized (this) {
try {
Thread.sleep(1000 * 2);//模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setBalance(this.getBalance() - money);
}
6.4java对象的线程安全问题
- 实例变量:在堆中,堆只有一个
- 静态变量:在方法区中,方法区中有一个
堆和方法区都是多线程共享的,可能存在线程安全问题 - 局部变量:在栈中,不共享,不会有线程安全问题
常量也不存在线程安全问题,因为它不可被修改
非线程安全 | 线程安全 |
---|---|
StringBuilder | StringBuffer |
ArrayList | Vector |
HashMap HashSet | Hashtable |
6.5synchronized的用法
- 同步代码块:灵活
- 在实例方法上使用synchronized,表示共享对象一定是this,同步代码快是整个方法,这种方式不灵活,因为整个方法体都需要同步,可能回无故扩大同步范围,导致执行效率低。但是若果共享的对象就是this,并且需要同步整个方法体,建议使用这种方式
public synchronized void withdraw(double miney){} - 在静态方法上使用synchronized,表示找类锁,类锁中有一把,100个对象100把锁
6.6synchronized面试题
(1)doOther方法的执行需要等待doSome方法结束吗
不需要,doOther方法没有被synchronized修饰,t2直接执行
public class Exam01 {
public static void main(String[] args) throws InterruptedException {
MyClass mc = new MyClass();
Thread t1 = new MyThread(mc);
Thread t2 = new MyThread(mc);
t1.setName("t1");
t2.setName("t2");
t1.start();
Thread.sleep(1000);
t2.start();
}
}
class MyThread extends Thread {
private MyClass mc;
public MyThread(MyClass mc) {this.mc = mc;}
public void run() {
if (Thread.currentThread().getName().equals("t1")) {
mc.doSome();}
if (Thread.currentThread().getName().equals("t2")) {
mc.doOther();}
}
}
class MyClass {
public synchronized void doSome() {
System.out.println("doSome begin");
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public void doOther() {
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
(2)doOther方法的执行需要等待doSome方法结束吗
需要,一个mc对象一把锁,是排它锁,t2执行时需要等待t1执行完毕doSome
class MyClass {
public synchronized void doSome() {
System.out.println("doSome begin");
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public synchronized void doOther() {
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
(3)doOther方法的执行需要等待doSome方法结束吗
不需要,MyClass对象是两个,两把锁
public class Exam02 {
public static void main(String[] args) throws InterruptedException {
MyClass mc1 = new MyClass();
MyClass mc2 = new MyClass();
Thread t1 = new MyThread(mc1);
Thread t2 = new MyThread(mc2);
t1.setName("t1");
t2.setName("t2");
t1.start();
Thread.sleep(1000);
t2.start();
}
}
(4)doOther方法的执行需要等待doSome方法结束吗
需要,虽然new了两个MyClass对象,但doSome是静态方法找类锁,MyClass只有一把锁
class MyClass {
public synchronized static void doSome() {
System.out.println("doSome begin");
try {
Thread.sleep(1000 * 5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("doSome over");
}
public synchronized static void doOther() {
System.out.println("doOther begin");
System.out.println("doOther over");
}
}
7.死锁
会写死锁,这样在开发中才会注意到这个事
synchronized在开发中最好不要嵌套使用,一不小心会导致死锁现象发生
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new MyThread1(o1, o2);
Thread t2 = new MyThread2(o1, o2);
//o1和o2数据共享
t1.start();
t2.start();
}
}
class MyThread1 extends Thread {
Object o1;
Object o2;
public MyThread1(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
public void run() {
synchronized (o1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
}
}
}
}
class MyThread2 extends Thread {
Object o1;
Object o2;
public MyThread2(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
public void run() {
synchronized (o2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
}
}
}
}
8.守护线程
java中线程可分为两大类:用户线程(例如主方法main线程)与守护线程(后台线程,代表性:GC)
守护线程的特点:一般是一个死循环,所有的用户线程结束,守护线程自动结束
守护线程的使用:实现功能每天零点自动备份,将定时器设置为守护线程,所有用户想吃结束,守护线程自动退出
守护线程的实现:
public class ThreadTest08 {
public static void main(String[] args) {
Thread t = new BakeDateThread();
t.setName("备份数据的线程");
t.setDaemon(true);//启动线程前,设置为守护线程
t.start();
//主线程
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "--->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class BakeDateThread extends Thread {
public void run() {
int i = 0;
while (true) {
System.out.println(Thread.currentThread().getName() + "--->" + (++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
9.定时器
定时器的作用:间隔特定的时间,执行特定的程序
java中可以采多种方式实现这种功能
- sleep方法,设置睡眠时间(最原始)
- java类库中java.uitl.Timer(实际开发中少用,有许多更高级的框架支持定时任务)
- 较多使用Spring框架中的springTask框架,这个管家只要进行简单的配置,就可以完成定时器的任务
public class TimerTest {
public static void main(String[] args) throws ParseException {
Timer timer = new Timer();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date firstTime = sdf.parse("2020-08-14 17:00:00");
timer.schedule(new LogTimerTask(), firstTime, 1000 * 10);
}
}
class LogTimerTask extends TimerTask {
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String strTime = sdf.format(new Date());
System.out.println(strTime + ":完成了数据备份");
}
}
10.wait和notify
10.1注意点
- wait和notify不是形成对象的方法,不能通过线程对象调用,是Object类自带的
- wait方法的作用:让o对象上活动的线程进入等待状态,直到被再次唤醒
- notify方法的作用:唤醒正在o对象上等待的线程;notifyAll的作用,唤醒o对象上所有处于等待状态的线程
10.2生产者和消费者模式
模拟这样的场景:仓库采用List集合,只能存储一个元素,实现生产一个修复一个,集合中最多存储一个元素
public class ThreadTest10 {
public static void main(String[] args) {
List list = new ArrayList();
Thread t1 = new Thread(new Produce(list));
Thread t2 = new Thread(new Consumer(list));
t1.setName("生产者线程");
t2.setName("消费者线程");
t1.start();
t2.start();
}
}
class Produce implements Runnable {
private List list;
public Produce(List list) {this.list = list;}
public void run() {
while (true) { //使用死循环模拟一直生产
synchronized (list) {
if (list.size() > 0) {
try {
list.wait();//当前线程进入等待,释放之前占有的list集合的锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object obj = new Object();
list.add(obj);
System.out.println(Thread.currentThread().getName() + "--->" + obj);
list.notifyAll();//唤醒所有等待的线程,可能被当前线程抢到,但有元素时进入等待
}
}
}
}
class Consumer implements Runnable {
private List list;
public Consumer(List list) {this.list = list;}
public void run() {
while (true) {
synchronized (list) {
if (list.size() == 0) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object obj = list.remove(0);
System.out.println(Thread.currentThread().getName() + "--->" + obj);
list.notifyAll();
}
}
}
}
传送门
上一篇:JavaSE进阶 第十二章 IO流
下一篇:JavaSE进阶 第十四章 反射机制