1.什么是进程?
进程是操作系统结构的基础,是一次程序的执行;是一个程序及其数据在处理机上顺序执行时所发生的活动;是程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。
2.什么是线程?
线程可以理解成是在进程中独立运行的子任务。
使用多线程的优点:
3.继承Thread类
java JDK自带对多线程支持。实现多线程编程的方式主要有两种,一种是继承Thread类,另一种是实现Runnable接口。
Thread类实现了Runnable接口,他们之间具有多态关系。
使用继承Thread类的方式创建新线程,最大的局限就是不支持多继承,这是由于java语言的特点就是单根继承。所以,为了支持多继承,完全可以实现Runnable接口,两种方式创建线程在工作时性质一样。
使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序无关(线程调用的随机性)。若多次调用start方法,会出现异常。
public class MyThread{
public static void main(String[] args){
Thread1 t1=new Thread1();
t1.start();
try{
for(int i=0;i<10;i++){
int time=(int)(Math.random()*1000);
Thread.sleep(time);
System.out.println("main="+Thread.currentThread().getName());
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
public class Thread1 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(i+" run="+Thread.currentThread().getName());
}
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
main=main
0 run=Thread-0
main=main
1 run=Thread-0
main=main
2 run=Thread-0
3 run=Thread-0
4 run=Thread-0
5 run=Thread-0
main=main
main=main
main=main
main=main
6 run=Thread-0
7 run=Thread-0
main=main
8 run=Thread-0
main=main
9 run=Thread-0
main=main
Thread.java类中的start方法通知“线程规划器”此线程已准备就绪,等待调用线程对象的run方法。这个过程就是让系统安排时间调用run方法,启动线程,具有异步执行的效果。如果调用代码thread,run()就不是异步执行,而是同步,那么此线程就不会交给“线程规划器”来处理,而是由main主线程来调用run()方法,也就是必须等run()方法中的代码执行完后才能执行后面的代码。另外,执行start顺序不代表线程启动顺序。
public class Thread2 extends Thread{
private int i;
public Thread2(int i){
super();
this.i=i;
}
@Override
public void run() {
// TODO 自动生成的方法存根
System.out.println(i);
}
}
public class MyThread{
public static void main(String[] args){
Thread2 t21=new Thread2(1);
Thread2 t22=new Thread2(2);
Thread2 t23=new Thread2(3);
Thread2 t24=new Thread2(4);
Thread2 t25=new Thread2(5);
Thread2 t26=new Thread2(6);
t21.start();
t22.start();
t23.start();
t24.start();
t25.start();
t26.start();
}
}
2
1
3
4
6
5
4.实现Runnable接口
如果欲创建的线程类已经有一个父类了,则不能继承Thread类,此时需要实现Runnable接口来解决这类情况。在Thread.java类的8个构造函数中,有Thread(Runnable target)和Thread(Runnable target,String name)可以传递Runnable接口。
由于Thread.java类也实现了Runnable接口,意味着构造函数Thread(Runnable target)还可以传入一个Thread类的对象,这样做完全可以将一个Thread对象中的run()方法交由其他线程进行调用。
5.实例变量和线程安全
自定义线程类中的实例变量针对其他线程有共享与不共享之分,涉及多线程交互。
(1)不共享数据
public class MyThread{
public static void main(String[] args){
Thread3 t21=new Thread3("A");
Thread3 t22=new Thread3("B");
Thread3 t23=new Thread3("C");
t21.start();
t22.start();
t23.start();
}
}
public class Thread3 extends Thread{
private int count=5;
public Thread3(String name){
super();
this.setName(name);//设置线程名字
}
@Override
public void run() {
// TODO 自动生成的方法存根
super.run();
while(count>0){
count--;
System.out.println("由"+this.currentThread().getName()+" 计算 count="+count);
}
}
}
由C 计算 count=4
由A 计算 count=4
由B 计算 count=4
由A 计算 count=3
由C 计算 count=3
由A 计算 count=2
由B 计算 count=3
由A 计算 count=1
由C 计算 count=2
由A 计算 count=0
由B 计算 count=2
由C 计算 count=1
由B 计算 count=1
由C 计算 count=0
由B 计算 count=0
(2)共享数据情况
多线程可以访问同一个变量。
public class MyThread{
public static void main(String[] args){
Thread4 thread=new Thread4();
Thread a=new Thread(thread,"A");
Thread b=new Thread(thread,"B");
Thread c=new Thread(thread,"C");
a.start();
b.start();
c.start();
}
}
public class Thread4 extends Thread{
private int count=5;
@Override
public void run() {
// TODO 自动生成的方法存根
super.run();
count--;
System.out.println("由"+this.currentThread().getName()+"计算count="+count);
}
}
由C计算count=2
由B计算count=2
由A计算count=2
结果看出产生了“非线程安全”问题。修改代码:synchronized public void run()后运行得到:
由A计算count=4
由B计算count=3
由C计算count=2
通过加入synchronized关键字,使多线程在执行run方法时,以排队的方式进行处理。当一个线程调用run前,先判断run方法有没有被上锁。若上锁,则说明有其他线程在调用run方法,必须等待其他线程结束调用后才能执行run方法。synchronized可以对任意对象及方法加锁,而加锁的断码段称为“互斥区或”临界区”。当一个线程想要执行同步方法里面的代码时,线程首先尝试去拿这把锁,若拿到则执行,反之则不断地尝试去拿这把锁,直到拿到为止,而且是多个线程同时去争取这把锁。
非线程安全主要是指多个线程对同一个对象中的同一个实例变量进行操作时出现值被更改,值不同步情况,进而影响程序的执行流程。
6.i–与System.out.println()异常
public class MyThread{
public static void main(String[] args){
Thread5 thread=new Thread5();
Thread a=new Thread(thread,"A");
Thread b=new Thread(thread,"B");
Thread c=new Thread(thread,"C");
a.start();
b.start();
c.start();
}
}
public class Thread5 extends Thread{
/**
* 说明i--与sysout的异常
*/
private int i=5;
@Override
public void run() {
// TODO 自动生成的方法存根
System.out.println("i="+(i--)+"threadName="+Thread.currentThread().getName());
}
}
i=5 threadName=C
i=4 threadName=A
i=4 threadName=B
println方法在内部是同步的,但是i–操作却是在进入println之前发生的,所以有发生非线程安全问题的概率。要避免这类问题应使用同步方法。
参考书籍《Java多线程编程核心技术》