1.线程命名与取得
若没有设置线程名字,则会自动分配一个线程名字
注:如果直接调用线程对象的run()方法,则run()方法里不能直接通过getName()方法来获得当前执行线程的名字,而是需要使用Thread.currentThread()方法先获得当前线程,再调用线程对象的getName()方法来获得线程的名字。
代码实现:
public class ThreadMethod extends Thread {
@Override
public void run() {
//this: ThreadMethod类的对象, getName()获取当前线程的名称
System.out.println(this.getName() + " thread(extends)");
}
}
public static void main(String[] args) {
Thread thread1 = new ThreadMethod();
thread1.setName("打印输出线程");
thread1.start();
for (int i = 0; i < 10; i++) {
Runnable runnable1 = new RunnableMethod();
Thread thread2 = new Thread(runnable1, "线程-" + i);
thread2.setName("业务线程-" + i);
thread2.start();
//主程序(程序入口)线程main
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
主方法本身就是一个线程,所有的线程都是通过主线程创建并启动的
每当使用了java命令去解释程序的时候,都表示启动了一个新的JVM进程。而主方法只是这个进程上的一个线程而已
线程死亡:
线程会以如下三种方式结束,结束后就处于死亡状态
(1)run()或call()方法执行完成,线程正常结束
(2)线程抛出一个未捕获的Exception或Error
(3)直接调用该线程的stop()方法来结束该线程——该方法容易导致死锁
2.线程休眠:sleep()方法
线程休眠,等到了预计时间之后再恢复执行(又到就绪状态)
sleep()方法有两种重载形式:
(1)static void sleep(long millis):让当前正在执行的线程暂停millis毫秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响。
(2)static void sleep(long millis,int nanos):让当前正在执行的线程暂停millis毫秒加nanos毫微秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响。
对于单核CPU:真正意义上是串行执行
对于多核CPU:同一时间真正意义上的并行执行
Thread.sleep:
(1)线程暂停执行,进入阻塞状态
(2)交出CPU
(3)不释放占用的资源锁
(4)休眠时间到了回到就绪状态
代码实现:
public static void main(String[] args) {
new Thread(() -> {
System.out.println("Start: " + LocalDateTime.now());
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("End: " + LocalDateTime.now());
}, "Thread-Sleep").start();
}
3.线程让步:yield()方法
(1)线程暂停执行,进入就绪状态
(2)交出CPU,但不确定具体时间
(3)不释放占用的资源锁
(4)相同优先级的线程拥有交出CPU的权限
调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
sleep()方法声明抛出了 异常,所以调用sleep()方法时要么捕获该异常,要么显示声明抛出该异常;而yield()方法则没有声明抛出任何异常
具体代码实现:
public static void main3(String[] args) {
Runnable runnable = () -> {
System.out.println("Start: " + LocalDateTime.now());
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread.yield();
}
System.out.println("End: " + LocalDateTime.now());
};
//单核CPU
new Thread(runnable, "Thread-Sleep-1").start();//sleep
new Thread(runnable, "Thread-Sleep-2").start();
new Thread(runnable, "Thread-Sleep-3").start();
}
4.join()方法
在线程A中调用线程B的join方法,线程A暂停执行,直到线程B执行完,线程A才继续执行。
join()方法通常由使用线程的程序调用,以将大问题划分成许多小问题,每个小问题分配一个线程。当所有的小问题都得到处理后,再调用主线程来进一步操作。
代码实现:
public static void main(String[] args) {
Thread thread = new Thread(new RunnableJoin(), "Thread-A");
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "main中的代码");
}
class RunnableJoin implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 执行开始时间 :" + LocalDateTime.now());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 大量代码");
System.out.println(Thread.currentThread().getName() + " 执行结束时间 :" + LocalDateTime.now());
}
}
5.线程停止
线程停止的三种方式:
(1) 设置标记位,可以是线程正常退出。
(2) 使用stop方法强制使线程退出,但是该方法不太安全所以已经被废弃了。
(3) 使用Thread类中的一个interrupt() 可以中断线程
代码实现:
class RunnableStop implements Runnable {
private boolean flag = true;
@Override
public void run() {
int i = 0;
while (flag) {
try {
//这里阻塞之后,线程被调用了interrupte()方法,
// 清除中断标志,就会抛出一个异常
// java.lang.InterruptedException
Thread.sleep(1000);//阻塞
//非阻塞
//此处有代码
boolean status = Thread.currentThread().isInterrupted();
if (status) {
System.out.println("非阻塞状态 " + status);
break;
}
i++;
System.out.println(Thread.currentThread().getName() + " 运行了 " + i + " 次");
} catch (InterruptedException e) {
e.printStackTrace();
//阻塞状态
boolean status = Thread.currentThread().isInterrupted();
//false 中断标志位清除
System.out.println("阻塞状态 " + status);
break;
}
}
System.out.println(Thread.currentThread().getName() + " 终于停了");
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
public static void main6(String[] args) {
RunnableStop runnableStop = new RunnableStop();
Thread thread = new Thread(runnableStop, "Thread-Stop-1");
//thread.stop();
thread.interrupt();
System.out.println(Thread.currentThread().getName() + "代码执行完了");
}
interrupt() 方法只是改变中断状态而已,它不会中断一个正在运行的线程
interrupt()方法并不会立即执行中断操作;具体而言,这个方法只会给线程设置一个为true的中断标志(中断标志只是一个布尔类型的变量),而设置之后,则根据线程当前的状态进行不同的后续操作。如果,线程的当前状
态处于非阻塞状态,那么仅仅是线程的中断标志被修改为true而已;如果线程的当前状态处于阻塞状态,那么在将中断标志设置为true后,还会有如下情况之一的操作:
如果是wait、sleep以及join三个方法引起的阻塞,那么会将线程的中断标志重新设置为false,并抛出一个InterruptedException;
如果在中断时,线程正处于非阻塞状态,则将中断标志修改为true,而在此基础上,一旦进入阻塞状态,则按照阻塞状态的情况来进行处理;
通过触发中断,则由开发者决定如何处理线程业务
线程的优先级:
设置优先级:
public final void setPriority(int newPriority)
取得优先级:
public final int getPriority()
对于优先级设置的内容可以通过Thread类的几个常量来决定
- 最高优先级:public final static int MAX_PRIORITY = 10;
- 中等优先级:public final static int NORM_PRIORITY = 5;
- 最低优先级:public final static int MIN_PRIORITY = 1;
主线程(main) 默认优先级是5
线程优先级具有继承性,如果线程A中创建线程B,则B的默认优先级继承线程A的优先级
具体的实现代码:
public static void main(String[] args) {
Thread thread1 = new Thread(() -> System.out.println(Thread.currentThread().getName() + "优先级" + Thread.currentThread().getPriority()), "Thread-1");
thread1.setPriority(10);
thread1.start();
System.out.println(Thread.currentThread().getPriority());
//main默认优先级是5
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "优先级" + Thread.currentThread().getPriority());
Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "优先级" + Thread.currentThread().getPriority());
}
}, "Thread-Parent-A-Name-is-B").start();
}
}, "Thread-Parent-main-Name-is-A").start();
}
}
牛客题:
代码如下所示:
import java.util.ArrayList;
import java.util.List;
public class NameList {
private List names=new ArrayList();
public synchronized void add(String name){
names.add(name);
}
public synchronized void printAll(){
for(int i=0;i<names.size();i++){
System.out.println(names.get(i)+"");
}
}
public static void main(String[] args) {
final NameList s1=new NameList();
for(int i=0;i<2;i++){
new Thread(){
public void run(){
s1.add("A");
s1.add("B");
s1.add("C");
s1.printAll();
}
}.start();
}
}
}
分析;
线程1占领add锁,执行ABC后释放锁
线程2占领add锁,执行ABC后释放锁
期间线程1占领printAll锁输出,所以线程1输出3到6个元素
线程1执行完printAll方法后释放锁,线程二执行printAll方法并占领锁,此时add移执行完,所以第二次输出的字符个数等于6
所以执行结果:ABCABCABC
也可能是:AABBCCAABBCC ,ABCAABCABC等
因为添加字符顺序是不确定的,但是确定第一个添加的字符肯定是A.