1,为什么需要线程?

作用:提升cpu的利用率,如,早期的dos系统,执行2个命令时( command 1, command 2 ),如果command1【假如是磁盘遍历文件的IO操作】执行的时间比较长,那么command 2必须等待,这种方式就是同步阻塞,

cpu就闲置了,为了提高cpu的利用率,我们就要使用多线程,如果一个任务时间比较长,cpu就暂时挂起他,去执行另外的线程,所以线程一般是异步的。

2,每一个进程至少会有一个线程在运行

public class Test {

    public static void main(String[] args) {
        //打印线程的名称
        System.out.println( Thread.currentThread().getName() );
        
    }

}

输出结果为 "main" ,注意这个main是线程的名字,跟main函数的名字相同而已。

3,在java中实现多线程有2种方式

继承Thread类

实现Runnable接口

在run方法中写线程要执行的任务

class MyThread extends Thread{
    public void run(){
        System.out.println( "MyThread::run" );
    }
}

public class ThreadUse1 {

    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start();
        System.out.println( "运行结束" );
    }

}

图片描述

从运行结果可知,run方法是在之后执行的,虽然start开启线程比 【System.out.println( "运行结束" );】 他早,这说明,CPU在调用线程的时候,是随机的

4,再次验证cpu调用线程的随机性

class MyThreadRand extends Thread{
    public void run(){
        try{
            for ( int i = 0; i < 10; i++ ) {
                int time = ( int )( Math.random() * 1000 );
                Thread.sleep( time );
                System.out.println( "MyThread:" + Thread.currentThread().getName() );
            }
        }catch( InterruptedException e ){
            e.printStackTrace();
        }
    }
}

public class RandThread {

    public static void main(String[] args) {
        
        try{
            MyThreadRand mt = new MyThreadRand();
            mt.setName( "自定义线程" );
            mt.start();
            for ( int i = 0; i < 10; i++ ) {
                int time = ( int )( Math.random() * 1000 );
                Thread.sleep( time );
                System.out.println( "MainThread:" + Thread.currentThread().getName() );
            }
        }catch( InterruptedException e ){
            e.printStackTrace();
        }
    }

}

图片描述

从执行结果可知,线程的调度没有什么规律,是随机的, 这里补充一点,start方法作用是通知 “线程规划器”,这个线程已经准备好了,等待调用线程的run方法,就是让系统安排一个时间来调用run方法。如果直接调用run方法,线程就变成同步方式了,必须等待MyThreadRand的run方法执行完成之后,才会执行main函数中的线程

5,start方法的顺序,不代表线程的启动顺序

class MyThreadStart extends Thread{
    private int i;
    public MyThreadStart( int i ) {
        this.i = i;
    }
    public void run(){
        System.out.println( i );
    }
}

public class RandThread2 {

    public static void main(String[] args) {
        MyThreadStart s1 = new MyThreadStart( 1 );
        MyThreadStart s2 = new MyThreadStart( 2 );
        MyThreadStart s3 = new MyThreadStart( 3 );
        MyThreadStart s4 = new MyThreadStart( 4 );
        MyThreadStart s5 = new MyThreadStart( 5 );
        MyThreadStart s6 = new MyThreadStart( 6 );
        MyThreadStart s7 = new MyThreadStart( 7 );
        MyThreadStart s8 = new MyThreadStart( 8 );
        MyThreadStart s9 = new MyThreadStart( 9 );
        MyThreadStart s10 = new MyThreadStart( 10 );
        
        s1.start();
        s2.start();
        s3.start();
        s4.start();
        s5.start();
        s6.start();
        s7.start();
        s8.start();
        s9.start();
        s10.start();
    }

}

图片描述

6,实现Runnable接口

class MyThreadRunnable implements Runnable {
    public void run(){
        System.out.println( Thread.currentThread().getName() );
    }
}

public class ThreadRunnable {

    public static void main(String[] args) {

        MyThreadRunnable mt = new MyThreadRunnable();
        Thread t = new Thread( mt );
        t.setName( "自定义线程1" );
        t.start();
    }

}

那么两种多线程的实现方式,有什么不同呢?

继承Thread类

实现Runnable接口

1,使用继承Thread类的方式,多线程之间的数据不共享

class MyThreadShare extends Thread{
    private int count = 5;
    public MyThreadShare( String name ){
        this.setName( name );
    }
    public void run(){
        while( count-- > 0 ){
            System.out.println( Thread.currentThread().getName() + "->" + count );
        }
    }
}

public class ThreadShare {
    public static void main(String[] args) {        
        MyThreadShare mt1 = new MyThreadShare( "A" );
        MyThreadShare mt2 = new MyThreadShare( "B" );
        MyThreadShare mt3 = new MyThreadShare( "C" );
        
        mt1.start();
        mt2.start();
        mt3.start();
    }
}

图片描述

2,而要想实现线程之间的数据共享,我们可以改一下

备注:线程数据共享与不共享,都有对应的场景,比如火车站4个窗口卖票,很显然需要线程共享数据。如:总共用10张票,如果窗口卖了1张,其他窗口就指剩下9张,这才是比较贴近实际的,如果用第一种方式,相当于有40张余票了。

class MyThreadShare2 extends Thread{
    private int count = 5;    
    public void run(){
        while( count-- > 0 ){
            System.out.println( Thread.currentThread().getName() + "->" + count );
        }
    }
}

public class ThreadShare2 {
    public static void main(String[] args) {        
        
        MyThreadShare2 mt = new MyThreadShare2();
        Thread ta = new Thread( mt, "A" );
        Thread tb = new Thread( mt, "B" );
        Thread tc = new Thread( mt, "C" );
        
        ta.start();
        tb.start();
        tc.start();
        
    }
}

图片描述

从结果上看,好像实现了,数据共享,但是有点异常,B->3 很明显不对,这种现象,在多线程编程里面,叫“线程非安全”。现实生活中也有类似场景,比如4S店卖车,两个客户同时预订了这辆车。那估计少不了一番辩论。怎么解决这个问题呢?一般来说,在客户订车之前,销售员要先查看库存,如果客户下单,要把库存占用。表明有人预订,其他销售员看见了,就知道车被预订了。程序中也是类似。如果要访问这个变量,我们就给他加锁,类似于销售员占用库存。在方法前加上synchronized关键字。那么其他线程访问的时候,必须拿到这把锁,才能访问。synchronized可以在任意对象或者方法上加锁。

class MyThreadShare2 extends Thread{
    private int count = 5;    
//    public void run(){  //产生线程非安全问题
    synchronized public void run(){
        while( count-- > 0 ){
            System.out.println( Thread.currentThread().getName() + "->" + count );
        }
    }
}

public class ThreadShare2 {
    public static void main(String[] args) {        
        
        MyThreadShare2 mt = new MyThreadShare2();
        Thread ta = new Thread( mt, "A" );
        Thread tb = new Thread( mt, "B" );
        Thread tc = new Thread( mt, "C" );
        
        ta.start();
        tb.start();
        tc.start();
        
    }
}

3,模拟用户登录场景,如果有两个用户登录,我们让其中一个用户线程占时挂起。看下会出现什么情况

class Login {
    private static String userName;
    private static String userPwd;
    
    public static void doPost( String _userName, String _userPwd ){
        try {
            userName = _userName;
            if( userName.equals( "ghostwu" ) ) {
                Thread.sleep( 3000 );                
            }
            userPwd = _userPwd;
            System.out.println( userName + "---->" + userPwd );
        }catch( InterruptedException e ){
            e.printStackTrace();
        }
    }
}

class ThreadA extends Thread{
    public void run(){
        Login.doPost( "ghostwu", "abc123" );
    }
}

class ThreadB extends Thread{
    public void run(){
        Login.doPost( "ghostwuB", "abc1234" );
    }
}

public class UserLogin {

    public static void main(String[] args) {
        
        ThreadA ta = new ThreadA();
        ThreadB tb = new ThreadB();
        
        ta.start();
        tb.start();
    }

}

图片描述

在A线程挂起的时候,他之前的赋值已经被B线程改变了,所以结果与预想的ghostwu abc123不同。很明显,我们要上锁。

synchronized public static void doPost( String _userName, String _userPwd ){
        try {
            userName = _userName;
            if( userName.equals( "ghostwu" ) ) {
                Thread.sleep( 3000 );                
            }
            userPwd = _userPwd;
            System.out.println( userName + "---->" + userPwd );
        }catch( InterruptedException e ){
            e.printStackTrace();
        }
    }