java多线程总结

1 线程与进程

什么是进程?
进程指的是操作系统中运行的一个任务。是一块包含了某些资源的内存区域,操作系统利用进程把它的工作划分为一些功能单元。
什么是线程?
进程中包含的一个或多个执行单元称为线程。一个线程是进程的顺序执行流。
线程和进程的区别与联系?
一个进程至少有一个线程,线程的划分尺度小于进程,使得多线程程序的并发性高。另外,进程在执行过程中有独立的内存单元,而多个线程共享内存,从而极大的提高了程序的执行效率。
线程的使用场合?
线程通常用于在一个程序中需要同时完成多个任务的情况。我们可以将每个任务定义为一个线程,使它们一起工作。也可以在单一线程中完成但多线程可以更快。比如下载文件。
并发原理:
多个线程同时工作其实只是我们的感官体验,事实上线程是并发运行的,操作系统将时间划分为许多的时间片段(时间片),尽可能均匀分配给每一个线程.获取时间片的线程被cpu执行,而其他的线程则全部等待。所以微观上是走走停停,而宏观上都在运行。这种现象叫并发。但绝不是“同时发生”。
线程状态:
这里写图片描述
同一时间只有一个线程处于Running状态,其他线程处于Runnable等待状态。

2 线程的创建

2.1 继承Thread类

Thread类是线程类,其每一个实例表示一个可以并发运行的线程。我们可以通过继承该类并重写run方法来定义一个具体的线程。run方法是线程要执行的逻辑,启动线程需要调用线程的start()方法。start()方法会将当前线程纳入线程调度,使当前线程可以并发运行。当线程获取时间片后自动执行run方法中的逻辑。
示例代码:

/**
 * 测试多线程
 * @author Administrator
 *
 */
public class TestThread {

public static void main(String[] args) {
    Thread t1=new MyThread();
    Thread t2=new MyThread();
    t1.start();
    t2.start();
}

}
/**
 * 线程
 * @author Administrator
 *
 */
class MyThread extends Thread{
@Override
public void run() {
    for(int i=0;i<10;i++){
        System.out.println(i);
    }
}
}

2.2 实现Runnable接口

实现Runnable接口并重写run方法来定义线程体。然后在创建线程的时候将Runnable实例传入。这样做的好处在于可以将线程与线程要执行的任务分离开,减少耦合。
代码示例:

public class TestRunnable  implements Runnable {

    @Override

    public void run() {
        for(int i=0;i<10;i++){
            System.out.println(i);
        }
    }
    public static void main(String[] args) {
     TestRunnable  runnable=new TestRunnable();
     Thread t=new Thread(runnable);
     t.start();
    }
}

其实更常用的一种方法是使用匿名内部类的形式创建线程。当一个线程仅需要一个实例时我们用这种方式。
示例代码:

public static void main(String[] args) {
    //使用匿名内部类的方式创建线程
    Thread t=new Thread(){
        public void run(){
            for(int i=0;i<10;i++){
                    System.out.println(i);
            }
            }       
       };
       t.start();
    }

3 线程操作API

Thread.currentThread方法可以用于获取运行当前代码片段的进程。
常用方法:

  • long getId() 返回线程的标识符
  • String getName() 返回当前线程的名字
  • int getPriority() 返回线程的优先级
  • boolean isAlive() 测试线程是否处于活动状态
  • boolean isDaemon() 测试线程是否为守护线程
  • boolean isInterrupted() 测试线程是否已经中断

3.1 线程优先级

线程的切换是由线程调度控制的,我们无法通过代码干涉。但我们可以通过提高优先级来最大限度改善获取时间片的概率。线程优先级被划分为十级,值为1-10.其中1最低10最高。线程提供了三个常量来表示最低,最高,和默认优先级:
- Thread.MIN_PRIORITY
- Thread.MAX_PRIORITY
- Thread.NORM_PRIORITY
void setPriority(int priority) 可通过这个方法设置优先级

3.2 守护线程(后台线程)

守护线程与普通线程没什么区别,有个特点是当进程中只剩下守护线程时,所有守护线程将强制终止。
void setDaemon(boolean b)
当参数为true时设置为守护线程

3.3 sleep方法

Thread的静态方法sleep用于使当前线程进入阻塞状态:
-static void sleep(long ms)
该方法会使当前线程进入阻塞状态指定时间,当时间过后 当前线程会重新进入Runnable状态等待分配时间片。

3.4 yield方法

该方法用于使当前线程主动让出cpu分配的时间片回到Runnable状态,等待分配时间片。
join()方法用于等待当前线程结束。
示例代码:

public class TestJoin {
    public static void main(String[] args) {
        //创建下载线程
        final Thread download=new Thread(){
            public void run(){
                System.out.println("图片开始下载");
                for(int i=1;i<=10;i++){
                    System.out.println("图片已下载:"+i*10+"%");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }
                System.out.println("图片下载完成");
            }
        };
    //创建显示线程
        Thread show=new Thread(){
            public void run(){
                System.out.println("等待图片下载完成");
                try {
                    download.join();//等待下载线程结束
                } catch (InterruptedException e) {
                }
                System.out.println("显示图片");
            }
        };
        download.start();
        show.start();
    }
}

上边代码创建了两个线程,一个下载线程一个显示线程。显示线程需要等待下载线程下载完成才能工作,所以我们在它的run方法里对下载线程执行run方法。下边是执行结果
这里写图片描述

4 线程同步

4.1 synchronized关键字

多个线程并发读写同一个临界资源时会发生线程安全问题。常见临界资源有实例变量,静态公共变量。下边举个例子来说明线程安全问题。
示例代码:

public class SyncDemo3 implements Runnable{
    private int  count=5;
    public void run() {
        for(int i=0;i<10;++i){
            if(count>0){
                try{
                    Thread.sleep(1000);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread()+":"+count--);
            }
        }
    }
    public static void main(String[] args) {
        SyncDemo3 he=new SyncDemo3();
        Thread h1=new Thread(he);
        Thread h2=new Thread(he);
        Thread h3=new Thread(he);
        h1.start();
        h2.start();
        h3.start();
    }
}

上边的代码我们让每个线程对count进行减一操作,我们希望的情况是当count等于0时就不在输出了。然而实际的运行结果是:
这里写图片描述
我们可以看到count甚至成了-1,为什么会产生这样的情况呢,这是因为可能线程2在运行的时候count是等于1的,然后在睡眠状态下线程1和线程0得到了运行的机会分别对count进行了减一操作然后线程2又得到时间片继续运行此时count已经等于-1了。这就是线程安全问题。若想解决线程安全问题,就需要将异步操作变为同步操作。

  • 异步操作:多线程并发的操作,相当于各干各的。
  • 同步操作:有先后顺序的操作,相当于你干完我再干。

synchronized关键字是java中的同步锁。它的用法是:
synchronized(锁对象的引用){
同步代码块
}
若方法所有代码都需要同步则可以直接给方法加锁。每个java对象都可以用做一个实现同步的锁。线程进入同步代码块前会自动获得锁,并且在退出这段代码块时自动释放锁(无论是正常退出还是抛异常退出)。其他线程要想访问上锁的代码块时必须要等待当前线程释放锁,这样就变成了同步操作。使用synchronized需要对一个对象上锁以保证线程同步,那么这个锁对象应该保证:多个需要同步的线程在访问该同步块时应该看到的是同一个对象的引用。我们通常使用this作为锁对象。上边代码加锁后

public class SyncDemo3 implements Runnable{
    private int  count=5;
    public void run() {
        for(int i=0;i<10;++i){
            //加锁,保证线程安全
            synchronized (this){
            if(count>0){
                try{
                    Thread.sleep(1000);
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread()+":"+count--);
            }
            }
        }
    }
    public static void main(String[] args) {
        SyncDemo3 he=new SyncDemo3();
        Thread h1=new Thread(he);
        Thread h2=new Thread(he);
        Thread h3=new Thread(he);
        h1.start();
        h2.start();
        h3.start();
    }
}

运行结果:
这里写图片描述
可以看到当我们对上述代码加锁后就是同步的了。
当我们对静态方法加锁时锁的是类对象。比如:
public synchronized static void xxx()
示例代码:

public class StaticSyncDemo {
    public static void main(String[] args) {
        final StaticDemo sd = new StaticDemo();
        final StaticDemo sd2 = new StaticDemo();
        Thread t1 = new Thread(){
            public void run(){
                //静态方法上锁后,同步是跨对象的
                sd.methodB();
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                sd2.methodB();
            }
        };
        t1.start();
        t2.start();
    }   
}

class StaticDemo{
    public synchronized void methodA(){
        String name = Thread.currentThread().getName();
        System.out.println(name+"调用了methodA方法");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
        }
        System.out.println(name+"调用methodA方法完毕");
    }

    public synchronized static void methodB(){
        String name = Thread.currentThread().getName();
        System.out.println(name+"调用了methodB静态方法");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
        }
        System.out.println(name+"调用methodB静态方法完毕");
    }
}

运行结果:
这里写图片描述
当一个类中的多个方法被synchronized修饰时,这些方法是互斥的。不能同时进入。如下所示:

public class SyncDemo2 {
    public static void main(String[] args) {
        final Demo demo = new Demo();
        Thread t1 = new Thread(){
            public void run(){
                demo.methodA();
            }
        };
        Thread t2 = new Thread(){
            public void run(){
                demo.methodB();
            }
        };
        t1.start();
        t2.start();
    }
}

class Demo{
    public synchronized void methodA(){
        String name = Thread.currentThread().getName();
        System.out.println(name+"调用了methodA方法");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+"调用methodA方法完毕");
    }
    public synchronized void methodB(){
        String name = Thread.currentThread().getName();
        System.out.println(name+"调用了methodB方法");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+"调用methodB方法完毕");
    }
}

运行结果:
这里写图片描述

4.2 wait和notify

wait和notify是Object定义的方法,wait导致线程进入等待状态,直到它被其他线程通过notify()或者notifyAll唤醒。该方法只能在同步方法中调用。notify随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态。该方法只能在同步方法或同步块内部调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。示例代码:

public class TestWaitAndNotify {
  private static boolean isFinish=false;
  public static void main(String[] args) {
      final Object obj=new Object();
      //下载图片的线程
      Thread download=new Thread(){
        public void run(){
            System.out.println("开始下载图片");
            for(int i=0;i<100;i++){
                System.out.println("下载了%"+i);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            isFinish=true;
            //通知显示线程可以开始工作了
            synchronized(obj){
                obj.notify();
            }
        }
    };
    //显示图片的线程
    Thread show=new Thread(){
    public void run(){
        System.out.println("show:开始显示图片");
        try {//在obj对象上等待
            synchronized(obj){
                /*
                 * wait方法要求:
                 * 调用哪个对象的wait方法就要将
                 * 该对象加锁
                 */
                obj.wait();
                }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(!isFinish){
            System.out.println("show:下载失败了");
        }else{
            System.out.println("show:下载成功了");
        }
    }
    };
    download.start();
    show.start();
}
}

上边代码当显示线程启动时,调用wait方法使obj对象进入等待阻塞状态,当下载线程完成后调用notify方法解除了对obj对象的阻塞。使显示线程可以继续工作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值