多线程
解释
- 进程-是一个应用程序
- 线程-是一个进程中的执行单元,一个进程可以启动多个线程
- 两个进程,A和B的内存独立不共享
- 栈内存是独立的,一个线程一个栈,但是堆内存和方法区只有一个
- 假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发
- 使用多线程,主线程结束,子线程不一定结束
实现线程的两种方式
- 继承线程类->java.lang.Thread,重写run方法
public class MyThread {
public static void main(String[] args) {
myThread myThread = new myThread();
//start方法的作用,启动子线程,在JVM中开辟一个新的栈空间,这段代码执行后立即结束
//这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开辟处理,start()方法就结束了,线程启动成功
//启动成功的线程会自动调用run,并且run方法在分支栈的栈底部(压栈)
//run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的,它不会启动线程,不会分配栈空间
//不用start函数而直接使用run方法实际使用的是单线程
myThread.start();
//主线程执行
for (int i=0;i<1000;i++){
System.out.println("主线程--->"+i);
}
}
}
//编写一个类继承Thread类,重写run方法
class myThread extends Thread{
@Override
public void run() {
super.run();
for (int i=0;i<100;i++){
System.out.println("分支线程--->"+i);
}
}
}
- 编写类,实现java.lang.Runnable接口
public class MyThread {
public static void main(String[] args) {
myRunnable myRunnable = new myRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
for (int i=0;i<100;i++){
System.out.println("主线程--->"+i);
}
}
}
//编写一个类显示Runable接口
class myRunnable implements Runnable{
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println("分支线程--->"+i);
}
}
}
- 建议使用第二种,面向接口编程,还可以继承其它的类,更灵活
- 使用匿名内部类实现
public class MyThread {
public static void main(String[] args) {
//创建匿名内部类
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<100;i++){
System.out.println("分支线程--->"+i);
}
}
});
thread.start();
for (int i=0;i<100;i++){
System.out.println("主线程--->"+i);
}
}
}
线程生命周期
常用方法
- 获取当前线程对象
- 获取线程对象的名字
- 修改线程对象的名字
public class MyThread {
public static void main(String[] args) {
//创建线程对象
Thread t = new myThread();
//设置线程的名字
t.setName("tttt");
//获取线程名字
String tName = t.getName();
//获取当前线程对象,这段代码出现在哪个线程,获得的就是哪个线程
//如现在这个指的就是主线程,如果这段代码出现在子线程中,获取的就是子线程
Thread currentThread = Thread.currentThread();
}
}
class myThread extends Thread{
@Override
public void run() {
super.run();
//当前获取的就是子线程
Thread currentThread = Thread.currentThread();
}
}
线程的sleep方法
- 静态方法
- 参数是毫秒
- 让当前线程进入休眠,进入“阻塞状态”,放弃占用CPU时间片
public class MyThread {
public static void main(String[] args) {
//创建线程对象
Thread t = new myThread();
//设置线程的名字
t.setName("tttt");
//获取线程名字
String tName = t.getName();
//主线程调用子线程对象的sleep方法不会使子线程阻塞,它是一个静态方法
//这段代码的作用,让当前线程进入休眠,也就是说main线程进入休眠
try {
t.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class myThread extends Thread{
@Override
public void run() {
super.run();
for (int i=0;i<100;i++){
System.out.println("子线程"+i);
}
}
}
线程手动终止睡眠
public class MyThread {
public static void main(String[] args) {
//创建线程对象
Thread t = new myThread();
//设置线程的名字
t.setName("tttt");
//获取线程名字
String tName = t.getName();
//主线程调用子线程对象的sleep方法不会使子线程阻塞,它是一个静态方法
//这段代码的作用,让当前线程进入休眠,也就是说main线程进入休眠
try {
t.sleep(1000*60*60);
} catch (InterruptedException e) {
e.printStackTrace();
}
//中断睡眠(这种中断睡眠的方式依靠java的异常处理机制)
//它会导致子线程抛出异常,所有sleep方法使用时都需要try/catch,
//这样中断睡眠时会捕获异常并继续执行,而不会中断线程
//注意这个方法是可以在主线程调用中断子线程睡眠
t.interrupt();
}
}
class myThread extends Thread{
@Override
public void run() {
super.run();
try {
Thread.sleep(1000*60*60);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i=0;i<100;i++){
System.out.println("子线程"+i);
}
}
}
强行终止一个线程
//5s后强制中断线程,这种方法是直接杀死线程,会导致数据丢失
t.stop(); //已过时,不建议使用
合理终止线程
public class MyThread {
public static void main(String[] args) {
//创建线程对象
myThread t = new myThread();
try {
t.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5s后强制中断线程,这种方法是直接杀死线程,会导致数据丢失
//t.stop(); //已过时,不建议使用
//合理中断线程,改变子线程的标志状态
t.run = false;
}
}
class myThread extends Thread{
boolean run = true; //设置线程执行的标志,方便终止线程
@Override
public void run() {
for (int i=0;i<100;i++){
if (run){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//终止当前线程
return;
}
}
}
}
线程调度
- 线程调度常见模式
- 抢占式调度模型-哪个线程的优先级高,抢到cpu时间片的概率就高,java采用的就是抢占式
- 均分式调度模型-平均分配cpu时间片,每个线程占有cpu时间片的时间长度一样
- java提供的常见线程优先级有关的方法
- void setPriority(int newPriority) 设置线程的优先级
- int getPriority() 获取线程优先级
- 最低优先级1->
Thread.MAX_PRIORITY
- 默认优先级5->
Thread.MIN_PRIORITY
- 最高优先级10->
Thread.NORM_PRIORITY
- yield() 让位方法,暂停当前正在执行的线程对象,它不是阻塞,它会让线程从运行状态回到就绪状态,继续参与cpu时间片的抢夺,优先级可能会降一些
- void join() 当前线程进入阻塞状态
public class MyThread {
public static void main(String[] args) throws InterruptedException {
//创建线程对象
myThread t = new myThread();
try {
t.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//这个方式会使当前线程进入阻塞,等待t线程执行结束后再继续执行当前线程
//注意它阻塞的是当前线程,而不是t线程
t.join();
}
}
class myThread extends Thread{
boolean run = true;
@Override
public void run() {
for (int i=0;i<100;i++){
if (run){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//终止当前线程
return;
}
}
}
}
线程优先级
- 最低优先级1->
Thread.MAX_PRIORITY
- 默认优先级5->
Thread.MIN_PRIORITY
- 最高优先级10->
Thread.NORM_PRIORITY
- int getPriority() 获取线程优先级
- 优先级高,只是抢到cpu时间片相对多一些,是处于运行的时间的多一些,而不是谁先抢到
线程安全
- 多线程修改共享数据时存在安全问题
- 解决方案:存在安全问题时使用线程同步机制
- 异步线程模型
- 同步编程模型
线程同步机制-synchronized
synchronized(){}
- synchronized三种写法
- 同步代码块:
synchronized(线程共享对象){同步代码块}
- 在实例方法上使用:对象锁,创建多少就有多少锁,所以共享一定要用this关键字修饰,
public synchronized void sett(){}
此时一定锁的是this,不能是其他对象,这种形式不灵活,且整个方法都需要同步
- 在静态方法上使用:表示找类锁,类锁永远只有一把
- 方法区越小,运行效率越高
- 在java语言中,任何一个对象都有一把锁,100个对象,100把锁,当执行过程中,遇到了synchronized时,该线程会占有这把锁,只有同步代码块执行完毕后,这把锁才可以重新被获取
- 最好不要嵌套使用,容易造成死锁,具体看死锁案例
public void Money(double m){
//以下几行代码必须是线程排队的,不能并发
//一个线程把这里的代码全部执行结束之后,另一个线程才能进来
/*
线程同步机制的语法是:
synchronized (){
//线程同步代码
}
synchronized后面小括号中传的这个“数据”是相当关键的
这个数据必须是多线程共享的数据。才能达到多线程排队
如:
假设t1,t2,t3,t4,t5,有5个线程
你只希望t1,t2,t3排队,t4,t5不需要排队
你一定要在()中写一个t1,t2,t3共享的对象,而这个对象对应t4,t5来说不是共享的
此处传入this是因为我们假设新建了一个账户对象,并且在这个对象中实现了取款方法,所以
这个账户对象就是我们的共享对象,所以添this,具体参数请根据实际情况填写
*/
synchronized (this){
double before = this.getMoney();
double after = before - m;
this.setMoney(after);
}
}
java中三大变量的线程共享问题
- 实例变量->在堆空间中
- 静态变量->在方法区中
- 局部变量->在栈中
- 局部变量用于都不会存在线程安全问题,因为局部变量不共享(一个线程一个栈),局部变量不会共享。
死锁
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);
}
}
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 (o1){
synchronized (o1){
synchronized(o2){
}
}
synchronized(o2){
}
}
}
}
其他
- java语言中线程分为两大类
- 守护线程->代表:垃圾回收线程
- 一般守护线程是一个死循环,用户线程结束,守护线程自动结束
public class DeadLock {
public static void main(String[] args) {
Thread t = new yourThread();
//设置成守护线程
t.setDaemon(true);
t.start();
}
}
class yourThread extends Thread{
@Override
public void run() {
super.run();
}
public void go(){
}
}
- 定时器->间隔一定时间执行
- 自定义sleep方法,太low,一般不用
- 使用开发好的方法:java.util.Timer
- 使用框架自带方法,底层一般也是使用的java.util.Timer
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.SimpleFormatter;
public class TimerTest {
public static void main(String[] args) throws Exception{
//创建定时器对象
Timer timer = new Timer();
//指定定时任务schedule(定时任务,第一次执行时间,间隔多久执行)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date fisterTime = sdf.parse("2020-03-14 09:30:00");
timer.schedule(new LogTimerTask(),fisterTime,1000*10);
}
}
//编写定时任务
class LogTimerTask extends TimerTask{
@Override
public void run() {
//编写需要执行的任务
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String strTime = sdf.format(new Date());
System.out.println(strTime+":成功完成任务");
}
}
- FutrueTask方式,实现Callable接口实现线程(java8新特性)
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class TimerTest {
public static void main(String[] args) throws Exception{
//创建一个“未来任务类”对象,参数需要给衣蛾Callable接口实现类对象
FutureTask task = new FutureTask(new Callable() { //这个方法返回一个泛型数据
@Override
public Object call() throws Exception { //call方法就相当于run方法
//线程执行一个任务,返回结果
System.out.println("call");
Thread.sleep(1000*10);
System.out.println("结束");
int a = 100;
return a;
}
});
//创建线程对象
Thread t = new Thread(task);
t.start();
//在主线程获取t线程返回结果,它会导致当前线程阻塞,直到拿到线程返回值
Object obj = task.get();
}
}
- Object类中的wait和notify方法(生产者消费者模型)
- wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为两个方式是Object类自带的
- wait方法的作用,会让调用此方法的对象进入等待状态
- notify 唤醒调用wait方法的对象
- notifyAll()方法,唤醒所有等待的线程