多线程的两种常用创建方式:
1. 继承Thread类
Thread类中的start方法通知“线程规划器”此线程已准备就绪,准备调用对象的run()方法。这个过程其实就是让系统安排一个时间来调用Thread中的run方法,也就是使线程得到运行,启动线程,具有异步执行的效果。如果调用代码thread.run()就不是异步执行了,而是同步,那么此线程对象并不交给“线程规划器”来进行处理,而是由main主线程来调用main方法,也就是必须等run方法中的代码执行完之后才可以执行后面的代码。
这是一个异步的过程,执行start方法的顺序不代表线程启动的顺序
2. 实现Runnable接口
适用于有一个父类,或者需要继承别的类的方法实现多继承,因为java是单继承的。
因为Thread类的构造器有Thread(Runnable target)这个,所以调用Runnable中的run方法是将Runnable的实现类当一个参数传入Thread的构造器中,这样就可以通过Thread.start()调用了。
For Example :
Runnable myRunnable = new myRunnable();
Thread myThread = new Thread(myRunnable);
通过调用myThread.start()方法实现Runnable的多线程调用
同样,因为在Thread的源码中已经实现了Runnable接口,所以一样可以将Thread对象传入Thread的构造器中使用,将一个Thread的对象的run方法交给另外一个Thread去调用
实例变量与线程安全:
1. 实例变量
自定义线程类的实例变量针对其他线程可以有共享与不共享之分,这是多个线程间交互
的关键点。
不共享数据的情况下:各个线程有单独的数据,不会跨线程共享
|
示例:
package com.webank.mythread;
public class Thread1 extends Thread { private int i = 5; public Thread1(String name) { //设置线程名称 this.setName(name); } @Override public void run() { while (i > 0) { i--; System.out.println("由" + Thread.currentThread().getName() + "计算i =" + i); } } } |
package com.webank.mythread; public class Run { public static void main(String[] args){ Thread1 thread1 = new Thread1("第一个线程"); Thread1 thread2 = new Thread1("第二个线程"); Thread1 thread3 = new Thread1("第三个线程"); thread1.start(); thread2.start(); thread3.start(); } } |
运行结果:
|
共享数据的情况下:共享数据的情况就是多个线程可以访问同一个变量,比如在实现投票功能的软件时,多个线程可以同时处理同一个人的票数。
|
示例:
public class Thread1 extends Thread { private int i = 5; @Override public void run() { i--; System.out.println("由" + Thread.currentThread().getName() + "计算i =" + i); }
} |
package com.webank.mythread; public class Run { public static void main(String[] args){ System.out.println("This is mianThrean:" + Thread.currentThread().getName()); Thread1 myThread1 = new Thread1(); Thread a = new Thread(myThread1, "A"); Thread b = new Thread(myThread1, "B"); Thread c = new Thread(myThread1, "C"); Thread d = new Thread(myThread1, "D"); Thread e = new Thread(myThread1, "E"); a.start(); b.start(); c.start(); d.start(); e.start(); } } |
运行结果如下:
|
B,C,D三个线程打印同一个值,说明线程是不安全的。因为在JVM中i--分为三步处理
1. 取得原有i值
2. 计算i-1
3. 对i进行赋值
这三个步骤,如果多个线程同时访问,一定会出现非线程安全问题。
非线程安全是指多个线程对同一个对象中的同一个实例变量进行操作时出现值被更改、值不同步的情况,进而影响程序的执行流程。
2. 线程安全
Synchronized关键字:可以对任何对象及方法加锁,而加锁的这段代码被称为互斥区或者临界区。
1. 作用于方法上(方法前加锁,synchronized method name)
上述例子中,如果要B,C,D三个线程不打印相同的i值的一种解决方法就是。
public class Thread1 extends Thread { private int i = 5; @Override synchronized public void run() { i--; System.out.println("由" + Thread.currentThread().getName() + "计算i =" + i); }
} |
通过在run方法前加入synchronized关键字,使多个线程在执行run方法时,以排队的方式进行处理。当一个线程调用run前,先判断run方法有没有被上锁,如果上锁,说明其他线程在调用run方法,必须等待其它线程执行完run方法后释放锁才能进行调用。(竞争机制,不一定哪个线程获得)
2. 作用于对象上,synchronized(new object){代码块}
注意:println()方法在内部是同步的,但是i--不是,所以,若是println(i--),这样也会是线程不安全的,因为i--在println()前操作的。