一个进程正在运行时,至少会有一个线程在运行。线程在后台默默执行,比如调用main方法的线程就是如此,它是由JVM创建的。class Test { public static void main(String[] args) { System.out.println(Thread ...
一个进程正在运行时,至少会有一个线程在运行。线程在后台默默执行,比如调用main方法的线程就是如此,它是由JVM创建的。
class Test { public static void main(String[] args) { System.out.println(Thread.currentThread().getName()); }}
程序执行后显示:
这个main是一个叫做main的线程在执行main()方法中的代号。main与main()方法没有什么关系,仅仅是名字相同而已。
1.2.1继承Thread类:
在java中实现多线程编程的方式主要有两种:一种是继承Thread类,另一种是实现Runnable接口。
注源码中:
public class Thread implements Runnable
Thread与Runnable是具有多态关系的。
通过继承Thread实现多线程的最大问题是无法继承其他类(因为java中是单根继承的),所以要想支持多继承,可以实现Runnable接口的同时继承其他类。
不论是继承Thread还是实现Runnable接口,创建的线程都是工作时的性质都是一样的。
继承Thread实现:
public class TestThread extends Thread{ @Override public void run() { super.run(); System.out.println("这是测试线程"); }}
main方法中:
public class Main { public static void main(String[] args) { TestThread tt = new TestThread(); tt.run(); System.out.println("运行结束"); }}
结果如图:
线程是一个子任务,CPU以不确定的方式,或者说以随机的时间来调用线程中的run()方法,所以才会先打印"运行结束"后打印"这是测试线程了"。
注:也许是cpu进步了?用的i7-7700
如果多次调用start()方法,则会出现异常:
上述表现了线程的随机性。
下面的案例将演示线程的随机性
继承Thread实现:
public class TestThread extends Thread{ @Override public void run() { super.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 (Exception e) { e.printStackTrace(); } }}
运行在main()的代码:
public class Main { public static void main(String[] args) { try { TestThread tt = new TestThread(); tt.setName("TestThread"); tt.start(); for (int i = 0; i < 10; i++) { int time = (int)(Math.random() * 1000); Thread.sleep(time); System.out.println("main=" + Thread.currentThread().getName()); } } catch (Exception e) { e.printStackTrace(); } }}
结果:
在代码中使用随机数的形式,使线程得到挂起的效果,从而表现cpu执行线程时具有不确定性。
Thread.start()方法是告诉“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法,这个过程就是让系统安排一个时间来调用Thread中的run()方法。从而使线程运行,启动线程,具有异步的效果。
注:如果调用Thread.run()方法,就是同步的了,因为此时线程对象会交给main线程来处理,此时在main()中执行过程为线性的。
执行start()方法的顺序不代表线程执行的顺序。(线程的执行在定义时就讲过了,是在线程间进行切换的,以为切换速度快,看起来是同一时间完成了多件事。)
1.2.2实现Runnable接口:
public class TestRunnable implements Runnable { @Override public void run() { System.out.println("线程运行中"); }}
通过Thread的构造方法使用TestRunnable测试类:
运行代码:
public class Main { public static void main(String[] args) { TestRunnable tr = new TestRunnable(); Thread t = new Thread(tr); t.start(); System.out.println("运行结束"); }}
运行结果:
注:Thread实现了Runnable,所以构造方法Thread(Runnable target)不光可以传入一个Runnable接口对象,还可以传入一个Thread类的对象,这样做完全可以将一个Thread对象中的run()交由其他线程完成。
1.2.3实例变量和线程安全:
自定义线程中的实例变量针对其他线程可以分为共享和不共享之分,这在多个线程间进行交互时是个很重要的技术点。
(1)不共享的情况:
线程代码:
public class TestShare extends Thread { private int count = 5; public TestShare(String name) { super(); this.setName(name);//设置线程名称。 } @Override public void run() { super.run(); while(count > 0) { count--; System.out.println("由" + currentThread().getName() + "计算 + count=" +count); } }}
执行代码:
public class Main { public static void main(String[] args) { TestShare ts1 = new TestShare("A"); TestShare ts2 = new TestShare("B"); TestShare ts3 = new TestShare("C"); ts1.start(); ts2.start(); ts3.start(); }}
执行结果:
每个线程都有各自的count数,都是从5开始计数,各自减少各自的,这种情况就是变量不共享。此例中不存在多个线程访问同一个实例变量的情况。
(2)共享的情况
:这种情况就有很多现实中的模型了:抢票,抢购,秒杀等等。
线程代码:
public class TestShare1 extends Thread { private int count = 5; @Override public void run() { super.run(); count--; //此示例不要用for语句,因为使用同步后其他线程就得不到运行的机会了。 //一直由一个线程进行减法运算。 System.out.println("由" + Thread.currentThread().getName() + "计算,count=" + count); }}
执行代码:
public class Main { public static void main(String[] args) { TestShare1 ts1 = new TestShare1(); Thread A = new Thread(ts1,"A"); Thread B = new Thread(ts1,"B"); Thread C = new Thread(ts1,"C"); Thread D = new Thread(ts1,"D"); Thread E = new Thread(ts1,"E"); A.start(); B.start(); C.start(); D.start(); E.start(); }}
执行结果:
B与A都是3,说明A与B同时对共享资源做了处理,这是非线程安全得,这里就产生了线程安全性问题。
原因:
在某些JVM中,i--的操作要经历下面三个步骤:
- 取得原有i值
- 计算i-1
- 对i进行赋值
在这3个步骤中,如果有多个线程同时访问,那么一定会出现非线程安全问题。
为了解决这个问题,需要在每次执行run()时进行同步(加锁,只有当run()执行完毕才会切换线程)。
线程代码:
public class TestShare2 extends Thread { private int count = 5; @Override synchronized public void run() { super.run(); count--; System.out.println("由"+ currentThread().getName() + "计算,count=" +count); }}
运行结果:
run()方法前加synchronized关键字,为线程加锁,当第一个线程运行到此处时,会进行加锁,在运行完之前不会放开锁,此时线程被切换其他线程运行到此时,就会进行排队,等到其他线程运行完run()才能够进入方法并运行。
注:synchronized关键字可以在任意对象及方法上加锁,而这种加锁的代码成为:“互斥区”或“临界区”。
注:术语:“非线程安全”。非线程安全是指:多个线程对同一个对象中的同一个实例变量进行操作时出现值被更改、值不同步的情况,进而影响程序的执行流程。
解决非线程安全示例:
模拟Servlet组件:
public class LoginServlet { private static String usernameRef; private static String passwordRef; public static void doPost(String username, String password) { try { usernameRef = username; if ("a".equals(usernameRef)) { Thread.sleep(500); } passwordRef = password; System.out.println("username=" + usernameRef + " password=" + password); } catch (Exception e) { e.printStackTrace(); } }}
线程A:
public class ALogin extends Thread { @Override public void run() { super.run(); LoginServlet.doPost("a","aa"); }}
线程B:
public class BLogin extends Thread { @Override public void run() { LoginServlet.doPost("b", "bb"); }}
执行代码:
public class Main { public static void main(String[] args) { ALogin a = new ALogin(); a.start(); BLogin b = new BLogin(); b.start(); }}
执行结果:
解决线程不安全的问题代码:
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(usernameRef)) { Thread.sleep(500); } passwordRef = password; System.out.println("username=" + usernameRef + " password=" + password); } catch (Exception e) { e.printStackTrace(); } }}
执行结果:
1.2.4留意i--与System.out.println()的异常
前面章节通过synchronized解决了非线程安全问题。
本节将细化println()方法与i++联合使用时“有可能”出现的另外一种异常情况。并说明其中原因。
线程代码:
public class FourThread extends Thread { private int i = 5; @Override public void run() { System.out.println("i=" + (i--) + " threadName= " + Thread.currentThread().getName()); }}
执行代码:
public class Main { public static void main(String[] args) { FourThread ft = new FourThread(); Thread t1 = new Thread(ft); Thread t2 = new Thread(ft); Thread t3 = new Thread(ft); Thread t4 = new Thread(ft); Thread t5 = new Thread(ft); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); }}
结果:
原因:
虽然println()方法在内部时同步的,但i--的操作却是在进入println()前发生的,所以有发生非线程安全问题的概率。
所以为了防止发生非线程安全问题,还是应该继续使用同步方法。
源码地址:https://github.com/lilinzhiyu/threadLearning
原标题:1.2使用多线程
关键词:线程
*特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们:
admin#shaoqun.com
(#换成@)。