要了解多线程,就要先了解线程和进程。
进程是指每个独立程序在计算机上的一次执行活动,例如运行的QQ,电影播放器等,运行一个程序就是启动一个进程。
线程是比进程更小的执行单位,基于线程的多任务处理就是一个程序可以执行多个任务,比如迅雷从网上下载一段视频,用户可以在下载完成前就可以播放已下载的内容,这是因为有两个线程,一个下载,一个播放。
线程和进程的区别
1、每个进程都有独立的代码和数据空间,进程间的切换开销大。
2、同一个进程内的多个线程共享相同的代码和数据空间,每个线程有独立的运行栈和程序计数器,线程间切换开销小。
所以,通常在以下情况可能要用到多线程:
1、程序需要同时执行两个或多个任务。
2、程序需要实现一些需要等待的任务,如用户输入、文件读写操作、网络操作等。
3、需要一些后台运行的程序时。
1、线程的创建与启动
java的线程需要通过Thread类来实现。
1)Thread类里有一个run()方法,线程运行的代码都是封装在run()里面,所以run()也称为线程体。
2)通过Thread对象的start()方法来调用这个线程。
除了Thread里的run()方法,我们也可以通过实现Runnable接口的run()方法往线程添加线程体。
<pre name="code" class="java">public class StrTest {
public static void main(String[] args) {
//线程一
Thread thread1=new MyThread();
//线程二
Thread thread2=new Thread(new MyRunnable());
//启动线程一
thread1.start();
//启动线程二
thread2.start();
}
}
//方法一
class MyThread extends Thread{
public void run(){
System.out.println("这是线程体");
}
}
// 方法二
class MyRunnable implements Runnable{
public void run(){
System.out.println("这是runnable线程体");
}
}
需要注意的是单核CPU里的Jvm在执行多线程时不是同步进行的,它是在多个线程里来回切换,在某一个时刻,jvm只再运行一个线程,切换的时间非常短,一般看不出来,若以看起来就好像多个线程在同时执行。
这里不得不有个疑问,既然Cpu只能做一个时刻执行一个线程,为什么还需要多线程呢?
因为多线程程序作为一种多任务并发的工作方式,具有以下优点:
1、改善应用程序的响应。当一个操作耗时很长,整个系统都会等这个操作,就不能执行其他操作,而多线程将耗时较长的操作放到一个新线程执行,其他操作也可以得到执行,加强用户体验。
2、提高计算机CPU的利用率。
3、改善程序结构。
2、线程的分类
java中线程分两类:一种叫守护线程,另一种叫用户线程。上面的例子都是用户线程。那么什么是守护线程呢?守护线程是一种”在后台提供通用性支持“的线程,它不属于程序本体。
那么如何判断哪个线程是守护线程,哪个是用户线程。
其实用户线程和守护线程基本是没有区别的,唯一的区别是判断虚拟机什么时候离开。
1、用户线程:java虚拟机在它所有非守护线程都已经离开后自动离开。
2、守护线程:守护线程是用来服务用户线程的,如果没有其他用户线程在运行,守护线程才离开。
这部分不怎么好理解,其实用户线程就可以简单的理解成你需要运行的代码,而守护线程就是当运行这些代码时,你需要的一个环境,例如jvm里的垃圾回收器就是一个典型的守护线程。
public class StrTest {
public static void main(String[] args) {
//线程一
Thread thread1=new MyThread();
//线程二
Thread thread2=new Thread(new MyRunnable());
//把线程二设置为守护线程
thread2.setDaemon(true);
//启动线程一
thread1.start();
//启动线程二
thread2.start();
}
}
//方法一
class MyThread extends Thread{
public void run(){
for(int x=1;x<10;x++)
System.out.println("这是线程体");
}
}
// 方法二
class MyRunnable implements Runnable{
public void run(){
for(int x=1;x<10000;x++)
System.out.println("这是runnable线程体");
}
}
把上面例子改了一下,把thread2设置为守护线程,由于线程2的循环次数远大于线程一的循环次数,按道理来说线程2要打印10000 次这是runnable线程体,可是结果就是在线程一打完没多久线程2也结束了,所以上面的守护线程也不难理解了 。
3、线程的状态
线程的状态分成开始、可运行、阻塞、时间等待、等待唤醒、结束
4、线程的同步
当两个或多个线程需要共享对同一个数据的访问,如果每个线程都会调用修改数据的方法,那么这些线程就会互相影响对方,从而影响运行的结果。
为什么会这样呢?
前文提过,多线程的执行是跟jvm根据 cpu所分配的时间片来运行的,线程间是来回切换的而且线程都有独立的运算栈,所以共享数据修改容易发生错误。
这时候,我们就需要对多线程共享数据操作进行同步。
同步有两种方法,一个是synchronized(){}代码块,一个是同步函数。每一种方法里面都需要一个对象锁。
等待唤醒机制是同步的一个重要组成部分,当调用同步代码时,可以加上wait()方法让线程暂停,并放弃对象锁。notify()方法用来唤醒线程。
下面是一个同步等待唤醒的一个经典模型,是模拟产品生产销售的例子。
public class ProducerConsumerDemo1 {
public static void main(String[] args) {
Resource1 r = new Resource1();
Producer1 p =new Producer1(r);
Consumer1 c=new Consumer1(r);
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(c);
Thread t4 = new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Resource1{
private String name;
private int count=1;
private boolean flag = false;
private Lock lock = new ReentrantLock();
private Condition con_pro = lock.newCondition();
private Condition con_con = lock.newCondition();
public void set(String name)throws InterruptedException{
lock.lock();
try {
while(flag) {
con_pro.await();
}
this.name=name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"生产者..."+this.name);
flag=true;
con_con.signal();
}
finally{
lock.unlock();
}
}
public void out() throws InterruptedException{
lock.lock();
try{
while(!flag) {
con_con.await();
}
System.out.println(Thread.currentThread().getName()+"消费者........."+this.name);
flag =false;
con_pro.signal();
}
finally{
lock.unlock();
}
}
}
class Producer1 implements Runnable{
private Resource1 res;
Producer1(Resource1 res){
this.res=res;
}
public void run(){
while(true){
try {
res.set("商品");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
class Consumer1 implements Runnable{
private Resource1 res;
Consumer1(Resource1 res){
this.res=res;
}
public void run(){
while(true){
try {
res.out();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}