线程

1.程序、进程、线程
进程可以细化为多个线程;每个线程拥有自己独立的:栈、程序计数器;多个线程共享一个进程中的结构:方法区、堆
2.单核cpu、多核cpu
单核cpu:是一种假的多线程;多核cpu,能够更好的发挥多线程的效率
一个java.exe至少有3个线程:main()主线程、gc()垃圾回收线程、异常处理线程
3.并行、并发
并行:多个cpu同时执行多个任务
并发:一个cpu同时执行多个任务

4.创建线程的方式

(1)创建线程的方式一

/*
*创建多线程步骤:
* 1.继承Thread类
* 2.重写THread类的run方法--》将线程执行的操作声明在run()中
* 3.创建Thread的子类的对象
* 4.使用子类调用start()方法
*
* */
class MyThread extends Thread{
    @Override
    public void run() {
        for(int i = 0;i < 20;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() +"...."+ i);
            }
        }
    }
}
public static void main(String[] args) {
        MyThread thread_01 = new MyThread();
        //thread_01线程中执行
        thread_01.start();
}

使用匿名类创建多个线程

public static void main(String[] args) {
        //new Thread().start();//此种方式并不是创建匿名的thread对象,而是开启Thread这个父类线程
        new Thread(){//创建匿名子类线程1
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "....");
            }
        }.start();
        new Thread(){//创建匿名子类线程2
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "....");
            }
        }.start();
    }
Thread中常用的方法

1.设置name
方式1.setName()

MyThread thread_01 = new MyThread();
thread_01.setName("xiancheng");

方式2.构造器中调用父类构造器

MyThread thread_01 = new MyThread("xian");
class MyThread extends Thread{
    public MyThread(String name){
        super(name);
    }

2.yield:线程让步,即释放当前cpu执行权
3.join:在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态
4.sleep(time):让当前线程睡眠指定毫秒,在指定时间内,当前线程是阻塞状态
5.isAlive:判断当前线程是否存活

线程的调度

线程优先级:
1.获取线程优先级:thread_01.getPriority();
2.设置线程优先级: MAX_PRIORITY=10;MIN_PRIORITY=1,NORM_PRIORITY=5(默认值)
thread_01.setPriority(Thread.MAX_PRIORITY);
同优先级线程:先到先服务
对高优先级:高优先级的线程抢占cpu,也有可能抢不到,只是概率高而已

(2)创建线程的方式2:实现Runnable接口

/*
* 方式二创建多线程
* 1.实现Runnable接口
* 2.重写run方法
* 3.创建实现类的对象
* 4.将此对象作为参数,传递到Thread类的构造器中
* 5.使用Thread类对象调用start方法
* */
public class Thread_02 {
    public static void main(String[] args) {
        //创建实现类对象
        MyThread_02 my = new MyThread_02();
        //将对象作为参数,放入Thread类的构造器中
        Thread t = new Thread(my);
        //使用Thread类的对象调用start
        t.start();
    }
}
class MyThread_02 implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i < 100;i++){
            if(i % 2 == 0){
//        System.out.println(getName()+".........");不能直接getName,原因是该类并没有继承Thread类
                System.out.println(Thread.currentThread().getName()+"........."+i);
            }
        }

    }
}

售票:

/*
* 创建3个卖票窗口,一共有100张票
* 存在线程安全问题
* */

//实现接口方式
public class WindowTest_02 {
    public static void main(String[] args) {
        //创建1个Window_02
        Window_02 w  = new Window_02();

        Thread t1 = new Thread(w);//不同线程使用同一个对象
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        //保证任意一个窗口售票时,其他窗口不运行,且获得公有的对象,即static
        t1.setName("1号");
        t2.setName("2号");
        t3.setName("3号");

        t1.start();
        t2.start();
        t3.start();

    }
}

class Window_02 implements Runnable {
    private  int i = 100;//未使用static方法
    ......
}

//继承方式
public class WindowTest {
    public static void main(String[] args) {
        //创建3个卖票窗口
        Window w1  = new Window();
        Window w2  = new Window();
        Window w3  = new Window();
        //保证任意一个窗口售票时,其他窗口不运行,且获得公有的对象,即static
        w1.setName("1号");
        w2.setName("2号");
        w3.setName("3号");

        w1.start();
        w2.start();
        w3.start();

    }
}
class Window extends Thread {
    private static int i = 100;
     .......
}
比较线程的两种方式

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

线程通信:

wait()、notify()、notifyAll():此三种方法定义在Object类中
线程分类:守护线程、分类线程

线程的生命周期

线程_第1张图片
线程生命周期.png

线程同步,解决线程安全

1.以上述售票为例,会出现的问题:重票、错票---》即线程安全问题
2.问题出现原因:当某个线程还未完成操作时,有新的线程进入,也对其进行操作
3.解决办法:当某个线程还未完成操作时,其他线程必须在外等待,知道该线程完成操作才其它线程才可以运行。这种情况即使在线程出现阻塞时,也不能被改变

4.在java中,使用同步机制,解决线程安全问题:

(1)同步代码块:要求多个线程必须共用同一把锁;坏处,相当于单线程,效率低
sychronized(同步监视器){
//需要被同步的代码,不能多也不能少,即操作共享数据(多个线程共同操作的变量)的代码
}
同步监视器:俗称锁。任何一个对象都可以充当锁
1).实现Runnable接口方式的锁

class Window_02 implements Runnable {
    private  int i = 100;//未使用static方法
    Object obj = new Object();//所有线程共享一个
    @Override
    public void run() {
        while(true){
            synchronized(obj){//锁;
            synchronized(this){//此时的this,表示当前Window_02对象,implements Runnable接口时,多个线程公用一个线程子类对象
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(i > 0){
                    System.out.println(Thread.currentThread().getName()+"卖票:票号为"+i);
                    i--;
                }else{
                    break;
                }
            }

        }
    }

2).继承Thread方式的锁

class Window extends Thread {
    private static int i = 100;//必须使用static
    private static Object obj = new Object();//必须使用static
    @Override
    public void run() {
        while(true){
            synchronized(obj){//正确
            synchronized(Window.class){//正确,以类为对象,只会加载一次,即Class clazz = Window.class
            //synchronized(this){//错误,因为extends Thread时,会创建多个线程对象,即,t1,t2,t3,他们不共用一个线程
              ......
            }
        }
    }
}

(2)同步方法
如果操作共享数据的代码完整的声明在一个方法中,则将此方法声明为同步的,此方法为同步方法
总结:同步方法不需要显示的声明;非静态同步方法,同步监视器是this,静态同步方法,同步监视器是当前类本身
1)继承Thread

class Window extends Thread {
    private static int i = 100;
    @Override
    public void run() {
       show();
    }
    private static synchronized void show() {//使用static关键字修饰该锁,则所有成员共享同一个锁;该锁是当前类Window.class
//    private synchronized void show() {//错误,此锁代表this,但创建的子类Thread对象创建了好几个,表示不同对象
        while(true){
            if(i > 0){
                    System.out.println(Thread.currentThread().getName()+"卖票:票号为"+i);
                    i--;
                }else{
                    break;
                }
            }
    }
}

2)实现Runnable接口

class Window_02 implements Runnable {
    private  int i = 100;//未使用static方法
    @Override
    public void run() {
        show();
    }

    private synchronized void show() {//此时锁代表this
        while(true){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(i > 0){
                    System.out.println(Thread.currentThread().getName()+"卖票:票号为"+i);
                    i--;
                }else{
                    break;
                }
            }
    }
}

死锁

1.不同线程分别占用对方需要的同步资源不放弃,都在等待对方释放资源;出现死锁后,不会出现异常和提示,只是所有线程都阻塞,无法继续
2.解决办法:尽量避免同步或者使用算法

5.lock锁解决线程安全(jdk5.0新特性)

同步锁使用Lock充当对象;使用ReentranLock的对象实现Lock

class MyLock extends Thread {
    private static int i = 40;
    private ReentrantLock lock = new ReentrantLock();//创建lock锁对象
    @Override
    public void run() {
        try {
            //打开锁
            lock.lock();
            while (true) {
                if (i > 0) {
                    System.out.println(getName() + "...." + i);
                    i--;
                } else {//跳出来,否则程序一直在运行
                    break;
                }
            }
        }finally {
            //释放锁
            lock.unlock();
        }
    }
}
synchronized和lock异同:

相同点:都能解决线程安全问题
不同点:synchronized机制在执行完相应的同步代码块后,自动释放同步监视器;lock需要手动启动(lock)或结束(unlock)同步;lock只有代码块锁,synchronized有代码块锁和方法锁
优先使用顺序:lock--》同步代码块--》同步方法

例题:

银行有一个账户,有两个用户分别向同一个账户存3000元,每次存1000,存3次。每次存完打印账户余额

public class Bank {
    public static void main(String[] args) {
        //此种方式类似于实现runnable接口的创建调用start方法
        Account acct = new Account(0);
        Customer c1 = new Customer(acct);//创建名为甲的客户
        Customer c2 = new Customer(acct);

        c1.start();
        c2.start();
    }
}

//存储用户,每个用户共存款3次
class Customer extends Thread {
    private Account acct;//创建公有的Account账户

    //创建Account的构造方法
    public Customer(Account acct) {
        this.acct = acct;
    }
     @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {      
                //调用Account存钱的方法
                acct.deposit(1000);
          }
    }
}

//银行账户,共存到6000,即balance是共有数据
class Account {
    private int balance = 0;
    //创建Account的构造器,即一创建Account账户,便初始化其balance
    Account(int balance){
        this.balance = balance;
    }
//方式一:lock
锁
 private static ReentrantLock lock  = new ReentrantLock();
 void deposit(int money){
        try{
            lock.lock();
            if(money >= 0){
                balance += money;
                System.out.println(Thread.currentThread().getName() + "存款,共存" + balance);
            }
        }finally {
            lock.unlock();
        }
    }
//方式二,同步方法
 synchronized void deposit(int money){

            if(money >= 0){
                balance += money;
                System.out.println(Thread.currentThread().getName() + "存款,共存" + balance);
            }
    }
}

线程通信

wait()、notify()/notifyAll()必须搭配使用,且都属于Object方法;必须使用在同步代码块或同步方法中;调用者必须是同步代码块或同步方法中的同步监视器,否则出现IllegalMonitorStateException
wait:一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
notify:唤醒被wait的一个线程,若有多个线程,则唤醒优先级高的
notifyAll:唤醒所有wait的线程

/*
* 使用线程1,2交替打印1~100的数
* */
public class Communication {
    public static void main(String[] args) {
        new Number().start();
        new Number().start();
    }
}
class Number extends Thread{
    private static int num = 1;
    private static Object obj = new Object();
    @Override
    public void run() {
        while(true){
            //继承方式的不能使用this,因为每次this的对象不同
            synchronized (obj){
                obj.notify();
                if(num <= 20){
                    System.out.println(getName() + "打印" + num);
                    num++;
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    break;
                }
            }
        }
    }
}
例题:

sleep和wait的异同:
相同点:都能使当前线程进入阻塞状态
不同点:(1)位置不同,Thread类中声明sleep,Object类中声明wait
(2)调用要求不同,sleep可以在任意场景,wait必须在同步代码块或同步方法中
(3)是否释放同步监视器,若两者都使用在同步代码块或同步方法中,sleep不会释放锁,wait会释放锁

/*
* 线程通信的应用:生产者/消费者问题(经典例题)
* 生产者(Product)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(如:20)
* 如果生产者试图生产更多的产品,店员会让生产者停止,若店中有空位再通知生产者继续生产;如果店中没有产品,店员会让消费者
* 等一下,若有产品了再通知消费者取走产品
*
* 分析:
* 1.多线程:生产者、消费者
* 2.线程安全:共享数据,产品或店主
* 3.解决线程安全:同步(3种)
* 4.线程间相互通信:无产品,无空位时
*
* */
public class ProductList {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Productor p = new Productor(clerk);
        Thread t = new Thread(p);
        Customer_P c = new Customer_P(clerk);

        t.setName("生产");
        c.setName("顾客");

        t.start();
        c.start();
    }
}
//共享数据
class Clerk{
    private int num = 0;
    //消费者消费产品
    public synchronized void cust_Product() {//解决同步问题
        if(num > 0){
            System.out.println(Thread.currentThread().getName() + "..."+num);
            num--;
            notify();//消费者消费产品后,就唤醒生产者
        }else {
            try {
                wait();//当没有产品,消费者处于等待状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
    //生产者生产产品
    public synchronized void pro_Product() {//解决同步问题
        if(num <= 20){
            num++;
            System.out.println(Thread.currentThread().getName() + "...."+num);
            notify();//生产了产品,就唤醒消费者
        }else {
            try {
                wait();//当产品多于20,生产者停止生产
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//implements Runnable设置Productor线程
class Productor implements Runnable{
    private Clerk clerk;
    Productor(Clerk clerk){
        this.clerk = clerk;
    }
    @Override
    public void run() {
        while (true){
            clerk.pro_Product();
        }

    }
}
//extends Thread设置Customer线程
class Customer_P extends Thread{
    private Clerk clerk;
    Customer_P(Clerk clerk){
        this.clerk = clerk;
    }
    @Override
    public void run() {
        while (true){
            clerk.cust_Product();
        }
    }
}

jdk5.0新增线程创建方式

新增方式1:实现Callable接口

与实现Runnable接口相比较,Callable功能更强大
(1)相比run方法,可以有返回值
(2)方法可以抛出异常
(3)支持泛型返回值
(4)需要借助FutureTask类,比如获得返回结果

/*
* 创建多线程方式3:实现Callable接口
* 1.实现Callable接口
* 2.重写call方法
* 3.创建接口实现类的对象
* 4.将该对象传入FutureTask的构造器中
* 5.将FutureTask对象传递到Thread类的构造器中
* 6.使用该对象的get方法得到重写call的返回值
*
* */
public class Callable_01 {
    public static void main(String[] args) {
        //3.创建接口实现类的对象
        NumThread num = new NumThread();
        //4.将接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask task = new FutureTask(num);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法
        new Thread(task).start();

        try {
            //6.获得FutureTask构造器参数Callable实现类的重写方法call的返回值
            Object o = task.get();
            System.out.println("总和为" + o);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
class NumThread implements Callable{
    private int sum = 0;
    @Override
    public Object call() throws Exception {
        for (int i = 0; i <= 100; i++) {
            if(i % 2 == 0) {
                sum += i;
                System.out.println( sum);

            }
        }
        return sum;
    }
}

新增方式2:使用线程池(开发中常用)

好处:响应速度提高;提高资源重用率;便于管理

public class ThreadPool {
    public static void main(String[] args) {
        //1.提供指定数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(5);//创建线程的个数
        //ExecutorService是一个接口,使用子类ThreadPoolExecutor可以调用其属性方法
//        ThreadPoolExecutor service1= (ThreadPoolExecutor) service;
//        service1.getMaximumPoolSize();
                System.out.println(service.getClass());
        //2.执行指定线程的操作,需要提供Runnable或Callable接口
        service.execute(new NumPool());//适合Runnable接口
        //service.submit(new Callable);//适合Callable接口

        //3.关闭线程池
        service.shutdown();

    }
}

你可能感兴趣的:(线程)