Java多线程基础

1.1进程和线程的概念

1.什么是进程?

进程是收操作系统管理的基本单元。

Java 嵌套线程池使用 线程可以嵌套吗_ide

我们可以看到,一个exe程序就可以看做是一个进程。

2.什么是线程?

线程就是在进程中独立运行的子任务。

例如我们使用QQ时,可以一边聊天,一边传输文件,一边添加好友等等。这些都可以看做是一个独立的线程。

1.2使用多线程

每一个进程都至少有一个线程在运行它。那么我们平常写的小程序有线程吗?当然有,这个线程就是main线程。

public class Test{
    public static void main(String[] args){
        //获取当前线程的名称
        System.out.println(Thread.currentThread().getName());
        /*
        *运行结果main
        *事实上这个线程就叫main,但是它和main方法没有任何关系,只是名字相同而已
        */
    }
}

Java中创建线程的方式主要有两种:(一)继承Thread类,(二)实现Runnable接口

1.2.1 继承Thread类

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

public class MyThreadTest {
    public static void main(String[] args) {
        //创建线程对象
        MyThread mt = new MyThread();
        //调用start方法启动线程
        mt.start();
        System.out.println("运行结束");
        /*
        * 从运行结果上看,在使用多线程的时候,代码运行的结果和代码的顺序或调用顺序是没有关系的
        * */
        //mt.start();//错误写法,java.lang.IllegalThreadStateException
        /*
        * 注意事项:
        * 不能多次调用start方法,会抛出IllegalThreadStateException
        * 多次调用运行结果如右下图
        * */
    }
}

Java 嵌套线程池使用 线程可以嵌套吗_ide_02

 

Java 嵌套线程池使用 线程可以嵌套吗_System_03

从运行结果上看,在使用多线程的时候,代码运行的结果和代码的顺序或调用顺序是没有关系的。也就是说在多线程的时候,方法的执行顺序是不确定的。

注意事项:start方法不能多次启动一个线程,否则会抛出异常IllegalThreadStateException。

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



public class MyThreadRandomTest {
    public static void main(String[] args) {
        MyThreadRandom mtr = new MyThreadRandom();
        mtr.setName("myThreadRandom");
        mtr.start();
//        mtr.run();
        try{
            for (int i = 0; i < 10; i++) {
                int time = (int)(Math.random()*1000);
                Thread.sleep(time);
                System.out.println("main:"+Thread.currentThread().getName());
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}


Java 嵌套线程池使用 线程可以嵌套吗_Java多线程_04

调用start方法

 


Java 嵌套线程池使用 线程可以嵌套吗_Java多线程_05

调用run方法

从上面调用start方法运行结果,可以看到,多线程在线程的调用顺序上也是随机的。

注意事项:多线程的启动时调用start方法,而不是调用run方法。调用start方法,就是告诉“线程规划器”,我已经准备好运行run方法了。这时等待该线程的运行即可。这是异步的;如果调用run方法,那么此线程对象没有交给线程规划器,而是由main线程运行,所以这时候它不是异步的,而是同步的。

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



public class MyThreadSequenceTest {
    public static void main(String[] args) {
        MyThreadSequence t1 = new MyThreadSequence(1);
        MyThreadSequence t2 = new MyThreadSequence(2);
        MyThreadSequence t3 = new MyThreadSequence(3);
        MyThreadSequence t4 = new MyThreadSequence(4);
        MyThreadSequence t5 = new MyThreadSequence(5);
        MyThreadSequence t6 = new MyThreadSequence(6);
        MyThreadSequence t7 = new MyThreadSequence(7);
        MyThreadSequence t8 = new MyThreadSequence(8);
        MyThreadSequence t9 = new MyThreadSequence(9);
        MyThreadSequence t10 = new MyThreadSequence(10);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t6.start();
        t7.start();
        t8.start();
        t9.start();
        t10.start();
    }
}

Java 嵌套线程池使用 线程可以嵌套吗_System_06

从运行结果上看,执行start方法的顺序也不代表线程启动的顺序。

总结:

多线程中:

1.代码的顺序不代表执行的顺序。

2.多线程程序,线程的调用也是随机的。

3.执行start方法的顺序不代表线程启动的顺序。

注意事项:

1.不要多次对一个线程对象调用start方法,会抛出IllegalThreadStateException.

2.直接调用重写的run方法是没有用的,那样不是异步效果,而是同步效果。

1.2.2实现Runnable接口

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("运行中");
    }
}

public class MyRunnableTest {
    public static void main(String[] args) {
        MyRunnable mr = new MyRunnable();
        Thread thread = new Thread(mr);
        thread.start();
        System.out.println("运行结束!");
    }
}

 

Java 嵌套线程池使用 线程可以嵌套吗_多线程_07

创建多线程的第二种方式:实现Runnable接口。由于Java是单继承,不能多继承,所以当某个类已经继承了另外一个类时,如果想要实现多线程的方式,就可以使用实现Runnable接口的方式来创建多线程。因为接口可以多实现。事实上,Thread类的源码中也是实现了Runnable接口。

1.2.3实例变量与线程安全

实例变量与线程安全的问题分为共享数据和不共享数据两种情况。

首先,来看看不共享数据的情况:

public class MyThread extends Thread {
    private int count = 5;

    public MyThread(String name) {
        super();
        this.setName(name);
    }

    @Override
    public void run() {
        super.run();
        while(count > 0){
            count--;
            System.out.println("由"+this.currentThread().getName() + "计算,count = "+count);
        }
    }
}


public class MyThreadTest {
    public static void main(String[] args) {
        MyThread a = new MyThread("A");
        MyThread b = new MyThread("B");
        MyThread c = new MyThread("C");
        a.start();
        b.start();
        c.start();
    }
}

Java 嵌套线程池使用 线程可以嵌套吗_System_08

从运行结果上看,每个线程都是一个对象,都有自己的count值。各个线程之间计算的也是自己的值,互不干扰。

再来看看,数据共享的情况:

public class MyThread extends Thread {
    private int ticket = 10;

    @Override
    public void run() {
        super.run();
        ticket--;
        System.out.println("由"+this.currentThread().getName()+"计算,ticket = "+ ticket);
    }
}


public class MyThreadTest {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread a = new Thread(myThread,"a");
        Thread b = new Thread(myThread,"b");
        Thread c = new Thread(myThread,"c");
        Thread d = new Thread(myThread,"d");
        Thread e = new Thread(myThread,"e");
        a.start();
        b.start();
        c.start();
        d.start();
        e.start();
    }
}

Java 嵌套线程池使用 线程可以嵌套吗_Java多线程_09

 

新创建的5个线程,都调用MyThread类的run方法,所以共享实例变量。从运行结果上看,程序是异步的。事实上,ticket--是分为三个步骤:

1.读取ticket的值;2.进行自减操作;3.写入ticket的值

所以在这三个步骤中,有多个线程来操作时,一定会出现非线程安全问题。为了解决这个问题,我们使用synchronized关键字修饰run方法。

什么是非线程安全问题呢?就是在多个线程访问实例变量进行操作的时候,出现值更改,不同步的情况,进而影响程序的执行流程。

public class MyThreadSynchronized extends Thread {
    private int count = 10;

    @Override
    synchronized public void run() {
        super.run();
        count--;
        System.out.println("由"+this.currentThread().getName()+"计算,count = "+ count);
    }
}


public class MyThreadSynchronizedTest {
    public static void main(String[] args) {
        MyThreadSynchronized mts = new MyThreadSynchronized();
        Thread a = new Thread(mts,"1");
        Thread b = new Thread(mts,"2");
        Thread c = new Thread(mts,"3");
        Thread d = new Thread(mts,"4");
        Thread e = new Thread(mts,"5");
        a.start();
        b.start();
        c.start();
        d.start();
        e.start();
    }
}

Java 嵌套线程池使用 线程可以嵌套吗_多线程_10

这时候,我们可以看到多个线程之间同步效果,多个线程按顺序排队的方式进行减1操作。

线程安全问题解决办法:使用同步方法 。即在方法上加入synchronized关键字。

public class LoginServlet {
    private static String usernameRef;
    private static String passwordRef;

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

public class BLogin extends Thread {
    @Override
    public void run() {
        LoginServlet.doPost("b","bb");
    }
}
public class LoginServletTest {
    public static void main(String[] args) {
        ALogin a = new ALogin();
        a.start();
        BLogin b = new BLogin();
        b.start();
    }
}


Java 嵌套线程池使用 线程可以嵌套吗_System_11

doPost方法不加synchronized关键字

Java 嵌套线程池使用 线程可以嵌套吗_ide_12

doPost方法加上synchronized关键字

 

从运行结果上看,不加synchronized关键字的时候,打印结果是由问题的,而加上synchronized关键字后打印结果没有问题。

synchronized关键字相当于给方法上了一个锁,当某个线程拿到这个锁时,该线程会执行run方法里面的内容,而其他线程此时无法执行run方法里面的内容,只有当这个线程执行完毕,释放锁后,其他线程再拿到该锁,才能执行run方法里面的内容。