多线程拾遗-1

1, 概念

概念(线程,进程,生命周期,并行与并发,用户线程与系统线程)
对象以及变量的并发访问,侧重于synchronize和lock。加一些volatile和threadlocal。
线程间的通信,以及一些实例
单例模式详解
查漏补缺

简单来所,进程是程序的一次运行,比如跑两个QQ,就是两个进程。而线程是更小的划分,是一个任务的执行。比如说QQ聊天时的一次视频。
- 进程
- 线程
- 线程的不安全性
- 线程常见的方法


* 进程

    /*
        待完成
    */

* 线程

    **线程是一次任务的执行,他在单核心的机器上只可以并发执行,就是说同一时间只会有一个线程占用cpu资源,这是因为,我们显式指定的线程,其实对操作系统而言是不可见的。这个线程任务的调度,其实jvm去调用系统线程来执行的,对用户来言是不可见的。**

在某些时候,我们可能需要指定某个线程到特定的cpu,比如将UI线程限制在一个cpu上,讲其他实时性较高的线程限制在另一个cpu上,这样当UI大量占用cpu时间的时候,也不会影响其他线程的执行。
一个进程中至少会有一个线程,即主线程,比如:

public class Test1{
    public static void main(String[] args){
        //会输出 main
        System.out.println(Thread.currentThread().getName());
    }
}

线程的实现

线程的实现主要有两种方式,一个是继承Thread类,另一个则是实现Runnable接口,还有一个Callable接口,会在别的地方拿出来和Runnable接口比较。**

public class MyThread extends Thread{
    @Override
    public void run(){}
}
public class MyThread implements Runnable{
    @Override
    public void run(){}
}
无论采用哪种方式,线程之间的执行都是随机的,任何时间都可能会让出cpu给别的线程。

线程的不安全

public class MyThread extends Thread{
    private int count = 5;
@Override
    public void run(){
            count--;
    System.out.println(“线程”+Thread.currentThread().getName()+ “ 计算后的count:” +count);
    }
}

测试类:
public class Run{
    public static void main(){
        MyThread mythread = new MyThread();
        new Thread(mythread,”A”).start();
        new Thread(mythread,”B”).start();
        new Thread(mythread,”C”).start();
        new Thread(mythread,”D”).start();
        new Thread(mythread,”E”).start();
        //输出结果可能不是5,4,3,2,1的顺序。
}
}
这是因为在jvm中,i—会分成3部分。
1,  取值
2,  计算i-1;
3,  赋值。
当多个线程同时访问的时候,可能会出现比如两个同时取值,同时计算,会导致后面的赋值覆盖前面的赋值,造成只执行了一次的假象。多次运行,会出现不同的情况。
当在run方法前面加上synchronize修饰时,结果会出现正常的5,4,3,2,1.这是因为synchronize修饰会导致线程的同步执行。

再举一个在学习web项目中经常出现的多线程登录出错的问题。

public class Test {
    public static void main(String[] args) {
        ALogin a = new ALogin();
        a.start();
        BLogin b = new BLogin();
        b.start();
    }
}

class LoginServlet{
    private static String usernameRef;
    private static String passwordRef;
    public static void doPost(String username,String password){
        try{
            usernameRef = username;
            if(username.equals("a")){
                Thread.sleep(5000);
            }
            passwordRef = password;
            System.out.println("username = "+ usernameRef + " password= "+passwordRef);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

class ALogin extends Thread{
    @Override
    public synchronized void start() {
        LoginServlet.doPost("a","aa");
    }
}
class BLogin extends Thread{
    @Override
    public synchronized void start() {
        LoginServlet.doPost("b","bb");
    }
}
多次运行Test类,会发现大概率会出现 
b aa
b bb
的情况,这是因为在a线程进行登录验证时发生了Thread.sleep(5000);导致b线程占用cpu并将usernameRef改为了b。同样,当加上synchronize关键字来修改doPost方法时,该问题便可以得到解决。

值得注意的是,System.out.println(i–)这个情况,虽然方法本身是线程安全的,但是i—在该方法执行前就进行的,所以依然是不安全的。

一些常见的方法:

Thread.currentThread()是一个静态方法,是一个native static方法,根据阿里规约,不要使用实例去调用static方法,要使用Thread.currentThread();

isAlive()方法,判断当前线程是否处于活动状态,System.out.println(thread.isAlive());不能作为判断该线程是否结束的依据,同理,thread.isAlive()是发生在输出方法之前的。

sleep()方法的作用是让线程在指定的毫秒数内休眠,即this.currentThread()返回的线程。
yield()方法的作用是让线程放弃cpu资源,然后进入就绪状态,有可能执行了yield方法后依然是该线程的cpu。

getId(),返回该线程的唯一标识。

停止线程(stop、suspend和resume 都是过期作废不安全的,不考虑使用,使用return来正确的停止一个线程)

interrupt()方法,可以理解为是为一个需要停止的线程打了“待停止”Tag,在别的地方可以通过查询这个tag来判断是否终止此线程。

Thread.interrupt()测试当前线程是否已经中断,该方法会在查询过后制终止状态为“未终止”,比如,对一个已经打了终止tag的线程执行两次interrupt()方法,第一次会返回true,而第二次会返回false,因为第一次查询过后已经清楚了该线程的待终止tag。
isInterrupted()测试线程是否已经中断。
这里要注意,如果在sleep睡眠中执行了interrupt方法,会报InterruptException异常,然后清除待终止tag。
在给线程打了带终止tag后,可以在线程内部即run()方法内建立逻辑判断,通过抛异常的方法来停止该线程。当然,更推荐的方式是return。

线程的优先级问题,默认为5,MIN_PRIORITY=1,MAX_PRIOORITY=10。线程的优先级只能保证大概率的情况下会优先执行优先级高的线程。比如windows中有一个优先级推进器,会把执行次数多的线程优先级提高。毕竟线程的最终的执行顺序还是由操作系统决定的。

线程优先级的继承性:
如果在A线程启动B线程,那么B线程会有和A线程一样的优先级。

守护线程:当线程中不存在非守护线程时,守护线程会自动销毁,比如GC垃圾回收器。
通过setDaemon(true);

猜你喜欢

转载自blog.csdn.net/u012795231/article/details/80411619