进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结
构的基础。线程是在进程中独立运行的子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法。
一、使用多线程
实现多线程编程的方式主要有两种,一种是继承Thread类,另一种是实现Runnable接口。
1.继承Thread类
①调用Thread.java类中的start方法:
通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run()方法。
这个过程其实就是让系统安排一个时间来调用Thread中的run()方法,也就是使线程得到运行,启动线程,具有异步执行的效果。
②调用Thread.java类中的run方法:
由main主线程来调用run()方法,也就是必须等run()方法中的代码执行完后才可以执行后面的代码,具有同步执行的效果
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(InterruptedException e){
e.printStackTrace();
}
}
public static void main(String[] args) {
try{
MyThread t = new MyThread();
t.setName("mythread");
t.start(); //通知“线程规划器”此线程已经准备就绪,等待调用线程对象的run方法
//t.run(); //由main主线程来调用run()的方法,同步
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();
}
}
}
注意:执行start()方法的顺序不代表线程启动的顺序。
2.实现Runnable接口
如果欲创建的线程类已经有一个父类了,这时就不能再继承自Thread类了, 因为Java不支持多继承,所以就需要实现Runnable接口来应对这样的情况。
public class MyThread implements Runnable {
@Override
public void run() {
System.out.println("运行中");
}
public static void main(String[] args) {
Runnable runnable = new MyThread();
Thread t = new Thread(runnable);
t.start();
System.out.println("运行结束");
}
}
注意:Thread.java类也实现了Runnable接口,意味着构造函数Thread(Runnable target)不光可以传入Runnable接口的对象,还可以传入一个Thread类的对象,从而将一个Thread对象中的run()方法交由其他的线程进行调用。
3.实例变量与线程安全
自定义线程类中的实例变量针对其他线程可以有共享与不共享之分,这在多个线程之间进行交互时是很重要的一个技术点。
3.1变量不共享
此示例一共创建了3个线程,每个线程都有各自的count变量,自己减少自己的count变量的值。这样的情况并不存在多个线程访问同一个实例变量的情况。
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 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();
}
}
3.2 变量共享
共享数据的情况就是多个线程可以访问同一个变量。
public class MyThread extends Thread{
private int count = 5;
@Override
public void run(){
super.run();
/* 此示例不要用循环,因为使用同步后其他线程就得不到运行的机会了,
* 一直由一个线程进行减法运算*/
count--;
System.out.println("由 " + this.currentThread().getName() +
" 计算,count=" + count);
}
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();
}
}
原因:
上图结果因为几个线程间对共享变量count执行的操作count--并不是原子操作,从而产生了“非线程安全”的问题。
解决方法:
在Mythread的run()方法前加入synchronized关键字,将run()方法加锁,只有拿到“钥匙”的线程可以进入“临界区”。(详见操作系统的概念)
package Thread_01;
public class MyThread extends Thread{
private int count = 5;
@Override
synchronized public void run(){ //将run()方法加上同步锁
super.run();
/* 此示例不要用循环,因为使用同步后其他线程就得不到运行的机会了,
* 一直由一个线程进行减法运算*/
count--;
System.out.println("由 " + this.currentThread().getName() +
" 计算,count=" + count);
}
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();
}
}
加上同步锁后的结果: