多线程
1. 常用方法:
-
start():启动当前线程;调用当前线程的run()
-
run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
-
currentThread():静态方法,返回执行当前代码的线程
-
getName():获取当前线程的名字
-
setName():设置当前线程的名字
-
yield():释放当前cpu的执行权
-
join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
-
stop():已过时。当执行此方法时,强制结束当前线程。
-
sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
-
isAlive():判断当前线程是否存活
代码示例:
- 匿名内部实现Runnable接口
@Test
public void test01() {
Runnable r = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
};
for (int i = 0; i < 50; i++) {
if (i % 5 == 0) {
new Thread(r, "线程1").start();
new Thread(r, "线程2").start();
}
}
}
运行结果:
- sleep()方法:
class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i < 50;i++){
if (i%5 == 0) {
try {
System.out.println("睡眠开始");
MyThread.sleep(1000);
System.out.println("睡眠结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName() + " : " + i);
}
}
}
}
public class SleepT {
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
}
}
运行结果:
每打印一次都会等待1S以上
- join()方法的使用:
class MyThread1 extends Thread{
@Override
public void run() {
for(int i=0;i < 50;i++){
System.out.println(this.getName() + " : " + i);
}
}
}
public class JoinT {
public static void main(String[] args) throws InterruptedException {
MyThread1 mt = new MyThread1();
mt.start();
mt.join();
for(int i=0;i < 20;i++){
System.out.println("--------------当前线程----------------" + Thread.currentThread().getName());
}
}
}
运行结果:
可以看到,Thread-0线程结束后main线程才开始执行
- yield()方法得使用
public class YThread {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i < 100;i++){
if (i == 20) {
System.out.println("线程让步");
Thread.currentThread().yield();
System.out.println("线程回来");
}else {
System.out.println("线程1" + " : " + i);
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i=0;i < 100;i++){
System.out.println("线程2" + " : " + i);
}
}
});
t1.start();
t2.start();
}
}
运行结果:
可以看到,让步后的线程暂时会失去CPU时间片的争夺权
2. 线程的分类:
守护线程
- 最后执行的线程,一般用于JVM的垃圾回收机制
用户线程
- 用户线程就是普通线程
守护线程
class D1 extends Thread{
@Override
public void run() {
for(int i=0;i < 2000;i++){
System.out.println("-----------守护线程执行------------" + this.getName() + ":" + i);
}
}
}
class D2 extends Thread{
@Override
public void run() {
for(int i=0;i < 1000;i++){
System.out.println("-----------------------" + this.getName() + ":" + i);
}
}
}
public class DThread {
public static void main(String[] args) {
for(int i=0;i < 100;i++){
System.out.println("------当前线程执行" + Thread.currentThread().getName());
}
D1 d1 = new D1();
//在启动线程之前设置为守护线程
d1.setDaemon(true);
d1.setName("守护线程");
d1.start();
D2 d2 = new D2();
d2.setName("普通线程");
d2.start();
}
}
运行结果:
可以看到,守护线程都是最后执行的且不一定需要执行完毕的,要根据是否还有用户线程在运行来判断自身是否要提早结束
同时,守护线程的执行周期要比其他所有普通线程要长,确保最后执行,且能够对普通线程的执行结果做处理
3. 线程的优先级:
线程的优先级分为10个等级:1-10
最高等级:MAX_PRIORITY:10
最低等级:MIN _PRIORITY:1
默认等级:NORM_PRIORITY:5
优先级越高,线程分到时间片的概率也就越大,但这并不是说优先级高的线程一定比优先级低的线程先执行完
两个方法:
-
getPriority() :返回线程优先值
-
setPriority(int newPriority) :改变线程的优先级
说明:
-
线程创建时继承父线程的优先级
-
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用
代码示例:
class T1 extends Thread{
String name;
public T1(String name) {
super();
this.name = name;
}
@Override
public void run() {
for(int i=0;i < 50;i++){
System.out.println(name + " : " + i);
}
}
}
public class PThread {
public static void main(String[] args) {
T1 t1 = new T1("大佬");
T1 t2 = new T1("小弟");
//设置大佬的优先级为10
t1.setPriority(10);
//设置大佬的优先级为1
t2.setPriority(1);
t1.start();
t2.start();
}
}
运行结果:
可以看到,"大佬"占用的时间片比较多
4. 线程的生命周期:
-
新建
当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建 状态 -
就绪
处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已 具备了运行的条件,只是没分配到CPU资源 -
运行
当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线 程的操作和功能 -
阻塞
在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中 止自己的执行,进入阻塞状态 -
死亡
线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
5. 同步机制:
当多条线程并发执行时,可能会发生两条线程都要处理同一个数据的情况,就比如两个人同时上网去买火车票,火车票只有一张了,如果没有同步机制,那么两个人都会买到火车票,但是其实火车票只有一张,这样就会闹出乌龙
synchronized是JDK为多线程提供的一种同步锁,如果多条线程需要处理同一个对象,就需要用到线程同步
synchronized说明:
-
既可以同步对象,也可以同步方法
-
先解锁方法,再解锁对象
代码示例:
public class Test4 {
public static void main(String[] args) {
Shower s = new Shower();
new Thread() {
@Override
public void run() {
//调用方法一
s.show();
}
}.start();
new Thread() {
@Override
public void run() {
//调用方法二
s.show2();
}
}.start();
}
}
//先解锁方法再解锁对象
class Shower {
//锁方法
public synchronized void show() {
System.out.print("长");
System.out.print("恨");
System.out.print("人");
System.out.print("心");
System.out.print("不");
System.out.print("如");
System.out.print("水");
}
//锁对象
public void show2() {
synchronized (this){
System.out.print("等");
System.out.print("闲");
System.out.print("平");
System.out.print("地");
System.out.print("起");
System.out.print("波");
System.out.print("澜");
}
}
}
运行结果:
当然,上面提到的买票也可以加锁:
class Tickets2 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
//注意锁一定要加在判断的外面
synchronized (Tickets2.class) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "开始售票");
System.out.println(Thread.currentThread().getName() + "正在售票,票号为" + (ticket--));
System.out.println(Thread.currentThread().getName() + "已售票");
} else {
break;
}
}
}
}
}
public class Test3 {
public static void main(String[] args) {
Tickets2 t = new Tickets2();
Thread t1 = new Thread(t, "线程1");
Thread t2 = new Thread(t, "线程2");
Thread t3 = new Thread(t, "线程3");
t1.start();
t2.start();
t3.start();
}
}
运行结果:
可以看到,每个线程都是等待其他线程执行完才开始执行的
Lock(锁)
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和
内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以
显式加锁、释放锁。
代码格式:
public class Test7 {
private final ReentrantLock lock = new ReentrantLock();
public void m() {
//加锁
lock.lock();
try {
//保证线程安全的代码
} catch (Exception e) {
// 异常处理
e.printStackTrace();
}finally {
//关锁,一定要关锁!!!
lock.unlock();
}
}
}
synchronized 与 Lock 的对比
-
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是
隐式锁,出了作用域自动释放 -
Lock只有代码块锁,synchronized有代码块锁和方法锁
-
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有
更好的扩展性(提供更多的子类)
优先使用顺序:
Lock -> 同步代码块(已经进入了方法体,分配了相应资源) 同步方法
(在方法体之外)
代码示例:
public class Test7 {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
new Thread(new Runnable() {
@Override
public void run() {
//同步锁
lock.lock();
try {
// 保证线程安全的代码
System.out.println("第1次获得锁" + lock);
int count = 1;
while (true) {
try {
//上一个锁需要等待这个锁解锁
lock.lock();
System.out.println("第" + (++count) + "次得锁" + lock);
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
} finally {
lock.unlock();
}
if (count == 10) {
break;
}
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
} finally {
lock.unlock();
}
}
}).start();
}
}
运行结果:
多线程这一块还得多研究…