本文主要介绍线程的基础知识,相应的一些方法。
进程和线程区别:一个进程就是一个程序,当运行一个程序时,计算机会为这个程序开启一个进程。线程是用来真正执行程序的,一个进程最少有一个线程。补充:linux环境:一个进程最多可开1000个线程。windows环境:一个进程最多可开2000个线程。操作系统添加线程上线是因为线程切换需要耗费时间、占用内存,线程太多会将计算机资源耗尽导致死机。
CPU时间片轮换机制:又称RR调度,计算机底层会多次切换线程。
并行和并发:并行是指同一时间执行多个任务,并发是指同一时间能交替执行不同的任务。比如排队取票,并行是指如果有两个窗口,会排两队,这两队会同时进行取票操作。并发是指有一个窗口,然后拍了两队,这两队的人员交替的在这个窗口取票。二者的区别:一个是同时执行,一个是交替执行。
线程启动方式:两种方式启动线程:extends Thread 或 implements Runnable(将实现Runnable的方法交给Thread执行).(Thread.class 源码中注释: There are two ways to create a new thread of execution.)
public class CreateThread {
public static void main(String[] args) {
//调用线程
Thread MyThread1 = new MyThread1();
MyThread1.start();
MyThread2 MyThread2 = new MyThread2();
new Thread(MyThread2).start();
}
/*
*创建线程方式1:继承Thread
*/
private static class MyThread1 extends Thread{
@Override
public void run() {
super.run();
System.out.println("重写run(),开始写线程1的逻辑");
}
}
/*
* 创建线程方式2:实现Runnable
*/
private static class MyThread2 implements Runnable{
public void run() {
System.out.println("重写run(),开始写线程2的逻辑");
}
}
}
Thread和Runnable的区别:Thread是对线程的抽象,Runnable是对任务的抽象。Thread 才是Java 里对线程的唯一抽象,Runnable 只是对任务(业务逻辑)的抽象。Thread 可以接受任意一个Runnable 的实例并执行。
线程终止和中断:线程终止:线程运行完或报异常线程会终止。
线程中断:在线程未执行完之前使线程停止执行并不会继续执行。
中断方法(stop()、interrupt()、isInterrupted()、isInterrupted()):
stop():Thread类中的stop(),线程停止的方法,不过jdk已经标记为不建议使用的方法,因为stop()方法会立即停止线程,不考虑资源是否释放(例如占有锁时,锁不会被释放)。
interrupt():将线程标记为中断,但不会自己去中断线程,需要在程序中手动去判断该标记是否是被中断的,如果是,则停止线程,没被中断则继续执行。若不在程序中手动判断,则即便调用了intreeupt()也不会停止线程,所有interrupt()方法不会自己去中断线程,更不会立即中断线程,只是进行中断线程的标记,通知线程可以中断。
isInterrupted():获取线程是否中断的标记,默认是true,当调用interrupt()方法后,修改为false,即线程不会继续执行。
Thread.isInterrupted():static标记的isInterrupted(),可以获取线程是否被中断的标记,此时和非静态的isInterrupted()功能一样,但该方法会在获取标记后将标记改为true。该方法在实际中用的不多,面试时出现的几率较大。
public class EndThread {
private static class UseThread extends Thread{
public UseThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName+" interrrupt flag ="+isInterrupted());
while(!isInterrupted()){
// while(!Thread.interrupted()){
//while(true){
System.out.println(threadName+" is running");
System.out.println(threadName+"inner interrrupt flag ="
+isInterrupted());
}
System.out.println(threadName+" interrrupt flag ="+isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
Thread endThread = new UseThread("endThread");
endThread.start();
Thread.sleep(20);
endThread.interrupt();//中断线程,其实设置线程的标识位true
}
}
sleep()和中断线程的关系:通过源码可知sleep()方法会抛出InterruptedException(线程中断异常)。该异常会把isInterrupted()置位true。所以如果在中断线程后调用sleep()方法(调用中断线程和调用sleep方法不在同一个线程中,否则不会出现这种情况),需要手动在sleep方法的异常处理中在调用一个中断线程(interrupt()),否则线程不会中断。优点:留给了程序员手动干预的时间。如果InterruptedException异常不修改标志位,就和Thread类中的stop()方法一样了,直接中断了线程,如果此时占有资源(例如锁),资源并不会释放。
/**
*类说明:阻塞方法中抛出InterruptedException异常后,如果需要继续中断,需要手动再中断一次
*/
public class HasInterrputException {
private static class UseThread extends Thread{
public UseThread(String name) {
super(name);
}
@Override
public void run() {
while(!isInterrupted()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() +" in InterruptedException interrupt flag is " +isInterrupted());
//需再手动中断一次,InterruptedException这样设计是给资源释放留点时间
interrupt();
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " I am extends Thread.");
}
System.out.println(Thread.currentThread().getName() +" interrupt flag is "+isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
Thread endThread = new UseThread("HasInterrputEx");
endThread.start();
Thread.sleep(500);
endThread.interrupt();
}
}
**切记:处于死锁状态的线程不会被中断。
获取当前线程的方法:Thread.currentThread() :当使用继承Thread创建线程时可以获取到当前线程的实例,继而获取当前线程,当使用实现Runnable方法时无法获取当前线程的实例,此时就可以通过该方法获取当前线程。
***start()和run()***::我们通过new Thread()其实只是new 出一个Thread 的实例,还没有与操作系统中真正的线程挂起钩来。只有执行了start()方法后,才实现了真正意义上的启动线程。
start()方法让一个线程进入就绪队列等待分配cpu,分到cpu 后才调用实现的run()方法,start()方法不能重复调用,如果重复调用会抛出异常()。而run() 方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并没有任何区别,可以重复执行,也可以被单独调用。
线程其他方法:
yield():让出CPU的执行权,从运行状态变为就绪状态,但不会释放资源。所有调用yield()方法的线程都有可能再次被CPU选中进行执行。因为yield()方法不会释放资源,所以可以把资源都释放掉之后在调用该方法。
join():线程A正在执行,调用线程B的join()方法,此时线程A会让出CPU的执行权,让线程B执行完后,CPU才会继续执行线程A。类似于压栈出栈。
join()考点:如何让线程顺序的执行? 使用join方法。
线程优先级:线程优先级只是给CPU的时间片轮转机制设置一个轮转到改线程的时间,线程并不一定按照这个优先级去执行,所以这个优先级的意义不大。
守护线程:用户线程执行完后,守护线程会自动关闭(守护线程可用来做内存管理等)。一般业务开发不需要设置守护线程,在写底层框架时可能会用到守护线程。Daemon(守护)线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。这意味着,当一个Java 虚拟机中不存在非Daemon 线程的时候,Java 虚拟机将会退出。可以通过调用Thread.setDaemon(true)将线程设置为Daemon 线程。我们一般用不上,比如垃圾回收线程就是Daemon 线程。Daemon 线程被用作完成支持性工作,但是在Java 虚拟机退出时Daemon 线程中的finally 块并不一定会执行。在构建Daemon 线程时,不能依靠finally 块中的内容来确保执行关闭或清理资源的逻辑。
synchronized(锁的是对象):是一个内置锁。可以锁代码块、对象、和类。
锁代码块:将synchronized作用于一段代码块上。
对象锁:对象锁是用于对象实例方法,或者一个对象实例上。
类锁:用于类的静态方法或者一个类的class 对象上的。我们知道,类的对象实例可以有很多个,但是每个类只有一个class 对象,所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,类锁其实锁的是每个类的对应的class 对象。类锁和对象锁之间也是互不干扰的。
synchronized错误的加锁:加锁的对象不是同一个对象,这样加锁是没有效果的。
package concurrent.syn;
/**
* synchronized加锁
*/
public class SysTest {
public static void main(String[] args) {
MyThread myThread = new MyThread(1);
for (int j = 0; j <5 ; j++) {
new Thread(myThread).start();
}
}
private static class MyThread implements Runnable{
private Integer i;
private Object obj = new Object();
public MyThread(Integer a){
this.i=a;
}
@Override
public void run() {
/*method1:线程安全*/
//synchronized (obj){
/*method2:线程不安全*/
// synchronized (new Object()){
/*method3:线程不安全*/
synchronized (i){ //i++内部实现是new Integer(i),和method1一个问题
//获取当前线程
Thread thread = Thread.currentThread();
i++;
System.out.println("线程名称="+thread.getName()+"----线程打印值"+i);
try {
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
}
volatile:轻量的同步机制,只保证数据的可见性,但并不隔离线程,适用于一写多度的情况。
ThreadLocal:
ThreadLocal 和 Synchonized 都用于解决多线程并发訪问。可是 ThreadLocal 与 synchronized 有本质的差别。synchronized 是利用锁的机制,使变量或代码块 在某一时该仅仅能被一个线程訪问。而 ThreadLocal 为每个线程都提供了变量的 副本,使得每个线程在某一时间訪问到的并非同一个对象,这样就隔离了多个线 程对数据的数据共享。
threadLocal详见:https://mp.csdn.net/mdeditor/95492233#