多线程
多线程
一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc()
垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
实现多线程的方式一: 继承Thread类
-
方法介绍
void run(): 在线程开启后,吃方法执行
void start(): 开启线程的标志,Jvm在这之后会调用run() 方法
-
实现步骤:
- 定义一个继承了Thread的子类
- 在子类中重写run()方法 //将此线程要做的事情 写在run方法中
- 创建子类的对象
- 启动线程
public class MyThread extends Thread{ @Override public void run() { for (int i = 0; i <10 ; i++) { System.out.println(Thread.currentThread().getName());//线程默认提供了name 0 1 2 类推 currentThread():静态方法,返回当前代码执行的线程 System.out.println(i); } } } public class TestMyThread { public static void main(String[] args) { MyThread my1 = new MyThread(); MyThread my2 = new MyThread(); my1.start(); //一个线程只能start一次 my2.start(); } }为什么重写run方法?
因为run()是用来封装被线程执行的代码
run()方法和start()方法的区别?
run(): 封装线程执行的代码,直接调用,相当于普通方法的调用
start(): ①启动当前线程;②然后由JVM调用此线程的run()方法
设置和获取线程的名称
void setName(String name) 将此线程的名称更改为等于参数name
String getName(): 返回此线程的名称
Thread currentThread(): 放回当前正在执行的线程对象的引用
public class MyThread extends Thread{
public MyThread(String name) {
super(name);
}
public MyThread() {
}
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
System.out.println(getName()+""+i);
}
}
}
class TesetDemo{
public static void main(String[] args) {
// 调用的无参构造方法 void setName()
// MyThread m1 = new MyThread();
// MyThread m2 = new MyThread();
//
// m1.setName("飞机");
// m2.setName("汽车");
//
// m1.start();
// m2.start();
//有参构造方法 void setName(String name)
MyThread my1 = new MyThread("飞机");
MyThread my2 = new MyThread("高铁");
my1.start();
my2.start();
}
}
线程优先级
线程调度:
-
两种调度方式
-
分时调度模式:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
-
抢占式调度模型: 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取cpu时间片相对多一些。
-
java使用的是抢占式调度模式
-
随机性
- 假如计算机之后一个CPU,那么CPU在某一个时刻只能执行一条命令,线程只有得到CPU时间片,也就是使用权,才可以执行指令,所以说多线程的执行是随机的。
优先级相关方法:
final int getPriority(): 返回此线程的优先级
final void setPriority(int new Priority): 更改线程优先级,默认是5; 范围是 1-10
更改优先级并不能保证线程一定是第一个执行,只是CPU获取它的概率更大
-
线程控制
相关方法
static void sleep(long millis): 使当前执行的线程停留(暂停执行)指定的毫秒数
void join(): 等待这个线程死亡
void setDaemon(boolean on): 将此线程标记为守护线程,当运行的线程都是守护线程时,JVM 将退出
守护线程: 当被守护的线程结束后,守护它的线程也会相继结束
public class TheadDaemon extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+ ":"+i);
}
}
}
class TheadDaemonDemo{
public static void main(String[] args) {
TheadDaemon td1 = new TheadDaemon();//创建对象
TheadDaemon td2 = new TheadDaemon();
td1.setName("关羽");
td2.setName("张飞");
//设置主线程为刘备
Thread.currentThread().setName("刘备");
//设置守护线程
td1.setDaemon(true);
td2.setDaemon(true);
td1.start();
td2.start();
for (int i = 0; i < 10; i++) {//当被守护的线程执行结束后其他的线程也会跟着结束
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
实现线程的第二种方式: 实现Runnable接口
-
Thread构造方法
Thread(Runnable target): 分配一个新的Thread对象
Thread(runnable target,String name): 分配一个新的Thread对象
-
实现步骤
- 定义一个类实现Runnable接口
- 在子类中重写run() 方法
- 创建子类的对象
- 创建Thread类的对象,把子类对象作为构造方法的参数
- 启动线程
public class MyRunnable implements Runnable {
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName()+":"+i);
}
} }
public class MyRunnableDemo {
public static void main(String[] args) {
// //
} }
//创建MyRunnable类的对象
MyRunnable my = new MyRunnable();
//创建Thread类的对象,把MyRunnable对象作为构造方法的参数
//Thread(Runnable target)
Thread t1 = new Thread(my);
Thread t2 = new Thread(my);
//Thread(Runnable target, String name) Thread t1 = new Thread(my,"高铁");
Thread t2 = new Thread(my,"飞机");
//启动线程 t1.start(); t2.start();
}
}
-
多线程的实现方法有两种
- 继承Thread类: (类似于创建三个线程,三个线程各自去完成自己的事情(各自循环10次))
- 实现Runnable接口: (创建了三个线程,三个线程同时处理同一件事情(三个线程一共执行10次)
-
相比继承Thread类,实现Runnable接口的好处
- 避免了java单继承的局限性
- 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码,数据有效分离,较好的实现了面向对象的设计思想
同步代码块解决数据安全问题
-
安全问题出现的条件
- 是多线程环境
- 有共享数据
- 有多条语句操作共享数据
-
如何解决多线程问题?
- 打破其中的一条: 基本思想是:让程序没有安全问题的环境
-
怎样实现?
- 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行
- Java提供了同步代码块的方法来解决
-
同步代码块的格式:
同步监视器:俗称: 锁! 任何类的对象都可以充当锁 要求:多个线程必须要共享同一把锁!要保证唯一性 synchronized(同步监视器){ 多条语句操作共享数据的代码 //也就是需要同步的代码 操作共享数据的代码,即为需要被同步的代码 }synchronize(任意对象):就相当于给代码加了锁,任意对象就可以看成是一把锁
-
同步的好处和弊端
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这很耗费资源,无形中降低了运行效率
注意: 使用同步时看实现的方式是继承了Thread方法,还是实现了Runnable接口,并且注意是创建了一个对象,还是多个对象,如果是一个对象,在创建锁的时候,可以不加static就能确保唯一性,如果是多个对象,加上static类确保锁的唯一性。 相对简单的锁的对象可使用 this,此时的this表示的是唯一的对象,多个对象的时候不能使用 this,或者直接(xxx.class),在继承Thread类创建多线程的时候,谨慎使用this来确定唯一性
-
同步方法格式
- 如果操作共享数据的代码正好在一个方法中,我们不妨将此方法声明为同步方法
同步方法: 就是把synchronize关键字加到方法上
修饰符 synchronize 返回值类型 方法名(方法参数){ 方法体; }同步方法锁的对象是什么呢?
类名.class
使用继承时创建了三个对象,但是默认的监视器锁是this,这样就会造成锁的对象不唯一,线程依然不安全。将方法设置成static的时候,锁是 类名.class
1,同步方法依然涉及到同步监视器,只是不需要显示的声明。
2,非静态的同步方法,同步监视器是:this
静态方法,同步监视器是: 当前类本身
线程的死锁问题
死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要同步的资源,就形成了线程的死锁
死锁出现后,不会出现异常,不会有提示,只是所有的线程都处于阻塞状态,无法继续。
Lock(锁)
Lock是接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常有的是ReentrantLock,可以显式加锁、释放锁。
1,面试题: synchronized 与Lock的异同?
同: 都可以解决线程的安全问题
不同:synchronized,在执行完相应的同步代码块之后,会自动释放同步监视器
Lock需要手动的启动同步(Lock()),同时结束同步也需要手动的结束(unlock())
线程通信
涉及到的三个方法:
wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify():一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程就唤醒优先级高的那个。
notifyAll():一旦执行此方法,就会唤醒被wait的所有线程
说明:
1,wait(),notify(),notifyAll()三个方法必须使用在同步代码块或者同步方法中。
2,wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或者同步方法中的同步监视器,否则会出现 IllegalMonitorStateException异常
3,wait(),notify(),notifyAll()三个方法定义在java.lang.Object类中的
面试题:sleep()方法和wait() 的异同?
1、相同点:一旦执行都会是当前线程进入阻塞。
2、不同点:1)两个方法声明的位置不同:Thread类中声明sleep(),Object类中声明wait();
2)调用范围不同:
sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中调用。
3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁(同步监视器)
浙公网安备 33010602011771号