Java 多线程

1. 多线程

进程:正在运行的程序,是系统进行资源和调度的独立单位,每一进程都有独立的内存空间和系统资源

多进程的意义:单进程的计算机只能做一件事情,多进程可以执行多个进程 ,提供CPU的使用率,实际是CPU在不同进程之间的高效切换

线程:在同一个进程内可以执行多个任务,而每一个任务都可以看成是一个线程,是程序的执行单元,执行路径,是程序使用CPU的最基本单位,多线程有多条执行路径

多线程的意义:提供程序的使用率,程序的执行都是在抢CPU的执行权,线程的执行具有随机性

Java程序的运行原理:由java命令启动JVM,JVM启动相当于启动了一个进程

JVM虚拟机的启动时单线程的,垃圾回收线程也要启动,最少需要启动两个 线程

2. 多线程实现

2.1 继承Thread类,该子类重写run()方法,创建对象,启动线程

run()和start()的区别:

run():仅仅是封装被线程执行的代码,调用时普通方法。

start():首先启动了线程,然后再由JVM去调用线程的run()方法

    public static void main(String[] args){
        MyThread my1 = new MyThread();
        MyThread my2 = new MyThread();

      my1.setName("xiaojignzi");
      my2.setName("xiaojunzi");

        my1.start();
        //my1.start();    IllegalThreadStateException:非法的线程状态异常,相当于一个线程被调用了两次
        my2.start();
    }

获取线程对象的名称

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

如何获取main方法所在的线程对象的名称

Thread.currentThread().getName()

线程的调度

计算机只有一个CPU时,CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。

线程具有两种调度模型

1. 分时调度模型:所有的线程轮流调用CPU的使用权,平均分配给每个线程占用CPU的时间片

2. 抢占式调度模型,优先让优先级高的线程使用CPU,如果线程的优先级相同会随机选择一个,优先级高的线程获取CPU时间片相对多一些

java使用抢占式调度模型:

int getPriority():获取线程的优先级

setPriority(int):设置线程的优先级

默认的优先级为5,可以设置的优先级范围是1-10

线程的控制:

线程休眠:public static void sleep(long millis):在指定的毫秒内让当前正在执行的线程休眠(暂停实现)。此操作受到系统计时器和调度程序的精确度和准确性的影响

线程加入:public final void join():等待本线程终止,程序再继续向后面执行

线程礼让:public static void yield():暂停当前正在执行的线程对象,并执行其他线程,不能靠它保证每个线程轮流依次指向

线程守护:public final void setDaemon(boolean on):将该线程标记为守护线程或者用户线程,当正在运行的线程都是守护线程时,java虚拟机退出,该方法必须在启动线程前调用,写在main函数时,main主线程执行完,则退出

线程结束:public void stop:让线程停止,已经过时的函数。

        public void interrupt:中断线程

线程的生命周期:

新建:创建线程对象

就绪:有执行资格,没有执行权

运行:有执行资格,有执行权

   阻塞:由于一些操作可以让线程处于该状态,没有执行资格,没有执行权,而另一些操作可以把它激活,激活后处于就绪状态

死亡:线程对象变成垃圾,等待被回收

2.2 实现Runnable接口

1. 自定义类MyRunnable实现Runnable接口

2. 重写run()方法

3. 创建MyRunnable类的对象

4. 创建Thread类的对象,并把3步骤的对象作为构造函数传递

    public static void main(String[] args){
        MyRunnable my = new MyRunnable();
        Thread my1 = new Thread(my);
        Thread my2 = new Thread(my);
        
        my1.setName("wangwang");
        my2.setName("hahah");
        
        my1.start();
        my2.start();
        
    }

public class MyRunnable implements Runnable{

    @Override
    public void run() {
        for(int x = 0; x < 100; x++){
            System.out.println(Thread.currentThread().getName() + x);
        }
    }

}

好处:可以避免由于java单继承带来的局限性

     适合多个相同程序的代码去处理一个资源的情况,把线程通程序的代码,数据有效的分离,较好的体现了面向对象的设计思想

线程的安全性问题:

A:是否是多线程环境

B:是否有共享数据

C:是否有多条语句操作共享数据

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

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(true){
            if(tickets > 0){
                System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
    
}
public class SellTicketsDemo {
    public static void main(String[] args){
        SellTicket st = new SellTicket();
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);
        Thread t3 = new Thread(st);
        
        t1.start();
        t2.start();
        t3.start();
    }
}

上述的操作存在线程的安全性问题:

解决方法:把多条语句的共享数据包装成一个整体,当有线程在执行时,别的线程不能执行

同步代码块:synchronized(obj){}

public class SellTicket implements Runnable {

    private int tickets = 100;
    private Object obj = new Object();

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {
            synchronized (obj) {
                if (tickets > 0) {
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票");
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

 同步代码块的锁是任意的对象。

同步方法的格式以及锁的问题:this

静态方法的锁对象:对象名.class

线程安全的类:

StringBuffer,Vector,Hashtable

 

JDK5后出现的Lock锁的应用:

Lock(接口):

void lock():获取锁

void unlock():释放锁

ReentrantLock是Lock的实现类

例火车票卖票:

public class SellTickesDemo {
    public static void main(String[] args){
        SellTicket st = new SellTicket();
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);
        Thread t3 = new Thread(st);
        
        t1.start();
        t2.start();
        t3.start();
        
    }
}
public class SellTicket implements Runnable {
    private int tickets = 100;
    private Lock lock = new ReentrantLock();

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(true){
            try{
                lock.lock();
                if(tickets > 0){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets-- + "张票");
                }
            }finally{
                lock.unlock();
            }
        }
    }
}

死锁问题的概述和使用:

同步的弊端:效率低,如果出现了同步嵌套,就容易出现死锁的问题

死锁的问题和代码:

指两个或者两个以上的线程在执行的过程中,因争夺资源而产生的一种互相等待的现象

public class MyLock {
    // 创建两把锁对象
    public static final Object objA = new Object();
    public static final Object objB = new Object();
    
}
public class DieLock extends Thread { private boolean flag; public DieLock(boolean flag){ this.flag = flag; } @Override public void run(){ if(flag){ synchronized(MyLock.objA){ System.out.println("if objA"); synchronized(MyLock.objB){ System.out.println("if objB"); } } }else{ synchronized(MyLock.objB){ System.out.println("else objB"); synchronized(MyLock.objA){ System.out.println("else obja"); } } } } }
public class DeadDemo { public static void main(String[] args){ DieLock d1 = new DieLock(true); DieLock d2 = new DieLock(false); d1.start(); d2.start(); } }
有可能产生死锁现象:此时的输出为:
else objB  if objA

多线程之间的通信问题:同步锁解决。

等待唤醒机制

生产者:是否有数据,如果有就等待,没有就生产,生产完通知消费者来消费

消费者:是否有数据,如果有就消费,没有就等待,通知生产者生产数据

Object提供了三个方法

wait():等待

notify():唤醒单个线程

notifyAll():唤醒所有线程,所以定义在object中

这些方法的调用必须通过锁对象来调用

锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。

等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中

notify和notifyAll的区别

如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。

当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只要一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争

优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。

循环里调用 wait 和 notify,不是在 If 语句

现在你知道wait应该永远在被synchronized的背景下和那个被多线程共享的对象上调用,下一个一定要记住的问题就是,你应该永远在while循环,而不是if语句中调用wait。因为线程是在某些条件下等待的——在我们的例子里,即“如果缓冲区队列是满的话,那么生产者线程应该等待”,你可能直觉就会写一个if语句。但if语句存在一些微妙的小问题,导致即使条件没被满足,你的线程你也有可能被错误地唤醒。所以如果你不在线程被唤醒后再次使用while循环检查唤醒条件是否被满足,你的程序就有可能会出错——例如在缓冲区为满的时候生产者继续生成数据,或者缓冲区为空的时候消费者开始小号数据。所以记住,永远在while循环而不是if语句中使用wait!

出处:https://blog.csdn.net/djzhao/article/details/79410229

举例:生产者和消费者:

public class Student {
    String name;
    int age;
    boolean flag;
}

public class GetThread implements Runnable {
    private Student s;

    public GetThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {
            synchronized (s) {
                while (!s.flag) {
                    try {
                        s.wait();    // 等待以后立即释放锁,上面使用while的区别在于执行完这个语句以后仍然会去判断条件是否满足
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                System.out.println(s.name + "-----" + s.age);
                s.flag = false;
                s.notify();
            }
        }
    }
}

public class SetThread implements Runnable {
    private Student s;
    private int x = 0;

    public SetThread(Student s) {
        // TODO Auto-generated constructor stub
        this.s = s;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(true){
            synchronized (s) {
                while(s.flag){
                    try {
                        s.wait();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

                if (x % 2 == 0) {
                    s.name = "xiaojignz";
                    s.age = 20;
                } else {
                    s.name = "xiaoxiao";
                    s.age = 100;
                }
                x++;
                s.flag = true;
                s.notify();
            }
        }
    }
}
public static void main(String[] args){ Student s = new Student(); SetThread st = new SetThread(s); GetThread gt = new GetThread(s); Thread t1 = new Thread(st); Thread t2 = new Thread(gt); t1.start(); t2.start(); }

 生产者和消费者改进版

public class Student {
    private String name;
    private int age;
    private boolean flag;
    
    public synchronized void set(String name, int age){
        while(this.flag){
            try{
                this.wait();
            }catch(InterruptedException e){
                e.printStackTrace();
            }
        }
        
        this.name = name;
        this.age = age;
        this.flag = true;
        this.notify();
    }
    
    public synchronized void get(){
        while(!this.flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println(this.name + "------" + this.age);
        this.flag = false;
        this.notify();
    }
}

public class GetThread implements Runnable {
    private Student s;

    public GetThread(Student s) {
        this.s = s;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {
            s.get();
        }
    }
}

public class SetThread implements Runnable {
    private Student s;
    private int x = 0;

    public SetThread(Student s) {
        // TODO Auto-generated constructor stub
        this.s = s;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while (true) {
            if (x % 2 == 0) {
                s.set("ha", 30);
            } else {
                s.set("wang", 20);
            }
            x++;
        }
    }
}

public class StudentDemo {
    public static void main(String[] args){
        Student s = new Student();
        
        SetThread st = new SetThread(s);
        GetThread gt = new GetThread(s);
        
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(gt);
        
        t1.start();
        t2.start();
        
        
    }
}

线程组:Java中使用ThreadGroup来表示线程组,它可以对一批线程进行分类和管理,Java允许程序直接对线程组进行控制,默认情况下,所有的线程都属于主线程组

获取当前线程组:public final ThreadGroup getThreadGroup()

设置线程组:Thread(ThreadGroup group, Runnable target, String name)

public class ThreadGroupDemo {
    public static void main(String[] args) {
        MyRunnable my = new MyRunnable();
        ThreadGroup tg = new ThreadGroup("新组");

        Thread t1 = new Thread(tg, my, "xiaojingzi");
        Thread t2 = new Thread(tg, my, "xiaoxiao");
        
        ThreadGroup t3 = t1.getThreadGroup();
        ThreadGroup t4 = t2.getThreadGroup();
        
        System.out.print(t3.getName() + "-----" + t4.getName() + "-----");
        System.out.println(Thread.currentThread().getThreadGroup().getName());
        
        tg.setDaemon(true);  //没有明显的效果
        t1.setDaemon(true);
        t2.setDaemon(true);
        t1.start();
        t2.start();
    }
}
新组-----新组-----main
xiaojingzi----0
xiaoxiao----0
xiaojingzi----1
xiaoxiao----1
xiaojingzi----2
xiaoxiao----2
xiaojingzi----3
xiaoxiao----3
xiaoxiao----4
xiaoxiao----5
xiaoxiao----6

线程池

程序器启动一个新的线程的成本是很高的,因为它设计到要与操作系统 进行交互,而使用线程池可以很高的提高性能,尤其是当程序中要创建大量的生存周期很短的线程时,更应该考虑使用线程池

JDK5新增Executors工厂类来产生线程池:

public static ExecutorService newCachedThreadPool();

public static ExecutorService newFixedThreadPool(int nThreads)

public static ExecutorService newSingleThreadExecutor()

这些方法的返回值都是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程,它提供了如下的方法:

Future<?> submit(Runnable task)

<T> Future<T> submit(Callable<T> task)

public class ExecutorsPoolDemo {
    public static void main(String[] args){
        ExecutorService pool = Executors.newFixedThreadPool(2);
        pool.submit(new MyRunnable());
        pool.submit(new MyRunnable());
        pool.shutdown();        //用完以后等待可以回收继续使用,shutdown结束
    }
}

2.3 实现Callable接口(带泛型,依赖于线程池存在)

public class MyCallable implements Callable<Integer> {
    private int number;
    
    public MyCallable(int number){
        this.number = number;
    }

    @Override
    public Integer call() throws Exception {
        // TODO Auto-generated method stub
        int sum = 0;
        for(int x = 0; x <= number; x++){
            sum += x;
        }
        return sum;
    }
}

public class MyCallabelDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException{
        ExecutorService pool = Executors.newFixedThreadPool(2);
        Future<Integer> f1 = pool.submit(new MyCallable(10));
        Future<Integer> f2 = pool.submit(new MyCallable(20));
        Integer i1 = f1.get();
        Integer i2 = f2.get();
        System.out.println(i1 + "-----" + i2);
        pool.shutdown();
    }
}

匿名内部类方式实现多线程程序

public class ThreadDemo {
    public static void main(String[] args){
        new Thread(){
            @Override
            public void run(){
                for(int x = 0; x < 100; x++){
                    System.out.println(Thread.currentThread().getName() + "----" + x);
                }
            }
        }.start();
        
        new Thread(new Runnable(){
            @Override
            public void run(){
                for(int x = 0; x < 100; x++){
                    System.out.println(Thread.currentThread().getName() + "----" + x);
                }
            }
        }){}.start();
        
    }
}

定时器:定时器是一个十分应用广泛的线程工具,可以用于调度多个定时任务以后台线程的方式执行,在java中,可以通过Timer和TimerTask类来实现定义调度的功能

Timer:

public Timer()

public void schedule(TimerTask task, long delay):安排在指定延迟后执行指定的任务

public void shcedule(TimerTask task, Date time):安排在指定的时间执行指定的任务

public void schedule(TimerTask task, long delay, long period):安排在指定的时间后指定的周期执行任务

public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period):安排指定的任务在指定的时间开始重复的固定延迟执行

public void cancel():终止此计数器,丢弃当前所有已经安排的任务

TimerTask:

public abstract void run()

public boolean cancel():终止此计时器,丢弃任何当前计划的任务。

public class TimerDemo {
    public static void main(String[] args){
        Timer t = new Timer();
        Timer m = new Timer();
//        t.schedule(new MyTask(t), 3000);
//        t.schedule(new MyTask(), 3000, 2000);
        m.scheduleAtFixedRate(new MyTask(m), new Date(), 3000);
    }
}

class MyTask extends TimerTask{
    private Timer t;
    
    
    public MyTask() {
        super();
        // TODO Auto-generated constructor stub
    }

    
    public MyTask(Timer t) {
        super();
        this.t = t;
    }


    @Override
    public void run(){
        System.out.println("GG");
//        t.cancel();
    }
}

3. 单例模式的饿汉式

public class RuntimeDemo {
    public static void main(String[] args) throws IOException{
        Runtime r = Runtime.getRuntime();
        r.exec("calc");    // 执行dos命令
    }
}
原文地址:https://www.cnblogs.com/feng-ying/p/9597675.html