1.线程的获取
Java中所有的代码都是靠线程运行的,main方法也不例外。当JVM启动后就会创建一条线程来执行main方法,并且该线程被赋予了一个名字也叫"main",我们一般称呼它为"主线程"。但是它和我们创建的线程本身没有区别。
线程提供了一个静态方法:
static Thread currentThread()
该方法可以获取运行这个方法的线程。
2.守护线程
-
也称为"后台线程"。是通过普通线程(也称为用户线程)调用方法:
setDaemon(true)
-
守护线程在结束时机上与普通线程有一点不同,那就是进程的结束。
进程结束:
- 当java进程中所有的普通线程都结束时,进程就会结束,此时它会杀死所有还在运行的守护线程。
3.线程的优先级
- 当一个线程调用start方法后就纳入到了线程调度器中被统一管理,此时线程只能被动的被分配时间。
- 片并发运行,是不能主动索取时间片的。而线程调度器会尽可能均匀的分配时间片给每个线程。
- 通过调整线程的优先级可以最大程度的改善获取时间片的概率。优先级越高的线程获取时间片的次数越多。
- 线程的优先级有10个等级,分别中整数1-10表示。其中1最低,5为默认值,10最高。
- 常量:MIN_PRIORITY,NORM_PRIORITY,MAX_PRIORITY。
分别表示:最低优先级, 默认优先级, 最高优先级
4.sleep阻塞
-
线程提供了一个静态方法:
static void sleep(long ms)
-
当一个线程调用sleep方法后就会进入阻塞状态指定毫秒。
-
sleep方法要求我们必须处理中断异常:InterruptedException
-
当一个线程调用sleep方法处于睡眠阻塞的过程中若该线程的interrupt方法被调用,那么此时会强制中断它的睡眠阻塞并抛出异常InterruptedException
5.多线程
- 多线程可以并发执行多个代码片段。
- 线程的第一种创建方式:
- 继承Thread重写run方法。
public class ThreadDemo1 {
public static void main(String[] args) {
Thread t1 = new MyThread1();
Thread t2 = new MyThread2();
/*
要将线程运行起来需要调用start方法,而不是直接调用run方法!
当start方法执行完毕,线程的run方法会很快的被自动执行。
*/
t1.start();
t2.start();
}
}
/*
该种方式创建线程的优点:
结构简单,利于匿名内部类形式创建
缺点:
1:存在继承冲突问题,因为java是单继承。继承了Thread就无法再继承其它类去复用方法
2:定义线程的同时就将任务定义在里面(重写了run方法),这导致线程与任务存在了必然的耦合
关系,不利于线程的重用。
*/
class MyThread1 extends Thread{
public void run(){
for(int i=0;i<1000;i++){
System.out.println("你是谁啊?");
}
}
}
class MyThread2 extends Thread{
public void run(){
for(int i=0;i<1000;i++){
System.out.println("开门!查水表的!");
}
}
}
- 可以实现Runnable接口来单独定义线程任务
public class ThreadDemo2 {
public static void main(String[] args) {
//创建线程任务
Runnable r1 = new MyRunnable1();
Runnable r2 = new MyRunnable2();
//创建两个线程
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
class MyRunnable1 implements Runnable{
public void run(){
for(int i=0;i<1000;i++){
System.out.println("你是谁啊?");
}
}
}
class MyRunnable2 implements Runnable{
public void run(){
for(int i=0;i<1000;i++){
System.out.println("我是查水表的!");
}
}
}
- 使用匿名内部类的形式完成线程的两种创建
public class ThreadDemo3 {
public static void main(String[] args) {
Thread t1 = new Thread(){//继承Thread重写run方法的形式
public void run(){
for(int i=0;i<1000;i++){
System.out.println("你是谁啊?");
}
}
};
// Runnable r2 = new Runnable() {//实现Runnable接口重写run方法的形式
// @Override
// public void run() {
// for(int i=0;i<1000;i++){
// System.out.println("我是查水表的!");
// }
// }
// };
//Runnable可以使用lambda表达式创建
Thread t2 = new Thread(()->{
for(int i=0;i<1000;i++){
System.out.println("我是查水表的!");
}
});
t1.start();
t2.start();
}
}
5.1 多线程并发安全问题
- 当多个线程并发操作同一临界资源,由于线程切换实际不确定,导致操作顺序出现混乱,从而导致
- 程序bug或更严重的程序问题。
- 通俗的讲就是当多个线程同时"抢"同一个东西产生了混乱。
- 临界资源:同一时刻只能被单线程操作的资源称为临界资源。
5.2 同步块的使用
- 有效的缩小同步范围可以在保证并发安全的前提下尽可能的提高并发效率。
语法:
synchronized(同步监视器对象){
需要多个线程同步执行的代码片段
}
- 同步和异步执行:这两种执行都是说多线程的执行。
- 同步执行:多个线程的执行过程出现了先后顺序。
- 异步执行:多个线程各执行各的,之间没有顺序。
静态方法上使用synchronized后,那么该方法一定具有同步效果。
public class SyncDemo3 {
public static void main(String[] args) {
Boo b1 = new Boo();
Boo b2 = new Boo();
Thread t1 = new Thread(){
public void run(){
b1.dosome();
}
};
Thread t2 = new Thread(){
public void run(){
b2.dosome();
}
};
t1.start();
t2.start();
}
}
class Boo{
/*
静态方法上使用synchronized后,那么同步监视器对象为当前类的类对象。
即:Class的实例。
知识引入:JVM中每一个被加载的类都有且只有一个Class的实例与之对应,该实例称为
这个类的类对象。
获取类对象的方式:类名.class
该知识点会在后期反射中详细说明
*/
// public static synchronized void dosome(){
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() + ":执行dosome方法完毕!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
5.3 互斥锁
- 当使用多个synchronized锁定多个代码片段,但是指定的锁对象相同时,这些代码片段就是互斥的
- 即:多个线程不能同时执行这些代码片段
public class SyncDemo4 {
public static void main(String[] args) {
Foo foo = new Foo();
Thread t1 = new Thread(){
public void run(){
foo.methodA();
}
};
Thread t2 = new Thread(){
public void run(){
foo.methodB();
}
};
t1.start();
t2.start();
}
}
class Foo{
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) {
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) {
e.printStackTrace();
}
}
}
}
6. 查看线程相关信息的一组方法
public class ThreadInfoDemo {
public static void main(String[] args) {
Thread main = Thread.currentThread();//获取主线程
//获取线程的名字
String name = main.getName();
System.out.println(name);
//获取线程的唯一标识
long id = main.getId();
System.out.println(id);
//获取线程的优先级(是一个数字,范围在1-10之间)
int priority = main.getPriority();
System.out.println("优先级:"+priority);
//查看是否为守护线程
boolean daemon = main.isDaemon();
//查看是否活着
boolean alive = main.isAlive();
//查看是否被中断了
boolean interrupted = main.isInterrupted();
System.out.println("是否为守护线程"+daemon);
System.out.println("是否活着"+alive);
System.out.println("是否被中断"+interrupted);
}
}