Java 线程基础

何时需要多线程?

程序需要同时执行两个或多个任务。
程序实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索。
需要一些后台运行的程序时。比如 垃圾回收。

线程的创建方式一 -- 通过继承Thread类创建线程类

package com.sheting.thread.demo1;

/**
 * Create Time: 2018-03-11 06:02
 *
 * @author sheting
 */
public class TestThread {

    public static void main(String[] args) {
        //创建一个线程
        SubTread subTread = new SubTread();
        //启动线程
        subTread.start();
        //一个线程的实例只能调用一次start()方法启动一个线程,如果要启动用多个线程,要创建多个线程实例,各自调用start()方法
        //subTread.start();
        //不能直接运行run方法,这样没有启动一个线程,只是普通类,调用了普通方法。
        //subTread.run();

        //主线程
        for (int i = 0; i < 100; i++) {
            System.out.println(String.format("%s--%s",Thread.currentThread().getName(),i));
        }
    }
}

/**
 *  通过继承Thread类,重写run方法 创建线程类。
 */
class SubTread extends Thread {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(String.format("%s--%s",Thread.currentThread().getName(),i));
        }
    }
}

线程常用的方法

  • start() 启动线程并执行run方法
  • run() 子线程要执行的代码放入run()方法中
  • currentThread() 静态的,调取当前的线程
  • getName() 获取线程的名字
  • setName() 设置线程的名字
  • yield() 调用此方法的线程释放当前CPU的执行权
  • join() 在A线程中调用B线程的join()方法,表示:当执行到此方法,A线程停止执行,直至B线程执行完毕。
  • isAlive() 判断当前线程是否存活
  • sleep(long l) 显示的让当前线程睡眠指定毫秒数
  • 线程通信 wait() nofigy() natifyAll()
  • 设置线程的优先级 setPriority(), getPriority()
package com.sheting.thread.demo1;

/**
 * Create Time: 2018-03-11 06:02
 * 线程常用的方法
 * @author sheting
 */
public class TestThread {

    public static void main(String[] args) {
        //创建一个线程
        SubTread subTread = new SubTread();
        //setName()设置线程名称
        subTread.setName("子线程");
        //设置线程的优先级(优先级从1到10, 默认是5。 数字越大抢占到CPU的执行权的机会变大)
        subTread.setPriority(Thread.MAX_PRIORITY);
        //启动线程,并执行 run()方法
        subTread.start();

        //主线程名字设置
        Thread.currentThread().setName("主线程");

        /*
        for (int i = 0; i < 1000; i++) {
            System.out.println(String.format("%s--%s",Thread.currentThread().getName(),i));
            if(i%10 == 0){
                //不应该通过类实例访问静态成员(避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问)
                //Thread.currentThread().yield();
                //调用此方法的线程释放当前CPU的执行权,但是也有可能重新被当前线程获取CPU执行权
                Thread.yield();
            }
        }*/

        for (int i = 0; i < 100; i++) {
            System.out.println(String.format("%s--%s", Thread.currentThread().getName(), i));
            if (i == 20) {
                try {
                    //在A线程中调用B线程的join()方法,表示当执行到此方法,线程A停止执行,直至B线程执行完成。
                    subTread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        //判断当前线程是否存活
        boolean alive = subTread.isAlive();
        System.out.println(alive);
    }
}

/**
 * 通过继承Thread类,重写run方法 创建线程类。
 */
class SubTread extends Thread {

    /**
     * 子线程要执行的代码,放入run()方法
     */
    @Override
    public void run() {
        for (int i = 0; i < 300; i++) {
            try {
                //显示指定当前线程睡眠多少毫秒
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //currentThread() 静态的,获取当前线程
            //getName() 获取线程的名字
            System.out.println(String.format("%s--%s", Thread.currentThread().getName(), i));
        }
    }
}

线程的创建方式二 --实现Runnable接口

package com.sheting.thread.demo1;

/**
 * Create Time: 2018-03-13 06:10
 *
 * @author sheting
 */
public class TestRunnable {

    public static void main(String[] args) {
        //启动一个线程,必须调用start()
        PrintNum printNum = new PrintNum();
        Thread thread = new Thread(printNum);
        thread.start();

        //再启动一个线程
        Thread thread2 = new Thread(printNum);
        thread2.start();
    }

}

class PrintNum implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                System.out.println(String.format("%s: %s", Thread.currentThread().getName(), i));
            }
        }
    }
}
继承Thread 和 实现Runnable比较
  • 1.public class Thread implements Runnable {} Thread 也实现了Runnable接口
    1. 实现的方式 优于继承的方式
      (1) 避免了Java单继承的局限性
      (2) 如果多个线程要操作同一份资源(数据),更适合使用实现的方式
      比如 如下代码 printNum对象只new了一次, thread和 thread2两个线程共享printNum对象的成员属性等
        PrintNum printNum = new PrintNum();
        Thread thread = new Thread(printNum);
        Thread thread2 = new Thread(printNum);
        thread.start();
        thread2.start();

多线程程序的优点

(1)提供应用程序的响应。对图形化界面更有意义,可增强用户体验。
(2) 提供计算机系统CPU的利用率。
(3) 改善程序结构。将既长又负载的进程分为多个线程,独立运行,利于理解和修改。

守护线程

Java中的线程分为两类:一种是守护线程, 一种是用户线程
守护线程是用来服务用户线程的,通过在start()方法前调用thread.setDaemon(true)可以把一个用户线程变为一个守护线程。

守护线程和用户线程的没啥本质的区别:唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。Java垃圾回收就是一个典型的守护线程。

在使用守护线程时需要注意一下几点:

(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。

(2) 在Daemon线程中产生的新线程也是Daemon的。

(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。

线程的生命周期

新建:当一个Thread类或其自雷的对象被声明并创建时,新生的线程对象处于新建状态。
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件。
运行: 当就绪的线程被调度并获得处理器资源时,便进入运行状态,run()方法定义了线程的操作和功能。
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性中止。

Java 线程基础_第1张图片

synchronized关键字

线程安全问题存在的原因?
由于一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在安全问题。

如何解决线程的安全问题?
必须让一个线程操作共享数据完毕以后,其他线程才有机会参与共享数据的操作。

多线程只有有共享数据,才涉及同步。

Java如何实现线程的安全:线程的同步机制
方式一: 同步代码块

synchronized(同步监视器){
      //需要被同步的代码块(即 操作共享数据的代码)
}

共享数据: 多个线程共同操作的同一个数据(变量)
同步监视器:由一个类的对象来充当。那个线程获取此监视器,它就执行大括号里被同步的代码。俗称 锁

特别重要,重在理解
注意:所有的线程要共用同一把锁(也就是同一个对象)。
在实现Runnable接口的方式中,考虑同步的话,可以使用this来充当锁。但是在继承的方式中,慎用this, 而且锁对象最好是静态的全局变量。

方式二:同步方法
将操作共享数据的方法声明为synchronized。即此方法为同步方法,能够保证 当其中一个线程执行此方法时,其它线程在外等待直至此线程执行完此方法。
同步方法的锁:this 因此同步方法不一定能保证线程安全,必须同步方法也必须是同一个this.

线程同步的弊端:由于同一个时间只能有一个线程访问共享数据,效率变低了。

释放锁的操作:

  • 当前线程的同步方法、同步代码块执行结束
  • 当前线程在同步代码块、同步方法中遇到break、return终止了代该代码块、该方法的继续执行。
  • 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束。
  • 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。

不会释放锁的操作:

  • 线程执行同步代码块或同步方法时,程序调用Thread.sleep()/Thread.yield()方法暂停当前线程的执行。
  • 线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放锁。
    应尽量避免使用suspend()和resume()来控制线程。

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

package com.sheting.singleton;

/**
 * Create Time: 2018-03-14 08:50
 *
 * @author sheting
 */
public class Singleton {

    private Singleton() {
    }

    private static Singleton instance = null;

    public static Singleton getInstance() {
        //if的作用是当多个线程时,如果已经实例化了instance,就不需要等待
        if(instance == null){
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

线程同步练习

线程的通信

wait() 令当前线程挂起并放弃CPU、同步资源,使别的线程可访问并修改共享资源,而当前线程排队等候再次对资源的访问。
notify() 唤醒正在排队等待同步资源的线程中优先级最高者结束等待。
notifyAll() 唤醒正在排队等待资源的所有线程结束等待。

java.lang.Object提供的这三个方法只有在synchronized方法或者synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常

package com.sheting.thread.demo;

/**
 * Create Time: 2018-03-16 05:54
 *
 * @author sheting
 */
public class TestCommunication {

    public static void main(String[] args) {
        PrintNum printNum = new PrintNum();
        Thread thread1 = new Thread(printNum);
        Thread thread2 = new Thread(printNum);

        thread1.start();
        thread2.start();
    }
}

class PrintNum implements Runnable {
    int num = 1;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                notify();
                if (num < 100) {
                    System.out.println(String.format("%s:%s", Thread.currentThread().getName(), num));
                    num++;
                } else {
                    break;
                }
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

生产者消费者

package com.sheting.thread.demo;

/**
 * Create Time: 2018-03-16 07:00
 *
 * @author sheting
 */
public class TestProductConsume {

    public static void main(String[] args) {
        Dispatcher dispatcher = new Dispatcher();
        Producer producer = new Producer(dispatcher);
        Consumer consumer = new Consumer(dispatcher);

        new Thread(producer).start();
        new Thread(consumer).start();
    }
}


class Producer implements Runnable{
    Dispatcher dispatcher;

    public Producer(Dispatcher dispatcher){
        this.dispatcher = dispatcher;
    }

    @Override
    public void run() {
        while (true){
            dispatcher.addProducer();
        }
    }
}

class Consumer implements Runnable{
    Dispatcher dispatcher;

    public Consumer(Dispatcher dispatcher){
        this.dispatcher = dispatcher;
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            dispatcher.consumeProduct();
        }
    }
}

class Dispatcher{

    int producerNum;

    public synchronized void addProducer()  {
        if(producerNum >= 20){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            producerNum++;
            System.out.println(String.format("%s:生成了第%s个产品", Thread.currentThread().getName(), producerNum));
            notifyAll();
        }
    }

    public synchronized void consumeProduct(){
        if(producerNum <= 0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }else {
            System.out.println(String.format("%s:消费了第%s个产品", Thread.currentThread().getName(), producerNum));
            producerNum--;
            notifyAll();
        }
    }
}

线程的死锁

死锁:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。

package com.sheting.thread.demo;

/**
 * Create Time: 2018-03-16 07:36
 *
 * @author sheting
 */
public class DeadLock {

    static StringBuffer sb1 = new StringBuffer();
    static StringBuffer sb2 = new StringBuffer();

    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                synchronized (sb1){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    sb1.append("A");
                    synchronized (sb2){
                        sb2.append("B");
                    }
                }
            }
        }.start();

        new Thread(){
            @Override
            public void run() {
                synchronized (sb2){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    sb1.append("C");
                    synchronized (sb1){
                        sb2.append("D");
                    }
                }
            }
        }.start();
    }
}

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