首页 > 编程语言 > 详细

多线程

时间:2021-04-20 23:18:31      阅读:37      评论:0      收藏:0      [点我收藏+]

一、程序 进程 线程

1.程序:为了完成某个指定的任务,用否种编程语言编写的一组指令的集合。即指一段静态的代码(静态对象)

2.进程:正在运行的一个程序(动态过程)

3.线程:是一个程序内部的一条执行路径

举例:电脑管家:当没点击打开之前,就是一个程序;当点击打开后,将电脑管家加载在内存中,此时相当于一个进程;电脑管家每一项(体检,杀毒,清理)都是一个线程。

二、多线程的创建与使用

1.多线程的创建

方式一:继承Thread类

package com.slxy.thread1;
/*
多线程创建方式一:继承于Thread类
1.创建一个继承于Thread的子类
2.重写Thread中的run()方法
3.创建Thread子类的对象
4.通过子类对象来调Thread中的start()方法
 */
public class ThreadTest extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }

    public static void main(String[] args) {
        ThreadTest threadTest1 = new ThreadTest();
        threadTest1.start();//1.启动当前线程 2.调用当前线程的run()方法
        ThreadTest threadTest2 = new ThreadTest();
        threadTest2.start();
        //threadTest1.run(); 错误原因:不能通过直接调用run()方法的方式来启动线程
        
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
}

拓展://创建Thread类匿名子类的方式:
    new Thread(){
          @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
                }
        }
    }.strat();

图解:

技术分享图片

方式二:实现Runnable接口

package com.slxy.thread1;
/*
创建多线程的方式二:实现Runnable接口
1.创建一个类来实现Runnable接口
2.实现类去实现Runnable中的抽象方法:run()
3.创建实现类的对象
4.将此对象作为参数传递到Thread类的构造器中,创建Thread对象
5.通过Thread类的对象调用start()方法:a.启动线程 b.调用当前线程的run()-----调用了Runnable类型的target的run()
 */
class Test implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {                    System.out.println(Thread.currentThread().getName()+""+i);
        }
    }
}
public class RunnableTest {
    public static void main(String[] args) {
        Test test = new Test();
        Thread t1 = new Thread(test);
        t1.start();
        Thread t2 = new Thread(test);
        t2.start();
    }
}

JDK5.0新增线程创建方式-->方式三、四

方式三:实现Callable接口

package com.slxy.thread;

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

/*创建线程的第三种方式:实现Callable接口
1.创建一个类来实现Callable接口
2.实现call()方法,将此线程需要执行的操作声明在call方法中(有返回值,声明抛出异常,支持泛型操作)
3.创建实现类的对象
4.以实现类作为参数创建FutureTask对象
5.以FutureTask作为参数创建Thread对象
6.通过Thread对象调用start()方法
*/

public class CallableTest implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int num=0;
        for(int i=0;i<=10;i++){
            num += i;
        }
        return num;
    }

    public static void main(String[] args) {
        CallableTest ct = new CallableTest();
        FutureTask ft = new FutureTask(ct);
        Thread t = new Thread(ft);
        t.start();
        try {
            Integer i = (Integer)ft.get();//返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值
            System.out.println("1-10的和:"+i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

方式四:使用Executor框架创建线程池

使用线程池的优点:

1.提高响应速度(减少创建新线程的时间)

2.降低资源的消耗(重复利用线程池中的线程,不需要每次都创建)

3.便于线程管理(设置核心池的大小,最大线程数)

package com.slxy.thread;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*创建线程的第四种方式:使用Executor框架创建线程池
1.提供指定线程数量的线程池
2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
3.关闭连接池
*/
public class ThreadPoolTest implements Runnable{
    @Override
    public void run() {
        for (int i=0;i<100;i++){
            System.out.println(i);
        }
    }

    public static void main(String[] args) {
        //创建线程池
        ExecutorService service =                                    Executors.newFixedThreadPool(10);
        //执行操作
        service.execute(new ThreadPoolTest());//适用于Runnable
        //service.submit(Callable callable);适用于Callable
        //关闭线程池
        service.shutdown();
    }
}

2.Thread类中的常用方法:

1)启动当前线程,调用当前线程的run()方法

2)run():通常需要重写Thread中的此方法,将创建线程所要执行的操作写在里面

3)Thread.currentThread():静态方法,返回执行当前代码的线程

4)getName():获取当前线程的名字

5)setName():设置当前线程的名字

6)yield():释放当前cpu的执行权

7)join():在线程A中调用线程B的join(),此时线程A就进入阻塞状态,直到线程B完全执行完后A才结束阻塞状态。

8)stop():方法过时

9)sleep(long millis):让当前线程睡眠(阻塞),单位毫秒(1000ms = 1s)

10)isAlive():判断当前线程是否还存活,返回值为boolean类型

/*线程命名的两种方式:
1.通过线程对象.setName()的方式
 eg:t1.setName("线程1");
2.通过创建构造器的方式
 eg:ThreadMethodTest(String name){
        super(name);
    }
*/
public class ThreadMethodTest extends Thread{
    ThreadMethodTest(String name){
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":"+i);
            if(i % 10 == 0){
                yield();
            }
        }
    }

    public static void main(String[] args) {
        ThreadMethodTest t1 = new ThreadMethodTest("线程1");
//        t1.setName("线程1");
        t1.start();
        Thread.currentThread().setName("主线程");
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName()+":"+i);
            if(i == 10){
                try {
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        System.out.println(t1.getName()+":"+t1.isAlive());
    }
}

3.线程的优先级:

MAX_PRIORITY:10//最大优先级

MIN_PRIORITY:1//最小优先级

NORM_PRIORITY:5//默认优先级

如何获取和设置优先级:

getPriority();

setPriority();

注意:优先级越高只是意味着高概率的被先执行,但是不代表是优先级高的先执行完后优先级低的才执行。

4.卖票举例:

package com.slxy.thread1;
//卖票:创建3个窗口,总票数为:100张
//用继承Thread类的方法
class Window extends Thread{
    private static int ticket = 100;
    @Override
    public void run() {
        while(true){
            if(ticket > 0){
                System.out.println(Thread.currentThread().getName()+"买票,票号为:"+ticket);
                ticket--;
            }else {
                break;
            }
        }
    }
}

public class WindowTest {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();
        w1.setName("线程1");
        w2.setName("线程2");
        w3.setName("线程3");
        w1.start();
        w2.start();
        w3.start();
    }
}
package com.slxy.thread1;
//卖票:创建3个窗口,总票数为:100张
//用实现Runnable接口的方法
class Test1 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 WindowTest1 {
    public static void main(String[] args) {
        Test1 test1 = new Test1();
        Thread t1 = new Thread(test1);
        Thread t2 = new Thread(test1);
        Thread t3 = new Thread(test1);
        t1.start();
        t2.start();
        t3.start();
    }
}

说明:

开发中优先选择实现Runnable接口的方式

原因:1.实现的方式没有单继承性的局限性

2.实现的方式更适合来处理多线程有共享数据的情况(eg:卖票)

联系:public class Thread implements Runnable{}

相同点:两种方式都重写了run()方法,将线程要执行的逻辑声明在run()中。

5.线程的生命周期

1.新建 2.就绪 3.运行 4.阻塞 5.死亡

图解:

 技术分享图片

6.线程安全问题的产生---以卖票为例:

1.问题:买票过程中出现重票,错票问题---出现线程安全问题

2.原因:在某个线程操作车票的过程中,尚未操作完成时,其他线程也参与进来了

3.解决:在a线程操作车票时,b线程只有当a线程操作完成时才能参与进来,a线程阻塞状态时也不能。

//会出现错票现象,也就是票数<=0的情况
//当ticket只剩1号票时,线程0 1 2 同时进入堵塞状态,当线程0先于1  2 sleep()结束时,ticket--将1号票卖了,线程1卖的就是0号票,线程2卖的就是-1号票,这时就出现错票现象。
public void run() {
        while(true){
            if(ticket > 0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"卖票,票号为"+ticket);
                ticket--;
            }else{
                break;
            }
        }

    }

图解:

技术分享图片

//会出现重票现象:当ticket=1,线程1进来进入堵塞状态,此时一号窗口卖的票号为ticket=1,这时线程2也进来进入堵塞状态,此时二号窗口卖的还是ticket=1,再ticket--,就会出现重票号现象。
while(true){
            if(ticket > 0){
                System.out.println(Thread.currentThread().getName()+"卖票,票号为"+ticket);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket--;
            }else{
                break;
            }
        }

    }

7.解决线程安全问题:在java中通过同步机制

同步方式的优点与局限性:

优点:解决了线程安全问题

局限性:效率低。因为操作同步代码时,只能有一个线程参加,也就相当于单线程的过程。

方式一:同步代码块

synchronized(同步监视器){

//需要被同步的代码

}

说明:1.需要被同步的代码指的是:操作共享数据的代码

2.共享数据是指:多个线程共同操作的变量,如ticket

3.同步监视器是指:俗称锁。任何一个类的对象都可以充当锁(obj)

4.要求:多个线程必须要共用一把锁

//同步代码块处理实现Runnable接口的线程安全问题
class Test1 implements Runnable{
    private int ticket = 100;
    Object obj = new Object();
    @Override
    public void run() {
        while(true){
            synchronized(obj){//synchronized(this){//写法二,this指当前类的对象
                if(ticket > 0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
              System.out.println(Thread.currentThread().getName()+"卖票,票号为"+ticket);
                    ticket--;
                }else{
                    break;
                }
            }
        }
    }
}
public class WindowTest1 {
    public static void main(String[] args) {
        Test1 test1 = new Test1();
        Thread t1 = new Thread(test1);
        Thread t2 = new Thread(test1);
        Thread t3 = new Thread(test1);
        t1.start();
        t2.start();
        t3.start();
    }
}
//同步代码块处理继承Thread类线程安全问题
class Window extends Thread{
    private static int ticket = 100;
    private static Object obj = new Object();//加static保证了共用一把锁
    @Override
    public void run() {
        while(true){
            synchronized (obj){//synchronized(Window.class){//方式二:Window.class只会加载一次
                if(ticket > 0){
                    System.out.println(Thread.currentThread().getName()+"买票,票号为:"+ticket);
                    ticket--;
                }else {
                    break;
                }
            }
        }
    }
}

public class WindowTest {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();
        w1.setName("线程1");
        w2.setName("线程2");
        w3.setName("线程3");
        w1.start();
        w2.start();
        w3.start();
    }
}

方式二:同步方法

如果操作共享数据的代码完整的声明在一个方法中,此时可以将此方法声明为同步的。

//同步方法处理实现Runnable接口的线程安全问题
class Test2 implements Runnable{
    private int ticket = 100;
    @Override
    public void run() {
        while(true){
           show();

        }
    }
    private synchronized void show(){//同步监视器:this
            if(ticket > 0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"卖票,票号为"+ticket);
                ticket--;
            }
    }
}
public class WindowTest2 {
    public static void main(String[] args) {
        Test2 test2 = new Test2();
        Thread t1 = new Thread(test2);
        Thread t2 = new Thread(test2);
        Thread t3 = new Thread(test2);
        t1.start();
        t2.start();
        t3.start();
    }
}
//同步方法处理继承Thread类的线程安全问题
class Window3 extends Thread{
    private static int ticket = 100;
    @Override
    public void run() {
        while(true){
            show();
        }
    }
    private static synchronized void show(){//同步监视器:当前类(Window3)
            if(ticket > 0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"买票,票号为:"+ticket);
                ticket--;
            }
        }
    }
public class WindowTest3 {
    public static void main(String[] args) {
        Window3 w1 = new Window3();
        Window3 w2 = new Window3();
        Window3 w3 = new Window3();
        w1.setName("线程1");
        w2.setName("线程2");
        w3.setName("线程3");
        w1.start();
        w2.start();
        w3.start();
    }
}

使用同步机制将单例模式中的懒汉式改写为线程安全的。

class Bank{
    private Bank(){}
    private static Bank instance = null;
    public static 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;
    }
}

方式三:加Lock锁-----JDK5.0新增

package com.slxy.thread1;

import com.slxy.thread.ThreadPoolTest;

import java.util.concurrent.locks.ReentrantLock;
//解决线程安全问题的另一种方法:加Lock锁
public class LockTest implements Runnable{
    private int ticket = 100;
    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock(true);//fire默认值为false----是否公平
    @Override
    public void run() {
        while(true){
            try{
                //2.调用锁定lock()方法
                lock.lock();
                if(ticket > 0){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"窗口售票,票号为:"+ticket);
                    ticket--;
                }else{
                    break;
                }
            }finally {
                //3.调用解锁方法
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        LockTest lockTest = new LockTest();
        Thread t1 = new Thread(lockTest);
        Thread t2 = new Thread(lockTest);
        t1.start();
        t2.start();
    }
}

问题:

synchronized 和 Lock的异同:

相同:都可以解决线程安全问题

不同:synchronized机制在执行相应的代码块时,自动的释放同步监视器

lock需要手动的启动同步需要实现lock(),同时结束同步也需要手动实现unlock()

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

9.线程通信

//例子:使用两个线程打印1-100,线程1和线程2交替执行
package com.slxy.thread1;
public class CommunicationTest extends Thread{
    private static int num = 1;
    @Override
    public void run() {
        while(true){
            synchronized (CommunicationTest.class){
                CommunicationTest.class.notify();
                if(num <= 100){
                    System.out.println(Thread.currentThread().getName()+":"+num);
                    num++;
                    try {
                        CommunicationTest.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else {
                    break;
                }
            }
        }
    }
    public static void main(String[] args) {
        CommunicationTest test1 = new CommunicationTest();
        CommunicationTest test2 = new CommunicationTest();
        test1.setName("线程一");
        test2.setName("线程二");
        test1.start();
        test2.start();
    }
}
/*线程通信的应用例子:生产者和消费者的问题
问题:有个工厂它的最大库存量为10,当库存量大于10时生产者等待,唤醒消费者消费;当库存量等于0时消费者等待,唤醒生产者生产。
分析:1.多线程问题:生产者线程,消费者线程
     2.有线程安全问题:存在共享资源(产品)
     3.同步机制解决线程安全问题
     4.涉及线程通信
*/
package com.slxy.thread1;

//工厂
class Factory{
    private int count = 0;//初始库存量为0
    //生产方法
    public synchronized void add(){
            if(count < 10){
                count++;
                System.out.println(Thread.currentThread().getName()+"生产第"+count+"个产品");
                this.notify();
            }else{
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    }
    //消费方法
    public synchronized void get(){
            if(count > 0){
                System.out.println(Thread.currentThread().getName()+"消费第"+count+"个产品");
                count--;
                this.notify();
            }else{
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    }

}
//生产者
class Producer extends Thread{
    Factory factory;

    public Producer(Factory factory) {
        this.factory = factory;
    }

    @Override
    public void run() {
        while(true){
            try {
                sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            factory.add();
        }
    }
}
//消费者
class Customer extends Thread{
    Factory factory;

    public Customer(Factory factory) {
        this.factory = factory;
    }

    @Override
    public void run() {
        while(true){
            try {
                sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            factory.get();
        }
    }
}
public class FactoryTest {
    public static void main(String[] args) {
        Factory factory = new Factory();
        Producer producer = new Producer(factory);
        Customer customer = new Customer(factory);
        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(customer);
        t1.setName("生产者");
        t2.setName("消费者");
        t1.start();
        t2.start();
    }
}

10.问题:sleep()与wait()方法的区别:

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

不同点:1)两个方法声明的位置不同:Thread类中声明sleep(),object类中声明wait()。

2)调用的要求不同:sleep()可以在任何需要的场景下调用。wait()必须使用在同步代码块或同步方法中。

3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中时,sleep()不释放锁,线程虽然进入休眠,其他线程依然无法访问这个对象;wait释放锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程

多线程

原文:https://www.cnblogs.com/miao-wu/p/14682997.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!