在学校的时候其实也已经学了多线程方面的东西,可是当时学的就是一些基础概念,
是从书本方面了解了多线程是干什么的,至于上班之后开发无非就是SSH这一套,
偶尔会用到多线程。但是多线程在并发编程中是非常重要的。所以笔者打算结合
《Java多线程编程核心技术》将多线程常用的琐碎知识整理一下。
1.进程和线程
什么是进程?进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
什么是线程?线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。
在单个程序中同时运行多个线程完成不同的工作,称为多线程。
初看这两个概念挺抽象的,《Java多线程编程核心技术》中这样举例:
打开你的资源管理器,可以看到很多exe程序在运行,这些exe程序就可以看成是一个进程。比如QQ.exe,在QQ中我们可以开启语音通话,发送文字,视频通话,当我们启用这些
功能时,相当于为每个任务开辟了一个线程。
2.多线程编程的方法
1.继承Thread类
public class ThreadTest extends Thread{
public void run(){
System.out.println("运行该线程");
}
public static void main(String[] args){
ThreadTest t = new ThreadTest();
t.start();
System.out.println("执行完成");
}
}
重写run()方法,调用的时候调用t.start()即可,至于结果:
执行完成
运行该线程
可以看到先调用的方法的结果并没有先输出,说明多线程下结果与代码调用顺序无关。
2.实现Runnable接口(常用的实现方式)
public class Myrunnable implements Runnable{
public void run(){
System.out.println("运行该线程");
}
public static void main(String[] args){
Runnable t = new Myrunnable();
Thread thread = new Thread(t);
thread.start();
System.out.println("执行完成");
}
}
之所以采用实现接口的方式而不提倡继承类的方式实现多线程是因为Java继承的特性,Java只能单根继承。
3.什么是线程安全?
这个问题简单来说可以这样理解: 如果一个程序在单线程和多线程下得到的结果是一致的,那么它就是线程安全的。
4.共享数据和不共享数据
首先看下几个线程共享数据的情况,我们定义一个变量count,开几个线程,让他们一起做自减操作,代码如下:
public class MyThread extends Thread{
private int count =5;
public MyThread(String name){
super();
this.setName(name);
}
public void run(){
super.run();
while(count>0){
count--;
System.out.println(this.currentThread().getName()+":count=="+count);
}
}
public static void main(String[] args){
MyThread thread1 = new MyThread("A");
MyThread thread2 = new MyThread("B");
MyThread thread3 = new MyThread("C");
thread1.start();
thread2.start();
thread3.start();
System.out.println("执行完成");
}
}
执行结果如下:
A:count==4
A:count==3
A:count==2
A:count==1
A:count==0
B:count==4
B:count==3
B:count==2
C:count==4
C:count==3
C:count==2
C:count==1
C:count==0
执行完成
B:count==1
B:count==0
可以看到每个线程分别对自己的变量做运算,如何让多个线程操作同一个变量?去掉while循环即可。让其他线程也有操作这个变量的机会。
实现如下:
public class MyThread extends Thread{
private int count =5;
public void run(){
super.run();
count--;
System.out.println(this.currentThread().getName()+":count=="+count);
}
public static void main(String[] args){
MyThread t = new MyThread();
Thread thread1 = new Thread(t,"A");
Thread thread2 = new Thread(t,"B");
Thread thread3 = new Thread(t,"C");
Thread thread4 = new Thread(t,"D");
Thread thread5 = new Thread(t,"E");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
运行结果如下:
A:count==3
B:count==3
D:count==2
E:count==1
C:count==0
可以看到出现了上面说到的线程安全的问题,因为结果不是预想的4,3,2,1,0,所以是非线程安全的。
因为JVM中i--这个过程分三步:1,取得i的值,2;i-1 3,对i赋值,这三步中如果多个线程同时访问,必然会产生非线程安全的问题。
5.Synchronized关键字
如何避免非线程安全的情况发生?简单,在run方法前面加上该关键字即可,这个关键字加上之后,我们说获得了这段代码的锁,加锁的这段代码
称为互斥区或者临界区。想象一下,大学宿舍的我们,想去洗澡,一般是一个人在某个时间洗,我们洗的时候关上门,其他人只能等我们洗完
打开门之后再进来洗。跟这个过程是非常像的,当某个线程持有该对象锁的时候,他不释放锁其他线程则无法访问这段代码。
现在我们看下加上Synchronized关键字之后的代码:
public class MyThread extends Thread{
private int count =5;
synchronized public void run(){
super.run();
count--;
System.out.println(this.currentThread().getName()+":count=="+count);
}
public static void main(String[] args){
MyThread t = new MyThread();
Thread thread1 = new Thread(t,"A");
Thread thread2 = new Thread(t,"B");
Thread thread3 = new Thread(t,"C");
Thread thread4 = new Thread(t,"D");
Thread thread5 = new Thread(t,"E");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
thread5.start();
}
}
运行结果:
B:count==4
A:count==3
C:count==2
E:count==1
D:count==0
Synchronized关键字的两个特性:原子性和可见性。正因为他的原子性,所以用到Synchronized关键字的地方一段时间只能有一个线程处理,所以加上Synchronized关键字
的程序是线程安全的。可见性:跟Volatile关键字一样,作用是确保每次线程读取最新的变量值。但是Volatile不保证原子性,所以对变量自增自减这种类似的操作,它还会出现非线程安全的问题,所以多线程情况下我们的第一选择是Synchronized关键字。
区别: Synchronized:可见性,原子性,线程安全 Volatile:可见性,非线程安全。
今天暂时先写这些,后面对于多线程的知识我会积极补充。