Java:线程、线程安全问题及解决、同步

1、线程-多线程原理

Java:线程、线程安全问题及解决、同步_第1张图片

2、线程-创建线程方式一-继承Thread类及常用方法

1)、自定义线程,继承Thread类,重写run方法

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("i = " + i);
        }
    }
}

2)、启动线程

public class Demo {
    public static void main(String[] args) {
        //1.启动线程
        MyThread t = new MyThread();
        t.start();
        //2.主线程继续
        for (int k = 0; k < 100; k++) {
            System.out.println("k = " + k);
        }
    }
}

注意:

重写的是run方法,但是启动线程使用start方法

对于一个线程对象,只能调用一次start方法启动线程

对于一个线程类,可以创建多个线程对象,每个线程对象都可以独立的进行运行

1)、Thread类的构造方法:

  • public Thread():分配一个新的线程对象。

  • public Thread(String name):分配一个指定名字的新的线程对象。

  • public Thread(Runnable target):分配一个带有指定目标新的线程对象。(使用实现Runnable接口的对象)

  • public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

2)、常用方法:

  • public String getName():获取当前线程名称。

  • public String setName():设置线程名称

    任何一个线程对象都有一个默认名称:Thread-[索引]

  • public void start():导致此线程开始执行; Java虚拟机调用此线程的run方法。

  • public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。静态方法

  • public static Thread currentThread():返回对当前正在执行的线程对象的引用。静态方法

3、线程- 创建线程方式二- 实现Runnable接口及特点

1)、自定义线程,实现Runnable接口,重写run方法

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

2)、启动线程时要先创建一个自定义的类,再创建Thread对象

public class Demo {
    public static void main(String[] args) {
        //1.创建自定义类对象
        MyRunnable myRun = new MyRunnable();

        //2.创建一个Thread对象
        Thread t1 = new Thread(myRun);
        Thread t2 = new Thread(myRun);
        Thread t3 = new Thread(myRun);

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

        for (int k = 0; k < 100; k++) {
            System.out.println("k = " + k);
        }
    }
}

注意:

自定义类实现了Runnable接口,并没有继承自Thread,所以不是一个线程

当启动线程时,仍然需要创建Thread对象

将一个自定义类的对象传给多个Thread对象

4、线程-俩种方法的区别

1)、第一种方式需要子类继承Thread,因为只能单继承,所以子类就会很不方便

2)、第二种方式需要实现Runable接口,Java允许子类多实现多个接口,所以比较方便(多使用)

5、线程-匿名内部类实现线程

1)、方式一(Thread的匿名子类):创建Thread子类,创建子类对象,子类对象.start()三步合一

new Thread(){//子类类体}.start();

例:

new Thread(){
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("i = " + i);
        }
    }
}.start();

2)、方式二:(Runnable的匿名子类):创建实现Runnable接口子类,创建Thread对象,Thread对象构造方法接收Runnable子类对象,Thread.start()四步合一

new Thread(new Runnable(){//Runnable子类类体}).start;

例:

new Thread(new Runnable() {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("i = " + i);
        }
    }
}).start();

6、线程安全-多线程的安全性问题

当多线程共同访问同一共享资源时,就会产生安全性的问题(一个进程没有彻底访问结束这个资源,另一个进程就要进来,就使得结果资源不完整)

Java:线程、线程安全问题及解决、同步_第2张图片

例:

实现Runnable类:

public class MyRunnable implements Runnable {
    public int money = 0;
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            money++;
        }
    }
}

测试类:

public class Demo {
    public static void main(String[] args) throws  
                                      InterruptedException {

        MyRunnable myRun = new MyRunnable();

        Thread t1 = new Thread(myRun);
        Thread t2 = new Thread(myRun);

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

        System.out.println("主线程休息1秒,目的让两个线程执行完毕...");
        Thread.sleep(1000);

        System.out.println("num的最终结果是:" + myRun.money);
    }
}

t1和t2一起访问 money,一起对money进行操作,结果本该是2000,但是在俩个线程运行时,有时t1还没有彻底对money操作完成,线程t2就要对money进行操作,这使得t1对money的操作丢失,造成数据不安全的问题(在线程中对一个数据操作的越久,这种问题就越突出

7、线程安全-线程同步解决多线程的安全性问题-同步方法

在线程中定义一个对数据的操作方法时使用synchronized进行修饰,保证一个线程进入时,其它后续线程在后面排队,从而使得数据安全

public class Tickets implements Runnable {
    private int tickets = 100;

    @Override
    public  void run() {
        while (true) {
            int t = 0;
            try {
                t = getTicket();
            } catch (InterruptedException e) {//方法中的sleep()导致的异常
                e.printStackTrace();
            }
            if (t == -1) {
                break;//结束循环
            }
            System.out.println(Thread.currentThread().getName() + "取走一张票:" + t);
        }

    }
    //synchronized(同步)关键字保证一个线程进入时,其它后续线程在后面排队。
    private synchronized int getTicket() throws InterruptedException {//sleep异常
        if (tickets > 0) {
            int n = tickets;
            Thread.sleep(1);//目的让错误更明显
            tickets--;
            return n;
        }else{
            return -1;
        }
    }
}

8、线程安全-线程同步解决多线程的安全性问题-同步代码块

语法:

synchronized(锁对象){
    //同步代码
}

例:(锁对象可以是任何对象)

public class Tickets implements Runnable {
    private int tickets = 100;
     //做为"锁"对象的,可以是任何对象。
    private Object obj = new Object();

    @Override
    public void run() {
        while (true) {
          //必须保证多个“线程”使用的是同一把锁
            synchronized (obj) {//窗口2,窗口3
                //窗口1--进入后,加锁
                if (tickets <= 0) {
                    break;
                }
                System.out.println(Thread.currentThread().getName() + " 来取走一张票:" + tickets);
                tickets--;
            }
        }
    }
}

多个线程使用同一个锁才能达到数据安全的效果

9、线程安全-线程同步解决多线程的安全性问题-Lock锁

1)、语法

Lock l = ...; 
l.lock(); //加锁
try { 
    // access the resource protected by this lock 
} finally { 
    l.unlock(); //解锁
} 

2)、例:

public class Tickets implements Runnable {
    private int tickets = 100;
     //定义一个Lock锁对象
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true) {
            lock.lock();//加锁
            try {

                if (tickets <= 0) {
                    break;
                }
                System.out.println(Thread.currentThread().getName()+ " 来取走一张票:" + tickets);
                tickets--;
            }finally {
                lock.unlock();//解锁
            }
        }
    }
}

10、线程状态-概述

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

1)、新建:MyThread t= new MyThread();

2)、可运行(待运行):t.start();调用该方法时,该线程可能在运行,可能没有运行,这取决于操作系统

3)、锁阻塞:一个线程运行后调用某个方法,但是此方法正在被其他线程使用且上了锁

4)、无限等待:线程开启后,调用了.wait()方法,暂停运行

5)、计时等待:线程开启后,调用sleep()方法,等待sleep中的时间结束后,线程在次进入待运行

6)、被终止:run()方法正常运行完毕

11、线程状态-等待与唤醒

调用线程的wite()方法吗,使该线程进入等待;

其他线程调用notify()方法,使在等待的线程唤醒;

例:

public class Demo {
    private static Object obj = new Object();
    public static void main(String[] args) throws InterruptedException {

        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    synchronized (obj) {
                        System.out.println("i = " + i);
                        if (i == 20) {
                            try {
                                //当前持有这把锁的线程,开始无限等待....
                                System.out.println("线程开始进入无限等待....");
                                obj.wait();
                                System.out.println("线程被唤醒.....");
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
        }.start();

        System.out.println("主线程休息2秒...目的让前面的线程先运行...");
        Thread.sleep(2000);

        new Thread(){
            @Override
            public void run() {
                synchronized (obj) {
                    //唤醒所有obj锁上等待的线程
                    System.out.println("我要唤醒所有等待的线程....");
                    obj.notifyAll();
                }
            }
        }.start();
    }
}

12、线程状态-状态图

Java:线程、线程安全问题及解决、同步_第3张图片

复习

  • [ ] 能够描述Java中多线程运行原理

    在栈中,每个线程都有其独立的栈区;

  • [ ] 能够使用继承类的方式创建多线程

    class MyThread entends Thread{
      public void run(){
      }
    }
  • [ ] 能够使用实现接口的方式创建多线程

    class MyRunnable implements Runnable{
      public void run(){
      }
    }
    main(){
      MyRunnable myRun = new MyRunnable();
      Thread t = new Thread(myRun);
      t.start();
    }
  • [ ] 能够说出实现接口方式的好处

    多实现,继承只能单继承

  • [ ] 能够解释安全问题的出现的原因

    多个线程访问同一个资源,对这个资源进行操作,当一个线程还没有完成对这个数据的操作,下一个线程就要进入,使得上一次的操作的数据丢失

  • [ ] 能够使用同步代码块解决线程安全问题

    synchronized (锁对象){线程内代码}
  • [ ] 能够使用同步方法解决线程安全问题

    public synchronized void show(){
      //同步代码
    }
  • [ ] 能够说出线程6个状态的名称

    1)、新建

    2)、可运行

    3)、锁阻塞

    4)、无线等待

    5)、及时等待

    6)、被终止

你可能感兴趣的:(Java)