多线程(二)

线程的调度

在这里插入图片描述
在这里插入图片描述

/*
线程的优先级:
1.
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
2.如何获取和设置当前线程的优先级:
getPriority():获取线程的优先级
setPriority(int p):设置线程的优先级
 */

class myThread extends Thread{
    @Override
    public void run() {
        for (int i=0;i<100;i++)
            //获取当前线程名字
            System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()+":"+i);
    }
}

public class ThreadTest {
    //遍历100以内的所有偶数
    public static void main(String[] args) {
        myThread h1 = new myThread();
        h1.setName("线程一");
        h1.start();
        //给主线程设置名称
        Thread.currentThread().setName("主线程");
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+":"+Thread.currentThread().getPriority()+":"+i);
            if(i==20){
                try{
                    h1.join();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }

    }
}

在这里插入图片描述
两个线程的优先级都是默认的5;
可以对线程的优先级进行修改
在这里插入图片描述
在这里插入图片描述
由此看出,线程优先级高不一定先执行,但是高概率先执行。
高优先级的线程要抢占低优先级线程cpu的执行权,但是只是从概率上讲,高优先级的线程高概率的情况下被执行,并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。

例子:创建三个窗口卖票

class myThread extends Thread{
    private static int tickt=100;//静态的,这样保证三个线程是同一个ticket
    //而不是三个ticket
    @Override
    public void run() {
        while (true){
            if(tickt>0){
                System.out.println(getName()+":卖票,票号为:"+tickt);
                tickt--;
            }else{
                break;
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        myThread h1 = new myThread();
        myThread h2 = new myThread();
        myThread h3 = new myThread();
        h1.setName("窗口一");
        h2.setName("窗口二");
        h3.setName("窗口三");
        h1.start();
        h2.start();
        h3.start();
    }
}

在这里插入图片描述
该程序并不完美,出现了线程安全问题,多个票数同号,这里先忽略,后面会解决。
所以要用到第二种多线程的创建方法。

多线程的创建方法二

实现Runnable接口

/*
创建多线程的方式二:实现Runnable接口
1.创建一个实现了Runnable接口的类
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5.通过调用Thread类的对象调用start()
 */

class myThread implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        myThread h = new myThread();
        Thread t1= new Thread(h);
        t1.setName("线程一");
        t1.start();
        Thread t2=new Thread(h);
        t2.setName("线程二");
        t2.start();
    }
}

实现多窗口卖票

class myThread implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while (true){
            if(ticket>0){
                System.out.println(Thread.currentThread().getName()+":"+ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
    //创建实现接口类的对象
        myThread h = new myThread();
        //将对象作为参数传入Thread构造器
        //创建Thread的对象
        Thread t1= new Thread(h);
        t1.setName("窗口一");
        t1.start();
        Thread t2=new Thread(h);
        t2.setName("窗口二");
        t2.start();
    }
}

此方式仍然存在线程安全问题,这里暂时忽略。

比较创建线程的两种方式

开发中优先选择实现Runnable接口的方式
原因:
1.实现的方式没有类的单继承性的局限性
2.实现的方式更适合来处理多个线程有共享数据的情况。
联系:public class Thread implements Runnable
相同点:两种方式都需要重写run(),将线程要执行的内容声明在run()中

进程:运行中的代码
进程可以细化为多个线程,每个线程拥有自己独立的栈和程序计数器。多个线程共享同一个进程中的结构。

并行:多个cpu同时执行多个任务。比如多个人同时做不同的事。
并发:一个cpu(采用时间片)同时执行多个任务。比如秒杀,多个人做同一件事。

线程的分类
守护线程、用户线程
在这里插入图片描述
一个java程序执行时最少有两个线程,都会启动一个jvm,每个jvm实际上就是操作系统中启动一个进程,java本身具备了垃圾回收机制,所以每个java运行时至少会启动两个线程,一个main线程,另外一个是gc垃圾回收线程。

线程的生命周期

在这里插入图片描述
线程的最终状态一定是死亡而不是阻塞。

在这里插入图片描述

线程安全问题

在这里插入图片描述

解决线程安全问题

问题:卖票过程中出现了冲票、错票—>线程安全问题
出现问题的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
==如何解决:==当一个线程a在操作ticket的时候,其他线程不能参与进来,知道线程a操作玩ticket时,其他线程才可以操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
在java中,我们通过同步机制来解决线程安全问题。

同步的方式,解决了线程的安全问题。操作同步代码时,只能有一个线程参与,其他线程等待。
相当于是一个单线程的过程,效率更低,但没太大影响。

方式一:同步代码块

synchronized (同步监视器){
            //需要被同步的代码(即操作共享数据的代码)
            //共享数据:多个线程共同操作的变量。比如ticket就是共享数据
            //同步监视器,俗称:锁。任何一个类的对象都可以充当锁
        }

多个线程必须要公用同一把锁。

这样写出现线程安全问题的概率为0。
同步代码块解决Runnable接口的方式创建的线程安全问题

class myThread implements Runnable{
    private int ticket=100;
    private Object obj=new Object();//必须new在run方法外面
    //不然多个线程用的不是同一把锁
    @Override
    public void run() {
        while (true) {
            synchronized (obj){
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":" + ticket);
                ticket--;
            } else {
                break;
            }
          }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        myThread h = new myThread();
        Thread t1= new Thread(h);
        t1.setName("线程一");
        t1.start();
        Thread t2=new Thread(h);
        t2.setName("线程二");
        t2.start();
    }
}

在实现Runnable接口创建多线程的方式中,可以考虑使用this充当同步监视器,(具体情况具体分析),不一定所有情况都可以。
这里的this代表的是myThread的对象h。

同步代码块解决继承Thread类创建的线程安全问题

class myThread extends Thread{
    private static int ticket=100;//记得用private修饰
    private static Object obj=new Object();//记得要用private修饰
    @Override
    public void run() {
        while (true) {
            synchronized (obj){
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":" + ticket);
                ticket--;
            } else {
                break;
            }
          }
        }
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        myThread t1 = new myThread();
        t1.setName("线程一");
        myThread t2 = new myThread();
        t2.setName("线程二");
        t1.start();
        t2.start();
        //new了两个myThread对象,所以ojb和ticket要用static,不然就有两个ticket和obj
    }
}

这种方式慎用this代替obj,因为this在两条线程里分别代表t1,t2两个对象。但有些情况能用。
不过可以考虑用myThread.class代替obj,也就是当前类代替obj。类也是对象且这个类是唯一的。也是具体情况具体分析。

需要被同步的代码被synchronize大括号扩住,不能扩多了也不能扩少了。

方式二:同步方法
如果操作共享数据的代码完整地声明在一个方法中,我们不妨将此方法声明同步的。

/*
使用同步方法解决实现Runnable接口的线程安全问题
 */
class myThread implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while (true){
            show();
            if(ticket==0)
                break;
        }
    }
    //将同步代码部分挪出来声明在方法里
    //该方法用synchronize修饰(上锁)
    private synchronized void show() {
    //同步监视器为this,就是h
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":" + ticket);
                ticket--;
            }
        }

}


public class ThreadTest {
    public static void main(String[] args) {
        myThread h = new myThread();
        Thread t1=new Thread(h);
        t1.setName("线程一");
        Thread t2=new Thread(h);
        t2.setName("线程二");
        t1.start();
        t2.start();
    }
}

这里的同步监视器是this。
同样的,该方法用到解决实现继承Thread创建多线程的线程安全问题中就有问题出现,因为此时两个线程的锁(同步监视器)有两个this,分别为t1,t2。不唯一。所以需要修改。

/*
使用同步方法解决实现继承Thread的线程安全问题
 */
class myThread extends Thread{
    private static int ticket=100;
    @Override
    public void run() {
        while (true){
            show();
            if(ticket==0)
                break;
        }
    }
    private static synchronized void show() {
        //此时的同步监视器是当前类:myThread.class
            if (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":" + ticket);
                ticket--;
            }
        }

}


public class ThreadTest {
    public static void main(String[] args) {
        myThread t1=new myThread();
        myThread t2=new myThread();
        t1.setName("线程一");
        t2.setName("线程二");
        t1.start();
        t2.start();
    }
}

关于同步方法的总结
1.同步方法仍然涉及到同步监视器,只是不需要我们显式的声明(系统有默认)
2.非静态的同步方法,同步监视器是this(当前类的对象)
3.静态的同步方法,同步监视器是当前类本身。

线程安全的单例模式之懒汉式

/*
使用同步机制将单例模式中的懒汉式改写为线程安全的
 */
public class Demo1 {

}
class Bank{
    private Bank(){}
    private static Bank instance=null;
    private static synchronized Bank getInstance(){
        //同步监视器为Bank类本身(Bank.class)
        if(instance==null){
            instance=new Bank();
        }
        return instance;
    }
}

也可以用同步代码解决:

public class Demo1 {

}
class Bank{
    private Bank(){}
    private static Bank instance=null;
    private synchronized Bank getInstance(){
        /*方式一:效率稍差
        synchronized (Bank.class){
            if(instance==null){
                instance=new Bank();
            }
            return instance;
        }*/
        //方式二:效率更高
        if(instance==null){
            synchronized (Bank.class){
                if(instance==null){
                    instance=new Bank();
                }
            }
        }
        return instance;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值