文章目录
定义
- 一个进程可以启用多个线程。火车站购票是一个进程,飞机购票是一个进程,两个进程独立;在火车站购票大厅里面,有若干个窗口,可供旅客同时购票,购票窗口是线程,多线程并发购票。
- 进程是一个应用程序(一个进程是一个软件),进程之间内存独立不共享
- 线程是一个进程中的执行场景/执行单元,在java语言中,对于线程A、线程B来说,堆内存和方法区内存共享,但是栈内存独立,一个线程一个栈。
在Dos窗口输入java HelloWorld回车之后
会先启用JVM,而JVM就是一个进程
JVM
启动一个主线程调用main方法
启动一个垃圾回收线程看护、回收垃圾
- 对于单核的CPU来说,真的可以做到真正的多线程并发吗?
- 真正的多线程并发:线程A执行线程A的工作,线程B执行线程B的事情,互不干扰
- 对于多核CPU来说,相当于同一时间点上有多个大脑一齐工作,可以真正的实现线程的并发执行。
- 单核CPU相当于只有一个大脑,不能做到真正的并发执行,但是宏观上可以做到“多线程并发”的感觉。对于单核CPU来说,某一个时间结点上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,从宏观上来看:给人感觉是多个事情同时在做。
实现多线程:创建分支栈
- java支持多线程,并且java已经实现了多线程,程序员只需要继承就可以
- java语言中实现线程有两种方式
- 编写一个类,直接继承java.lang.Thread,重写run方法
- 编写一个类,实现java.lang.Runnable接口
- 代码框架
- 新建类实现接口/继承方法
- 创建线程
- 启动线程
方法一 extends Thread
- 编写一个类,直接继承java.lang.Thread,重写run方法
package Advance.thread;
/**
* @author 衣鱼
* java实现多线程的第一个方法
* 编写一个类,直接继承java.lang.Thread,重写run方法
*
* 新建类之后,重写了run方法 ——创建线程对象(new)——启动线程(start方法)
*/
public class ThreadTest01 {
public static void main(String[] args) {
//新建一个分支线程对象
MyThread mytread = new MyThread();
//启动线程
mytread.start();
//下面的for循环,依旧是在main方法中,即在主栈中执行
for(int i =0;i<100;i++) {
System.out.println("主线程执行——"+i);
}
}
}
class MyThread extends Thread{
@Override
public void run() {
//重写run方法,这段程序运行在分支栈中
for(int i =0;i<100;i++) {
System.out.println("分支线程——"+i);
}
}
}
方法二 implements Runnable
- 编写一个类,实现java.lang.Runnable接口
package Advance.thread;
/**
* @author 衣鱼
* 实现线程的第二种方式
* 编写一个类,实现java.lang.Runnable接口
*/
public class ThreadTest02 {
public static void main(String[] args) {
//创建一个可运行对象
MyRunnable mr = new MyRunnable();
//将可运行对象封装成一个线程
//Thread(Runnable target) 分配新的线程对象
Thread r = new Thread(mr);
//启动线程
r.start();
for(int i =0;i<100;i++) {
System.out.println("主线程执行——"+i);
}
}
}
//MyRunnable 不是一个线程类,是一个可运行对象,不是一个线程
//所以不能和方法一一样利用MyRunnable创建线程
class MyRunnable implements Runnable{
public void run() {
// TODO Auto-generated method stub
for(int i =0;i<100;i++) {
System.out.println("分支线程——"+i);
}
}
}
方法三 Callable
- 实现Callable接口 JDK8的新特性
- 这个方法可以获取线程的返回值
- 上述的两种方法都无法获取线程的返回值(run返回void类型)
package Advance.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadTest09{
public static void main(String[] args) throws Exception{
//创建一个“未来任务类”
FutureTask task = new FutureTask(new Callable() {
//内部类方式
//call() 相当于run方法
public Object call() throws Exception{
System.out.println("开始");
Thread.sleep(1000*3);
System.out.println("结束");
return 100;
}
});
//创建线程对象
Thread t = new Thread(task);
//启动
t.start();
//此时程序执行是主方法,如何在主方法中得到线程t的返回值
Object o =task.get(); //注意该处的get是main线程获取t线程的返回值
//如果t线程执行失败,那么就get不到值,main阻塞。
}
}
线程名
- 获取线程名字:线程对象.getName()
- 设置线程名字:线程对象.setName(“新名字”)
- 当线程对象采用默认名字时,默认名字的规律:Thread-0 Thread-1
获取当前线程
- Thread t = Thread.currentThread();
package Advance.thread;
/**
* @author 衣鱼
* 获取当前线程
* Thread t = Thread.currentThread();
*/
public class ThreadTest03 {
public static void main(String[] args) {
//获取当前线程 :main主线程
Thread t = Thread.currentThread();
System.out.println(t.getName()); //main线程的名字
//新建一个分支线程对象
MyThread2 t1= new MyThread2();
t1.setName("t1");
MyThread2 t2= new MyThread2();
t2.setName("t2");
//启动线程
t1.start();
t2.start();
}
}
class MyThread2 extends Thread{
@Override
public void run() {
//重写run方法,这段程序运行在分支栈中
Thread t = Thread.currentThread();
/**
* Thread.currentThread()获取的是当前线程
* t1线程执行run方法,当前线程就是t1
* t2线程执行run方法,当前线程就是t2
* */
for(int i =0;i<100;i++) {
System.out.println(t.getName()+"——>"+i);
}
}
}
生命周期
- 新建状态
- 就绪状态
- 运行状态
- 阻塞状态
- 死亡状态
线程睡眠
-
static void sleep(long millis)
- 静态方法 :和哪个对象调用它没有关系。
使用static 静态变量:所有对象都有这个属性,且所有对象的这个属性值一样,建议定义为静态变量,节省内存开销。static 修饰的所有元素,都是类级别特征,与具体对象无关。静态变量在类加载的时候初始化,内存在方法区中开辟,访问的时候不需要创建对象,直接使用类名.静态变量属性名,使用引用.的方式访问,也不会报错!
- 参数是毫秒
- 作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU的时间片,让给其他线程使用。
- 静态方法 :和哪个对象调用它没有关系。
-
间隔特定的时间,执行特定的代码
package Advance.thread;
/**
* @author 衣鱼
* 间隔特定的时间,执行特定的代码
*/
public class ThreadTest04 {
public static void main(String[] args) {
//当前线程休眠5秒
//main主线程休眠5秒
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//5秒之后执行输出
System.out.println("hello world!");
for(int i =0;i<10;i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"——>"+i);
}
}
}
- sleep面试题
叫醒线程
终止线程
- 强行终止线程 线程对象.stop();
- 缺点:容易丢失数据,因为这种方法直接将线程杀死了,线程没有保存的数据将丢失。【类似进入电脑windows系统的任务管理器结束一个程序】
- 合理终止线程 设置run标记
线程调度
- 常见的线程调度模型
- 抢占式调度模型:线程的优先级高,占用CPU时间片的概率高。【java采用】
- 均分式调度模型:平均分配CPU时间片,每个线程占用时间片长度一样
- java中提供线程调度的方法
实例方法:void setPriority(int newPriority) | 最低优先级为1;最高优先级为10;默认优先级为5;优先级高,抢占的CPU时间片相对多 |
---|---|
静态方法:Static void yield() 让位方法 | 暂停当前正在执行的线程对象,执行其他线程;yield方法不是阻塞方法,方法执行时会让当前线程从“运行状态”回到“就绪状态” 【回到就绪状态的线程,有可能再次抢到CPU】 |
实例方法:void join() 合并线程 | 使当前线程进入阻塞状态,执行其他线程,当其他线程执行完毕,退出CPU后,当前线程才能继续执行【例:t.jion(); 意思是 t 线程合并到当前线程,当前线程进入阻塞状态,执行 t 线程】 |
⭐线程安全⭐线程同步⭐
- 在多线程并发的环境下,保证数据的安全
- 在开发过程中,项目都是运行在服务器当中,服务器已经将线程的定义、创建、启动实现完毕。不需要额外编写
- 在此时,程序员编写的程序放置在多线程环境下运行,对于程序员来说,更需要关注这些数据在多线程并发环境中是否安全。
- 多线程并发存在安全问题的条件,同时满足以上三个条件之后,存在线程安全问题
- 多线程并发
- 共享数据
- 共享数据有修改行为
- 如何解决线程安全问题
- 多线程并发环境下,共享数据被修改,存在线程安全问题。解决方法:线程同步机制
- 线程同步机制 :线程排队执行,(不能并发)。线程同步就会牺牲一部分效率,保证数据安全为首位。
异步编程模型 | 线程t1和线程t2各自执行各自的,谁也不需要等谁。——多线程并发。 |
---|---|
同步编程模型 | 线程t1和线程t2;线程t1执行时,必须等线程t2执行结束(反之亦然);两个线程之间发生了等待关系。效率较低——线程排队 |
synchronized
- 线程同步机制语法
synchronized(共享对象){
//线程同步代码块
}
synchronized后面()小括号传的”数据“必须是线程共享的数据,才能达到多线程排队
假设有t1、t2、t3、t4、t5,五个线程,程序只需要t1、t3、t4三个线程进行同步处理,t4、t5不需要排队。
那么,在()中就需要标明确线程排队的对象。
-
synchronized代码执行原理
- 在java语言中,每个对象都对应一把锁。如果没有synchronized关键词修饰,就不需要找锁,直接执行
- 假设t1、t2线程并发,开始执行synchronized同步代码块的时候,肯定是一先一后执行的。
- synchrnized的共享数据不能是包装类类型,i++相当于i = new Integer(i+1),i++之后的对象已经不是之前的i了。
- 假设t1先执行,遇到synchronized,这个时候t1会找()里面的共享对象的对象锁,找到之后,并占用这把锁,直到同步代码块代码结束,这把锁才会释放。
- 假设t1已经占用这把锁了,但是尚未执行完代码块释放锁,此时t2也遇到synchronized关键字,它也会去寻找后面的共享对象的这把锁,找到之后发现该锁被t1占有,t2只能在同步代码块外等待t1 的结束。直到t1把同步代码块执行结束之后,t1会归还这把锁,这时t2才会拿到该锁,并占用该锁,进入同步代码块执行程序。
注:()内的共享对象需要谨慎选择,共享对象是线程(需要排队执行的线程)所共享的
-
synchronized于五个状态的关系
死锁
- synchronized在开发中尽量不要嵌套使用,容易造成死锁!
package Advance.thread;
/**
* @author 衣鱼
* 死锁
*/
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 =new Object();
//t1 t2 共享o1、o2
Thread t1 = new MyThread1(o1, o2);
Thread t2 = new MyThread10(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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(o2) {}
}
}
}
class MyThread10 extends Thread{
Object o1;
Object o2;
public MyThread10(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
public void run() {
synchronized(o2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized(o1) {}
}
}
}
银行取款
- 设置一个用户类、一个线程类、一个测试类
- 当同一用户使用多线程取款时,如何保证存取的安全。
- 针对存取款的同步机制处理
package Advance.thread.ThreadSafeSynchronized;
public class Account {
//账户
private String actno;
//余额
private double banlance;
//测试synchronized
Object obj = new Object();
//余额方法
public void withdraw(double money) {
synchronized(this) //此时的共享对象是act账户,Account类的构造方法传入了该参数
//线程1、线程2共享的act-1用户,它们两个同步
//如果此时有线程3针对的act-2用户,那么采用”this“关键字时线程三不需要等候线程1、2
//线程3可以直接取得act-2对象对应的锁
//即:只有针对同一账户的存取行为需要同步,不同账户异步
//synchronized("abc"); //传入的参数为字符串,创建在字符串常量池里面,如此哪怕线程1、线程3针对的不是一个用户
//即不是一个共享对象,此时只有一个字符串,也就是只有一把锁,所以线程1和线程3仍会同步......
//即:一个人取款;整个银行等候
//synchronized(obj) //传入静态变量obj参数,也可以达到效果
//Object partObj = new Object();
//synchronized(partObj) //传入该局部变量参数,执行错误,因为传入是partObj不是共享的,每一次都是新对象
{ //取款之前余额
double before = this.getBanlance();
//取款之后余额
double after = before - money;
//更新余额
this.setBanlance(after);
}
}
public Account() {
super();
}
public Account(String actno, double banlance) {
super();
this.actno = actno;
this.banlance = banlance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBanlance() {
return banlance;
}
public void setBanlance(double banlance) {
this.banlance = banlance;
}
}
变量的线程安全
- java中的三大变量
- 静态变量:方法区 ,可能存在线程安全问题
- 实例变量:堆内存 , 可能存在线程安全问题
- 局部变量:栈内存 ,局部变量不共享。不会存在线程安全问题,局部变量在栈中,一个线程一个栈
- 常量:不可修改,不会有线程安全问题。
- 因为使用了线程安全,所以会降低执行效率,当使用局部变量的时候,建议使用非线程安全的类型——提高效率
- StringBulid 非线程安全
- ArrayList 非线程安全
- HashMap 非线程安全
- Vector 线程安全
- Hashtable 线程安全
开发中解决线程安全问题
- synchronized在开发中不是首先项,因为执行效率降低,用户体验感差。在不得已的情况下使用线程同步机制
- 解决线程安全问题
- 方案一:尽量使用局部变量代替“实例变量和静态变量”
- 方案二:如果必须是实例变量,可以考虑创建多个对象,只有实例变量的内存不再共享。一个线程一个对象,对象不共享,就没有数据安全问题
- 方案三:不能使用局部变量、对象不能创建多个,则使用synchronized。
守护线程
- java语言中的线程分为两大类
- 用户线程 : 主线程main方法是一个用户线程
- 守护线程(后台线程) :代表性的线程—垃圾回收器线程(守护线程)
- 守护线程的缺点
- 守护线程是一个死循环,所有的用户线程只要结束,守护线程自动结束
- 守护线程的使用
- 每天定时数据备份,将定时器设置为守护线程,每到约定的时间就备份一次。所有的用户线程如果结束,守护线程自动退出,没有必要进行数据备份。
- 假设设置一个线程,要求该线程随着main线程的结束而结束,将该线程在开始之前设置为守护线程。t.setDaemon(true);
定时器
- 间隔特定的时间,执行特定的程序
- 采用sleep方法 设置睡眠时间
- java类库中已经写好一个定时器,java.until.Timer
- 现实开发中,较多使用Spring框架的SpringTask框架,因为这个框架简单配置,就可以实现定时器功能。
生产者 消费者 模式
- 生产线程负责生产,消费线程负责消费。生产和消费要达到均衡。这样的特殊情况需要使用到wait、notify方法
- wait和notify方法不是线程对象的方法,是java中任何java对象都有的方法,因为这两个方法都是Object类中自带的。
- wait和notify方法建立在线程同步的基础之上,因为多线程要同时操作一个仓库,有线程安全问题。
- Object o = new Object();
- o.wait();
- o.notifyAll(); 唤醒o对象上处于等待的所有线程
- 注意:下图中,仓库是共享的,那么wait和notify方法是被仓库调用的!仓库的wait方法。可以让在仓库上活动的线程进入等待状态…
- 模拟需求:仓库采用List集合。List集合假设只能存储一个元素,一个元素就代表仓库存满,如果List集合元素个数是0 就代表仓库为空,保证List集合中永远最多存储一个元素
package Advance.thread;
import java.util.ArrayList;
import java.util.List;
/**
* @author 衣鱼
* 使用wait方法和notify方法实现“生产者消费者模式”
*
* 模拟需求
* 仓库采用List集合
* List集合假设只能存储一个元素
* 一个元素就代表仓库存满
* 如果List集合元素个数是0 就代表仓库为空
* 保证List集合中永远最多存储一个元素
*/
public class ThreadTest10 {
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;
}
public void run() {
//一直生产
while(true) {
synchronized(list) { //——加锁
if(list.size()>0) { //证明仓库满了,有产品
try {
list.wait(); //——放锁
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//仓库没有产品 生产产品
Object o = new Object();
list.add(o);
System.out.println(Thread.currentThread().getName()+"——>"+o);
//唤醒消费者消费
list.notify();
}
}
}
}
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//产品还可以被购买
Object obj =list.remove(0); //根据下标删除
System.out.println(Thread.currentThread().getName()+"——>"+obj);
//唤醒生成者生产
list.notify();
}
}
}
}