java多线程入门学习(一)
一.java多线程之前
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。
线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
多进程是指操作系统能同时运行多个任务(程序)。
多线程是指在同一程序中有多个顺序流在执行。
在java中要想实现多线程,有两种手段,一种是继承Thread类,另外一种是实现Runnable接口,为什么会有两种方式,原因是由于java具有单继承性质,当有些类已经实现继承,但需要多线程时,我们给它另外一种办法,实现Runnable接口
二.继承Thread类
在学习如何创建新的线程以前,首先让我们看下Thread类的结构:
public class Thread implements Runnable
从上面的源码中可以发现Thread类实现了Runnable接口,它们之间是多态的关系。
以下开始写代码,继承Thread类,我们在run()方法中写线程要执行的任务代码:
package cn.sun.thread;
public class MyThread extends Thread{
@Override
public void run() {
super.run();
System.out.println("this is my first Thread : MyThread");
}
}
测试类如下,虽然我们复写的是run()方法,但是执行的却是start()方法:
package cn.sun.test;
import cn.sun.thread.MyThread;
public class ThreadTest01 {
public static void main(String[] args) {
MyThread mythread = new MyThread();
mythread.start();
System.out.println("program over");
}
}
从下面执行结果来看,可以知道使用多线程时代码的运行结构与代码执行顺序无关:
program over
this is my first Thread : MyThread
上面介绍了线程调用的随机性,下面这个例子将展示线程的随机性:
package cn.sun.thread;
public class MyThread 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 (Exception e) {
e.printStackTrace();
}
}
}
package cn.sun.thread;
public class MyThreadTest02 {
public static void main(String[] args) {
try {
MyThread thread = new MyThread();
thread.setName("myThread");
thread.start();
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();
}
}
}
结果如下:
run main
run MyThread
run main
run main
run MyThread
run MyThread
run main
run MyThread
run main
run main
run MyThread
run main
run MyThread
run MyThread
run MyThread
run main
run main
run MyThread
run main
run MyThread
从上面就可以看出来线程的随机性,为什么会出现这种形式(随机性),主要是因为我们执行的是它的start()方法,当执行start()时将通知“线程规划器”此线程已准备好,等待调用run()方法,也就是让系统安排一个时间去执行它,达到异步的效果,如果我们直接执行run()方法,则没有这种异步的效果,直接同步了,多线程亦没有了意义。
有一点需要说明,当我们同时启动多个线程时,执行start()方法的顺序不代表线程启动的顺序,还是那句话,真正的执行依赖于系统所分配的时间片。
三.实现Runnable接口
此类的应用场景相信大家已经知道了,就是当某个类已经有一个父类时,无法再继承Thread类,我们就用Runnable接口去实现它
package cn.sun.runable;
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("MyRunnable run...");
}
}
package cn.sun.test;
import cn.sun.runable.MyRunnable;
public class MyRunnableTest01 {
public static void main(String[] args) {
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
System.out.println("main over...");
}
}
结果如下:
main over...
MyRunnable run...
在上面的代码中,Thread
thread =newThread(runnable);的代码不仅可以传入一个Runnable对象,还可以传入一个Thread类对象,这样做可以将一个Thread对象中的run()方法交给其他的线程进行调用。
三.实例变量的共享与私有
3.1 数据不共享形式:
package cn.sun.safe;
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()+"‘s count is :" +count);
}
}
}
package cn.sun.test;
import cn.sun.safe.MyThread;
public class ThreadTest02 {
public static void main(String[] args) {
MyThread threadA = new MyThread("A");
MyThread threadB = new MyThread("B");
MyThread threadC = new MyThread("C");
threadA.start();
threadB.start();
threadC.start();
}
}
结果如下,各个实例对象的实例变量保持了私有,并没有共享:
A‘s count is :4
A‘s count is :3
A‘s count is :2
A‘s count is :1
A‘s count is :0
C‘s count is :4
C‘s count is :3
C‘s count is :2
B‘s count is :4
B‘s count is :3
B‘s count is :2
C‘s count is :1
C‘s count is :0
B‘s count is :1
B‘s count is :0
3.2 共享数据的情况
上一个例子并不存在多个线程访问同一个实例变量的情况,这里我们试试三个线程对同一变量进行操作:
package cn.sun.safe;
public class MyThreadShare extends Thread {
private int count = 5;
@Override
public void run() {
super.run();
count--;
System.out.println(this.currentThread().getName()+"‘s count is :" +count);
}
}
package cn.sun.test;
import cn.sun.safe.MyThreadShare;
public class ThreadTest03 {
public static void main(String[] args) {
MyThreadShare myThread = new MyThreadShare();
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();
}
}
结果发现这样的情况:
B‘s count is :3
C‘s count is :2
A‘s count is :3
D‘s count is :1
E‘s count is :0
当中的3产生了非线程安全问题,有两个线程打印出同样的数字,这样肯定是不行的,我们应该出现的效果是依次递减,而不是重复数字递减
这里我们需要了解这样一个事情,在有些JVM中 i-- 的操作分为如下三步:
(1)取得原有i的值
(2)计算 i-1
(3)对i进行赋值
当多个线程同时进来访问时,肯定会出现非线程安全问题,我们将这个步骤用synchronized进行同步化就可以解决这个问题
package cn.sun.safe;
public class MyThreadShare extends Thread {
private int count = 5;
@Override
synchronized public void run() {
super.run();
count--;
System.out.println(this.currentThread().getName()+"‘s count is :" +count);
}
}
这样之后运行结果就呈现这样的样子:
A‘s count is :4
C‘s count is :3
B‘s count is :2
E‘s count is :1
D‘s count is :0
synchronized可以在任意对象及方法上加锁,加锁的这段代码称为“互斥区”或者“临界区”