Java基础之多线程

记一次失败的学习方式

线程的三中创建方式,先上代码:

/*
    创建新线程的三种方式:
  1、继承Thread类;
  2、实现Runable接口;
  3、实现Callable接口;

需求:创建多线程对象,开启多线程。在子线程中输出1-100之间的偶数,主线程输出1-100之间的奇数。
 */
public class Test9 {
    public static void main(String[] args) {
/*
        线程启动必须调用start()方法,而非run()
        //方式一
        ThreadTest1 tt1 = new ThreadTest1();
        tt1.setPriority(10);//设置线程优先级,默认为5,范围1-10
        tt1.start();
*/
/*
        //方式二
        ThreadTest2 tt2 = new ThreadTest2();
        Thread theTt2Thread = new Thread(tt2);
        theTt2Thread.start();
*/
        //方式三
        Thread tt3 = new Thread(){
            @Override
            public void run(){
                //使用匿名类可以很方便的访问外部变量(这里并未访问外部局部变量)
                //但是在JDK7以前,就必须使用final修饰
                for (int i = 0; i < 100; i++) {
                    if (i % 2 == 0) System.out.println("子线程:" + i);
                }
            }
        };
        //tt3.setPriority(10);//线程优先级太高导致子线程打印完主线程才打印┓( ´∀` )┏233333(主线程默认5)
        tt3.start();
        //同时,该匿名类可以使用lambda表达式简化
        Thread tt4 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                if (i % 2 == 0) System.out.println("子线程:" + i);
            }
        });

        //主线程打印奇数
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 1) System.out.println("主线程:" + i);
        }
    }
}

//通过继承Thread类来创建新线程
class ThreadTest1 extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) System.out.println("子线程:" + i);
        }
    }
}

//通过实现Runnable接口来创建新线程
class ThreadTest2 implements Runnable {
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) System.out.println("子线程:" + i);
        }
    }
}

~~最开始执行该代码时,会产生这样的结果:

子线程居然在主线程打印完了才开始打印,这曾一度让我以为没有创建出新线程,刚开始的时候start()方法在主方法下,我以为是顺序的原因,所以我先调用子线程的start()方法,然后在主方法打印(也就是现在的代码),发现结果还是这样,我甚至以为是因为ThreadTest方法不是公共的原因,然后想到之前看C#的时候有讲到优先级问题,然后百度了一下发现了具体原因:~~

结果居然变了

子线程居然又开始工作了,我花了一晚上查线程优先级问题,最后发现最开始的情况居然复现不了了???(绝对不是主线程for循环在上面的原因)不行我得把我查到的结论整理一下o(╥﹏╥)o,等以后再遇到这种情况再说吧

线程优先级

1.在任意时刻,当有多个线程处于可运行状态时,运行系统总是挑选一个优先级最高的线程执行,只有当线程停止、退出或者由于某些原因不执行的时候,低优先级的线程才可能被执行
2.两个优先级相同的线程同时等待执行时,那么运行系统会以round-robin的方式选择一个线程执行(即轮询调度,以该算法所定的)(Java的优先级策略是抢占式调度!)
3.被选中的线程可因为一下原因退出,而给其他线程执行的机会:
  1) 一个更高优先级的线程处于可运行状态(Runnable)
  2)线程主动退出(yield),或它的run方法结束
  3)在支持分时方式的系统上,分配给该线程的时间片结束
4.Java运行系统的线程调度算法是抢占式(preemptive)的,当更高优先级的线程出现并处于Runnable状态时,运行系统将选择高优先级的线程执行
5.例外地,当高优先级的线程处于阻塞状态且CPU处于空闲时,低优先级的线程也会被调度执行

参见这里

放个很详细的博客,不过有些地方还不是特别明白,以后再仔细研读


线程状态及同步处理

线程的状态,这里有个线程状态图:
线程状态图
其实上面这张图并不是特别完整,下面对于状态的转换会更完整一点:
线程状态图2

线程状态导致状态发生条件
NEW(新建)线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行)线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed_Waiting(计时等待)同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

有三种方式完成同步操作:

  1. 同步代码块。
  2. 同步方法。
  3. 锁机制。

直接上代码:


线程同步来模拟过山洞

/*
模拟多个人通过一个山洞:
1.这个山洞每次只能通过一个人,每个人通过山洞的时间为1秒;
2.随机生成10个人,同时准备过此山洞,并且定义一个变量用于记录通过隧道的人数。
显示每次通过山洞人的姓名,和通过顺序;
 */
public class Test9 {
    public static void main(String[] args) {
/*
        Hole hole1 = new Hole();
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(hole1);
            t.start();
        }
*/
        //使用锁
        int numOfCrossed = 0;
        int[] theTest = new int[1];
        //锁必须是同一个,下面synchronized的参数同理,关于synchronized参数并没有限制
        Lock lock = new ReentrantLock();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                lock.lock();
                crossTheHole(theTest);
                lock.unlock();
            }).start();
        }
    }

    public static void crossTheHole(int[] numOfCrossed){
        String name = Thread.currentThread().getName();
        System.out.println(name+"正在穿过洞");
        try {
            Thread.sleep(600);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        numOfCrossed[0]++;
        System.out.println(name+"穿过了山洞,他是第"+ numOfCrossed[0] +"个");
    }
}

class Hole implements Runnable{
    int numOfCrossed = 0;
    Object lock = new Object();
    @Override
    public void run(){
        crossTheHole2();
    }
    //同步代码块
    public void crossTheHole(){
        synchronized (lock){
            String name = Thread.currentThread().getName();
            System.out.println(name+"正在穿过洞");
            try {
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            numOfCrossed++;
            System.out.println(name+"穿过了山洞,他是第"+ numOfCrossed +"个");
        }
    }
    //同步方法
    public synchronized void crossTheHole2(){
        String name = Thread.currentThread().getName();
        System.out.println(name+"正在穿过洞");
        try {
            Thread.sleep(500);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        numOfCrossed++;
        System.out.println(name+"穿过了山洞,他是第"+ numOfCrossed +"个");
    }
}

线程通信及锁

进程间通信使用wait()和notify()进行等待和唤醒,注意使用的锁需要是同一个,并且线程被唤醒后并不是立即进入运行状态,而是从WAITING释放与其他线程进行竞争,如果获取到锁,则进入RUNNABLE状态,否则进入BLOCKED状态(即未获取到锁)。
TIMED_WAITING在API中描述为:一个正在限时等待另一个线程执行一个(唤醒)动作的线程处于这一状态,最常见的就是sleep()方法,当然,sleep单独一个线程也可以使用,注意sleep()并不会释放当前锁权限
关于wait()和notify():

Thread类的方法:sleep(),yield()等
Object的方法:wait()和notify()等  
每个对象都有一个机锁来控制同步访问。Synchronized关键字可以和对象的机锁交互,来实现线程的同步。
由于sleep()方法是Thread 类的方法,因此它不能改变对象的机锁。所以当在一个Synchronized方法中调用sleep()时,线程虽然休眠了,但是对象的机锁没有被释放,其他线程仍然无法访问这个对象。而wait()方法则会在线程休眠的同时释放掉机锁,其他线程可以访问该对象。
Yield()方法是停止当前线程,让同等优先权的线程运行。如果没有同等优先权的线程,那么Yield() 方法将不会起作用。
一个线程结束的标志是:run()方法结束。
一个机锁被释放的标志是:synchronized块或方法结束。
Wait()方法和notify()方法:当一个线程执行到wait()方法时,它就进入到一个和该对象相关的等待池中,同时失去了对象的机锁。当它被一个notify()方法唤醒时,等待池中的线程就被放到了锁池中。该线程从锁池中获得机锁,然后回到wait()前的中断现场。
join()方法使当前线程停下来等待,直至另一个调用join方法的线程终止。
值得注意的是:线程的在被激活后不一定马上就运行,而是进入到可运行线程的队列中。

另外,Timed Waiting(计时等待) 与 Waiting(无限等待) 状态联系还是很紧密的,比如Waiting(无限等待) 状态中wait方法是空参的,而timed waiting(计时等待) 中wait方法是带参的。

关于多线程的更多信息请参见我的另一篇博文:Java多线程之以7种方式让主线程等待子线程结束,里面用到了concurrent这个强大的多线程包。


synchronized:
synchronized同步代码块时锁可以使任意对象
synchronized同步非静态方法时,锁是this(因此同一对象的多个同步方法不能同时执行)
synchronized同步静态方法时,锁是该类的类对象,即类名.class(即多个同步静态方法不能同时执行)

标签: Java, 多线程

添加新评论