概论
进程是一个程序
线程是一个进程中的执行场景/执行单元
一个进程可以启动多个线程
(详见操作系统)
对于Java来说,当在DOS命令窗口输入:
Java HelloWorld 回车之后,会先启动JVM。JVM就是一个进程
JVM在启动主线程,调用main方法。在启动一个垃圾回收线程负责看护。现在的Java程序最少两个线程
进程A和进程B内存独立不共享
同进程的线程之间堆内存和方法区内存共享,栈内存独立
单核CPU不能真正多线程,多核才有可能。但是单核可以做到“多线程并发的感觉”(通过时间片)
Java语言中,实现线程两种方式
1、编写一个类,继承java.lang.Thread,重写run方法
*2、编写一个类实现java.lang.Runnable接口(主要)
线程的实现方法
第一种方法:类继承java.lang.Thread,重写run方法
线程对象通过.start()方法启动线程
public class ThreadTest2 {
public static void main(String[] args) {
//创建:
MyThread mt=new MyThread();
//启动:
//start()方法的作用是启动一个分支线程,在JVM中开辟新的栈空间,这段代码瞬间结束
//这段代码任务是开辟一个新的栈空间,开辟之后start()方法就结束了,线程启动成功
//启动成功的线程会自动调用run()方法,并且run()在分支栈最底部
mt.start();
//mt.start();先执行完了之后,后面的程序才能执行。mt.start()瞬间执行
//此后主线程和分支线程同时运行
//这里的代码还是运行在主线程中
for(int i=0;i<1000;i++){
System.out.println("主线程--->"+i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
super.run();
//编写程序,运行在分支线程中
for(int i=0;i<1000;i++){
System.out.println("分支线程--->"+i);
}
}
}
第二种方法:类实现java.lang.Runnable接口(推荐)
也需重写run()方法
public class ThreadTest3 {
public static void main(String[] args) {
//创建一个可运行的对象
MyRunnable r=new MyRunnable();
//将可运行的对象封装成一个线程对象
Thread t=new Thread(r);
Thread tt=new Thread(new MyRunnable());//合并
//启动线程
t.start();
for(int i=0;i<1000;i++){
System.out.println("主线程--->"+i);
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<1000;i++){
System.out.println("分支线程--->"+i);
}
}
}
线程的生命周期
新建态–>就绪态<–>运行态–>死亡态
^ v
阻塞态
线程的常用方法
1.getName(),.setName(“xxx”)
/*
获取当前线程对象:(返回Thread) static Thread currentThread();静态方法
获取线程对象名字:.getName()
修改当前线程对象名字:.setName("xxx")
默认名字Thread-0、Thread-1......
*/
public class THreadTest4 {
public static void main(String[] args) {
//ct 当前线程对象,代码出现在main中,当前线程就是主线程
Thread ct=Thread.currentThread();
MyThread2 mt=new MyThread2();
//设置名字,默认名字Thread-0
mt.setName("mt");
//获取名字
String mtName=mt.getName();
System.out.println(mtName);
//获取另一线程的名字
//System.out.println(new MyThread2().getName());合并
MyThread2 mt2=new MyThread2();
mt2.setName("mt2");
mt2.start();
mt.start();
}
}
class MyThread2 extends Thread{
public void run(){
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"分支线程-->"+i);
}
}
}
线程睡眠和唤醒
睡眠static void sleep(long millis)静态方法
package com.xianchen.java.thread;
/*
static void sleep(long millis)方法:静态,参数是毫秒,作用是让当前线程进入阻塞状态,放弃cpu时间片,让给其他线程
出现在哪个线程,哪个线程就休眠
可以做到间隔特定的时间去执行特定的代码
*/
public class ThreadTest5 {
public static void main(String[] args) {
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello wordl");
}
}
唤醒.interrupt() 实例方法
不是终断线程执行,是终止线程的睡眠。.interrupt()干扰 通过异常机制实现。
public class ThreadTest6 {
public static void main(String[] args) {
Thread t=new Thread(new MyRunnable2());
t.setName("t");
t.start();
//5秒后t线程醒来(5秒后主线程任务完成)
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//终断t线程睡眠
t.interrupt();//干扰会让Thread.sleep(1000860*60*24*365);异常,
//这种终断方法依靠异常,结束try也就结束睡眠
}
}
class MyRunnable2 implements Runnable{
@Override
public void run() {
//**run()只能try/catch,不能抛出。因为子类重写父类方法不能抛出更宽泛的异常。run()在父类中没有抛出异常
//其他方法可以throws,不影响。
System.out.println(Thread.currentThread().getName()+"--->begin");
try {
//睡一年
Thread.sleep(1000860*60*24*365);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"--->end");
}
}
强行终止线程执行
.stop()方法
线程 .stop()存在缺点:容易损坏、丢失数据,这种方式直接杀死线程,线程没有保存的数据会丢失
public class ThreadTest7 {
public static void main(String[] args) {
Thread t=new Thread(new MyRunnable3());
t.setName("t");
t.start();
//模拟睡眠5秒
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒后强行终止t线程
t.stop();//已过时,不建议使用
}
}
class MyRunnable3 implements Runnable{
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"---->"+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//e.printStackTrace();
}
}
}
}
改进方法 通过打标记的方法
public class ThreadTest8 {
public static void main(String[] args) {
MyRunnable4 r=new MyRunnable4();
Thread t=new Thread(r);
t.setName("t");
t.start();
//模拟睡眠5秒
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
//5秒后强行终止t线程
r.run=false;//在想终止的时候改false
}
}
class MyRunnable4 implements Runnable{
//打一个boolean标记
boolean run =true;
@Override
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;//return之前可以保存数据
}
}
}
}
线程调度
1、线程调度模型
抢占调度:优先级高,抢到时间片多。Java采用
均分调度:平均分配时间片
2、Java线程调度方法
实例方法
int getPriority()//获取线程优先级,最低优先级是1.默认优先级是5,最高是10
void setPriority(int)//修改线程优先级
void join()//合并线程
例class MyThread1 extends Thread{
public void doSome(){
MyThread2 t=new NyThread2();
t.jion();//当前线程阻塞,t线程执行,直到t线程结束,当前线程继续
}
}
class MyThread2 extends Thread{
}
静态方法
static void yield()当前线程让位,yield()方法不是阻塞 方法,是让当前线程从运行态到就绪态
多线程
存在安全问题的条件:多线程并发、有共享数据、共享数据有修改行为。
解决线程安全问题:线程排队执行,不能并发。——线程同步机制。 注:会牺牲部分效率
同步:线程t1执行的时候必须等线程t2结束。线程之间有等待关系。也就是线程排队。
异步:线程之间各自执行自己的,谁也不需要管谁。也就是多线程并发。
无线程同步机制
例如:
账户类:
import java.util.Objects;
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 double getBalance() {
return balance;
}
public void setActno(String actno) {
this.actno = actno;
}
public void setBalance(double balance) {
this.balance = balance;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Account)) return false;
Account account = (Account) o;
return Double.compare(account.getBalance(), getBalance()) == 0 &&
Objects.equals(getActno(), account.getActno());
}
@Override
public int hashCode() {
return Objects.hash(getActno(), getBalance());
}
@Override
public String toString() {
return "Account{" +
"actno='" + actno + '\'' +
", balance=" + balance +
'}';
}
//取款方法
public void withdraw(double money){
double before =this.balance;
double after=before-money;
//模拟网络延时
try {
Thread.sleep(100);
} catch (InterruptedException e) {
//e.printStackTrace();
}
setBalance(after);
}
}
线程:
public class AccountThread extends Thread{
//两个线程共享一个账户
private Account act;
//通过构造方法传入
public AccountThread(Account act) {
this.act = act;
}
@Override
public void run() {
super.run();
//取款操作
double money=5000;
act.withdraw(5000);
System.out.println(Thread.currentThread().getName()+"取款成功,余额:"+act.getBalance());
}
}
主程序:
public class Test {
public static void main(String[] args) {
//创建账户对象
Account act=new Account("zhangsan",10000);
Thread t1=new AccountThread(act);
Thread t2=new AccountThread(act);
t1.setName("t1");
t2.setName("t2");
t1.start();
t2.start();
}
}
会出现
t1取款成功,余额:5000.0
t2取款成功,余额:5000.0
总结:不使用 线程同步机制,多线程对同一账户进行取款,出现线程安全问题
改进 使用线程同步机制 synchronized
线程同步机制语法:
语法:
synchronized(共享对象) {
//线程同步代码块
}
synchronized()括号中的数据是多线程共享的数据。才能达到多线程排队
()中写什么,要看想让那些线程同步。
假设t1 t2 t3 t4 t5,只希望t1 t2 t3排队,t4 t5不需要排队。
一定要在synchronized()括号中写t1 t2 t3共享的对象,这个对象对于t4 t5不共享
上述代码只需改写 Account类,就能完成线程同步
import java.util.Objects;
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 double getBalance() {
return balance;
}
public void setActno(String actno) {
this.actno = actno;
}
public void setBalance(double balance) {
this.balance = balance;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Account)) return false;
Account account = (Account) o;
return Double.compare(account.getBalance(), getBalance()) == 0 &&
Objects.equals(getActno(), account.getActno());
}
@Override
public int hashCode() {
return Objects.hash(getActno(), getBalance());
}
@Override
public String toString() {
return "Account{" +
"actno='" + actno + '\'' +
", balance=" + balance +
'}';
}
//取款方法
public void withdraw(double money){
//以下代码必须线程排队,不能并发
/*
线程同步机制
语法:
synchronized(共享对象) {
//线程同步代码块
}
synchronized()括号中的数据是多线程共享的数据。才能达到多线程排队
()中写什么,要看想让那些线程同步。
假设t1 t2 t3 t4 t5,只希望t1 t2 t3排队,t4 t5不需要排队。
一定要在synchronized()括号中写t1 t2 t3共享的对象,这个对象对于t4 t5不共享
*/
//这里的共享对象是账户对象本身
synchronized(this) {
double before = this.balance;
double after = before - money;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
//e.printStackTrace();
}
setBalance(after);
}
}
}
输出
t1取款成功,余额:5000.0
t2取款成功,余额:0.0
总结:在Java语言中,任何对象都有一把锁,也就是一个标记。
上述代码原理:假设t1和t2并发,开始执行代码的时候,肯定有一个先一个后。
假设t1先执行,遇到了synchronized,这是会自动找后面共享对象的对象锁,找到后占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直占有这把锁,直到代码结束,释放这把锁。
假设t1已经占有了这把锁,此时t2也遇到了synchronized关键字,也会去占有后面共享对象的对象锁。但是这把锁被t1占有了。t2只能在同步代码块外等待t1完成之后,才能进入同步代码块
从此达到线程同步。
注:
java三大变量中,局部变量在栈中,是线程独享不会共享,也就不会有安全问题。
静态变量(方法区)和对象(堆)都不是线程安全的。
synchronized( ){
同步代码块
}
同步代码块越小,效率越高
synchronized三种用法:
第一种
synchronized(共享数据){
同步代码块
}
第二种
修饰实例方法
第三种
修饰静态方法
synchronized 关键字其他用法
在实例方法上使用 synchronized 关键字
缺点:1、锁的是this,且不能修改
2、整个方法题都会同步,可能会扩大同步范围,导致效率降低
优点:1、代码写得少,简洁了
public synchronized void withdraw(double money){
double before = this.balance;
double after = before - money;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
//e.printStackTrace();
}
setBalance(after);
}
死锁
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);
t1.start();
t2.start();
}
}
class MyThread1 extends Thread{
Object o1;
Object o2;
public MyThread1(Object o1,Object o2){
this.o1=o1;
this.o2=o2;
}
@Override
public void run() {
super.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;
}
@Override
public void run() {
super.run();
synchronized (o2){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){
};
}
}
}
总结:
synchronized(共享数据){
同步代码块
}
最好不要嵌套使用
守护线程
Java中线程分两类:用户线程和守护线程(后台)
守护线程特点是:一般是死循环。所用用户线程结束,守护线程自动结束
main线程是用户线程
例如
每天0点自动备份数据
public class ThreadTest9 {
public static void main(String[] args) {
Thread t=new BakDataThread();
t.setName("备份数据线程");
//**启动线程之前,将线程设置为守护线程
t.setDaemon(true);
t.start();
//主线程
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class BakDataThread extends Thread{
@Override
public void run() {
super.run();
int i=0;
while(true){
System.out.println(Thread.currentThread().getName()+"--->"+i++);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
定时器
作用:间隔特定的时间,执行特定程序。
例如:每天数据备份操作
实现:可以使用sleep实现
java.tutil.Timer
框架中实现,例如Spring的SpringTask框架
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
public static void main(String[] args) throws ParseException {
//创建定时器对象
Timer t1=new Timer();
//守护线程的方式
Timer t2=new Timer(true);
//指定定时任务t1.schedule(定时任务,第一次时间,间隔多久一次);
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
Date firstTime=sdf.parse("2020-12-14 23:37:00 000");
t1.schedule(new LogTimerTask(),firstTime,1000*10);
}
}
//编写定时任务类
class LogTimerTask extends TimerTask{
@Override
public void run() {
//编写任务
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String strTime=sdf.format(new Date());
System.out.println(strTime+":完成一次");
}
}
wait和notify方法
wait和notify是不线程专有,而是每一个Java对象都有的方法。是Object类自带的
wait():
Object o=new Object();
o.wait();
让正在o对象上活动的线程进入等待状态,直到被唤醒。
notify():
Object o=new Object();
o.notify();
让正在o对象上等待的线程进入就绪状态。
notifyAll():
唤醒所有o对象上处于等待的线程
例如:
生产者和消费者模式
一个线程负责生产,一个线程负责消费,中间是仓库。
仓库是多线程共享的。最终保持生产者和消费者平衡问题
wait方法和notify方法建立在synchronized线程同步上面的
o.wait()会让当前正在o对象上活动的当前线程进入等待状态,并且释放o对象锁
o.notify()只会通知,不会释放之前占有的o对象锁
import java.util.ArrayList;
import java.util.List;
/*
wait¬ify实现生产者和消费者模式
生产线程负责生成,消费线程负责消费
生成线程和消费线程需要均衡
wait和notify是普通Java对象都有的方法
wait和notify方法建立在线程同步的基础上
因为多线程异步会有线程安全问题
o.wait()会让当前正在o对象上活动的当前线程进入等待状态,并且释放o对象锁
o.notify()只会通知,不会释放之前占有的o对象锁
仓库采用List集合,假设List集合中只能存储一个元素
*/
public class ThreadTest11 {
public static void main(String[] args) {
//创建一个仓库
List list=new ArrayList();
//创建两个线程对象
//生产者
Thread t1= new Thread(new Producer(list));
//消费者
Thread t2=new Thread(new Consumer(list));
t1.setName("生产者线程");
t2.setName("消费者线程");
t1.start();
t2.start();
}
}
//生产线程
class Producer implements Runnable{
private List list;
public Producer(List list) {
this.list = list;
}
@Override
public void run() {
//一直生产,死循环模拟一直生产
int i=0;
while(i<100){
//给仓库加锁
System.out.println("生产者正在抢锁");
synchronized (list) {
if (list.size() > 0) {
System.out.println("生产者抢到了但需释放");
try {
//当前线程进入等待状态,释放锁
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序到这里,说明仓库是空的,可以生产
Object o=new Object();
list.add(o);
System.out.println(Thread.currentThread().getName()+"->"+o);
//唤醒消费者,唤醒不会释放锁
list.notify();
i++;
}
}
}
}
//消费线程
class Consumer implements Runnable{
private List list;
public Consumer(List list) {
this.list = list;
}
@Override
public void run() {
//一直消费
int i=0;
while (i<100){
System.out.println("消费者正在抢锁");
synchronized (list){
if(list.size()==0){
System.out.println("消费者抢到了但需释放");
try {
//仓库空了
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//程序执行到这里,说明仓库中有数据可以消费
Object o=list.remove(0);
System.out.println(Thread.currentThread().getName()+"->"+o);
//唤醒生产者生产,唤醒不会释放锁,synchronized代码块执行完才会释放锁
list.notify();
i++;
}
}
}
}