平时总是借鉴和学习大佬和大神们写的代码,第一次上手写,心情十分激动。这是我在java入门道路上的一些新得体会,希望能带给你帮助。
多线程的含义
多线程可以让多个代码片段"同时"运行,实际上是并发运行的,多个代码片段是走走停停的,并不是所谓的同时运行,只是电脑效率太快,相对于我们而言,成了同时运行。
多线程的创建
因为我现在还未学习到线程池,可能有一些片面。在后续学习中我会有相应的改进。
创建线程有两种方式:
- 定义一个类继承Thread并重写run方法:
优点:第一种创建线程的方式优点为:简单,直接,适合匿名内部内形式创建。
缺点:
1.继承Thread,由于java是单继承,这会导致无法再继承其他类复用方法,实际开发很不便
2.线程任务定义在在线程中,导致线程复用性变差。
//此处使用到了匿名内部类的形式
//(运用匿名内部内建立的函数,不能用于需要重复建立此新线程的开发任务中)
/*
* java有一个语法要求:当一个方法的局部内部类中使用了该方法的
* 其它局部变量时,要求这个变量必须是final的
* JDK8之后,final关键字可以不写,但是这个语法要求还是在,
* 这意味着局部内部类中是不能修改该变量的值的,如果要修改,只能将该
* 变量定义为属性(或挪到该方法之外的其他地方定义)
*/
public static void main(String[] args){
new Thread(){
@Override
public void run() {
for(int i=0;i<1000;i++){
System.out.println("送水滴!");
}
}.start();
}
- 实现Runnable接口并单独定义线程任务:
1.第二种方式针对第一种有了相应的改进,但是与之对应的是在建立的同时代码变得麻烦了,不过用匿名内部类建立,代码量也是相差甚少。
// 实现Runnable接口并单独定义线程任务:
public static void main(String[] args){
new Thread(new Runnable(){
@Override
public void run() {
for(int i=0;i<1000;i++){
System.out.println("送水滴!");
}
}).start;
}
- 在网上搜了一下,搜到了一个可能出现在笔试题上的题目,故此分享给大家:
//面试官可能会问下面的代码块运行后,会输出什么内容
//运行后的答案为:你好呀!(大家在此要记住一点【就近原则】)
public static void main(String[] args) {
new Thread(new Runnable(){
@Override
public void run() {
System.out.println("hello");
}
}){
@Override
public void run() {
System.out.println("你好呀!");
}
}.start();
}
多线程API中方法介绍
java.lang.Thread类下的方法实属太多,我在此将讲解一些用得比较多的。
获取运行在该方法上的线程
public static void main(String[] args) {
//获取Main方法的线程(主线程)
Thread main=Thread.currentThread();
System.out.println("运行main方法的线程是:"+main);
//运行main方法的线程是:Thread[main, 5 , main]
// 线程名 优先级 最外层线程名
}
设置线程的优先级
/**
* 线程优先级
* 线程有10个优先级,分别用整数1—10来表示
* 其中1表示最低优先级,10是最高优先级,5是默认值。
* 理论上优先级越高的线程获取时间片的次数越多
* (实际上主要还是得看线程调度器分配的时间片,
* 只不过线程只给我们提供了此类方法来修改优先级)
*/
//最小级别0
min.setPriority(Thread.MIN_PRIORITY);
//中级(默认为5)
norm.setPriority(Thread.NORM_PRIORITY);
//最大级别10
max.setPriority(Thread.MAX_PRIORITY);
//这些是Thread类提供给我们的属性,我们完全可以采用1-10的整数
//来进行表示
设置守护线程
守护线程:
守护线程也称为后台线程,默认创建的线程都是普通线程,
守护线程是通过调用线程的setDaemon方法转变为的。
守护线程使用上与普通线程没有区别,但是在结束时机上
有一点不同:进程的退出。
当一个线程中所有的普通线程都结束时,进程就会结束,
此时所有正在运行的守护线程会被强制杀掉。
此时有一点需要强调(main方法不是守护线程,只有调用此方法才称之为守护线程)
public final void setDaemon(boolean on)
当参数为true时,将该线程转换为守护线程,需要注意:
必须在线程启动前进行守护线程转换。
等待该线程终止
线程的join方法用于协调线程之间的"同步运行",
线城是并发运行的,感官上是各干各的,这种运行方式称为异步运行。
实际开发中我们也需要让多个线程在特定环节上有顺序的运行,这种操作称为同步运行。join方法运行调用该方法的线程在该方法所属线程上等待(阻塞),直到其执行完毕后再继续后续工作。
- public final void join();
阻塞当前线程
- public static void sleep(long ms)
使运行该方法的线程阻塞指定毫秒,超时后线程会自动回到
可运行状态再次等待获取时间片并发运行。
sleep方法要求我们处理中断异常。 - 当一个线程调用sleep方法处于阻塞的过程中,此时该线程的中断方法被调用了,那么sleep方法会立即抛出中断异常InterruptedException。相当于中断了该线程的阻塞状态。
暂停当前线程,运行其他线程
public static void yield()
synchronized
多个线程并发操作统一临界资源时,由于线程切换的时机不太确定,导致执行操作的顺序混乱,
- 未按照代码设计顺序进行而出现的各种问题。
- 临界资源:只能被单线程操作的资源。
存在两种情况
第一种:普通方法
- 第一种方式:
当一个方法使用synchronized关键字修饰后,该方法称为"同步方法",即:不允许多个线程同时在方法内部执行。
public synchronized int getBean(){
if(beans==0){
throw new RuntimeException("没有豆子了");
}
return beans--;
}
- 第二种方式:同步代码块
同步块
语法: synchronized(同步监视器对象){
需要同步执行的代码片段
}
同步块可以更准确的锁定多个线程同步执行的代码片段,
有效滴缩小同步范围可以在保证开发安全的前提下尽可能的提高开发效率。
/*
* 同步块要求指定同步监视器对象。即:
* 上锁的对象.该对象可以是java中任何引用类型的实例。
* 只要保证多个需要同步执行该代码块的线程看到的这个对象是同一个即可。
*/
synchronized(this){
System.out.println(t.getName()+":正在试衣服!");
Thread.sleep(5000);
}
第二种:静态方法
静态方法全局就一份,所以在静态方法上使用synchronized修饰,
那么该方法一定具有同步效果
* 静态方法指定的同步监视器对象为当前类的类对象。(class的实例)
* JVM中每个被加载的类都有且只有一个Class的实例与之对应。
* 这就是类对象,反射知识点会介绍。
public static void dosome(){
synchronized (Boo.class) {
try {
Thread t=Thread.currentThread();
System.out.println(t.getName()+"正在执行dosome()方法...");
Thread.sleep(5000);
System.out.println(t.getName()+"执行完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
互斥锁
- 当使用synchronized锁定多个代码片段时,指定的同步监视器对象是同一个时,这些代码片段就是互斥的
- 所以synchronized可以是同步锁也可以是互斥锁
public synchronized void methodA(){
try {
Thread t=Thread.currentThread();
System.out.println(t.getName()+":执行A方法...");
Thread.sleep(5000);
System.out.println(t.getName()+":A方法执行结束!");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void methodB(){
synchronized (this) {
try {
Thread t=Thread.currentThread();
System.out.println(t.getName()+":执行B方法...");
Thread.sleep(5000);
System.out.println(t.getName()+":B方法执行结束!");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
//当两个方法这样用synchronized修饰后,就会产生互斥的作用