您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

Java多线程:使用Thread类

2024/6/8 21:21:28发布28次查看
thread类的基本用法1.创建子类,继承自thread并且重写run方法:
class mythread extends thread { @override public void run() { system.out.println("hello thread"); }}public class demo1 { public static void main(string[] args) { // 最基本的创建线程的办法. thread t = new mythread(); //调用了start方法才是真正的在系统中创建了线程,执行run方法 t.start(); }}
2.创建一个类,实现runnable接口再创建runnable是实例传给thread
class myrunnable implements runnable{ @override public void run() { system.out.println("hello"); }}public class demo3 { public static void main(string[] args) { thread t = new thread(new myrunnable()); t.start(); }}
3.匿名内部类
创建了一个匿名内部类,继承自thread类,同时重写run方法,再new出匿名内部类的实例
public class demo4 { public static void main(string[] args) { thread t = new thread(){ @override public void run() { system.out.println("hello"); } }; t.start(); }}
new的runnable,针对这个创建的匿名内部类,同时new出的runnable实例传给thread的构造方法
public class demo5 { public static void main(string[] args) { thread t = new thread(new runnable() { @override public void run() { system.out.println("hello"); } }); t.start(); }}
4.lambda表达式 lambda代替runnable
public class demo6 { public static void main(string[] args) { thread t = new thread(() ->{ system.out.println("hello"); }); t.start(); }}
线程指标1.isdaemon();
是否后台线程 后台线程不影响进程退出,不是后台线程会影响进程退出
2.isalive();
是否存活 在调用start前系统中是没有对应线程的,run方法执行完后线程就销毁了,t对象可能还存在
3.isinterrupted();
是否被中断
run和start的区别run单纯的只是一个普通方法描述了任务的内容 start则是一个特殊的方法,内部会在系统中创建线程
中断线程线程停下来的关键是要让对应run方法执行完,对于main线程来说main方法执行完了才会终止
1.手动设置标志位
在线程中控制这个标志位就能影响到这个线程结束,但是此处多个线程共用一片虚拟空间,因此main线程修改的isquit和t线程判断的isquit是同一个值
public class demo10 { // 通过这个变量来控制线程是否结束. private static boolean isquit = false; public static void main(string[] args) { thread t = new thread(() -> { while (!isquit) { system.out.println("hello thread"); try { thread.sleep(1000); } catch (interruptedexception e) { e.printstacktrace(); } } }); t.start(); // 就可以在 main 线程中通过修改 isquit 的值, 来影响到线程是否退出 try { thread.sleep(5000); } catch (interruptedexception e) { e.printstacktrace(); } // main 线程在 5s 之后, 修改 isquit 的状态. isquit = true; }}
2.使用thread中内置的一个标志位来判定 thread.interruted()这是一个静态方法 thread.currentthread().isinterrupted()这是一个实例方法,其中currentthread能够获取到当前线程的实例
public class demo7 { public static void main(string[] args) { thread t = new thread(() -> { while(!thread.currentthread().isinterrupted()){ system.out.println("hello"); try { thread.sleep(1000); } catch (interruptedexception e) { e.printstacktrace(); // 当触发异常之后, 立即就退出循环~ system.out.println("这是收尾工作"); break; } } }); t.start(); try{ thread.sleep(5000); }catch (interruptedexception e){ e.printstacktrace(); } // 在主线程中, 调用 interrupt 方法, 来中断这个线程. // t.interrupt 的意思就是让 t 线程被中断!! t.interrupt(); }}
需要注意的是调用这个方法t.interrupt()可能会产生两种情况:
1)如果t线程处在就绪就设置线程的标志位为true
2)如果t线程处在阻塞状态(sleep),就会触发一个interruptexeception
线程等待多个线程之间调度顺序是不确定的,有时候我们需要控制线程之间的顺序,线程等待就是一种控制线程执行顺序的手段,此处的线程等待只要是控制线程结束的先后顺序。
哪个线程中的join,哪个线程就会阻塞等待直到对应的线程执行完毕为止。
t.join();
调用这个方法的线程是main线程,针对t这个对象调用的此时就是让main等待t。
代码执行到join这一行就停下了,让t先结束然后main继续。
t.join(10000);
join提供了另一个版本为带一个参数的,参数为等待时间10s之后join直接返回不再等待
thread.currentthread()
能够获取当前线程的应用,哪个线程调用的currentthread就获取到哪个线程的实例 对比this如下:
对于这个代码来说,通过继承thread的方法来创建线程。此时run方法中直接通过this拿到的就是当前thread的实例
public class demo4 { public static void main(string[] args) { thread t = new thread(){ @override public void run() { system.out.println(thread.currentthread().getname()); system.out.println(this.getname()); } }; t.start(); }}
然而此处this不是指向thread类型,而是指向runnable,runnable只是一个单纯的任务没有name属性,要想拿到线程名字只能通过thread.currentthread()
public class demo5 { public static void main(string[] args) { thread t = new thread(new runnable() { @override public void run() { //err //system.out.println(this.getname()); //right system.out.println(thread.currentthread().getname()); } }); t.start(); }}
进程状态针对系统层面:
就绪
阻塞
java中thread类进一步细化:
new:把thread对象创建好了但是还没有调用start
terminated:操作系统中的线程已执行完毕销毁,但是thread对象还在获取到的状态
runnable:就绪状态,处在该状态的线程就是在就绪队列中,随时可以调度到cpu上
time_waiting:调用了sleep就会进入到该状态,join(超时时间) blocked:当前线程在等待锁导致了阻塞
waiting:当前线程在等待唤醒
状态转换图:
线程安全问题定义:操作系统中线程调度是随机的,导致程序的执行可能会出现一些bug。如果因为调度随机性引入了bug线程就是不安全的,反之则是安全的。
解决方法:加锁,给方法直接加上synchronized关键字,此时进入方法就会自动加锁,离开方法就会自动解锁。当一个线程加锁成功的时候,其他线程尝试加锁就会触发阻塞等待,阻塞会一直持续到占用锁的线程把锁释放为止。
synchronized public void increase() { count++;}
线程不安全产生的原因:
1.线程是抢占式执行,线程间的调度充满随机性。
2.多个线程对同一个变量进行修改操作
3.针对变量的操作不是原子的
4.内存可见性也会影响线程安全(针对同一个变量t1线程循环进行多次读操作,t2线程少次修改操作,t1就不会从内存读数据了而是从寄存器里读)
5.指令重排序,也是编译器优化的一种操作,保证逻辑不变的情况下调整顺序,解决方法synchronized。
内存可见性解决方法:
1.使用synchronized关键字 使用synchronized不光能保证指令的原子性,同时也能保证内存的可见性。被synchronized包裹起来的代码编译器就不会从寄存器里读。
2.使用volatile关键字 能够保证内存可见性,禁止编译器作出上述优化,编译器每次执行判定相等都会重新从内存读取。
synchronized用法在java中每个类都是继承自object,每个new出来的实例里面一方面包含自己安排的属性,另一方面包含了“对象头”即对象的一些元数据。加锁操作就是在这个对象头里面设置一个标志位。
1.直接修饰普通的方法使用synchronized的时候本质上是对某个“对象”进行加锁,此时的锁对象就是this。加锁操作就是在设置this的对象头的标志位,当两个线程同时尝试对同一个对象加锁的时候才有竞争,如果是两个线程在针对两个不同对象加锁就没有竞争。
class counter{ public int count; synchronized public void increase(){ count++; }}
2.修饰一个代码块需要显示制定针对那个对象加锁(java中的任意对象都可以作为锁对象)
public void increase(){ synchronized(this){ count++; }}
3.修饰一个静态方法相当于针对当前类的类对象加锁,类对象就是运行程序的时候。class文件被加载到jvm内存中的模样。
synchronized public static void func(){}
或者
public static void func(){ synchronized(counter.class){ }}
监视器锁monitor lock可重入锁就是同一个线程针对同一个锁,连续加锁两次,如果出现死锁就是不可重入锁,如果不会死锁就是可重入的。因此就把synchronized实现为可重入锁,下面的例子里啊连续加锁操作不会导致死锁。可重入锁内部会记录所被哪个线程占用也会记录加锁次数,因此后续再加锁就不是真的加锁而是单纯地把技术给自增。
synchronized public void increase(){ synchronized(this){ count++; }}
死锁的其他场景1.一个线程一把锁
2.两个线程两把锁
3.n个线程m把锁(哲学家就餐问题,解决方法:先拿编号小的筷子)
死锁的四个必要条件(前三个都是锁本身的特点)
1.互斥使用,一个锁被另一个线程占用后其他线程就用不了(锁的本质,保证原子性)
2.不可抢占,一个锁被一个线程占用后其他线程不可把这个锁给挖走
3.请求和保持,当一个线程占据了多把锁之后,除非显示的释放否则这些锁中都是该线程持有的
4.环路等待,等待关系成环(解决:遵循固定的顺序加锁就不会出现环路等待)
java线程类:
不安全的:arraylist,linkedlist,hashmap,treemap,hashset,treeset,stringbuilder
安全的:vector,hashtable,concurrenthashmap,stringbuffer,string
volatile禁止编译器优化保证内存可见性,产生原因:计算机想执行一些计算就需要把内存的数据读到cpu寄存器中,然后再从寄存器中计算写回到内存中,因为cpu访问寄存器的速度比访问内存快很多,当cpu连续多次访问内存结果都一样,cpu就会选择访问寄存器。
jmm(java memory model)java内存模型
就是把硬件结构在java中用专业的术语又重新抽象封装了一遍。
工作内存(work memory)其实指的不是内存,而是cpu寄存器。
主内存(main memeory)这才是主内存。
原因:java作为一个跨平台编程语言要把硬件细节封装起来,假设某个计算机没有cpu或者内存同样可以套到上述模型中。
寄存器,缓存和内存之间的关系
cpu从内存取数据太慢,因此把数据直接放到寄存器里来读,但寄存器空间太紧张于是又搞了一个存储空间,比寄存器大比内存小速度比寄存器慢比内存快称为缓存。寄存器和缓存统称为工作内存。
寄存器,缓存和内存之间的关系图
存储空间:cpu<l1<l2<l3<内存
速度:cpu>l1>l2>l3>内存
成本:cpu>l1>l2>l3>内存
volatile和synchronized的区别
volatile只是保证可见性不保证原子性,只是处理一个线程读和一个线程写的过程。
synchronized都能处理
wait和notify
等待和通知处理线程调度随机性问题的,join也是一种控制顺序的方式更倾向于控制线程结束。wait和notify都是object对象的方法,调用wait方法的线程就会陷入阻塞,阻塞到有线程通过notify来通知。
public class demo9 { public static void main(string[] args) throws interruptedexception { object object = new object(); system.out.println("wait前"); object.wait(); system.out.println("wait后"); }}
wait内部会做三件事;
1.先释放锁
2.等待其他线程的通知
3.收到通知后重新获得锁并继续往下执行
因此想用wait/notify就得搭配synchronized
public class demo9 { public static void main(string[] args) throws interruptedexception { object object = new object(); synchronized (object){ system.out.println("wait前"); object.wait(); system.out.println("wait后"); } }}
注意:wait notify都是针对同一对象来操作的,例如现在有一个对象o,有10个线程都调用了o.wait,此时10个线程都是阻塞状态。如果调用了o.notify就会把10个线程中的一个线程唤醒。而notifyall就会把所有10个线程全都给唤醒,此时就会竞争锁。
以上就是java多线程:使用thread类。的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product