多线程学习笔记 第一章  (一)

多线程学习笔记 第一章 (一)

  • 使用Thread创建线程
  • 使用Runnable创建线程
  • 实例变量与线程安全
  • currentThread()方法
  • isAlive()方法
  • sleep()方法
  • getId()方法

使用Thread创建线程

  • 使用多线程就是在使用异步;代码的执行顺序并不是线程执行的顺序,线程被调用的时机是随机的;
  • 可以使用两种方法创建线程,一种是继承Thread类,另一种是实现Runnable接口,由于Thread类也实现了Runnable接口,所以两者没有本质上的区别
/**
 * 注意: 使用多线程技术的时候,代码的运行结果 和代码的执行顺序或者 调用顺序无关
 *     线程是一个子任务,CPU以不确定的方式,或者说以随机的时间来调用线程中的run方法
 */
public class Code_01_SampleByThread {

    //线程类
    private static class MyThread extends Thread{
        @Override
        public void run() {
            super.run();
            System.out.println("MyThread....");
        }
    }

    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();//注意不能多次调用start方法,否则会出现异常
        System.out.println("main....");
    }
}

运行结果

main....
MyThread....

所以要注意代码的运行结果 和代码的执行顺序或者 调用顺序无关

线程调用的随机性


/**
 * 演示 线程具有随机性
 *      使用随机数的形势得到挂起的效果,从而表现出CPU执行哪个线程具有不确定性
 */
public class Code_02_RandomThreadTest {

    //线程类
    private static class MyThread extends Thread{
        @Override
        public void run() {
            for(int i = 0; i < 10; i++){
                int time = (int)(Math.random() * 1000); //休息0~1秒
                try {
                    Thread.sleep(time);
                    System.out.println("run = " + Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread t = new MyThread();
        t.setName("myThread");
        t.start();
        for(int i = 0; i < 10; i++){
            int time = (int)(Math.random() * 1000); //休息0~1秒
            Thread.sleep(time);
            System.out.println("run = " + Thread.currentThread().getName());
        }
    }
}

从上面的代码中,可以看出CPU执行那个线程具有不确定性;start方法和run方法的区别,start方法通知线程就绪,而run方法只是一个普通的方法;
多线程学习笔记 第一章  (一)_第1张图片
还有一点要注意的就是执行start()方法的顺序并不代表线程启动的顺序。


使用Runnable创建线程

  • 第二种方式就是使用实现Runnable接口的目标对象来创建线程;
  • 这样的好处是因为如果使用继承Thread类来创建线程,因为Java只支持单继承,所以限制了类的继承;
  • 注意因为Thread也实现了Runnable接口,所以构造方法Thread(Runnable target)也可以传入一个Thread类的对象,这样做完全可以将一个Thread对象中的run()方法交给其他的线程调用;
/**
 * 使用实现Runnable 接口的类,并通过Thread(Runnable target)传入该类 的方式来创建线程
 * 这个方式和继承Thread来创建线程没有本质的区别,但是由于 Java只支持单继承,所以更加灵活
 */
public class Code_04_RunnableTest {


    //目标对象
    private static class MyThread implements Runnable{

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

    public static void main(String[] args) {
        MyThread target = new MyThread();
        Thread t = new Thread(target);
        t.start();
        System.out.println("main.....");
    }
}

运行结果

main.....
MyThread...

实例变量与线程安全

  • 自定义线程类中的实例变量针对其他的线程可以有共享和不共享之分;
  • 共享的时候可以实现多个人对一个投票等功能;
  • 共享的时候可能产生多个线程同时访问一个变量导致结果不正确的现象;

首先看不共享的例子 :


/**
 * 线程之间不共享的  实例变量
 */
public class Code_05_ThreadSecurity1 {

    public static 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 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();
    }
}

运行结果

由 A 计算,count=4
由 A 计算,count=3
由 A 计算,count=2
由 A 计算,count=1
由 A 计算,count=0
由 B 计算,count=4
由 B 计算,count=3
由 B 计算,count=2
由 B 计算,count=1
由 B 计算,count=0
由 C 计算,count=4
由 C 计算,count=3
由 C 计算,count=2
由 C 计算,count=1
由 C 计算,count=0

第二种情况是当线程之间的实例变量共享且没有加锁的情况下,这种情况下,线程不安全,有可能发生同时修改count变量的操作。


/**
 * 线程共享的例子:  但是存在一个线程安全的问题  : 就是当两个线程同时=修改一个变量的时候,会产生错误的结果
 */
public class Code_06_ThreadSecurity2 {

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

        @Override
        public void run() {
            count--; //没有使用while循环 其他线程可以得到运行的机会
            System.out.println("由 " + this.currentThread().getName() + "计算,结果 = " +count);
        }
    }


    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        Thread t1 = new Thread(myThread,"A");
        Thread t2 = new Thread(myThread,"B");
        Thread t3 = new Thread(myThread,"C");
        Thread t4 = new Thread(myThread,"D");
        Thread t5 = new Thread(myThread,"E");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();

    }
}

上面的代码有可能产生多个线程同时访问变量的情况,这样的情况下会产生不正确的结果,解决办法是在run方法上加上synchronized 关键字,如下:

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

        @Override
        synchronized public void run() {
            count--; //没有使用while循环 其他线程可以得到运行的机会
            System.out.println("由 " + this.currentThread().getName() + "计算,结果 = " + count);
        }
    }

注意:就算把 System.out.println(“count =” + (count–) + this.currentThread().getName() + “计算”);也会产生非线程安区间问题,因为count–的操作是进入println()之前发生的。
多线程学习笔记 第一章  (一)_第2张图片
非线程安全主要是指多个线程对同一个对象中的同一个实例变量进行操作的时候会出现值被更改,值不同步的情况,进而影响程序的执行流程

再看一个关于线程安全的例子

package ThreadCoreTechnology.chapter1;

public class Code_08_ThreadSecurity4 {

    //模拟一个Servlet组件
    public static class LoginServlet {
        private static String usernameRef;
        private static String passwordRef;

        synchronized 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="
                        + password);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    //第一个线程
    public static class ALogin extends Thread {
        @Override
        public void run() { //调用LoginServlet的方法
            LoginServlet.doPost("a", "aa");
        }
    }

    //第二个线程
    public static class BLogin extends Thread {
        @Override
        public void run() {
            LoginServlet.doPost("b", "bb");
        }
    }

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

}

上面的输出结果

username = a password=aa
username = b password=bb

而如果没有加synchronized关键字的输出 :

username = b password=bb
username = b password=aa

currentThread()方法

  • 这个方法返回的是代码段正在被哪个线程调用;
  • 注意线程的构造函数和run方法在启动的时候不是同一个线程调用的,看下面的例子;
public class Code_09_CurrentThreadTest {

    private static class MyThread extends Thread{
        //构造函数 这个的线程是main线程
        public MyThread(){
            System.out.println("构造函数: " + currentThread().getName());
        }

        @Override
        public void run() {
            System.out.println("run方法: " + currentThread().getName());

        }
    }

    public static void main(String[] args) {
        MyThread t = new MyThread();
        t.start();
//        t.run();
    }
}

输出:

构造函数: main
run方法: Thread-0

将t.start()改为run()方法之后:

构造函数: main
run方法: main

上面的例子说明的是run方法只是一个普通的方法,自动调用,而start方法是启动线程的方法。
再看下面的例子

/**
 * 构造函数和run方法的不同
 */
public class Code_10_CurrentThreadTest2 {

    public static class MyThread extends Thread {

        public MyThread() {
            System.out.println("构造函数---begin");
            System.out.println("Thread.currentThread().getName()="
                    + Thread.currentThread().getName());
            System.out.println("this.getName()=" + this.getName());
            System.out.println("构造函数---end");
        }

        @Override
        public void run() {
            System.out.println("run方法---begin");
            System.out.println("Thread.currentThread().getName()="
                    + Thread.currentThread().getName());
            System.out.println("this.getName()=" + this.getName());
            System.out.println("run方法---end");
        }

    }

    public static void main(String[] args) {
        MyThread c = new MyThread();

        Thread t1 = new Thread(c);
        t1.setName("A");
        t1.start();
    }

}

输出

构造函数---begin
Thread.currentThread().getName()=main
this.getName()=Thread-0
构造函数---end
run方法---begin
Thread.currentThread().getName()=A
this.getName()=Thread-0
run方法---end

isAlive()方法

  • 判断当前的线程是否处于活动状态;
  • 活动状态是指线程已经启动,且尚未终止,线程处于正在运行或者准备开始的状态,就认为线程是alive的
public class Code_11_IsAliveTest {

    public static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("run=" + this.isAlive());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread mythread = new MyThread();
        System.out.println("begin ==" + mythread.isAlive());
        mythread.start();
        //Thread.sleep(1000);//睡眠一秒之后 mythread对象已经在1秒内执行完毕,所以end = false
        System.out.println("end ==" + mythread.isAlive());
    }
}

输出:

begin ==false
end ==true
run=true

注意上面我注释的代码,如果加上休眠1秒,end = false
再看下面的例子


/**
 * 如果将线程对象以构造参数的方式传递给Thread对象,进行start启动后是,运行结果和前面不同,
 * 由于this和Thread.currentThread方法差异
 */
public class Code_12_IsAliveTest2 {

    public static class MyThread extends Thread {

        public MyThread() {
            System.out.println("构造函数---begin");

            System.out.println("Thread.currentThread().getName()="
                    + Thread.currentThread().getName());
            System.out.println("Thread.currentThread().isAlive()="
                    + Thread.currentThread().isAlive());

            System.out.println("this.getName()=" + this.getName());
            System.out.println("this.isAlive()=" + this.isAlive());

            System.out.println("构造函数---end");
        }

        @Override
        public void run() {
            System.out.println("run---begin");

            System.out.println("Thread.currentThread().getName()="
                    + Thread.currentThread().getName());
            System.out.println("Thread.currentThread().isAlive()="
                    + Thread.currentThread().isAlive());

            System.out.println("this.getName()=" + this.getName());
            System.out.println("this.isAlive()=" + this.isAlive());

            System.out.println("run---end");
        }

    }


    public static void main(String[] args) {
        MyThread c = new MyThread();
        Thread t1 = new Thread(c);

        System.out.println("main begin t1 isAlive=" + t1.isAlive());

        t1.setName("A");
        t1.start();

        System.out.println("main end t1 isAlive=" + t1.isAlive());
    }

}

输出

构造函数---begin
Thread.currentThread().getName()=main
Thread.currentThread().isAlive()=true
this.getName()=Thread-0
this.isAlive()=false
构造函数---end
main begin t1 isAlive=false
main end t1 isAlive=true
run---begin
Thread.currentThread().getName()=A
Thread.currentThread().isAlive()=true
this.getName()=Thread-0
this.isAlive()=false
run---end

关于this和Thread.currentThread的区别看这篇博客

sleep()方法

  • 作用是在指定的毫秒数内让当前”正在执行的线程”休眠,这个线程指的是this.currentThread()返回的线程;

看下面两个例子的不同
(1)线程调用run()方法;

public class Code_13_SleepTest {
    public static class MyThread1 extends Thread {
        @Override
        public void run() {
            try {
                System.out.println("run threadName="
                        + this.currentThread().getName() + " begin");
                Thread.sleep(2000);
                System.out.println("run threadName="
                        + this.currentThread().getName() + " end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        MyThread1 t = new MyThread1();
        System.out.println("begin =" + System.currentTimeMillis());
        t.run();
        System.out.println("end   =" + System.currentTimeMillis());
    }

}

输出:

begin =1533039885875
run threadName=main begin
run threadName=main end
end   =1533039887876

(2)线程调用start方法

public class Code_13_SleepTest {
    public static class MyThread1 extends Thread {
        @Override
        public void run() {
            try {
                System.out.println("run threadName="
                        + this.currentThread().getName() + " begin");
                Thread.sleep(2000);
                System.out.println("run threadName="
                        + this.currentThread().getName() + " end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        MyThread1 t = new MyThread1();
        System.out.println("begin =" + System.currentTimeMillis());
        t.start();
        System.out.println("end   =" + System.currentTimeMillis());
    }
}

输出:

begin =1533039926824
end   =1533039926825
run threadName=Thread-0 begin
run threadName=Thread-0 end

原因是第二个是异步执行的,所以不需要等待;


getId()方法

  • 这个方法的作用是取得线程的唯一标识;

public class Code_14_GetIdTest {

    public static void main(String[] args) {
        Thread runThread = Thread.currentThread();
        System.out.println(runThread.getName() + " " + runThread.getId());
    }
}

输出

main 1

你可能感兴趣的:(Java多线程)