初识java多线程

一、创建多线程的方法一:

创建java.lang.Thread的子类,重写该类的run方法

基本用法例子:

public class ThreadTest1 {
  public static void main(String[] args) {
        // TODO Auto-generated method stub
        // 1、创建一个线程对象
        Thread thr = new FirstThread(); 
        // 2、调用线程对象的start()方法启动线程
        thr.start();
        String threadName = Thread.currentThread().getName();
            for (int i = 0; i < 100; i++){
                System.out.println(threadName + ": " + i);
            }
    }
}
//继承Thread类
class FirstThread extends Thread{
/*
* 线程体在run()方法中
*/
public void run(){
    // 显示当前线程名称
    String threadName = Thread.currentThread().getName();
        for (int i = 0; i < 100; i++){
        System.out.println(threadName + ": " + i);
        }
    }
}
//输出结果为:
main: 0
Thread-0: 0
Thread-0: 1
main: 1
main: 2
Thread-0: 2
main: 3
Thread-0: 3
Thread-0: 4
...
0~99 输出了两遍


思考一下如何去实现两个线程共享一个变量?
1. 思路一:是用static静态化一个变量


public class PrintNumber {
   public static void main(String[] args){
       Thread thread1 = new PrintThread("thread1");
       Thread thread2 = new PrintThread("thread2");
       thread1.start();
       thread2.start();
   }
}
class PrintThread extends Thread{
   private static int i;
   public PrintThread(String name){
       super(name);
   }
   public void run(){
       for (i = 0; i < 100; i++){
          System.out.println(getName() + ": " + i );
       }
   }
}

2.思路二: 传递一个对象给一个进程,通过对象在进程中共享数据


public class PrintNumber {
   public int i;
   public static void main(String[] args){
       PrintNumber pn1 = new PrintNumber();// 需要传递给进程的对象
       Thread thread1 = new PrintThread("thread1", pn1);
       Thread thread2 = new PrintThread("thread2", pn1);
       thread1.start();
       thread2.start();
   }
}
class PrintThread extends Thread{
   PrintNumber pn;
   public PrintThread(String name, PrintNumber pn){
       super(name);
       this.pn = pn;
   }
   public void run(){
       for (pn.i = 0; pn.i < 100; pn.i++){
          System.out.println(getName() + ": " + pn.i );
       }
   }
}



二、创建多线程的方法二:

实现Runnable接口的方式:

1、创建实现Runnable接口的实现类:必须实现run()方法

2、创建第一步那个实现类的实例对象
3、利用Thread(Runnable target)构造器,创建Thread对象

4、调用Thread类的start方法调用线程

例子:

//实现MyRunnbale是Runnable的实现类
public class MyRunnable implements Runnable {
    public static int i = 0;
    @Override
    // 实现run方法
    public void run() {
        for (; i < 100; i++){
            System.out.println(Thread.currentThread().getName() + ":  " + i);
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        MyRunnable mr = new MyRunnable();
        Thread th1 = new Thread(mr);
        Thread th2 = new Thread(mr);
        th1.start();
        th2.start();
    }
}



三、线程的生命周期

线程的生命周期指的是线程从启动,直到结束
可以调用Thread类的相关方法影响线程的运行状态

线程的运行状态有:

  • 新建(new)
  • 可执行(Runnable)
  • 运行(Running)
  • 阻塞(Blocking)
  • 死亡(Dead)

新建状态

当新建了一个Thread对象时,该进程处于新建状态,没有启动,所以无法执行

可执行状态

其他线程调用了处于新建状态线程的start方法,该线程对象转换到“可执行状态”
线程拥有获得cpu的权利,处于等待调度阶段

运行状态

处于可执行状态的线程一旦获得cpu的控制权,就会转换到运行状态,
在执行状态下,线程状态占用cpu时间片段,执行run方法中的代码,处于执行状态下的线程可以调用yield方法,该方法用于主动让出cpu控制权。线程对象让出控制权后,回到可执行状态,重新等待调度;

阻塞状态

线程在执行状态下,由于某种条件影响,会被迫让出cpu控制权,进入“阻塞状态”
进入阻塞状态有三种情况:
- 调用sleep 方法:
Thread的sleep方法能让线程暂止休眠一段时间,单位是毫秒


实际例子:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        // TODO Auto-generated method stub
        for (int i = 0; i < 10; i++){
            System.out.println(Thread.currentThread().getName() + ":  " + i);
            try {
                // Thread的sleep方法能让线程暂止休眠一段时间,单位是毫秒

                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        MyRunnable mr = new MyRunnable();
        Thread th1 = new Thread(mr);
        th1.start();
    }
}
  • 调用join方法
    处于执行状态的调用了其他线程的join方法,将会挂起,进入阻塞状态
    目标线程执行完毕后,才能解除阻塞,回到可执行状态;

public class ThreadJoin extends Thread{
    public void run(){
        for (int i = 0; i < 10; i++){
            System.out.println(getName() + ": " + i);
        }
    }

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ThreadJoin thread = new ThreadJoin();
        thread.start();
        for (int i = 0; i < 10; i++){
            System.out.println(Thread.currentThread().getName() + ": " + i);
            if (i == 5){
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
}
// 输出结果;
main: 0
Thread-0: 0
main: 1
main: 2
Thread-0: 1
main: 3
main: 4
Thread-0: 2
main: 5
Thread-0: 3
Thread-0: 4
Thread-0: 5
Thread-0: 6
Thread-0: 7
Thread-0: 8
Thread-0: 9
main: 6
main: 7
main: 8
main: 9
  • 执行I/O操作



    解除阻塞状态有三种情况:

  • 睡眠超时
  • 调用join后等待其他线程执行完毕
  • I/O操作完毕
  • 调用堵塞线程的interrupter方法(线程睡眠时,调用该方法会抛出InterrupterException)

public class Interrupter extends Thread {
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Interrupter interrupter = new Interrupter();
        interrupter.start();
        interrupter.interrupt(); //解除线程的堵塞状态
    }

    public void run(){
        for (int i = 0; i < 10; i++){
            System.out.println(getName() + ": " + i);
            if (i == 5){
                try {
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
}
执行结果:
Thread-0: 0
Thread-0: 1
Thread-0: 2
Thread-0: 3
Thread-0: 4
Thread-0: 5
java.lang.InterruptedException: sleep interrupted //这是提示运行时异常
Thread-0: 6
Thread-0: 7
Thread-0: 8
Thread-0: 9
    at java.lang.Thread.sleep(Native Method)
    at Interrupter.run(Interrupter.java:15)



死亡状态

处于执行状态的线程一旦从run()方法返回,无论是正常退出还是抛出异常,就会进入“死亡状态”;

已经死亡的线程不能重新运行,否则会抛出异常illegalThreadStateException

可以使用Thread类的isAlive方法判断线程是否活着;



四、线程的优先级

  • Thread类提供了设置和获取当前优先级的方法:

    setPriority();设置优先级不一定起作用,在不同的JVM、操作系统上,效果不同,操作系统不能保证设置了优先级的线程会优先执行或者获得更多的cpu时间

    getPriority();
  • Java设置了10个优先级,1~10;1最低,10最高,主线程默认优先级为5;
  • 三个代表优先级的常量:MIN_PRIORITY、MAX_PRIORITY、NORM_PRIORITY,分别代表1、10、5

public class PriortityTest extends Thread{
    public PriortityTest(String name) {
        // TODO Auto-generated constructor stub
        super(name);
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        PriortityTest priThread0 = new PriortityTest("线程一"),
                      priThread1 = new PriortityTest("线程二1");
        System.out.println(priThread0.getPriority());// 5
        System.out.println(priThread1.getPriority());// 5
        priThread0.setPriority(MIN_PRIORITY);//值为10,The maximum priority that a thread can have.
        priThread1.setPriority(MAX_PRIORITY);
        priThread0.start();
        priThread1.start();
    }
    public void run(){
        for (int i = 0; i < 10; i++){
            System.out.println(getName() + ": " + i);
        }
    }
}



五、线程共享以及安全问题:

首先来看个例子:有五个苹果,两个线程同时拿苹果,每拿一个,打印谁拿了,剩下几个; 利用多线程,两个线程共享资源appleCount,这样在其中一个线程拿完一个苹果后,还没来的及打印,第二个线程已经将appCount--;所以会导致打印剩下的苹果时出现错位;


public class ShareApple implements Runnable{
    private int appleCount = 5;
    boolean getApple(){
        if (appleCount > 0){
            appleCount--;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "拿走一个苹果," + "剩" + appleCount);
            return true;
        }
        return false;
    }
    public void run(){
        boolean flag = getApple();
        while (flag){

          flag = getApple();
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
       ShareApple sa = new ShareApple(); 
       Thread th1 = new Thread(sa);
       Thread th2 = new Thread(sa);
       th1.setName("shao");
       th2.setName("jinghong");
       th1.start();
       th2.start();
    }
}

//输出结果为:
shao拿走一个苹果,剩3
jinghong拿走一个苹果,剩3
shao拿走一个苹果,剩1
jinghong拿走一个苹果,剩1
shao拿走一个苹果,剩0


使用synchronized代码块解决线程安全问题

需要在synchronized代码块参照线程共同的对象:

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:
1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;

synchornized(){
    //...
}
  1. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
public synchronized void method(){
    // ...
}
  1. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
public synchronized static void method() {
   // todo
}
  1. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
class ClassName {
   public void method() {
      synchronized(ClassName.class) {
         // todo
      }
   }
}

synchronized关键字,确保共享资源只能在同一个时刻被一个线程访问,这种处理机制称为线程同步,或者线程互斥,基于“对象锁”的概念


public class ShareApple implements Runnable{
    private int appleCount = 5;
    boolean getApple(){
        synchronized(this){
            // this是一个共同的参照对象,这里指的是同一个ShareApple对象
            if (appleCount > 0){
                appleCount--;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "拿走一个苹果," + "剩" + appleCount);
                return true;
            }
            return false;
        }
    }
    public void run(){
        boolean flag = getApple();
        while (flag){
          flag = getApple();
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
       ShareApple sa = new ShareApple(); 
       Thread th1 = new Thread(sa);
       Thread th2 = new Thread(sa);
       th2.setName("shao");
       th1.setName("jinghong");
       th1.start();
       th2.start();
    }
}



线程通信

当一个线程在使用同步方法的某个参数时,而此变量又需要其他线程修改后才能符合本线程的需要,那么可以在线程中添加wait()方法;

相关方法:wait(), notify(), notifyAll();
这些方法必须在同步方法中调用。

例如:有如下情景,刘关张三个买票,售货员只有一张5元,张飞拿20元,刘备、关羽各拿五元,当张飞买票时,售货员会说:”没有足够多的零钱,需要等待后面的人买票后有零钱之后才能把票卖给你”;


public class Ticket implements Runnable{


    private int fiveCount = 1, tenCount = 0, twentyCount = 0;

    public synchronized void buy(){
        String name = Thread.currentThread().getName();
        if ("张飞".equals(name)){
           if (fiveCount < 3){
            try {
                System.out.println("没有足够多的零钱,需要等待有零钱之后才能把票卖给张飞");
                wait();
                System.out.println("卖给张飞一张票,并给他找零");
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
           }
        } else if ("关羽".equals(name)){
            System.out.println("关羽给售票员一张五元");
            fiveCount ++; //关羽给售票员一张五元
        } else if("刘备".equals(name)){
            System.out.println("刘备给售票员一张五元");
            fiveCount ++;// 刘备给售票员一张五元
        }
        // 唤醒进程的条件
        if (fiveCount == 3){
            notifyAll();
        }
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        buy();
    }
    public static void main(String[] args){
        Ticket ticket = new Ticket();
        Thread th1 = new Thread(ticket);
        Thread th2 = new Thread(ticket);
        Thread th3 = new Thread(ticket);
        th1.setName("张飞");
        th2.setName("关羽");
        th3.setName("刘备");
        th1.start();
        th2.start();
        th3.start();
    }
}

//输出结果为:
没有足够多的零钱,需要等待有零钱之后才能把票卖给张飞
刘备给售票员一张五元
关羽给售票员一张五元
卖给张飞一张票,并给他找零


两个线程如何交替打印a-z


public class TrigglePrint implements Runnable{
    char c = 'a';
    public synchronized void  print(){
          System.out.println(Thread.currentThread().getName() + ": " + c);
          notifyAll();
          try {
            wait();
          } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
          }
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        for (; c < 'z'; c++){
         print();
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        TrigglePrint tp = new TrigglePrint();
        Thread thread1 = new Thread(tp);
        Thread thread2 = new Thread(tp);
        thread1.start();
        thread2.start();
    }
}
//结果:
Thread-0: a
Thread-1: a
Thread-0: b
Thread-1: c
Thread-0: d
Thread-1: e
Thread-0: f
Thread-1: g
Thread-0: h
Thread-1: i
Thread-0: j
...
...
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值