Java基础——多线程

一、基本概念:程序、进程、线程

1、程序:是指为了完成特定的任务,用某种编程语言编写的一组指令集合,即一段静态的代码

2、进程:指正在运行的程序。确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。进程是资源分配的单位

3、线程:线程是进程中的一个执行单元,负责当前进程中程序的执行。一个进程中有多个线程。线程作为调度和执行的单位,每个线程拥有独立运行的栈程序计数器(PC)。若一个进程同时并发执行多个线程,就是支持多线程的。

二、单核CPU和多核CPU

1、单核CPU:单核CPU上运行的多线程程序, 同一时间只能一个线程在跑, 系统帮你切换线程而已, 系统给每个线程分配时间片来执行, 每个时间片大概10ms左右, 看起来像是同时跑, 但实际上是每个线程跑一点点就换到其它线程继续跑。

2、多核CPU:多核CPU指的是CPU有多个核心,多线程是程序有多个线程在同时执行。多核也要用多线程才能发挥优势。

Java应用程序java.exe中有main()主线程,gc()垃圾回收线程,异常处理的线程

三、并行和并发

1、并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。

2、并发:一个CPU(采用时间片)“同时”执行多个任务,比如:秒杀,多个人(线程)做同一件事。

四、多线程的创建方式

1、继承Thread类,并重写run方法

 

2、创建一个类去实现Runable接口,然后将这个类以参数的形式传递给Thread类

 

3、实现Callable接口,实现call()方法,相比Runnable接口,它可以有返回值、可以抛异常,支持泛型返回值,需要借助FutureTask类,获取返回值。FutureTask类是Future接口的唯一实现类

package cn.wyu.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * 使用一个线程计算1-100之间所有偶数之和
 * @author linwillen
 * @create 2020-04-26-15:46
 */
public class ThreadNew {
    public static void main(String[] args) {
        //2.创建Callable接口实现类对象
        NumThread numThread = new NumThread();
        //3.将Callable接口实现类对象作为参数传递到FutureTask构造器中,创建FutureTask对象
        FutureTask<Integer> futureTask = new FutureTask<Integer>(numThread);
        //4.将FutureTask对象作为参数传递到Thread类的构造器中,并调用start()方法
        new Thread(futureTask).start();
        try {
            //5.获取Callable中call的返回值,调用get()方法
            Integer sum = futureTask.get();
            System.out.println("总和为:"+sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

//1. 实现Callable接口并重写call()方法
class NumThread implements Callable<Integer>{

    private int sum = 0;
    @Override
    public Integer call() throws Exception {
        for (int i = 1;i<=100;i++){
            if(i%2==0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}

4、使用线程池的方法创建线程

package cn.wyu.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * 使用线程池创建线程
 * @author linwillen
 * @create 2020-04-26-16:24
 */
public class ThreadPool {
    public static void main(String[] args) {
        //1.提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        //ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //service.submit();//适用于Callable接口
        //2.执行指定线程的操作
        service.execute(new NumberThread());//适用于Runnable接口
        service.shutdown();

    }
}

class NumberThread implements Runnable{

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                System.out.println(i);
            }
        }
    }
}

设置线程池的属性:

五、线程的生命周期

1、JDK中Thread.State枚举类中定义了线程的几种状态

public enum State {
    /**
     * Thread state for a thread which has not yet started.(线程还没有调用start的时候)
     */
    NEW,(新建)
 
    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     * 调用start之后,jvm启动了这个任务,但是可能被其他资源占用线程变成WAITING
     */
    RUNNABLE,(准备就绪)
 
    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     * 线程被锁的时候,线程等待进去一个synchronized块方法或者可重入锁的时候
     */
    BLOCKED,(阻塞)
 
    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     * 线程调用object.wait thread.join LockSupport.park 的时候变成 WAITING
     */
    WAITING,(不见不散)
 
    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     * sleep 或者wait,join带时间参数等方法时
     */
    TIMED_WAITING,(过时不候)
 
    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     * 线程执行完成或者被中断的时候变成TERMINATED
     */
    TERMINATED;(终结)
}

六、线程的同步

1、卖票案例

/**
 * @author linwillen
 * @create 2020-04-26-0:24
 */
public class WindowTest {
    public static void main(String[] args) {
        Window window = new Window();
        Thread t1 = new Thread(window,"窗口1");
        Thread t2 = new Thread(window,"窗口2");
        Thread t3 = new Thread(window,"窗口3");

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

class Window implements Runnable{

    private int ticket = 100;

    @Override
    public void run() {
        while(true){
            if(ticket > 0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":卖票,卖出第"+ticket+"张");
                ticket--;
            }else{
                break;
            }
        }
    }
}

其中一次运行结果:出现重票还有错票的情况

对于以上情况可以使用synchronized关键字解决

2、synchronized的三种应用方式:

(1)修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁

synchronized(同步监视器){
    //需要同步的代码
}
//1.操作共享数据的代码,被称为同步代码
//2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据
//3.同步监视器:俗称锁,任何一个类的对象都可以充当锁。要求:多个线程必须公用同一把锁
package cn.wyu.thread;

/**
 * 使用同步代码块解决实现Runnable接口的线程安全问题
 * @author linwillen
 * @create 2020-04-26-0:24
 */
public class WindowTest1 {
    public static void main(String[] args) {
        Window1 window = new Window1();
        Thread t1 = new Thread(window,"窗口1");
        Thread t2 = new Thread(window,"窗口2");
        Thread t3 = new Thread(window,"窗口3");

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

class Window1 implements Runnable{

    private int ticket = 100;
    Object object = new Object();//object作为锁只能只有一个
    @Override
    public void run() {
        while(true){
            synchronized (object) {//同步代码块//synchronized (Window.class) Class clazz = Window.class
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,卖出第" + ticket + "张");
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

运行结果:

如果实现Thread类创建线程:则还是会出现同步问题。因为Window2创建了3次,每一个Window2对象都有一个object,因此同步代码块用的是不同的锁。想解决此问题需要将Object设置为static

package cn.wyu.thread;

/**
 * 使用同步代码块解决实现Thread类的线程安全问题
 * @author linwillen
 * @create 2020-04-26-0:24
 */
public class WindowTest2 {
    public static void main(String[] args) {
        Window2 t1 = new Window2();
        Window2 t2 = new Window2();
        Window2 t3 = new Window2();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

class Window2 extends Thread{

    private static int ticket = 100;
    private static Object object = new Object();


    public void run() {
        while(true){
            synchronized (object) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,卖出第" + ticket + "张");
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}

(2)修饰实例方法,作用于实例方法,给实例加锁

package cn.wyu.thread;


/**
 * 使用同步方法解决实现Runnable接口的线程安全问题
 * @author linwillen
 * @create 2020-04-26-0:24
 */
public class WindowTest3 {
    public static void main(String[] args) {
        Window3 window = new Window3();
        Thread t1 = new Thread(window,"窗口1");
        Thread t2 = new Thread(window,"窗口2");
        Thread t3 = new Thread(window,"窗口3");

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

class Window3 implements Runnable{

    private static int ticket = 100;
    @Override
    public void run() {
        while(true){
            show();
        }

    }
    public synchronized void show(){//同步监视器:this,指当前对象
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":卖票,卖出第" + ticket + "张");
            ticket--;
        }
    }
}

(3)修饰静态方法。给当前类加锁,要访问代码前,要获得类对象的锁

package cn.wyu.thread;

/**
 * 使用同步方法解决实现Thread类的线程安全问题
 * @author linwillen
 * @create 2020-04-26-0:24
 */
public class WindowTest4 {
    public static void main(String[] args) {
        Window4 t1 = new Window4();
        Window4 t2 = new Window4();
        Window4 t3 = new Window4();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

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

class Window4 extends Thread{

    private static int ticket = 100;

    public void run() {
        while(true){
            show();
        }
    }
    public static synchronized void show(){//同步监视器:当前类本身---Window4.class
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":卖票,卖出第" + ticket + "张");
            ticket--;
        }
    }
}

七、线程的死锁

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

出现死锁后,不会出现异常,不会提示,只是所有的线程都处于阻塞状态,无法继续执行。

死锁演示:

package cn.wyu.thread;

/**
 * @author linwillen
 * @create 2020-04-26-2:01
 */
public class DeadLock {
    public static void main(String[] args) {
        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();
        new Thread(){
            @Override
            public void run() {
                synchronized (s1){
                    s1.append("a");
                    s2.append("1");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s2){
                        s1.append("b");
                        s2.append("2");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2){
                    s1.append("c");
                    s2.append("3");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (s1){
                        s1.append("d");
                        s2.append("4");
                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();
    }
}

八、Lock锁

package cn.wyu.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 解决线程安全问题的方式二;Lock锁 ---JDK5.0后新增
 * @author linwillen
 * @create 2020-04-26-12:55
 */
public class LockTest {
    public static void main(String[] args) {
        Window w = new Window();
        Thread t1 = new Thread(w,"窗口1");
        Thread t2 = new Thread(w,"窗口2");
        Thread t3 = new Thread(w,"窗口3");
        t1.start();
        t2.start();
        t3.start();
    }
}

class Window implements Runnable{

    private static int ticket = 100;
    private Lock lock = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            //调用lock()方法锁定
            lock.lock();
            try {
                if(ticket > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":卖票,卖出第" + ticket + "张");
                    ticket--;
                }else{
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                //解锁
                lock.unlock();
            }

        }
    }
}

面试题:synchronized与Lock的区别?

1. Lock是显式锁(手动开启锁和手动关闭锁),synchronized是隐式锁,出了作用域自动释放锁。

2. Lock只有代码块锁,synchronized有代码块锁和方法锁

建议优先使用顺序:Lock->synchronized代码块->synchronized方法

面试题:解决线程安全问题有几种方式?

Lock锁,同步代码块,同步方法

九、线程的通信

package cn.wyu.thread;


/**
 * 线程通信的例子:使用两个线程打印1-100,线程一,线程二交替打印
 * @author linwillen
 * @create 2020-04-26-13:38
 */
public class CommunicationTest {
    public static void main(String[] args) {
        Number number = new Number();
        Thread t1 = new Thread(number, "线程一");
        Thread t2 = new Thread(number, "线程二");
        t1.start();
        t2.start();
    }
}

class Number implements Runnable{

    private static int number = 1;
    //private Object object = new Object();
    @Override
    public void run() {
        while (true){
            synchronized (this) {
            //synchronized (object) {
                this.notify();
                //object.notify();
                if(number <= 100){
                    System.out.println(Thread.currentThread().getName()+":"+number);
                    number++;
                    try {
                        this.wait();
                        //object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    break;
                }
            }
        }
    }
}

通信涉及到的方法:

1. wait():一旦执行这个方法,当前线程会进入阻塞状态,并释放同步监视器

2. notify():一旦执行这个方法。就会唤醒被wait()的一个线程,如果有多个线程被wait(),就会唤醒优先级高的,如果优先级高的有多个则,随机唤醒其中一个。

3. notifyAll():一旦执行这个方法,就会唤醒所有被wait()的线程。

注意:

1. wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中

2. wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或同步方法的同步监视器。否则会出现 java.lang.IllegalMonitorStateException异常

3.  wait(),notify(),notifyAll()三个方法都是定义在java.lang.Object类里面的

原因:因为任何类的对象都可以充当同步监视器,而在同步代码块中调用wait(),notify(),notifyAll()必须使用同步监视器来调用,因此,这三个方法都必须在任何对象中。

 

面试题:wait()方法和sleep()方法的异同?

相同点:两个方法执行都可以使得当前线程进入阻塞状态。

不同点:

1. 两个方法声明的位置不同:wait()方法在Object类中,sleep()方法在Thread类中

2. 调用的要求不同:sleep()方法在任何位置都可以使用,wait()方法必须在同步代码中使用

3. 是否释放锁:两个方法都在同步代码块或同步方法中时,调用sleep()方法的时候不会释放锁,调用wait()方法会释放手中的锁。

生产者消费者问题:

生产者(Producer)将产品交给店员(Clerk),而消费者(Customer)从店员取走产品,店员只能一次持有固定的产品数量(比如:20),如果生产者试图生产更多产品,店员会叫生产品停一下,如果店中有空位再叫生产者生产。如果店中没有产品的话,会叫消费者等一下,等店中有产品了,再叫消费者过来取走产品。

分析:

1. 是否是多线程问题:是,生产者线程,消费者线程。

2. 是否有共享数据:有,店员(或产品)

3. 如何解决线程安全问题:3种

4. 是否涉及线程的通信:是

package cn.wyu.thread;

/**
 * @author linwillen
 * @create 2020-04-26-15:02
 */
public class ProductTest {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Producer producer = new Producer(clerk);
        Customer customer = new Customer(clerk);
        new Thread(producer,"生产者").start();
        new Thread(customer,"消费者").start();
    }
}

class Clerk {
    private int productCount = 0;

    public synchronized void produceProduct() {
        if(productCount<20){
            productCount++;
            System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
            notify();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void consumeProduct() {
        if(productCount>0){
            System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
            productCount--;
            notify();
        }else{
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class Producer implements Runnable{

    private Clerk clerk;

    public Producer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":开始生产产品。。。");
        while (true){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.produceProduct();
        }
    }
}

class Customer implements Runnable{

    private Clerk clerk;

    public Customer(Clerk clerk) {
        this.clerk = clerk;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+":开始消费产品。。。");

        while (true){
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            clerk.consumeProduct();
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值