多线程详解Java三种实现方式+线程安全问题+死锁问题+线程池

Java多线程详解

    • 继承Thread类实现多线程
    • 实现Runnable接口实现多线程(更常用)
    • 实现Runnable比继承Thread的优势
    • 设置和获取线程的名称
    • 线程的休眠
    • 线程阻塞
    • 线程的中段
    • 守护线程
    • 线程的安全问题
    • 线程安全解决方案1-同步代码块(隐式锁)
    • 线程不安全解决方案2-同步方法(隐式锁)
    • 显示锁解决线程不安全的问题
    • 公平锁与不公平锁()
    • 线程死锁
    • 多线程通信问题(生产者与消费者问题)
    • 带返回值的创建线程的方法(Callable)(第三种实现方式)
    • 线程池

继承Thread类实现多线程

1、run方法(需要重写)
(1):run方法就是线程要执行任务的方法
(2):启动线程不需要调用run方法,而是调用threa里的start方法来启动任务
(3):main方法中的叫做主线程,它和其他线程同时执行,其中谁先执行,谁后执行是不一定的(两个线程强占时间分配)看例子:

package Day1;
public class Demo1 {
     
    public static void main(String[] args) {
     
        MyThread myThread = new MyThread();
        myThread.start();
        for (int i=0;i<10;i++){
     
            System.out.println("锄禾日当午"+i);
        }
    }
}
package Day1;
public class MyThread extends Thread{
     
    public void run(){
     
        //需要重写run方法
        for (int i=0;i<10;i++){
     
            System.out.println("汗滴禾下土"+i);
        }
    }
}

我们来看多次执行的结果:
多线程详解Java三种实现方式+线程安全问题+死锁问题+线程池_第1张图片
多线程详解Java三种实现方式+线程安全问题+死锁问题+线程池_第2张图片

我们可以发现两次的结果是不一样的。
(4):每个线程都有自己的栈空间,共用一份堆内存

实现Runnable接口实现多线程(更常用)

(1):创建一个任务对象(用Runnable创建)
(2):创建一个线程并为其分配任务(还要用到Thread创建对象,并将创建的任务对象传给Thread)
(3):执行这个程序(线程对象.start方法)
看例子:

package Day1;
public class Demo1 {
     
    public static void main(String[] args) {
     
        //创建任务对象
        MyRunnanle myRunnanle = new MyRunnanle();
        //创建一个线程,并讲任务赋给线程
        Thread thread = new Thread(myRunnanle);
        //执行任务
        thread.start();
        //主线程
        for (int i=0;i<10;i++){
     
            System.out.println("锄禾日当午"+i);
        }
    }
}
package Day1;
public class MyRunnanle implements Runnable{
     
    public void run(){
     
        //创建一个任务
        for (int i=0;i<10;i++){
     
            System.out.println("汗滴禾下土"+i);
        }
    }
}

我们来看执行的结果:
多线程详解Java三种实现方式+线程安全问题+死锁问题+线程池_第3张图片

同样是两个线程同时进行

实现Runnable比继承Thread的优势

1:通过创建任务,然后给线程分配的方式来实现多继承,更适合多个线程同时执行相同任务的情况
2:可以避免单继承所带来的局限性
3:线程与任务本身是分离的,提高了程序的健壮性
4:线程池技术只支持Runnable类型的任务,不接收Thread类型的线程

设置和获取线程的名称

获取当前线程的方法
Thread.currentThread();
获取线程名称的方法为:
Thread.currentThread().getName();
设置现成的名称
Thread.currentThread().getName();
看例子

package Day1;
public class Demo1 {
     
    public static void main(String[] args) {
     
        System.out.println(Thread.currentThread().getName());
        System.out.println(new Thread(new MyRunnanle(),"哈哈哈").getName());
        System.out.println(new Thread(new MyRunnanle()).getName());
        Thread thread = new Thread(new MyRunnanle());
        thread.setName("nihao");
        System.out.println(thread.getName());
        }
}
package Day1;
public class MyRunnanle implements Runnable{
     
    public void run(){
     
        //创建一个任务
        for (int i=0;i<10;i++){
     
            System.out.println("汗滴禾下土"+i);
        }
    }
}

运行结果:
多线程详解Java三种实现方式+线程安全问题+死锁问题+线程池_第4张图片

线程的休眠

Thread.sleep(millis:1000)休眠一秒钟参数可以是毫秒,也可以是毫秒加纳秒

线程阻塞

理解为消耗时间的部分例如:接受用户输入,读取文件内容。线程阻塞也称之为耗时操作

线程的中段

线程中断标记:
Thread.interruput();如果出现了中断标记则就会进入catch语句,我们如果想终断线程的话就需要再catch块中添加return语句就会将线程终止。
看例子:

package Day1;
public class Demo1 {
     
    public static void main(String[] args) {
     
        Thread thread = new Thread(new MyRunnanle(),"线程");
        thread.start();
        for (int i=0;i<10;i++){
     
            System.out.println(Thread.currentThread().getName()+i);
        }
        thread.interrupt();
    }
}
package Day1;
public class MyRunnanle implements Runnable{
     
    public void run(){
     
        //创建一个任务
        for (int i=0;i<10;i++){
     
            System.out.println(Thread.currentThread().getName()+i);
            try {
     
                Thread.sleep(1000);
            } catch (InterruptedException e) {
     
                //e.printStackTrace();
                System.out.println("我们发现中断标记,所以我们选择退出线程");
                return;
            }
        }
    }
}

运行结果:
多线程详解Java三种实现方式+线程安全问题+死锁问题+线程池_第5张图片

守护线程

线程的分类:
用户 线程:当一个进程不包含任何一个存活的用户线程时,进程结束。
守护线程:守护用户进程的,当最后一个用户进程结束时 ,所有守护线程自动死亡。
1、将一个线程设置为守护线程:
Thread thread = new Thread(new MyRunnable);
thread.setDaemon(true);
//一定要在线程开始前将其设置为守护线程
thread.start();
主线程结束之后无论守护线程是否结束,都会跟着死亡。

线程的安全问题

我们直接看这样一段代码:

package Day1;
public class Demo1 {
     
    public static void main(String[] args) {
     
       Runnable runnable =  new Ticket();
       new Thread(runnable).start();
       new Thread(runnable).start();
       new Thread(runnable).start();
    }
}
package Day1;
public class Ticket implements Runnable{
     
    int count = 10;
    public void run(){
     
        while (count >0){
     
            System.out.println("正在准备卖票");
            try {
     
                Thread.sleep(1000);
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
            count --;
            System.out.println("出票成功,余票"+count);
        }
    }
}

看运行结果:
多线程详解Java三种实现方式+线程安全问题+死锁问题+线程池_第6张图片
最后结果出现了-2.。
解释原因:
在count=1时第一个线程抢到时间片,但是因为遇到睡眠所以时间片丢失,被令外两个线程抢到。所以三个线程都满足count>0的条件所以都会买到票,随后count减去三就是-2.

线程安全解决方案1-同步代码块(隐式锁)

线程排队执行(枷锁机制)
线程同步:synchronized
格式:synchronized(锁对象){ 同步代码块 }
注意:锁对象可以是任何Java对象
锁对象的描述:
当线程开始之后会将任务的一部分加上锁标记,然后当其他线程开始这个任务时发现有锁标记,就会暂停,进行等待,当这个任务结束时,会将锁标记解除,然后多个线程就会去抢这个锁,谁抢到谁就执行这个任务,后面的线程在排队。特别注意:一定时多个线程看同一把锁,不能是一个线程都有一个自己的锁,这样时没有用的。(锁对象不能创建在run方法中,最好创建在任务类中)接下来我们看一个卖票的例子:

package Day1;
public class Demo1 {
     
    public static void main(String[] args) {
     
       Runnable ticket =  new Ticket();
       new Thread(ticket).start();
       new Thread(ticket).start();
       new Thread(ticket).start();
    }
}
package Day1;
public class Ticket implements Runnable{
     
    int count = 10;
    private Object o = new Object();
    public void run(){
     
        while (true){
     
            synchronized (o){
     
              if (count >0){
     
              System.out.println("正在准备卖票");
              try {
     
                Thread.sleep(50);
              } catch (InterruptedException e) {
     
                e.printStackTrace();
              }
              count --;
              System.out.println(Thread.currentThread().getName()+"出票成功,余票"+count);
              }else {
     
                  break;
              }
            }
        }
    }
}

运行结果:
多线程详解Java三种实现方式+线程安全问题+死锁问题+线程池_第7张图片

这个时候就不会出现余票为负数的情况。

线程不安全解决方案2-同步方法(隐式锁)

同步方法就是将同步代码块抽成了一个方法,然后将这个方法用synchronized修饰

package Day1;
public class Demo1 {
     
    public static void main(String[] args) {
     
       Runnable ticket =  new Ticket();
       new Thread(ticket).start();
       new Thread(ticket).start();
       new Thread(ticket).start();
    }
}
package Day1;
public class Ticket implements Runnable{
     
    int count = 10
    public void run(){
     
        while (true){
     
           int flag = sale();
           if(!flag){
     
             break;
           }
        }
    }
    public synchronized boolean sale(){
     
        //锁对象是this,就是调用这个方法的对象
        //如果是静态的锁对象是Ticket.class()
        if (count >0){
     
            System.out.println("正在准备卖票");
            try {
     
                Thread.sleep(50);
            } catch (InterruptedException e) {
     
                e.printStackTrace();
            }
            count --;
            System.out.println(Thread.currentThread().getName()+"出票成功,余票"+count);
            return true;
        }else {
     
            return false;
        }
    }
}

在这里要注意的是呢经过synchronized修饰的方法的锁对象就是this
如果使用static修饰的方法锁对象就是类名.class

显示锁解决线程不安全的问题

语法格式:private Lock l = new RenntrantLock();创建一个锁对象
l.lock();在需要排队的代码前面上锁。
l.unlock();解锁。

公平锁与不公平锁()

公平锁:先来先到。
非公平锁:Java中默认的都是非公平锁:线程同时抢,谁抢到就是谁的
语法:
private Lock l = new RenntrantLock(true);//传入true就是公平锁,本身默认就是false。

线程死锁

就是两个线程卡住的情况 : 两个线程锁住两块资源,但是A线程想要使用B线程的资源,同时B线程呢也想要使用A线程的资源,这时候两边的线程都在等待资源的释放,所以两个线程都进行不下去。看一个警察与绑匪的例子:

package Day1;

import org.w3c.dom.ls.LSOutput;

public class Demo2 {
     
    //线程死锁
    public static void main(String[] args) {
     
        Culprit c = new Culprit();
        Police p = new Police();
        new MyThread(c,p).start();
        c.say(p);
    }
    static class MyThread extends Thread{
     
        private Culprit c;
        private Police p;
        MyThread(Culprit c,Police p){
     
            this.c=c;
            this.p=p;
        }

        @Override
        public void run() {
     
            p.say(c);
        }
    }
    static class Culprit{
     
        public synchronized void say(Police p){
     
            System.out.println("你放了我,我放人质");
            p.fun();
        }
        public synchronized void fun(){
     
            System.out.println("罪犯被放了,罪犯也放了人质");
        }
    }
    static class Police{
     
        public synchronized void say(Culprit c){
     
            System.out.println("你放了人质,我放了你");
            c.fun();
        }
        public synchronized void fun(){
     
            System.out.println("警察救了人质,罪犯跑了");
        }
    }
}

运行结果呢有锁死的也有没锁死的:
多线程详解Java三种实现方式+线程安全问题+死锁问题+线程池_第8张图片

因为Mythread线程锁定了p对象,主线程锁定了c对象,所以Mytheread不能调用c对象的fun方法,同时主线程也不能调用p对象的fun方法,两边都在等,所以线程锁死
注意:这里锁死的是对象,是对象p和对象c,而不是对象里面的方法,这样就好理解了。

多线程通信问题(生产者与消费者问题)

这个问题可以用一个厨师与服务员的例子讲述一下:
厨师的线程是做饭。
服务员的线程是换取盘子端走饭。
那么这两个线程是要交替执行的:厨师做饭的时候服务员歇着。服务员端饭的时候厨师歇着(可能不是那么贴合,这么理解就完事了)
那么需要用到两个方法:
this.notifyAll()唤醒线程
this.wait();让线程睡着
这个地方本人属实讲的不是很清楚,重点理解这个思想就行了。

带返回值的创建线程的方法(Callable)(第三种实现方式)

1

  编写类实现Callable接口 , 实现call方法        
 class XXX implements Callable<T> {
       
          @Override      
  public <T> call() throws Exception {
          
             return T;            
      }
  }
  创建FutureTask对象 , 并传入第一步编写的Callable类对象
  FutureTask<Integer> future = new FutureTask<>(callable);
 通过Thread,启动线程        new Thread(future).start();

线程池

1、缓存线程池

    /*** 缓存线程池.* (长度无限制)* 执行流程:*
    1.  判断线程池是否存在空闲线程*      
    2.  存在则使用*      
    3.  不存在,则创建线程 并放入线程池, 然后使用*/ 
 ExecutorService service = Executors.newCachedThreadPool();//向线程池中加入新的任务
service.execute(new Runnable() {
     
        @Override 
public void run() {
     
   System.out.println("线程的名称:"+Thread.currentThread().getName());        }    });    
   service.execute(new Runnable() {
            
    @Override       
     public void run() {
                 
     System.out.println("线程的称:"+Thread.currentThread().getName());        }    });   
      service.execute(new Runnable() {
        
           @Override     
  public void run() {
          
    System.out.println("线程的名称:"+Thread.currentThread().getName());        }    });

2、 定长线程池
3. 单线程线程池
4. 周期性任务定长线程池这些就不一一列举了。并非重点。

你可能感兴趣的:(Java语法,java)